Validate model for certain action - ruby-on-rails

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

Related

How to skip validations when using the `validate` method?

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)

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 to validate a record in table before saving in ruby on rails

I am new to Ruby on Rails
I have a scenario in which I have a form which has some fields. One of the field values I need to validate against a table which has the data .
I want to restrict the user from saving any data unless the field is validated with the table records.
Initially I added the code in controller to validate that but I have other fields which I need to validate as empty so it did not work .
Also I want the the validation error to be part of other errors.
I tried the below code in the model file
before_create :validate_company_id
def validate_company_id
cp = Company.find_by_company_id(self.company)
if #cp != nil
return
else
self.status ||= "Invalid"
end
end
But its not validating , could you help me how I can validate it .
regards
Surjan
The guys answered correctly, but provided the other way for solution. You could ask yourself: "Why doesn't my code get executed?"
First of all, you have errors in your code - #cp is undefined. Also, I don't know what are you trying to achieve with self.status ||= "Invalid".
You also don't have to use self when you're calling an attribute, but you do have to call it when you're assignig a new attribute value. So self.company is unnecessary, you can just use company.
I've also noticed you have the company_id attribute in your companies table. That's not neccessary, common convention is using just an id instead. If you don't want to alter your table you can set the id field on your model like so:
class Company < ActiveRecord::Base
set_primary_key :company_id
# ... the rest of your model code ...
end
After that you can use Company.find instead of Company.find_by_company_id.
Okay, let's say you have the following code after the fixes:
before_create :validate_company_id
def validate_company_id
cp = Company.find(company)
if cp != nil
return
else
self.status ||= "Invalid"
end
end
First of all I would like to use ternary operator here
before_create :validate_company_id
def validate_company_id
Company.find(company) ? return : self.status ||= "Invalid"
end
Isn't this cleaner? It does the exact same thing.
Now about that self.status of yours. If you would like to invalidate the object in ActiveModel you have to set some values in errors hash. You're in misconception if you think that a model with the status attribute of "Invalid" is invalid. It's still perfectly valid model in Rails.
So how do you invalidate?
You put some values into errors hash. You can also specify a message and the attribute you're validation error refers to.
So let's do it on your model
before_create :validate_company_id
def validate_company_id
Company.find(company) ? return : errors.add(:company,"Invalid Company ID")
end
Now if you try to save your model with invalid company_id it will still pass and get saved to the DB. Why is that?
It's because of the ActiveModels lifecycle. Your method gets called too late.
Here are all the callback methods you can use
Creating
before_validation
after_validation
before_save
around_save
before_create
around_create
after_create
after_save
Updating
before_validation
after_validation
before_save
around_save
before_update
around_update
after_update
after_save
Destroying
before_destroy
around_destroy
after_destroy
Notice how your method gets called long after the validation cycle. So you should not use before_create, but after_validation or before_validation callbacks instead.
And here we are with the working validation method of your model.
after_validation :validate_company_id
def validate_company_id
Company.find(company) ? return : errors.add(:company,"Invalid Company ID")
end
instead of using before_create. You can tell the model to use a custom method for validation as follows
validate :validate_company_id
def validate_company_id
cp = Company.find_by_company_id(self.company)
if cp.nil?
errors.add(:company, 'Invalid Company ID')
end
end
Inside you model, you can add custom validations as below:
validate :validate_company_id
def validate_company_id
Your validations
Add error messages can be added as below
errors.add(:company, "is invalid")
end

How should my Rails before_validation callbacks handle bad data?

I have several before_validation callbacks that operate on the attributes being set on my model. I run into trouble when I have a situation like this:
class Foo < ActiveRecord::Base
before_validation :capitalize_title
validates :title, :presence => true
def capitalize_title
title.upcase
end
end
I write a test to ensure that 'nil' title is not allowed, but the test gets an error because the nil.upcase is not defined. I'd like to handle this error, but I already have error handling that runs after before_validation callbacks.
I don't want to put checks around all of my before_validation callbacks to make sure the data exists, if I can avoid it.
Is there a clean or accepted way to deal with this type of situation?
Just check if you have a title. And don't forget to save the modified title.
def capitalize_title
title = title.upcase if title
end
If you need to patch things up with a before_validation hook then you're stuck with taking care of invalid data in two places. If your validation was complicated you could factor it into two pieces: one piece that has to be true before the before_validation can run and one piece that has to be true after the before_validation has run:
before_validation :mangle_data
validate :data_is_okay
#...
def mangle_data
return if(!data_is_mangleable)
#... mangle away
end
def date_is_okay
if(!data_is_mangleable)
# complain
end
if(!data_is_properly_mangled)
# complain some more
end
end
def data_is_mangleable
return false if(thing.nil?)
# etc.
end
def data_is_properly_mangled
# check that stuff that the before_validation hook doesn't
# have to care about.
end

How to set some field in model as readonly when a condition is met?

I have models like this:
class Person
has_many :phones
...
end
class Phone
belongs_to :person
end
I want to forbid changing phones associated to person when some condition is met. Forbidden field is set to disabled in html form. When I added a custom validation to check it, it caused save error even when phone doesn't change. I think it is because a hash with attributes is passed to
#person.update_attributes(params[:person])
and there is some data with phone number (because form include fields for phone). How to update only attributes that changed? Or how to create validation that ignore saves when a field isn't changing? Or maybe I'm doing something wrong?
You might be able to use the
changed # => []
changed? # => true|false
changes # => {}
methods that are provided.
The changed method will return an array of changed attributes which you might be able to do an include?(...) against to build the functionality you are looking for.
Maybe something like
validate :check_for_changes
def check_for_changes
errors.add_to_base("Field is not changed") unless changed.include?("field")
end
def validate
errors.add :phone_number, "can't be updated" if phone_number_changed?
end
-- don't know if this works with associations though
Other way would be to override update_attributes, find values that haven't changed and remove them from params hash and finally call original update_attributes.
Why don't you use before_create, before_save callbacks in model to restrict create/update/save/delete or virtually any such operation. I think hooking up observers to decide whether you want to restrict the create or allow; would be a good approach. Following is a short example.
class Person < ActiveRecord::Base
#These callbacks are run every time a save/create is done.
before_save :ensure_my_condition_is_met
before_create :some_other_condition_check
protected
def some_other_condition_check
#checks here
end
def ensure_my_condition_is_met
# checks here
end
end
More information for callbacks can be obtained here:
http://guides.rubyonrails.org/activerecord_validations_callbacks.html#callbacks-overview
Hope it helps.

Resources