Differences between 2 ways of Rails module included callbacks? - ruby-on-rails

I'm new to Rails. I have a model called AdvItem, basically what I want to do is to move all its validation statements to a module named AdvItemValidation. After some searches here's what I get:
module AdvItemValidation
extend ActiveSupport::Concern
included do
# validations
validates :link, presence: true
validate :check_valid_link
end
def check_valid_link
...
end
end
But I've just seen another way to do this:
module AdvItemValidation
extend ActiveSupport::Concern
def self.included(base)
# validations
base.validates :link, presence: true
base.validate :check_valid_link
end
def check_valid_link
...
end
end
So what is the difference between these 2 ways of implementation? And which way is better providing that I have a lot of default and custom validation statements?
PS: For the 1st way CodeClimate reports this message "Very complex code in AdvItemValidation definition outside of methods", but imho I see it much shorter.
Thanks for the explanations.

The form below
def self.included(base)
is the default callback called when a module is included in another module and class.
The other form
included do
is provided by ActiveSupport::Concern
If you are using active support and Concerns you should preference this form. If not you will be limited to the first form anyway.
The include from Concern is largely syntactic sugar, although it does handle dependencies more gracefully.
Some further explanation here http://api.rubyonrails.org/classes/ActiveSupport/Concern.html

Well the second code snippet is ruby ruby code.
def self.included(base)
# validations
base.validates :link, presence: true
base.validate :check_valid_link
end
It has no dependency on any library.
The first piece of code, has dependency on ActiveSupport so won't work without including that library.
included do
# validations
validates :link, presence: true
validate :check_valid_link
end

Related

How can I store a regex expression string in a helper method to use to validate several different fields?

I have several different fields that store different phone numbers in my rails app as a string. I can use the built-in rails format validator with a regex expression. Is there a way to place this expression in a helper method for all my phone number validations instead of having to open each model file and paste the expression string.
You can require any file from application.rb:
# application.rb
require Rails.root.join "lib", "regexes.rb"
# lib/regexes.rb
PHONE_NUMBER_REGEX = /your regex/
Then you simply use the constant wherever needed
You can alternatively make use of the built in autoload functionality of Rails, for example with the concern approach the other commenter laid out - the concern file is autoloaded by default, as are models, controllers, etc
Loading custom files instead of using the Rails' defaults might not seem idiomatic or the "rails way". However, I do think it's important to understand that you can load any files you want. Some people autoload the entire lib/ folder and subfolders (see Auto-loading lib files in Rails 4)
Another alternative is to place your code somewhere in the config/initializers folder, these files are automatically loaded at startup and you can define shared classes/modules/constants there
Add a custom validator app/validators/phone_validator.rb
class PhoneValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless value.to_s =~ /YOUR_REGEX_HERE/
record.errors.add attribute, 'must be a valid phone number'
end
end
end
Then in your models
class MyModel < ApplicationRecord
#phone: true tells it to use the PhoneValidator defined above
validates :phone_number, presence: true, phone: true
end
One way to do this is to create a concern that will be included in each model that has a phone number. In models/concerns, create a new file called something like phonable.rb.
# models/concerns/phonable.rb
module Phonable
extend ActiveSupport::Concern
VALID_PHONE_REGEX = /\A\+?\d+\z/ # Use your regex here
end
Now include the concern like this:
# models/my_model.rb
class MyModel < ApplicationRecord
include Phonable
validates :phone, format: {with: VALID_PHONE_REGEX}
...
end
Now that you have a Phonable concern, you can put any other phone-number-related logic here as well, such as parsing and the like. The advantage of this approach is that all your logic related to phone numbers will be available for use in the models that need it, and none of that logic will be available in models that don't need it.
You can put it in ApplicationRecord (application_record.rb)
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
VALID_PHONE_NUMBER_REGEX = /\d{10}/ # your regex here
end
And then you can use it in any model that inherits fro ApplicationRecord

DRY-ing up ActiveAdmin

I've got multiple resources in my ActiveAdmin installation that share quite a lot of the same traits, like:
The same or similar scopes
Equal or similar controller methods (action_methods, for example)
Similar attributes (with code blocks) in the show action
Similar attributes (with code blocks) in the edit action
What is the best way to avoid duplicating this functionality across the different resources?
I have set up decorators to avoid duplicating functionality in the index view, but I'm not sure if (and how?) this could be used in the other cases.
You can also extend your module. For example:
module AccountManageable
def has_manageable_account
permit_params :name, :email, :role, :avatar
filter :name, as: :string
filter :email, as: :string
# ... other DSL methods
end
end
and then in your admin
ActiveAdmin.register Admin do
extend AccountManageable
has_manageable_account
end
You need to extend the DSL with monkey patch:
module ActiveAdmin
# This is the class where all the register blocks are evaluated.
class ResourceDSL < DSL
def your_custom_method attr
#common code
end
end
end
Now you can use your_custom_method in your registered resource file.
https://github.com/activeadmin/activeadmin/blob/master/lib/active_admin/resource_dsl.rb

Duplicated Validation Across Form Objects and Models

