How to skip validations when using the `validate` method? - ruby-on-rails

I am using Ruby on Rails 3.2.9. I implemented a custom validator and I would like to skip validations (on creating a new object in a migration file) when using the validate method. In general I can use :without_protection => true as a parameter of the create! method but in my case (see the code below) it seems do not work: validation are not skipped.
class Article < ActiveRecord::Base
validate do
# custom validation code
end
end
How can I skip validations?

You'll have to ensure that all of the other validations are disabled in order for this to be effective. ActiveRecord cannot selectively disable validations, but can omit them entirely.
For instance:
class Article < ActiveRecord::Base
validate :something,
:if => :validations_are_turned_on?
validate :always
protected
def validations_are_turned_on?
!#validations_disabled
end
end
Tagging any of the non-essential validations with the appropriate :if condition should work. In this case if #validations_disabled is not set then all validations will run.

Without protection does not turn off validations, it allows you to mass-assign protected attributes. To save without validating:
new_record = Article.new
new_record.save(:validate => false)

Related

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

Is it possible to create a callback for a changed mongoid embedded document field in Ruby on Rails?

Is there a way to run a callback only if an embedded document field was changed?
Currently, the following runs the callback on a normal field only if it was changed:
class user
field :email, type: String
embeds_many :connections, cascade_callbacks: true
before_save :run_callback, :if => :email_changed?
before_save :run_connection_callback, :if => :connections_changed? # DOES NOT WORK
end
For anybody seeing this answer in 2015
In Mongoid 4.x model.changed? and model.changes exist and behave like their ActiveRecord counterparts.
Mongoid won't define the method connections_changed? for you, but you can define it yourself by using a virtual field in User to keep track of when an embedded connection gets changed. That is:
class User
# define reader/writer methods for #connections_changed
attr_accessor :connections_changed
def connections_changed?
self.connections_changed
end
# the connections are no longer considered changed after the persistence action
after_save { self.connections_changed = false }
before_save :run_connection_callback, :if => :connections_changed?
end
class Connection
embedded_in :user
before_save :tell_user_about_change, :if => :changed?
def tell_user_about_change
user.connections_changed = true
end
end
One shortcoming of this method is that user.connections_changed only gets set when the document is saved. The callbacks are cascaded in such a way that the Connection before_save callback gets called first and then the User before save callback, which allows the above code to work for this use case. But if you need to know whether any connections have changed before calling save, you'll need to find another method.

How can I put a conditional validation on an ActiveModel attribute that has several validations already?

