渴望在rails中加载belongs_to关系

时间:2013-12-03 12:45:04

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

我的rails 4应用程序有许多Measures属于Station。 我试图在我的控制器中加载措施:

 @station = Station.includes(:measures).friendly.find(params[:id])
 @measures = @station.measures

但是,当我向测量模型添加一个访问工作站属性的方法时,每个小节会产生一个额外的查询:

 SELECT "stations".* FROM "stations" WHERE "stations"."id" = ? ORDER BY "stations"."id" ASC LIMIT 1 

这意味着每页加载大约50个查询,这完全可以提高性能。

如何正确加载关系以避免此n + 1查询?我错了吗?


github: /app/controllers/stations_controller.rb

class StationsController < ApplicationController
  ...

  # GET /stations/1
  # GET /stations/1.json
  def show
    @station = Station.includes(:measures).friendly.find(params[:id])
    @measures = @station.measures
  end

  ...
end

github: /app/models/measure.rb

class Measure < ActiveRecord::Base
  belongs_to :station, dependent: :destroy, inverse_of: :measures
  after_save :calibrate!
  after_initialize :calibrate_on_load

  ...

  def calibrate!
    # this causes the n+1 query
    unless self.calibrated
      unless self.station.speed_calibration.nil?
        self.speed            = (self.speed * self.station.speed_calibration).round(1)
        self.min_wind_speed   = (self.min_wind_speed * self.station.speed_calibration).round(1)
        self.max_wind_speed   = (self.max_wind_speed * self.station.speed_calibration).round(1)

        self.calibrated = true
      end
    end
  end

  def calibrate_on_load
    unless self.new_record?
      self.calibrate!
    end
  end

  def measure_cannot_be_calibrated
    if self.calibrated
      errors.add(:speed_calbration, "Calibrated measures cannot be saved!")
    end
  end

end

github: /app/models/stations.rb

class Station < ActiveRecord::Base

  # relations
  belongs_to :user, inverse_of: :stations
  has_many  :measures, inverse_of: :station, counter_cache: true

  # slugging
  extend FriendlyId
  friendly_id :name, :use => [:slugged, :history]

  ...

end

ADDITION 有趣的是,这不会导致n + 1查询。但我宁愿不在我的控制器上复制它。

 class Measure < ActiveRecord::Base
      ...
      # after_initialize :calibrate_on_load
      ...
 end      


 @station = Station.includes(:measures).friendly.find(params[:id])
 @measures = @station.measures
 @measures.each do |m|
   m.calibrate!
 end

3 个答案:

答案 0 :(得分:0)

尝试将station包括在measure中。

def show
  @station = Station.includes(:measures).friendly.find(params[:id])
  @measures = @station.measures.includes(:station)
end

答案 1 :(得分:0)

:includes选项通常会在您看到的单独查询中触发加载,因为在许多情况下它更具性能。如果您想确保在一个查询中完成,请尝试使用:joins,但要注意返回的结果记录将是只读的。

答案 2 :(得分:0)

after_initialize发生在关系由连接或包含加载之前。见https://github.com/rails/rails/issues/13156

我决定在使用不同的方法后提出一些好的建议,然后想出来:

class Station < ActiveRecord::Base

  ...

  alias_method :measures_orig, :measures
  def measures
      measures_orig.map do |m|
        m.calibrate!
      end
  end

end