Wes
Wes

Reputation: 33

How does Ruby on Rails execute a lambda without any arguments when validating an object?

Ruby Version

ruby 2.1.3p242 (2014-09-19 revision 47630)

Rails Version

rails-4.1.6

Example Object:

Job(id: integer, job_number: string, reviewed: boolean)

Example Model:

class Job < ActiveRecord::Base      
  validates :job_number, presence: true, if: -> { self.reviewed }
end

I'm trying to write something that helps with form input. For each input I call Job.validators_on(column). In this case Job.validators_on(:job_number) which returns

[#<ActiveRecord::Validations::PresenceValidator:0x007fc5c2a464f8
 @attributes=[:job_number],
 @options=
 {:if=>
 #<Proc:0x007fc5c2a46c50@blahblah/app/models/job.rb:2 (lambda)>}>
]

Now I take that validator and get the :if option so I can call the lambda and get true/false.

>Job.validators_on(:job_number).first.options[:if].call(Job.first)
ArgumentError: wrong number of arguments (1 for 0)
from blahblah/app/models/job.rb:2:in `block in <class:Job>'

Now I understand the fact that this lambda doesn't have any arguments and an arity of 0. I could also just fix this problem and add a param like:

validates :job_number, presence: true, if: -> (job) { job.reviewed }

or

validates :job_number, presence: true, if: Proc.new{ |job| job.reviewed }

I would like to avoid having to change all of the validations in the project to account for an argument, since I have A LOT of them. But how does the if: get executed within the scope of any Job object when you run .valid? and there are not any arguments on the lambda? If .valid? can do it, I can find a way to do it.

Upvotes: 3

Views: 293

Answers (1)

evanbikes
evanbikes

Reputation: 4171

If you have a block that you want to execute in the context of an object, you can call obj.instance_exec(&block).

So in your case:

job = Job.first
lamb = Job.validators_on(:job_number).first.options[:if]
job.instance_exec(&lamb)

This looks like a hack, and it is. Rails does not expect you to need to call validators manually. That is, this sounds like a problem that should be solved by the frontend, leaving Rails to just validate before saving and returning any errors for the frontend to display.

Upvotes: 4

Related Questions