before_validation, on: :save vs before_save - ruby-on-rails

Say I have a model where I may need to manipulate some of its attributes before saving it:
class User < ActiveRecord::Base
attr_accessible :name, :email
# before_validation :set_name_from_email, on: :save
# OR
# before_save :set_name_from_email
def set_name_from_email
self.name ||= email
end
end
If I had to validates :name, presence: true then of course this would have to go in a before_validation. But if there is (as the code stands now) no chance of the callback affecting the validity of the object, is it better to put it in before_save?
It seems neater to have all your data manipulating callbacks in either one or the other bucket, in case the code changes and the callback now COULD affect validity, but then again it seems bad to run callbacks unnecessarily when calling things like .valid?.
Any strong opinions either way?

Normally I would place all data manipulating in the before_save since it is logical to have all data manipulations in one place (before saving).
However, if you would have validations on the name field in the future (even when the data manipulation does not affect validity) you should put your data manipulation in a before_validation, because you don't want storing data y in your db while validating data x.
You can read more about this here:
http://bashar3a.com/2011/09/02/activerecord-callback-gotchas-before_save-vs-before_validate/

Since you are not actually validating anything, but manipulating an attribute, you should use a before_save callback.
Custom validation methods usually add an error to the model, and your set_name_from_email is not doing that.

Unless you want to validate the value that your own method assigned to "name", you can use both.
But if your method can result to an invalid name you should use before_validation.

Related

setting mandatory field automatically in Rails from Model callback, when and how?

I have this model which has a mandatory field which needs to be automatically set just before save. I'm struggling with the correct way to implement this:
build the logic in the controller before the save (and have validates rule in model)
build the logic in a before_save callback and have validates rule in model, but this seems to late in the flow? I do get validation errors this way.
build the logic in a before_save callback and don't define validation for this particular field
do it any of the ways above and don't assign a validates rule for the particular field
I was working on 2 since this seems like the correct way to implement this. Was considering the usage of before_validation, but I don't know what would happen when my other fields don't get validated... this could cause double assignment of the same value..
code for 2 which gives a basic idea of what I'm trying to achieve:
#category.rb
class Category < ActiveRecord::Base
before_create :set_position_number
def set_position_number
highest = Category.maximum(:position)
self.position = highest.to_i + 1
end
end
I'm struggling with the correct way to implement this
The most efficient way will be to use an ActiveRecord callback hook, such as you've posted:
#app/models/category.rb
class Category < ActiveRecord::Base
before_create :your_action
private
def your_action
#fires before create
end
end
but this seems to late in the flow
As mentioned in the comments, you can see the order of the callbacks (and thus their order in the flow):
Thus, if you want to populate some data before you validate, and then validate that data, you'll be best using the before_validation callback:
#app/models/category.rb
class Category < ActiveRecord::Base
before_validation :set_position_number, on: :create
validates :position, ______________
private
def set_position_number
highest = Category.maximum(:position)
self.position = highest.to_i + 1
end
end
Remember, a Rails model just populates certain attributes which are then to be either saved to the db, or validated. Rails does not care where those attributes come from; populating them before_validation is a good a source as the controller.
If you are setting a value automatically and don't take user input, you don't need validation. Write a unit test.
If the field is something like a position value, then you should indeed set it in a before_create callback.

ActiveRecord, validates_uniqueness_of :name not catching non-uniquness if I have a capitalize method

