问题
tI have a Player model in my rails app. 2 columns I am evaluating are highestLevel and highestScore. This is a stats tracking for a single player across multiple profiles, so there is the possibility that either of these values coming in could be lower than the current value in the db. Therefore I only wish it update a particular column IF the incoming posted value is greater than the one in the DB. Reading up on some of the built in validation options, I was not able to get any to work as I intended, however, I was able to write my own validations which work, but at the cost of calling Player.find(id) within the model. Is there a way around this so that my Player.update() does not result in both and UPDATE and SELECT?
Here is my model:
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
Any ideas on how to make this more efficient, or should I leave it alone? I'm always looking for the bigger and better mouse trap for Rails 4.x
回答1:
Rails automatically defines helpers to get the previous value of an attribute when the attribute has changed but the record hasn't been persisted yet. They're named e.g. attribute name_was
, so in this case Player#highestLevel_was
and highestScore_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
This is documented in ActiveModel::Dirty. A number of other useful methods are defined, for example:
attribute_name_changed?
returnstrue
if the attribute has changed.attribute_name_change
returns an array with two elements, the old value and the new value.
Armed with this knowledge we can actually simplify your callbacks a lot:
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
Since self.highestScore_change
will be an array of two numbers, we can call max
to get the higher one. We use Array#compact
because if either the old value or the new value is nil
we'd get an error ([nil,1].max # => ArgumentError: comparison of NilClass with 1 failed
). compact
removes any nil
s from the array first.
Or even more succinctly:
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
来源:https://stackoverflow.com/questions/25690614/rails-4-before-update-condition-on-individual-columns-sql-overhead