Why use Proc.new to call a method in a Rails callback? - ruby-on-rails

in all the tutorials for RoR I see instances where the coder chose to use Proc.new when seemingly it is both unnecessary and rather unattractive.
Example, here is a callback for placed in a model, one using Proc.new the other presumably doing the same thing:
class Order < ActiveRecord::Base
before_save :normalize_card_number,
:if => Proc.new { |order| order.paid_with_card? }
end
class Order < ActiveRecord::Base
before_save :normalize_card_number, :if => "paid_with_card?"
end
So what's the difference? Why use the Proc? Don't they both call the "paid_with_card?" method?
Thanks in advance

In the example above, using a symbol for the conditional method would probably be the best choice.
class Order < ActiveRecord::Base
before_save :normalize_card_number, :if => :paid_with_card?
end
The string option uses eval to evaluate the Ruby code in the string. So personally, I would prefer to use a symbol if calling a method or a Proc if writing a short inline condition.
Per the RailsGuides documentation:
Using a Proc object gives you the ability to write an inline condition instead of a separate method. This option is best suited for one-liners.
I think using a Proc might be better illustrated this way:
class Order < ActiveRecord::Base
before_save :normalize_card_number,
:if => Proc.new { |order| order.payment_type == "card" }
end
This would possibly eliminate the need for the paid_with_card? method.

I'd say it was the case that in older versions of Rails that's how we used to do things, and someone added the feature whereby you could pass a string to be evaluated as an instance method on the current model.
In simple scenarios it makes the older style redundant, but allows for the use of a Proc for more complex 'if' statements which would be impossible to achieve using a method on only the current instance.

Related

not sure why a Proc would be used here - not getting something simple

