Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to add active record validation errors inside custom setter?

I have custom attribute setter in a Rails model in which i'm adding validation errors. However when record attributes are being updated 'true' returns as result, which kinda confusing to me. Any hints how to use validation errors inside custom setter?

Model:

class Post < ActiveRecord::Base
  attr_accessible :body, :hidden_attribute, :title

  def hidden_attribute=(value)
    self.errors.add(:base, "not accepted")
    self.errors.add(:hidden_attribute, "not_accepted")
    write_attribute :hidden_attribute, value unless errors.any?
  end
end

Console Output:

1.9.3p194 :024 > Post.last
  Post Load (0.2ms)  SELECT "posts".* FROM "posts" ORDER BY "posts"."id" DESC LIMIT 1
 => #<Post id: 1, title: "asdsaD", body: "la", hidden_attribute: nil, created_at: "2013-11-13 16:55:44", updated_at: "2013-11-13 16:56:06">
1.9.3p194 :025 > Post.last.update_attribute :hidden_attribute, "ka"
  Post Load (0.2ms)  SELECT "posts".* FROM "posts" ORDER BY "posts"."id" DESC LIMIT 1
   (0.0ms)  begin transaction
   (0.0ms)  commit transaction
 => true

I've made an example application for this case.

like image 982
Mikhail Nikalyukin Avatar asked Dec 06 '22 04:12

Mikhail Nikalyukin


2 Answers

Okay, i understood the core of the issue. It's not possible to do what i want to achieve, because all the validations errors cleared out as soon as validation process starts.

https://github.com/rails/rails/blob/75b985e4e8b3319a4640a8d566d2f3eedce7918e/activemodel/lib/active_model/validations.rb#L178.

Custom setter kicks in way too early :(

like image 114
Mikhail Nikalyukin Avatar answered Dec 22 '22 00:12

Mikhail Nikalyukin


In your setters, you can store the error messages in a temporary hash. Then you can create an ActiveRecord validation method to check if this temporary hash is empty and copy the error messages to errors.

For example,

def age=(age)
  raise ArgumentError unless age.is_a? Integer
  self.age = age
rescue ArgumentError
  @setter_errors ||= {}
  @setter_errors[:age] ||= []
  @setter_errors[:age] << 'invalid input'
end

Here's the ActiveRecord validation

validate :validate_no_setter_errors

def validate_no_setter_errors
  @setter_errors.each do |attribute, messages|
    messages.each do |message|
      errors.add(attribute, message)
    end
  end
  @setter_errors.empty?
end

To see this in action:

[2] pry(main)> p.age = 'old'
=> "old"
[3] pry(main)> p.save!
   (1.0ms)  BEGIN
   (1.2ms)  ROLLBACK
ActiveRecord::RecordInvalid: Validation failed: Age invalid input
[4] pry(main)> p.errors.details
=> {:age=>[{:error=>"invalid input"}]}
like image 31
Zack Xu Avatar answered Dec 22 '22 00:12

Zack Xu