I have a simple capitalize method so that when user submits a new band in the band page it returns it with the first letter capitalized.
Inside my Band class I also have a validates_uniqueness_of :band_name to see if there is already a band with the same entry. See code below:
class Band < ActiveRecord::Base
has_and_belongs_to_many :venues
validates :band_name, :presence => true
before_save :title_case
validates_uniqueness_of :band_name
private
def title_case
self.band_name.capitalize!
end
end
So if I type in someband, it creates it and displays it as Someband. If I type someband again, ActiveRecord sees it as unique and I'll get another Someband. The only way it works is if I type Someband. How would I remedy this situation?
I think what you want to do is this
validates_uniqueness_of :band_name, :case_sensitive :false, allow_blank: false
Take a look at http://api.rubyonrails.org/classes/ActiveRecord/Validations/ClassMethods.html
:case_sensitive - Looks for an exact match. Ignored by non-text
columns (true by default).
The reason your code doesn't work is because validations happen before the before_save callbacks are triggered. Check out the list of ActiveRecord::Callbacks for the order in which things are called.
MZaragoza's answer is a great option for making your validation work regardless of what casing your users might enter. It will prevent things like "someband" and "SomeBand" from being added. I recommend including that as part of your solution.
Another option very similar to the code you already have is to switch to using the before_validation callback:
before_validation :title_case
I highly recommend using the before_validation callbacks instead of before_save callbacks whenever data changes that may be relevant to your validation rules, regardless of what other changes you make. That ensures that you are checking that actual state of the model that you plan to save to the database.
You can use attribute setter instead of before_save callback to capitalize your value without postponing.
def band_name=(value)
self['band_name'] = value && value.mb_chars.capitalize
end

Is there a nice way to find the trigger method of an ActiveRecored validation?

I have a model that has multiple validations. I want to skip one validation only if it was triggered (in my case, by saving) from a certain method.
To clarify, I don't want to skip the rest of validations.
I can inspect the call stack by the caller array, but I wonder if it can be done in a nicer way?
Yes, this is what you're looking for. Here is an example:
class Model < ActiveRecord::Base
attr_accessor :skip_validation
validates :my_validation, unless: :skip_validation
validates :another_validation
end
Then, wherever you want to skip my_validation just set skip_validation to true on your object before you call save.
m = Model.new
m.skip_validation = true
m.save # my_validation will not run, and another_validation will run

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 do I define synthetic attributes for an ActiveRecord model?

I have an ActiveRecord model whose fields mostly come from the database. There are additional attributes, which come from a nested serialised blob of stuff. This has been done so that I can use these attributes from forms without having to jump through hoops (or so I thought in the beginning, anyway) while allowing forwards and backwards compatibility without having to write complicated migrations.
Basically I am doing this:
class Licence < ActiveRecord::Base
attr_accessor :load_worker_count
strip_attributes!
validates_numericality_of :load_worker_count,
:greater_than => 2, :allow_nil => true, :allow_blank => true
before_save :serialise_fields_into_properties
def serialise_fields_into_properties
...
end
def after_initialize
...
end
...
end
The problem I noticed was that I can't get empty values in :load_worker_count to be accepted by the validator, because:
If I omit :allow_blank, it fails validation complaining about it being blank
If I put in :allow_blank, it converts the blank to 0, which when fails on the :greater_than => 2
In tracking down why these blank values are getting to the validation stage in the first place, I discovered the root of the problem: strip_attributes! only affects actual attributes, as returned by the attributes method. So the values which should be nil at time of validation are not. So it feels like the root cause is that the synthetic attributes I added in aren't seen when setting which attributes to strip, so therefore I ask:
Is there a proper way of creating synthetic attributes which are recognised as proper attributes by other code which integrates with ActiveRecord?
I assume you are talking of the strip_attributes plugin; looking at the code, it uses the method attributes, defined in active_record/base.rb, which uses #attributes, which is initialized (in initialize) as #attributes = attributes_from_column_definition.
Maybe it's possible to hack ActiveRecord::Base somehow, but it would be a hard work: #attributes is also used when getting/putting stuff from/to db, so you would have to do a lot of hacking.
There's a much simpler solution:
before_validate :serialise_fields_into_properties
...
def serialise_fields_into_properties
if load_worker_count.respond_to? :strip
load_worker_count = load_worker_count.blank? ? nil : load_worker_count.strip
end
...
end
After all, this is what strip_attributes! does.
Wouldn't it be easier to just use Rails' serialize macro here?
class License < ActiveRecord::Base
serialize :special_attributes
end
Now you can assign a hash or array or whatever you need to special_attributes and Rails will serialize it a text field in the database.
license = License.new
license.special_attributes = { :beer => true, :water => false }
This will keep your code clean and you don't have to worry about serializing/deserializing attributes yourself.

Resources