I understand the concept of a proc but sometime I see code like this (take from rails guide for validation http://guides.rubyonrails.org/active_record_validations_callbacks.html#using-if-and-unless-with-a-proc):
class Order < ActiveRecord::Base
before_save :normalize_card_number,
:if => Proc.new { |order| order.paid_with_card? }
end
it seems this could be more simply written as:
class Order < ActiveRecord::Base
before_save :normalize_card_number, :if => :paid_with_card?
end
What am I not getting about the advantage of using a Proc here?
thx in advance
In simple cases they are equivalent, but procs allow far more versatility without requiring a method to be defined simply for a validation if check.
Imagine this:
before_save :nuke, :if => Proc.new { |model| !model.nuked? && model.nukable? && model.region.nukable? }
You can always write that check in an instance method and reference it with a symbol, but for cases where the specific logic is only in the :if, it is valid to keep it in a proc.
They are equivalent if the receiver of the method is the object being validated. This isn't exactly how the ActiveModel validators work, but the concept is similar:
Calling to_proc on a symbol :sym gives you the functional equivalent of ->(x){ x.sym } - the symbol is sent as a message to the argument of the proc. Calling to_proc on a proc simply returns itself, so you can pass either a symbol or proc into a method and guarantee a proc:
def return_a_proc(symbol_or_proc)
symbol_or_proc.to_proc
end
In cases where the model instance is not the receiver, e.g. the validation method takes the model as an argument, or as in Daniel Evans' example, you need to explicitly construct the proc to specify what should be done with the proc's argument.

How to run validations of sub-class in Single Table Inheritance?

In my application, I have a class called Budget. The budget can be of many types.. For instance, let's say that there are two budgets: FlatRateBudget and HourlyRateBudget. Both inherit from the class Budget.
This is what I get so far:
class Budget < ActiveRecord::Base
validates_presence_of :price
end
class FlatRateBudget < Budget
end
class HourlyRateBudget < Budget
validates_presence_of :quantity
end
In the console, if I do:
b = HourlyRateBudget.new(:price => 10)
b.valid?
=> false
b.errors.full_messages
=> ["Quantity can't be blank"]
As, expected.
The problem is that the "type" field, on STI, comes from params.. So i need to do something like:
b = Budget.new(:type => "HourlyRateBudget", :price => 10)
b.valid?
=> true
Which means that rails is running validations in the super-class instead of instantiating the sub class after I set up the type.
I know that is the expected behaviour, since I'm instantiating a class that dosen't need the quantity field, but I wonder if there is anyway to tell rails to run the validations for the subclass instead of the super.
You could probably solve this with a custom validator, similar to the answer on this question: Two models, one STI and a Validation However, if you can simply instantiate the intended sub-type to begin with, you would avoid the need for a custom validator altogether in this case.
As you've noticed, setting the type field alone doesn't magically change an instance from one type to another. While ActiveRecord will use the type field to instantiate the proper class upon reading the object from the database, doing it the other way around (instantiating the superclass, then changing the type field manually) doesn't have the effect of changing the object's type while your app is running - it just doesn't work that way.
The custom validation method, on the other hand, could check the type field independently, instantiate a copy of the appropriate type (based on the value of the type field), and then run .valid? on that object, resulting in the validations on the sub-class being run in a way that appears to be dynamic, even though it's actually creating an instance of the appropriate sub-class in the process.
I've done something similar.
Adapting it to your problem:
class Budget < ActiveRecord::Base
validates_presence_of :price
validates_presence_of :quantity, if: :hourly_rate?
def hourly_rate?
self.class.name == 'HourlyRateBudget'
end
end
For anyone looking for example code, here's how I implemented the first answer:
validate :subclass_validations
def subclass_validations
# Typecast into subclass to check those validations
if self.class.descends_from_active_record?
subclass = self.becomes(self.type.classify.constantize)
self.errors.add(:base, "subclass validations are failing.") unless subclass.valid?
end
end
Instead of setting the type directly set the type like that... Instead, try:
new_type = params.fetch(:type)
class_type = case new_type
when "HourlyRateBudget"
HourlyRateBudget
when "FlatRateBudget"
FlatRateBudget
else
raise StandardError.new "unknown budget type: #{new_type}"
end
class_type.new(:price => 10)
You could even transform the string into its class by:
new_type.classify.constantize but if it's coming in from params, that seems a bit dangerous.
If you do this, then you'll get a class of HourlyRateBudget, otherwise it'll just be Budget.
Better yet, use type.constantize.new("10"), however this depends on that the type from params must be correct string identical to HourlyRateBudget.class.to_s
I also required the same and with the help of Bryce answer i did this:
class ActiveRecord::Base
validate :subclass_validations, :if => Proc.new{ is_sti_supported_table? }
def is_sti_supported_table?
self.class.columns_hash.include? (self.class.inheritance_column)
end
def subclass_validations
subclass = self.class.send(:compute_type, self.type)
unless subclass == self.class
subclass_obj= self.becomes(subclass)
self.errors.add(:base, subclass_obj.errors.full_messages.join(', ')) unless subclass_obj.valid?
end
end
end
Along the lines of #franzlorenzon's answer, but using duck typing to avoid referencing class type in the super class:
class Budget < ActiveRecord::Base
validates_presence_of :price
validates_presence_of :quantity, if: :hourly_rate?
def hourly_rate?
false
end
end
class HourlyRateBudget < Budget
def hourly_rate?
true
end
end

How to remove validation using instance_eval clause in Rails?

I would like to enhance existing class using instance_eval. There original definition contains validation, which require presence of certain fields, ie:
class Dummy < ActiveRecord::Base
validates :field, :presence => true
end
Now I want to change that to optional using instance_eval (or any other method, really):
Dummy.instance_eval do
...
end
What would be the proper syntax to remove the validation, so the field is optional. I would rather do this directly on the model layer, instead doing weird hacks in controllers or views. The use of instance_eval is not really required, but as far as I know, this is generally the best way to enhance classes in Rails.
Edit #1
In general - the original class is part of the gem and I don't want to fork it, nor tie to specific release. The general cause is not really important. Simply editing the original model has far worse consequences than monkey patching.
I found a solution, not sure how solid it is, but it works well in my case. #aVenger was actually close with his answer. It's just that the _validators accessor contains only information used for reflection, but not the actual validator callbacks! They are contained in the _validate_callbacks accessor, not to be confused with _validations_callbacks.
Dummy.class_eval do
_validators.reject!{ |key, _| key == :field }
_validate_callbacks.reject! do |callback|
callback.raw_filter.attributes == [:field]
end
end
This will remove all validators for :field. If you want to be more precise, you can reject the specific validator for _validators which is the same as the raw_filter accessor of validate callbacks.
I think this is the most actual solution at this moment (I'm using rails 4.1.6):
# Common ninja
class Ninja < ActiveRecord::Base
validates :name, :martial_art, presence: true
end
# Wow! He has no martial skills
Ninja.class_eval do
_validators[:martial_art]
.find { |v| v.is_a? ActiveRecord::Validations::PresenceValidator }
.attributes
.delete(:martial_art)
end
Easest way to remove all validations:
clear_validators!
As I was trying to do this to remove the phone validation from the spree Address model, below is the code I got to work. I added the type check for callback.raw_filter because I only wanted to remove the presence validator on the phone field. I also had to add it because it would fail when trying to run against one of the other validators specified in the Spree::Address model that did not have an 'attributes' key for callback.raw_filter, thus an exception was thrown.
Spree::Address.class_eval do
# Remove the requirement on :phone being present.
_validators.reject!{ |key, _| key == :phone }
_validate_callbacks.each do |callback|
callback.raw_filter.attributes.delete :phone if callback.raw_filter.is_a?(ActiveModel::Validations::PresenceValidator)
end
end
I had a similar problem and was able to get past it using:
class MyModel << Dummy
# erase the validations defined in the plugin/gem because they interfere with our own
Dummy.reset_callbacks(:validate)
...
end
This is under Rails 3.0. The caveat: It does remove ALL validations, so if there are others you want to keep you could try Dummy.skip_callback(...), but I could not figure out the right incantation of arguments to make that work.
One solution is to extend validates :
#no need of instance_eval just open the class
class Dummy < ActiveRecord::Base
#validates :field, :presence => true
def self.validates(*attributes)
if attributes.first == :field #=> add condition on option if necessary
return # don't validate
else
super(*attributes) #let normal behavior take over
end
end
end
And no that's not monkey-patching but extending or decorating a behavior. Rails 3.1 is built on the idea of "multi- inheritance" with module inclusion, specifically to allow this kind agility.
update #2
One caveat is you must load the class with the redefined validates method before the gem containing the call to validates. To do so, require the file in config/application.rb after require "rails/all" as suggested in the railsguides. Something like that :
require File.expand_path('../boot', __FILE__)
require 'rails/all' # this where rails (including active_record) is loaded
require File.expand_path('../dummy' __FILE__) #or wherever you want it
#this is where the gems are loaded...
# the most important is that active_record is loaded before dummy but...
# not after the gem containing the call to validate :field
if defined?(Bundler)
Bundler.require *Rails.groups(:assets => %w(development test))
end
Hope it works now!
Answer by aVenger has problems when you declare validations of more than one attribute in a line:
validates :name, :message, :presence => true
That's because this line creates a raw_filter with more than one attribute in attributes filter:
Model.send(:_validate_callbacks)
=> [#<ActiveSupport::Callbacks::Callback:0xa350da4 #klass=Model(...), ... , #raw_filter=#<ActiveModel::Validations::PresenceValidator:0x9da7470 #attributes=[:name, :message], #options={}>, #filter="_callback_before_75", #compiled_options="true", #callback_id=76>]
We have to delete the desired attribute from that array and reject the callbacks without attributes
Dummy.class_eval do
_validators.reject!{ |key, _| key == :field }
_validate_callbacks.each do |callback|
callback.raw_filter.attributes.delete :field
end
_validate_callbacks.reject! do |callback|
callback.raw_filter.attributes.empty? ||
callback.raw_filter.attributes == [:field]
end
end
I have this working on a Rails 3.2.11 app.
For rails 4.2 (~ 5.0) it can be used the following module with a method:
module ValidationCancel
def cancel_validates *attributes
attributes.select {|v| Symbol === v }.each do |attr|
self._validators.delete( attr )
self._validate_callbacks.select do |callback|
callback.raw_filter.try( :attributes ) == [ attr ] ;end
.each do |vc|
self._validate_callbacks.delete( vc ) ;end ;end ;end ;end
Note: Since the filtern can be a symbol of an association, or a specific validator, so we have to use #try.
Then we can use rails-friendly form in a class declaration:
class Dummy
extend ValidationCancel
cancel_validates :field ;end
Note: since removal of the validator is affecting to the whole class and its descendants globally, it is not recommended to use it to remove validations in such way, instead add if clause for the specific rule as follows:
module ValidationCancel
def cancel_validates *attributes
this = self
attributes.select {|v| Symbol === v }.each do |attr|
self._validate_callbacks.select do |callback|
callback.raw_filter.try( :attributes ) == [ attr ] ;end
.each do |vc|
ifs = vc.instance_variable_get( :#if )
ifs << proc { ! self.is_a?( this ) } ;end ;end ;end ;end
This restricts execution of the validation callback for the specified class and its descendants.
If you doesn't want to make any changes in Parent class then first clear all validations in child class and copy all required validation from parent class to child class
class Dummy < ActiveRecord::Base
validates :property, presence: true
validates :value, length: { maximum: 255 }
end
And override it in child class
Dummy.class_eval do
clear_validators!
validates :property, presence: true
end
If you really want to do this then here would be a good place to start digging: https://github.com/rails/rails/blob/ed7614aa7de2eaeba16c9af11cf09b4fd7ed6819/activemodel/lib/active_model/validations/validates.rb#L82
However, to be honest, inside of ActiveModel is not where I'd be poking with a stick.
If you can edit the constraint on the original model to put an :if => :some_function on it, you can easily change the behavior of the function it calls to return false. I tested this and it works pretty easily:
class Foo < ActiveRecord::Base
validates :field, :presence => true, :if => :stuff
attr_accessor :field
def stuff
return true;
end
end
and then somewhere else:
Foo.class_eval {
def stuff
false
end
}
Why not use #dummy.save_without_validation method to skip validations altogether? I prefer do something like this:
if #dummy.valid?
#dummy.save # no problem saving a valid record
else
if #dummy.errors.size == 1 and #dummy.errors.on(:field)
# skip validations b/c we have exactly one error and it is the validation that we want to skip
#dummy.save_without_validation
end
end
You could put this code in your model or in the controller, depending on your needs.
In Rails 4.1,
I was able to do _validate_callbacks.clear. In my case, I wanted all the validations for a gem removed, so I could create my own. I did this in a module that was patched into the class.
Module #Name
extend ActiveSupport::Concern
included do
_validate_callbacks.clear
#add your own validations now
end
end
Wanted to add that, if you're trying to clear validations on a instance of your Model (not the entire model class), don't do my_dummy._validate_callbacks.clear, as that will clear validations on every instance (and future instance) of your Dummy model class.
For just the instance (and if you wanted to reinstate the validations later), try the following:
Create a copy of the validate callbacks (if you want to reinstate later):
my_dummy_validate_callbacks = my_dummy._validate_callbacks.clone
Set the validate callbacks on your instance to empty:
my_dummy._validate_callbacks = {}
Do what you want on my_dummy validation free!
Reinstate the callbacks: my_dummy._validate_callbacks = my_dummy_validate_callbacks
I'd have to look more into the code and help, but I'm thining it might be possible to inspect the list of validators of the class, and then modify the entry for the validation you want to change to add in an :if => :some_function conditional to it.
You'll need to do it only once for production (so it can be put inside an initializer, but for development you'll need to put it in the model, or somewhere else that will get loaded each time the corresponding model is (perhaps an observer?).
(I'll edit the answer with more information as I come to research it.)
Every Rails validator, pre-defined or custom, is an object, and is expected to respond to #validate(record) method. You can monkey patch or stub this method.
# MyModel.validators_on(:attr1, :attr2, ...) is also useful
validator = MyModel.validators.detect do |v|
validator_i_am_looking_for?(v)
end
def validator.validate(*_)
true
end
# In RSpec you can also consider:
allow(validator).to receive(:validate).and_return(true)
Tested in Rails 5.1.
Don't do this unless you understand what you're doing ;)
This does not directly answer the question but here's an option you should consider in such a situation: instead of disabling validation, you could set the required fields in a before_validation hook.
Since you don't need those required fields, set them with some dummy data that satisfies the validation and forget about them.
No ugly monkey patching.
Assuming the original implementation of Dummy is defined in an engine there is a nasty hack that will do what you want. Define Dummy in your application to keep the original implementation of Dummy from being auto-loaded. Then load the source to Dummy and remove the line that does the validation. Eval the modified source.
Put the following in your app/models/dummy.rb
class Dummy < ActiveRecord::Base
end
# Replace DummyPlugin with name of engine
engine = Rails::Application::Railties.engines.find { |e| e.class == DummyPlugin::Engine }
dummy_source = File.read File.join(engine.config.root, "app", "models", "dummy.rb")
dummy_source = dummy_source.gsub(/validates :field, :presence => true.*/, "")
eval dummy_source
If it is regular gem instead of an engine the same concept would apply, just would need to load the source for Dummy from the gem root instead of the engine root.

How to call ActiveRecord validators as instance methods (ala Sequel)?

I've got a model that needs different validators depending on its current state. How should I go about calling ActiveRecord validators per instance? I'd like to reuse as much plumbing as possible, but I'm not sure how to continue.
class Order < ActiveRecord::Base
attr_accessible :state
validate :state_specific_validations
def state_specific_validations
if :paid == self.state
# Warning: here be Unicorns...
# Wishful thinking...
validate_presence_of :paid_at
validate_associated :purchaser
# Hopeful. What are the validators called internally in Rails?
errors << PresenceValidator.new(self, :paid_at).valid?
errors << AssociationValidator.new(self, :paid_at).valid?
# Plan B
# ... Hoping for help from the audience ...
else
# Even more complicated validator logic, hoping for some DRY validators
end
end
end
I could just use custom validators, but why would I need to duplicate all the built-in validator logic (i18n error messages, etc.)?
Is there a neat way of calling the Rails validators as instance methods?
I think Sequel's approach of instance-based validators is more reasonable than ActiveRecord's class-based, but I'm not here to judge. I'd just like to get back to solving more interesting problems. I'm just hoping that others have come across this and can point me to some interesting gist or gem.
I'm pretty sure all of the validate_* methods can take an :if option - which can point to another method (and likely accept a Proc as well), so you could break up your validations to be more something like:
validates_presence_of :paid_at, :if => :paid?
validates_association :purchaser, :if => :paid?
To clean things up further, there's the with_options helper:
with_options :if => :paid? do |v|
v.validates_presence_of :paid_at
v.validates_association :purchaser
end
Not sure if either can be used with a standard validate :custom_validate_method though - but it wouldn't surprise me.
Is there any reason why this is inappropriate? It seems like it might work, but maybe metaprogramming warped my brain...
class Order < ActiveRecord::Base
attr_accessible :state
validate :state_specific_validations
def state_specific_validations
if :paid == self.state
class << self
validate_presence_of :paid_at
validate_associated :purchaser
end
end
end
end
Worst part is that tests are passing, so I'm not sure if I solved it or I need better tests. For example, I'm not 100% positive this singleton modification does not affect other Orders.
sigh Need some sleep.

Ruby class evaluation, validates_inclusion_of with dynamic data

If I have an ActiveRecord model as follows
class Foo < ActiveRecord::Base
validates_inclusion_of :value, :in => self.allowed_types
def self.allowed_types
# some code that returns an enumerable
end
end
This doesn't work because the allowed_types method hasn't been defined at the time where the validation is evaluated. All the fixes I can think of basically all revolve around moving the method definition above the validation so that it's available when needed.
I appreciate that this may be more of a coding style question than anything (I want all my validations at the top of the model and methods at the bottom) but I feel there should be some kind of solution to this, possibly involving lazy evaluation of the initial model load?
is what I want to do even possible? Should I just be defining the method above the validation or is there a better validation solution to acheive what I want.
You should be able to use the lambda syntax for this purpose. Perhaps like this:
class Foo < ActiveRecord::Base
validates_inclusion_of :value, :in => lambda { |foo| foo.allowed_types }
def allowed_types
# some code that returns an enumerable
end
end
This way it will evaluate the lambda block at every validation and pass the instance of Foo to the block. It will then return the value from allowed_types in that instance so that it can be validated dynamically.
Also note that I removed self. from the allowed_types method declaration because that would create a class method instead of an instance method which is what you want here.
The :in option of the validates_inclusion_of method doesn't seem to accept a lambda or Proc. Here's another approach:
validates_each :product_id do |record, attrib, value|
begin
Product.find(value)
rescue ActiveRecord::ActiveRecordError
record.errors.add attrib, 'must be selected from list.'
end
end

Resources