Where do the basic validators lie when dealing with Form objects and regular Rails models?
Following the concept of decoupling forms from the persistence layer in Rails. I've setup a Form Object Cage that creates two objects together... say Animal and Plant.
Following Form Object examples from http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/ or https://github.com/solnic/virtus or https://github.com/makandra/active_type , each of these show the Form object itself has validations... no problem... part of the benefits include being able to validate objects in a more contextually aware way.
The issue:
class Animal < ActiveRecord::Base
validates :color, presence: true
validate :only_one_brown
private
def only_one_brown
if some_complex_thing
errors.add(:color, 'can not have more than one brown animal.')
end
end
end
class Plant < ActiveRecord::Base
validates :color, presence: true
end
class Cage
include Virtus.model # or ActiveType or whatever
include ActiveModel::Validations
attribute :bird_color, String
attribute :plant_color, String
validates :bird_color, presence: true
validates :plant_color, presence: true
def save
if valid?
animal.save!
plant.save!
true
else
false
end
end
def animal
#animal ||= Animal.new(color: bird_color)
end
def plant
#plant ||= Plant.new(color: plant_color)
end
end
How do I validate animal's "only one brown" rule without:
Too much duplication.
A lot of code to make Cage still act like an AR model
If we don't duplicate the validation code, when "only one brown" is false, Cage doesn't have an error for it... we'll raise, which requires the controller to catch and handle, which is bad.
If we do duplicate the code, and if there are several custom validations, we're duplicating a lot of code and each other form object that deals with Animal needs the duplicated validations now.
If we move the validation code out of Animal into Cage entirely, similar issue: all objects that interact with Animal need to know about the "only one brown" rule, which is just duplicating validators and opening up an easy way to forget to enforce it somewhere.
If we move Animal's error array up to Cage's, Animal's error is on :color, which is ambiguous to Cage, and shows an error on an attribute name the client never sent in. If you want to map Animal's error keys to Cage's, now you need to keep an map for each Form Object, feels stinky.
Are there any good patterns or ways to deal with this situation? I feel like it is very common when you start using Form Objects but all examples are quite trivial.
Thanks in advance!
At the end of point 3 on http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/ the author says: "As a bonus, since validation logic is often contextual, it can be defined in the place exactly where it matters instead of needing to guard validations in the ActiveRecord itself." I'm agree with Bryan Helmkamp, puts the validation where it matters, you don't need to duplicate it.
edited:
If I were you, I'll put the validation only on the ActiveRecord model. And I'll update the Cage class:
def save
if valid?
ActiveRecord::Base.transaction do
animal.save!
plant.save!
end
true
else
false
end
rescue Exception => exception
raise if valid?
false
end
And I'll add an errors method that returns the errors of Cage, Plant and Animal instances.
edited:
I think you can redefine the valid? method, and then errors works fine:
class Cage
include ActiveModel::Model
def valid_with_mymodels?
valid_without_mymodels? && animal.valid? && plant.valid?
animal.errors.each do |attribute, error|
self.errors.add :"bird_#{attribute.to_s}", error
end
plant.errors.each do |attribute, error|
self.errors.add :"plant_#{attribute.to_s}", error
end
errors.empty?
end
alias_method_chain :valid?, :mymodels
...
end
Just, be careful with the name of your attrs.
I'm not sure how works Virtus, with Rails 4 you can use ActiveModel::Model, if using rails 3 I need research.
edited:
If you are using Rails 3.2, you can't use ActiveModel::Model, but you get the same with this:
class Cage
extend ActiveModel::Naming
include ActiveModel::Conversion
include ActiveModel::Validations
...
end

How can I programmatically copy ActiveModel validators from one model to another?

I'm writing a library that will require programmatically copying validations from one model to another, but I'm stumped on how to pull this off.
I have a model that is an ActiveModel::Model with some validation:
class User < ActiveRecord::Base
validates :name, presence: true
end
And another model that I'd like to have the same validations:
class UserForm
include ActiveModel::Model
attr_accessor :name
end
Now I'd like to give UserForm the same validations as User, and without modifying User. Copying the validators over doesn't work, because ActiveModel::Validations hooks into callbacks during the validation check:
UserForm._validators = User._validators
UserForm.new.valid?
# => true # We wanted to see `false` here, but no validations
# are actually running because the :validate callback
# is empty.
Unfortunately, there doesn't seem to be an easy way that I can see to programmatically give one model another's validation callbacks and still have it work. I think my best bet is if I can ask Rails to regenerate the validation callbacks based on the validators that are present at a given moment in time.
Is that possible? If not, is there a better way to do this?
Checking into the code of activerecord/lib/active_record/validations/presence.rb reveals how this can be achieved:
# File activerecord/lib/active_record/validations/presence.rb, line 60
def validates_presence_of(*attr_names)
validates_with PresenceValidator, _merge_attributes(attr_names)
end
So I guess I would try to hook into validates_with with an alias_method
alias_method :orig_validates_with :validates_with
Now you have a chance to get ahold of the values passed, so you can store them somewhere and retrieve them when you need to recreate the validation on UserForm
alias_method :orig_validates_with, :validates_with
def validates_with(*args)
# save the stuff you need, so you can recreate this method call on UserForm
orig_validates_with(*args)
end
Then you should be able to just call UserForm.validates_with(*saved_attrs). Sorry this is not something you can just copy/paste, but this should get you started. HTH

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.

Resources