Clarifying a custom Rails 3.0 Validation with methods
I've created a custom validator in Rails 3.0 which validates whether a combination of columns is unique within a table. The entire code of the validation is:
class UniqueInProjectValidator < ActiveModel::EachValidator
def validate_each(object, attribute, value)
unless object.class.where("project_id = ? AND #{attribute} = ?", object.project_id, value).empty?
if object.new_record?
object.errors[attribute] << (options[:message] || "must be unique in each project")
else
orig_rec = object.class.find(object.i开发者_开发问答d)
if value != orig_rec.method(attribute).call || object.project_id != orig_rec.project_id
object.errors[attribute] << (options[:message] || "must be unique in each project")
end
end
end
end
Note that it is not easy to recognize what the if statements do, so I was hoping to be able to replace the unless
conditional with a def attribute_and_project_exist?
method and the second if
statement with a def attribute_or_project_changed?
method. However when creating those methods, the arguments from validates_each
do not pass because of encapsulation.
Now the question: Is there a way to somehow cleanly allow those variables to be accessed by my two newly created methods as one can do with column names in a model, or am I stuck with the options of either passing each argument again or leaving the hard to read conditional statements?
Thanks in advance!
I suppose you could clean it up a bit with one variable, one lambda, and one "return as soon as possible":
def validate_each(object, attribute, value)
# If there is no duplication then bail out right away as
# there is nothing to check. This reduces your nesting by
# one level. Using a variable here helps to make your
# intention clear.
attribute_and_project_exists = object.class.where("project_id = ? AND #{attribute} = ?", object.project_id, value).empty?
return unless attribute_and_project_exists
# This lambda wraps up your second chunk of ugly if-ness and saves
# you from computing the result unless you have to.
attribute_or_project_changed = lambda do
orig_rec = object.class.find(object.id)
value != orig_rec.method(attribute).call || object.project_id != orig_rec.project_id
end
# Note that || short-circuits so the lambda will only be
# called if you have an existing record.
if object.new_record? || attribute_or_project_changed.call
object.errors[attribute] << (options[:message] || "must be unique in each project")
end
end
I don't know how much better that is than your original but the logic and control flow is a lot clearer to me due to the nicer chunking.
精彩评论