Rails ActiveRecord: validate single attribute
Asked Answered
M

5

41

Is there any way I can validate a single attribute in ActiveRecord?

Something like:

ac_object.valid?(attribute_name)
Method answered 26/1, 2011 at 12:33 Comment(0)
Z
46

You can implement your own method in your model. Something like this

def valid_attribute?(attribute_name)
  self.valid?
  self.errors[attribute_name].blank?
end

Or add it to ActiveRecord::Base

Zoophilia answered 26/1, 2011 at 12:43 Comment(4)
In fact, this approach might be problematic if you are using some form builders. For example, you may start showing errors on fields which have not been input at all.Failsafe
One could run valid? and clear out all errors that are not in the attribute in question instead. That might work in some cases.Bolero
This does right the opposite: it validates everything in the background. It is a rather bad practice for the given use case as it will validate the whole object for each attribute.Exeat
can i use this method in controller?Trudietrudnak
E
47

Sometimes there are validations that are quite expensive (e.g. validations that need to perform database queries). In that case you need to avoid using valid? because it simply does a lot more than you need.

There is an alternative solution. You can use the validators_on method of ActiveModel::Validations.

validators_on(*attributes) public

List all validators that are being used to validate a specific attribute.

according to which you can manually validate for the attributes you want

e.g. we only want to validate the title of Post:

class Post < ActiveRecord::Base

  validates :body, caps_off: true 
  validates :body, no_swearing: true
  validates :body, spell_check_ok: true

  validates presence_of: :title
  validates length_of: :title, minimum: 30
end

Where no_swearing and spell_check_ok are complex methods that are extremely expensive.

We can do the following:

def validate_title(a_title)
  Post.validators_on(:title).each do |validator|
    validator.validate_each(self, :title, a_title)
  end
end

which will validate only the title attribute without invoking any other validations.

p = Post.new
p.validate_title("")
p.errors.messages
#=> {:title => ["title can not be empty"]

note

I am not completely confident that we are supposed to use validators_on safely so I would consider handling an exception in a sane way in validates_title.

Euhemerize answered 20/11, 2014 at 18:42 Comment(1)
Using self.class.validators_on and taking the attribute name as parameter makes it more reusable.Exeat
Z
46

You can implement your own method in your model. Something like this

def valid_attribute?(attribute_name)
  self.valid?
  self.errors[attribute_name].blank?
end

Or add it to ActiveRecord::Base

Zoophilia answered 26/1, 2011 at 12:43 Comment(4)
In fact, this approach might be problematic if you are using some form builders. For example, you may start showing errors on fields which have not been input at all.Failsafe
One could run valid? and clear out all errors that are not in the attribute in question instead. That might work in some cases.Bolero
This does right the opposite: it validates everything in the background. It is a rather bad practice for the given use case as it will validate the whole object for each attribute.Exeat
can i use this method in controller?Trudietrudnak
I
13

I wound up building on @xlembouras's answer and added this method to my ApplicationRecord:

class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true

  def valid_attributes?(*attributes)
    attributes.each do |attribute|
      self.class.validators_on(attribute).each do |validator|
        validator.validate_each(self, attribute, send(attribute))
      end
    end
    errors.none?
  end
end

Then I can do stuff like this in a controller:

if @post.valid_attributes?(:title, :date)
  render :post_preview
else
  render :new
end
Islas answered 11/8, 2017 at 17:33 Comment(2)
There's a bug: errors.none? may refer to other errors (e.g. if this function ran for different attributes previously). Possible improvements: if you only care about a boolean flag and not the actual errors you may consider returning in the the loop as: return false unless self.errors[attribute].blank?. I think self[attribute] is nicer than send(attribute) but that's only personal preference. Also you may extract it to a concern rather than having it in a parent class (again, preferences).Exeat
@Exeat If you want it to return the status just of the specified columns (having run the validations on only the specified columns), I would recommend slicing errors before calling none?: errors.slice(*attributes).none?. This isn't how ActiveModel::Validations tends to handle things though. // For what it's worth, accessing attributes with [] is not the same as using send. The latter will call the accessor method, which is what you want 99/100. Also, short-circuiting means not all errors are available for the view to render. That's probably not desirable.Islas
C
1

Building on @coreyward's answer, I also added a validate_attributes! method:

class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true

  def valid_attributes?(*attributes)
    attributes.each do |attribute|
      self.class.validators_on(attribute).each do |validator|
        validator.validate_each(self, attribute, send(attribute))
      end
    end
    errors.none?
  end

  def validate_attributes!(*attributes)
    valid_attributes?(*attributes) || raise(ActiveModel::ValidationError.new(self))
  end
end
Ciaphus answered 5/4, 2021 at 14:16 Comment(0)
R
1

I had problems with other solutions due to validator.validate_each being a private method.

Here's a snippet that's working under Rails 6:

class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true

  def valid_attributes?(*attributes)
    attributes.each do |attribute|
      self.class.validators_on(attribute).each do |validator|
        validator.validate(self)
      end
    end
    errors.none?
  end
end
Remunerative answered 24/5, 2023 at 12:2 Comment(2)
A nice aspect of using validator.validate(self) over ...validate_each is that it will take allow_nil and allow_blank options into account. github.com/rails/rails/blob/…Sanatorium
A side-effect of using validate is that it may validate fields outside or your scope. Example: validates :email, :first_name, presence: true and call valid_attributes?(:email), this will then also validate first_name.Sanatorium

© 2022 - 2025 — McMap. All rights reserved.