10

For example, if I have a user model and I need to validate login only (which can happen when validating a form via ajax), it would be great if I use the same model validations defined in the User model without actually instantiating a User instance.

So in the controller I'd be able to write code like

User.valid_attribute?(:login, "login value")

Is there anyway I can do this?

humanzz
  • 897
  • 2
  • 10
  • 18

8 Answers8

20

Since validations operate on instances (and they use the errors attribute of an instance as a container for error messages), you can't use them without having the object instantiated. Having said that, you can hide this needed behaviour into a class method:

class User < ActiveRecord::Base
  def self.valid_attribute?(attr, value)
    mock = self.new(attr => value)
    unless mock.valid?
      return mock.errors.has_key?(attr)
    end
    true
  end
end

Now, you can call

User.valid_attribute?(:login, "login value")

just as you intended.

(Ideally, you'd include that class method directly into the ActiveRecord::Base so it would be available to every model.)

Milan Novota
  • 15,506
  • 7
  • 54
  • 62
  • 2
    Note that, per Timo's answer below, the line `return mock.errors.has_key?(attr)` should most definitely be `return (not mock.errors.has_key?(attr))`. Other than that the answer works. – Peter Berg Jan 21 '14 at 15:36
5

Thank you Milan for your suggestion. Inspired by it I created a simple module one can use to add this functionality to any class. Note that the original Milans suggestion has a logic error as line:

return mock.errors.has_key?(attr)

should clearly be:

return (not mock.errors.has_key?(attr))

I've tested my solution and it should work, but ofc I give no guarantees. And here's my glorious solution. Basically a 2-liner if you take away the module stuff.. It accepts method names as stings or symbols.

module SingleAttributeValidation

  def self.included(klass)
    klass.extend(ClassMethods)
  end

  module ClassMethods
    def valid_attribute?(attr, value)
      mock = self.new(attr => value)
      (not mock.valid?) && (not mock.errors.has_key?(attr.class == Symbol ? attr : attr.to_sym))
    end
  end
end
Timo
  • 3,335
  • 30
  • 25
3

To use your standard validation routines:


User.new(:login => 'login_value').valid?

If that does not work for you, build a custom class method for this:


class User < ActiveRecord::Base

  validate do |user|
    user.errors.add('existing') unless User.valid_login?(user.login)
  end

  def self.valid_login?(login)
    # your validation here
    !User.exist?(:login=> login)
  end
end
wvanbergen
  • 2,294
  • 15
  • 15
2

I had a hell of a time getting this to work in Rails 3.1. This finally worked. (Not sure if it's the best way to do it, I'm kind of a newb.). The problem I was having was that value was being set to type ActiveSupport::SafeBuffer, and was failing validation.

def self.valid_attribute?(attr, value)
  mock = User.new(attr => "#{value}") # Rails3 SafeBuffer messes up validation
  unless mock.valid?
    return (not mock.errors.messages.has_key?(attr))
  end
  return true
end
Excalibur
  • 3,258
  • 2
  • 24
  • 32
  • Ok, this is partially due to me using `ActionController::Base.helpers.sanitize(params[:email])` in the controller before passing it in to the model. But the `errors.messages.has_key?` is still Rails3 specific. – Excalibur Oct 30 '11 at 01:32
1

I have gone with the custom class solution but I just wanted to make sure there was no better way

class ModelValidator
  def self.validate_atrribute(klass, attribute, value)
    obj = Klass.new
    obj.send("#{attribute}=", value)
    obj.valid?
    errors = obj.errors.on(attribute).to_a
    return (errors.length > 0), errors 
  end
end

and I can use it like

valid, errors = ModelValidator.validate_attribute(User, "login", "humanzz")

humanzz
  • 897
  • 2
  • 10
  • 18
0
class User < ActiveRecord::Base
  validates_each :login do |record, attr, value|
    record.errors.add attr, 'error message here' unless User.valid_login?(value)
  end

  def self.valid_login?(login)
    # do validation
  end
end

Just call User.valid_login?(login) to see if login itself is valid

aivarsak
  • 279
  • 1
  • 6
0

An implementation of the 'valid_attribute' method you are suggesting:

class ActiveRecord:Base
  def self.valid_attribute?(attribute, value)
    instance = new
    instance[attribute] = value
    instance.valid?

    list_of_errors = instance.errors.instance_variable_get('@errors')[attribute]

    list_of_errors && list_of_errors.size == 0
  end
end
August Lilleaas
  • 54,010
  • 13
  • 102
  • 111
0

How about:

User.columns_hash.has_key?('login')