Rails 4 before_update条件对单个列的SQL开销

时间:2014-09-05 16:55:16

标签: ruby validation ruby-on-rails-4 model conditional-statements

我的rails应用程序中有一个Player模型。我正在评估的2列是最高级别和最高分数。这是跨多个配置文件的单个玩家的统计信息跟踪,因此有可能这些值中的任何一个都可能低于db中的当前值。因此,我只希望它更新特定列,如果传入的发布值大于数据库中的值。阅读一些内置的验证选项,我无法像我预期的那样工作,但是,我能够编写自己的验证工作,但代价是调用Player.find(id)该模型。有没有办法解决这个问题,以便我的Player.update()不会同时导致UPDATE和SELECT?

这是我的模特:

class Player < ActiveRecord::Base
  #validates_numericality_of :highestLevel, greater_than: Proc.new { |r| r.highestLevel }
  #validates_numericality_of :highestScore, greater_than: Proc.new { |r| r.highestScore }

  before_update :player_record, :eval_highestLevel, :eval_highestScore

  # TODO: Find a more effective way to handle update evaluations with less SQL overhead
  private
    def eval_highestLevel
      # if highestLevel in DB has higher value , update the value
      if @p.highestLevel > self.highestLevel
        self.highestLevel = @p.highestLevel
      end
    end

    def eval_highestScore
      # if record in DB has higher value , update the value
      if @p.highestScore > self.highestScore
        self.highestScore = @p.highestScore
      end
    end

    def player_record
      @p = Player.find(id)
    end
end

关于如何提高效率的任何想法,还是我应该不管它?我一直在为Rails 4.x寻找更大更好的鼠标陷阱

1 个答案:

答案 0 :(得分:0)

当属性发生更改但记录尚未保留时,Rails会自动定义帮助程序以获取属性的先前值。它们被命名为例如attribute name_was,所以在这种情况下Player#highestLevel_washighestScore_was

def eval_highestLevel
  # if highestLevel in DB has higher value , update the value
  if self.highestLevel_was > self.highestLevel
    self.highestLevel = @p.highestLevel
  end
end

ActiveModel::Dirty中记录了这一点。定义了许多其他有用的方法,例如:

    如果属性发生了变化,
  • attribute_name_changed?会返回true
  • attribute_name_change返回一个包含两个元素的数组,旧值和新值。

有了这些知识,我们可以实际上简化你的回调:

class Player < ActiveRecord::Base
  before_update :ensure_highestLevel, if: :highestLevel_changed?
  before_update :ensure_highestScore, if: :highestLevel_changed?

  protected
  def ensure_highestLevel
    self.highestLevel = self.highestLevel_change.compact.max
  end

  def ensure_highestScore
    self.highestScore = self.highestScore_change.compact.max
  end
end

由于self.highestScore_change是两个数字的数组,我们可以调用max来获得更高的数字。我们使用Array#compact,因为如果旧值或新值为nil,我们会收到错误([nil,1].max # => ArgumentError: comparison of NilClass with 1 failed)。 compact首先从数组中删除任何nil

或者更简洁:

class Player < ActiveRecord::Base
  before_update ->{ highestLevel = highestLevel_change.compact.max },
    if: :highestLevel_changed?

  before_update ->{ highestScore = highestScore_change.compact.max },
    if: :highestScore_changed?
end