开发者

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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