I'm using Authlogic which by default puts a few validations(length, uniqueness, format, etc) on fields like login, email, and password.
I'd like to be able to skip all validations attached to one of the attributes if, say, another attribute is present.
Is this possible? Something like:
validate :email, :unless => :has_openid?
which will then skip the format, length, and uniqueness validations just on the email attribute.
I'm working with a Rails 3.1.x app and authlogic 3.1.0
Update: I was trying to follow this article, but I couldn't get it to work properly:
http://erikonrails.snowedin.net/?p=242
The way I've done this with authlogic is by passing a block to acts_as_authentic:
acts_as_authentic do |config|
with_options :unless => :has_openid? do
config.merge_validates_format_of_email_field_options
config.merge_validates_length_of_email_field_options
config.merge_validates_uniqueness_of_email_field_options
end
end
I've written a workaround and abstracted it out into a gem:
https://github.com/synth/conditional_attribute_validator
Gemfile:
gem 'conditional_attribute_validator', :git => "git://github.com/synth/conditional_attribute_validator.git"
Example:
class User
include ConditionalAttributeValidator
validate_attributes_with_condition :login, :password, :password_confirmation, :unless => :has_another_form_of_authentication?
end
Source:
def validate_attributes_with_condition(*args)
opts = args.extract_options!
raise "Must have an :if or :unless option" unless opts.has_key?(:if) or opts.has_key?(:unless)
merge_methods = self.methods.grep(/merge_.*_options/)
args.each do |field|
merge_methods.grep(/#{Regexp.quote(field)}/).each do |m|
self.send(m, opts)
end
end
end
Rails automagically creates the merge_attr_options methods based upon whatever validations have been specified in order to merge options into an existing validation. So I lookup these methods and iterate over them and check if that method applies to a particular field. If so, I call the merge_attr_options method and pass in the options.
I'm not too concerned about performance since this is just performed on initialization.

Rails 2.3.11 Create Model for Form And Use ActiveRecord Validation

In Rails 3 you simply include ActiveRecord modules in order to add validations to any non-database backed model. I want to create a model for a form (e.g. ContactForm model) and include ActiveRecord valiations. But you cannot simply include the ActiveRecord modules in Rails 2.3.11. Is there any way to accomplish the same behavior as Rails 3 in Rails 2.3.11?
If you just want to use the virtual class as a sort of validation proxy for more than one models, the following might help ( for 2.3.x, 3.x.x allows you to user ActiveModel as previously stated ):
class Registration
attr_accessor :profile, :other_ar_model, :unencrypted_pass, :unencrypted_pass_confirmation, :new_email
attr_accessor :errors
def initialize(*args)
# Create an Errors object, which is required by validations and to use some view methods.
#errors = ActiveRecord::Errors.new(self)
end
def save
profile.save
other_ar_model.save
end
def save!
profile.save!
other_ar_model.save!
end
def new_record?
false
end
def update_attribute
end
include ActiveRecord::Validations
validates_format_of :new_email, :with => /\A([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i
validates_presence_of :unencrypted_pass
validates_confirmation_of :unencrypted_pass
end
this way you can include the Validations submodule, which will complain that save and save! methods are not available if you attempt to include it before defining them. Probably not the best solution, but it works.

Validate model for certain action

I need to validate a model only for a certain action (:create). I know this is not a good tactic, but i just need to do this in my case.
I tried using something like :
validate :check_gold, :if => :create
or
validate :check_gold, :on => :create
But i get errors. The problem is that i cannot have my custom check_gold validation execute on edit, but only on create (since checking gold has to be done, only when alliance is created, not edited).
Thanx for reading :)
I'm appending some actual code :
attr_accessor :required_gold, :has_alliance
validate :check_gold
validate :check_has_alliance
This is the Alliance model. :required_gold and :has_alliance are both set in the controller(they are virtual attributes, because i need info from the controller). Now, the actual validators are:
def check_gold
self.errors.add(:you_need, "100 gold to create your alliance!") if required_gold < GOLD_NEEDED_TO_CREATE_ALLIANCE
end
def check_has_alliance
self.errors.add(:you_already, "have an alliance and you cannot create another one !") if has_alliance == true
end
This works great for create, but i want to restrict it to create alone and not edit or the other actions of the scaffold.
All ActiveRecord validators have a :on option.
validates_numericality_of :value, :on => :create
Use the validate_on_create callback instead of validate:
validate_on_create :check_gold
validate_on_create :check_has_alliance
Edit:
If you use validates_each you can use the standard options available for a validator declaration.
validates_each :required_gold, :has_alliance, :on => :create do |r, attr, value|
r.check_gold if attr == :required_gold
r.check_has_alliance if attr == :has_alliance
end
Like Sam said, you need a before_create callback. Callbacks basically mean 'execute this method whenever this action is triggered'. (More about callbacks here : http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html).
This is what you want in your model:
before_create :check_gold
# other methods go here
private # validations don't need to be called outside the model
def check_gold
# do your validation magic here
end
The above method is the simplest to do what you want, but FYI there's also a way to use a before_save callback to execute additional actions on creation:
before_save :check_gold_levels
# other methods
private
def check_gold_levels
initialize_gold_level if new? # this will be done only on creation (i.e. if this model's instance hasn't been persisted in the database yet)
verify_gold_level # this happens on every save
end
For more info on 'new?' see http://api.rubyonrails.org/classes/ActiveResource/Base.html#method-i-new%3F
You need to look into callbacks. Someone once told me this and I didn't understand what they meant. Just do a search for rails callbacks and you will get the picture.
In your model you need to do a callback. The callback you need is before_create and then before a object is created you will be able to do some logic for check for errors.
model.rb
before_create :check_gold_validation
def check_gold_validation
validate :check_gold
end
def check_gold
errors.add_to_base "Some Error" if self.some_condition?
end

Resources