Skip to content

JBuilder 2.11.3 introduces an extra DB query when rendering collection partials #514

@cupakromer

Description

@cupakromer

Ruby Version: 2.7.4
Rails Version: 5.2.6
JBuilder Version: 2.11.3

This was introduced by #501 with the options[:collection].empty? call:

if options.key?(:collection) && (options[:collection].nil? || options[:collection].empty?)

When the collection object is an Active Record association relation/collection the empty? call introduces a SELECT 1 query statement from this code in ActiveRecord::Relation#empty?:

    # Returns true if there are no records.
    def empty?
      return @records.empty? if loaded?
      !exists?
    end

This SQL statement occurs for the case when the collection is not yet loaded; which is very common for the main collection on index actions. The exists? code is run which is defined in ActiveRecord::FinderMethods#exists?. This causes SQL to be executed via connection.select_one.

The extra SQL statement is an unnecessary DB trip across the network when there is data.

For comparison, in JBuild 2.11.2 the same process passes the collection to array! which ends up loading the full collection without this select_one call.

This is easily reproduced with a controller action such as:

# app/controllers/widgets_controller.rb
class WidgetsController < ApplicationController
  # GET /widgets or /widgets.json
  def index
    @widgets = Widget.all
  end
end
# app/views/widgets/index.json.jbuilder
json.array! @widgets, partial: "widgets/widget", as: :widget
# app/views/widgets/_widget.json.jbuilder
json.extract! widget, :id, :name, :created_at, :updated_at
json.url widget_url(widget, format: :json)

When hit without data the Rails default logger shows:

Started GET "/widgets.json" for 127.0.0.1 at 2021-11-16 17:02:20 -0500
Processing by WidgetsController#index as JSON
  Rendering widgets/index.json.jbuilder
  Widget Exists (1.3ms)  SELECT  1 AS one FROM "widgets" LIMIT $1  [["LIMIT", 1]]
  ↳ app/views/widgets/index.json.jbuilder:1
  Rendered widgets/index.json.jbuilder (3.1ms)
Completed 200 OK in 7ms (Views: 4.4ms | ActiveRecord: 1.3ms)

When run with data, the SELECT 1 and the SELECT * are both present:

Started GET "/widgets.json" for 127.0.0.1 at 2021-11-16 17:04:04 -0500
Processing by WidgetsController#index as JSON
  Rendering widgets/index.json.jbuilder
  Widget Exists (0.3ms)  SELECT  1 AS one FROM "widgets" LIMIT $1  [["LIMIT", 1]]
  ↳ app/views/widgets/index.json.jbuilder:1
  Widget Load (0.2ms)  SELECT "widgets".* FROM "widgets"
  ↳ app/views/widgets/index.json.jbuilder:1
  Rendered widgets/_widget.json.jbuilder (0.5ms)
  Rendered widgets/_widget.json.jbuilder (0.2ms)
  Rendered widgets/_widget.json.jbuilder (0.2ms)
  Rendered widgets/index.json.jbuilder (4.9ms)
Completed 200 OK in 9ms (Views: 6.7ms | ActiveRecord: 0.5ms)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions