Executing custom validation before record created? - ruby-on-rails

I want to execute a custom validation before the record is created?
It looks as if this is the right method: before_validation_on_create. For example:
before_validation_on_create :custom_validation
But am not sure. Any help would be appreciated.

In rails 3
before_validation_on_create :do_something
has been replaced with:
before_validation :do_something, :on => :create

before_validation_on_create hooks happen before validation on create… but they aren't validations themselves.
What you probably want to do is use validate and a private method which adds to the error array. like this:
class IceCreamCone
validate :ensure_ice_cream_is_not_melted, :before => :create
private
def ensure_ice_cream_is_not_melted
if ice_cream.melted?
errors.add(:ice_cream, 'is melted.')
end
end
end

The following worked for me in Rails 5:
validate :custom_validation_method, :on => :create
Running
validate :custom_validation_method, :before => :create
gave me following error:
Unknown key: :before. Valid keys are: :on, :if, :unless, :prepend.

There's a great resource here for information on callbacks and the order they happen in:
http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html

Related

rails validates_uniqueness_of not working as it should

I have an account model like so
class Account < ActiveRecord::Base
end
I had some duplicates records on it in production so I've added this line to it
validates_uniqueness_of :owner_id, :on => :create
so it will only verify this on new records and NOT on save!
it works on localhost and even on console on production.
but I have a job that runs every 10 minutes that uses rails runner and it fails for
Validation failed: Owner has already been taken (ActiveRecord::RecordInvalid)
The runner does some actions on accounts and then saves them.
am I doing something wrong on this syntax ?
Only time I've seen this happen was when a controller "create" action was being sent as a :get request (instead of :post as it should be in REST). The result was the validation didn't realize it was doing a :create. Doesn't sound like what you've got here... but maybe similar?
FYI my fix at the time was to use if: :new_record? instead of on: :create until the request type could be fixed. You could try this.
Also, you can put a debug statement into the validation to interrogate the state of the object during that validation with e.g. if: -> { binding.pry } or if: -> { debugger } (depending on what you use to debug with, of course). Hopefully that will help figure out what the runner is doing different. You can ask the object if it's a new_record? or what changes it has to save, etc.
I have found that this method has no
:on => :create
only :if
so I've did this:
validates_uniqueness_of :owner_id, :if => Proc.new { |account| account.created_at > Time.now - 10.seconds }

Validation only in specific form

Is there any way to trigger validation only in specific forms(controller's action), not globally at every save or update?
Something like User.create(:validate=>true) flag.
Yes, you can supply conditionals to the validations, eg:
validates_presence_of :something, :if => :special?
private
def make_sepcial
#special = true
end
def special?
#special
end
Now all you have to do to turn on these validations is:
s = SomeModel.new
s.make_special
As you explained in the comments, you want to skip validation for new records. In that case, you can use thomasfedb's answer, but don't use the #special variable, but:
validates_presence_of :something, :if => :persisted?
This will validate only for saved Users, but not for new Users. See the API documentation on persisted?.
This is a bit old. But I found http://apidock.com/rails/Object/with_options to be a good way of handling this sort of behaviour.

Rails validation context

I need help with my ActiveRecord model. I have context based validations (mis)using the build-in context options for validations:
validates :foo, :on => :bar, :presence => true
model = Model.new
model.foo = nil
model.valid? # => true
model.save # works as expected
model.valid?(:bar) # => false
model.save(:context => :bar) # fails and returns false
But using my model in a accepts_nested_attributes_for :model and calling parent.save fails (the validation gets called and returns false), any suggestions or solutions?
Still no answer? To explain more about my problem: I have a model called Form which has many Fields. Users should see validation errors on submit, but the form should be saved anyway (with and without errors). There are different types of Fields, each with global validations (to ensure database consistency) and its own specific user-defined validations (to validate user-entered data). So my Fields look someway like that:
# Global validations, to ensure database consistency
# If this validations fail, the record should not be saved!
validates_associated :form, :on => :global
...
# Specific user-defined validations
# If this validations fail, the record should be saved but marked as invalid. (Which is done by a before_save filter btw.)
def validate
validations.each do |validation| # Array of `ActiveModel::Validations`, defined by the user and stored in a hash in the database
validation.new(:on => :specific).validate(self)
end
end
In my controller:
# def create
# ...
form.attributes = params[:form]
form.save!(:global)
form.save(:specific)
Is something similar possible in Rails using the built-in functionality? Btw this not my actual code, which is quite complicated. But I hope, you guys will get the idea.
Try conditional validations
class Customer
attr_accessor :managing
validates_presence_of :first_name
validates_presence_of :last_name
with_options :unless => :managing do |o|
o.validates_inclusion_of :city, :in=> ["San Diego","Rochester"]
o.validates_length_of :biography, :minimum => 100
end
end
#customer.managing = true
#customer.attributes = params[:customer]
#customer.save
"Ability to specify multiple contexts when defining a validation" was introduced in Rails 4.1 - check validate method, :on options description
Only for Rails 5+:
You are looking for
with_options on: :custom_event do
validates :foo, presence: true
validates :baz, inclusion: { in: ['b', 'c'] }
end
To validate or save use
model = YourModel.new
# Either
model.valid?(:custom_event)
# Or
model.save(context: :custom_event)
Change has_nested_attributes_for :model to accepts_nested_attributes_for :models.
Hope this helps.
Good luck.

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

Custom validation :on => :create not working

I have a custom validation method that I only want executed on create:
validate :post_count, :on => :create
def post_count
# validate stuff
end
However, it's getting fired on update (in addition to on create).
Does the :on => :create option not work with custom validation methods?
As far as I know, there's no :on option. Use
validate_on_create :post_count
instead. And there's validate_on_update also. You can read about this methods here.
This may be a Rails 2.x vs. Rails 3 issue but according to the Rails Guides on Validation the :on option is definitely valid (though I am fighting with why it's not firing for me in a similar way).

Resources