渴望加载,模型方法,fields_for,每个和N + 1

时间:2015-07-20 21:54:24

标签: ruby-on-rails ruby ruby-on-rails-4

我有一个相当复杂的应用程序(超过30个表),有一些持续的N + 1问题,而且我认为这是因为我没有做一些事情' The Rails Way™'

我将给出一个更复杂的例子。这一个包括四个表:clins, positions_tasks, taskslabor_hours

positions_tasksclins, positions(此示例不需要)和tasks以及{{1}之间的三向多对多联接}}。 labor_hours表有一年中每个月的整数字段和一些其他数据。方法has_many :labor_hours将所有月份总计为一年中的总小时数。在临床视图中,它显示临床信息和所有相关任务的表格[与其他相关数据],并汇总每个任务的小时数total_hours。我急切地加载所有相关的表,包括labor_hours,除了labor_hours之外,所有N + 1问题都消失了。

代码片段如下。

clins_controller的急切负载:

has_many :labor_hours, :through => :positions_tasks

在clins / _form.html.erb中显示表格行:

@clin = Clin.includes(:proposal).includes(:positions_tasks).includes(:tasks).includes(:labor_hours).includes(:wbss).find(params[:id])`

_task_row partial:

    <tbody>
        <% @clin.tasks.distinct.each do |t| %>
          <%= f.fields_for :task, t do |builder| %>
            <%= render "tasks/task_row", f: builder %>
          <% end %>
        <% end %>
    </tbody>

临床模型:

<tr>
    <td><%= f.object.wbs_line_item.wbs.wbs_title %></td>
    <td><%= f.object.wbs_line_item.wbs_line_item %></td>
    <td><%= f.object.description %></td>
    <td><%= f.object.labor_hours.distinct.each.sum(&:total_hours) %>
    <td><div id="jump">
      <%= link_to "Edit", {:controller => :tasks, :action => :edit, :id => f.object.id } %>
    </div></td>
</tr>

劳动时间模型:

class Clin < ActiveRecord::Base
  nilify_blanks

  belongs_to :proposal

  belongs_to :parent, :class_name => "Clin"
  has_many :children, :class_name => "Clin"

  has_many :positions_tasks
  has_many :labor_hours, :through => :positions_tasks
  has_many :tasks, :through => :positions_tasks
  has_many :wbs_line_items, :through => :tasks
  has_many :wbss, :through => :wbs_line_items
  has_many :pws_line_items, :through => :wbs_line_items
  has_many :pwss, :through => :wbss
end

positionsTask模型:

class LaborHours < ActiveRecord::Base
  nilify_blanks

  belongs_to :positions_task
  belongs_to :year

  has_one :proposal, :through => :positions_task
  has_many :valid_years, :through => :proposal, :source => :years

  def total_hours
    m1 + m2 + m3 + m4 + m5 + m6 + m7 + m8 + m9 + m10 + m11 + m12
  end
end

任务模型:

class PositionsTask < ActiveRecord::Base
  nilify_blanks

  belongs_to :task
  belongs_to :position
  belongs_to :clin

  has_many :labor_hours

  has_one :company, :through => :position
  has_one :proposal, :through => :clin
  has_one :wbs_line_item, :through => :task
  delegate :wbs, :to => :wbs_line_item

  delegate :pws_line_items, :to => :wbs_line_item
  delegate :pwss, :to => :wbs_line_item

  validates_presence_of :task
  validates_presence_of :position
  validates_presence_of :clin

  accepts_nested_attributes_for :labor_hours, allow_destroy: true
end

GET和SQL加载:

class Task < ActiveRecord::Base
  nilify_blanks

  belongs_to :wbs_line_item
  belongs_to :task_category

  has_many :positions_tasks

  has_many :labor_hours, :through => :positions_tasks
  has_many :positions, :through => :positions_tasks
  has_many :clins, :through => :positions_tasks
  has_many :proposals, :through => :positions_tasks

  delegate :wbs, :to => :wbs_line_item
  delegate :pws_line_items, :to => :wbs_line_item
  delegate :pwss, :to => :wbs

  accepts_nested_attributes_for :positions_tasks, allow_destroy: true
  accepts_nested_attributes_for :labor_hours, allow_destroy: true

  validates_associated :positions_tasks

end

我认为正在发生的事情是,_form.html.erb中的Started GET "/clins/11/edit" for 127.0.0.1 at 2015-07-20 17:48:49 -0400 Processing by ClinsController#edit as HTML Parameters: {"id"=>"11"} Clin Load (0.2ms) SELECT "clins".* FROM "clins" WHERE "clins"."id" = $1 LIMIT 1 [["id", 11]] Proposal Load (0.2ms) SELECT "proposals".* FROM "proposals" WHERE "proposals"."id" IN (1) PositionsTask Load (0.4ms) SELECT "positions_tasks".* FROM "positions_tasks" WHERE "positions_tasks"."clin_id" IN (11) Task Load (0.6ms) SELECT "tasks".* FROM "tasks" WHERE "tasks"."id" IN (1, 2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 14, 15, 16, 17, 18, 20, 23, 24) LaborHours Load (1.1ms) SELECT "labor_hours".* FROM "labor_hours" WHERE "labor_hours"."positions_task_id" IN (1, 2, 3, 6, 7, 8, 9, 10, 12, 13, 14, 18, 19, 20, 21, 23, 24, 25, 26, 27, 30, 35, 36, 37) WbsLineItem Load (0.5ms) SELECT "wbs_line_items".* FROM "wbs_line_items" WHERE "wbs_line_items"."id" IN (310, 312, 314, 316, 317, 318, 319, 413, 320, 321, 322, 324, 325, 326, 327, 328, 330, 333, 334) Wbs Load (0.4ms) SELECT "wbss".* FROM "wbss" WHERE "wbss"."id" IN (1) Clin Load (0.2ms) SELECT "clins".* FROM "clins" Rendered tasks/_task_header.html.erb (0.0ms) LaborHours Load (0.4ms) SELECT "labor_hours".* FROM "labor_hours" INNER JOIN "positions_tasks" ON "labor_hours"."positions_task_id" = "positions_tasks"."id" WHERE "positions_tasks"."task_id" = $1 [["task_id", 1]] Rendered tasks/_task_row.erb (2.6ms) LaborHours Load (0.3ms) SELECT "labor_hours".* FROM "labor_hours" INNER JOIN "positions_tasks" ON "labor_hours"."positions_task_id" = "positions_tasks"."id" WHERE "positions_tasks"."task_id" = $1 [["task_id", 2]] Rendered tasks/_task_row.erb (1.7ms) LaborHours Load (0.2ms) SELECT "labor_hours".* FROM "labor_hours" INNER JOIN "positions_tasks" ON "labor_hours"."positions_task_id" = "positions_tasks"."id" WHERE "positions_tasks"."task_id" = $1 [["task_id", 3]] Rendered tasks/_task_row.erb (1.4ms) LaborHours Load (0.2ms) SELECT "labor_hours".* FROM "labor_hours" INNER JOIN "positions_tasks" ON "labor_hours"."positions_task_id" = "positions_tasks"."id" WHERE "positions_tasks"."task_id" = $1 [["task_id", 5]] Rendered tasks/_task_row.erb (1.3ms) LaborHours Load (0.2ms) SELECT "labor_hours".* FROM "labor_hours" INNER JOIN "positions_tasks" ON "labor_hours"."positions_task_id" = "positions_tasks"."id" WHERE "positions_tasks"."task_id" = $1 [["task_id", 6]] Rendered tasks/_task_row.erb (1.4ms) LaborHours Load (0.2ms) SELECT "labor_hours".* FROM "labor_hours" INNER JOIN "positions_tasks" ON "labor_hours"."positions_task_id" = "positions_tasks"."id" WHERE "positions_tasks"."task_id" = $1 [["task_id", 7]] Rendered tasks/_task_row.erb (1.5ms) LaborHours Load (0.2ms) SELECT "labor_hours".* FROM "labor_hours" INNER JOIN "positions_tasks" ON "labor_hours"."positions_task_id" = "positions_tasks"."id" WHERE "positions_tasks"."task_id" = $1 [["task_id", 8]] Rendered tasks/_task_row.erb (1.3ms) LaborHours Load (0.2ms) SELECT "labor_hours".* FROM "labor_hours" INNER JOIN "positions_tasks" ON "labor_hours"."positions_task_id" = "positions_tasks"."id" WHERE "positions_tasks"."task_id" = $1 [["task_id", 9]] Rendered tasks/_task_row.erb (1.3ms) LaborHours Load (0.4ms) SELECT "labor_hours".* FROM "labor_hours" INNER JOIN "positions_tasks" ON "labor_hours"."positions_task_id" = "positions_tasks"."id" WHERE "positions_tasks"."task_id" = $1 [["task_id", 10]] Rendered tasks/_task_row.erb (1.9ms) LaborHours Load (0.2ms) SELECT "labor_hours".* FROM "labor_hours" INNER JOIN "positions_tasks" ON "labor_hours"."positions_task_id" = "positions_tasks"."id" WHERE "positions_tasks"."task_id" = $1 [["task_id", 11]] Rendered tasks/_task_row.erb (1.5ms) LaborHours Load (0.4ms) SELECT "labor_hours".* FROM "labor_hours" INNER JOIN "positions_tasks" ON "labor_hours"."positions_task_id" = "positions_tasks"."id" WHERE "positions_tasks"."task_id" = $1 [["task_id", 12]] Rendered tasks/_task_row.erb (2.2ms) LaborHours Load (0.5ms) SELECT "labor_hours".* FROM "labor_hours" INNER JOIN "positions_tasks" ON "labor_hours"."positions_task_id" = "positions_tasks"."id" WHERE "positions_tasks"."task_id" = $1 [["task_id", 14]] Rendered tasks/_task_row.erb (2.6ms) LaborHours Load (0.4ms) SELECT "labor_hours".* FROM "labor_hours" INNER JOIN "positions_tasks" ON "labor_hours"."positions_task_id" = "positions_tasks"."id" WHERE "positions_tasks"."task_id" = $1 [["task_id", 15]] Rendered tasks/_task_row.erb (2.2ms) LaborHours Load (0.2ms) SELECT "labor_hours".* FROM "labor_hours" INNER JOIN "positions_tasks" ON "labor_hours"."positions_task_id" = "positions_tasks"."id" WHERE "positions_tasks"."task_id" = $1 [["task_id", 16]] Rendered tasks/_task_row.erb (1.5ms) LaborHours Load (0.3ms) SELECT "labor_hours".* FROM "labor_hours" INNER JOIN "positions_tasks" ON "labor_hours"."positions_task_id" = "positions_tasks"."id" WHERE "positions_tasks"."task_id" = $1 [["task_id", 17]] Rendered tasks/_task_row.erb (1.9ms) LaborHours Load (0.3ms) SELECT "labor_hours".* FROM "labor_hours" INNER JOIN "positions_tasks" ON "labor_hours"."positions_task_id" = "positions_tasks"."id" WHERE "positions_tasks"."task_id" = $1 [["task_id", 18]] Rendered tasks/_task_row.erb (1.6ms) LaborHours Load (0.3ms) SELECT "labor_hours".* FROM "labor_hours" INNER JOIN "positions_tasks" ON "labor_hours"."positions_task_id" = "positions_tasks"."id" WHERE "positions_tasks"."task_id" = $1 [["task_id", 20]] Rendered tasks/_task_row.erb (1.9ms) LaborHours Load (0.2ms) SELECT "labor_hours".* FROM "labor_hours" INNER JOIN "positions_tasks" ON "labor_hours"."positions_task_id" = "positions_tasks"."id" WHERE "positions_tasks"."task_id" = $1 [["task_id", 23]] Rendered tasks/_task_row.erb (1.6ms) LaborHours Load (0.3ms) SELECT "labor_hours".* FROM "labor_hours" INNER JOIN "positions_tasks" ON "labor_hours"."positions_task_id" = "positions_tasks"."id" WHERE "positions_tasks"."task_id" = $1 [["task_id", 24]] Rendered tasks/_task_row.erb (1.9ms) Rendered clins/_form.html.erb (47.6ms) Rendered clins/_errors.html.erb (0.0ms) Rendered clins/edit.html.erb within layouts/application (48.6ms) Rendered layouts/_header.html.erb (60.5ms) Rendered layouts/_sidenav.html.erb (0.4ms) Rendered layouts/_footer.html.erb (0.0ms) Completed 200 OK in 140ms (Views: 106.4ms | ActiveRecord: 8.8ms) distinct.each中的急切加载会丢失,因为它传递的是任务对象而不是临床对象, /或者对total_hours的调用会导致调用它的每个对象的负载,但是我不确定如何确定它是什么,也不知道如何解决它们。

如何在没有N + 1加载劳动时间的情况下为表中的每项任务提供总计fields_for

1 个答案:

答案 0 :(得分:1)

我不确定,但我是一个理论。你有这个(我已经删除了刚刚关注我们的includes电话:

@clin = Clin.includes(:tasks).includes(:labor_hours).find(params[:id])

您在这里做的是急切加载与每个Clin相关联的任务以及与每个Clin相关联的LaborHours - 到目前为止,这很好,但在您看来,您正在这样做(或多或少) ):

@clin.tasks.distinct.each do |task|
  # inside the partial...
  task.labor_hours...
end

在这里,您无法访问与每个Clin相关联的LaborHours - 这是您渴望加载的 - 您正在访问与每个Clin关联的每个任务相关联的LaborHours。要访问与每个Clin关联的LaborHours,您必须执行此操作:

@clin.labor_hours.each do |labor_hour|
  # ...
end

但是既然你正在渲染任务(而不仅仅是工作时间),我不会认为这是你想要的。相反,你需要告诉Rails你想要加载二阶关联 - 即。与任务关联的LaborHours,而不是与Clins关联的LaborHours - 通过将哈希传递给includes

@clin = Clin.includes(:tasks => :labor_hours).find(params[:id])

P.S。您可以进行一些额外的改进 - 例如,看起来您实际上并未使用LaborHours的任何属性,您实际上只是使用了total_hours的总和柱。但是,只要让数据库执行它,计算Ruby中的总和是一种浪费。但是,这超出了这个答案的范围。