rails prevent object creation in before_create callback - ruby-on-rails

I want to check some attributes of the new record, and if certain condition is true, prevent the object from creation:
before_create :check_if_exists
def check_if_exists
if condition
#logic for not creating the object here
end
end
I am also open for better solutions!
I need this to prevent occasional repeated API calls.

before_create :check_if_exists
def check_if_exists
errors[:base] << "Add your validation message here"
return false if condition_fails
end
Better approach:
Instead of choosing callbacks, you should consider using validation here.Validation will definitely prevent object creation if condition fails. Hope it helps.
validate :save_object?
private:
def save_object?
unless condition_satisifed
errors[:attribute] << "Your validation message here"
return false
end
end

You can use a uniqueness validator too... in fact is a better approach as they are meant for those situations.
Another thing with the callback is that you have to be sure that it returns true (or a truthy value) if everything is fine, because if the callback returns false or nil, it will not be saved (and if your if condition evaluates to false and nothing else is run after that, as you have written as example, your method will return nil causing your record to not be saved)
The docs and The guide

Related

Updating the record in two Active Record Callbacks

Ok, stumbled upon this weirdness. I have this in my user model.
after_create :assign_role, :subscribe_to_basic_plan
def assign_role
self.role = 1
self.save
end
def subscribe_to_basic_plan
self.customer_id = "hello"
self.save
end
(code is simplified for illustration purposes)
When I create my user and check it in the console I get role: 1, customer_id: nil. But!, if I remove saving from the first callback everything works fine.
after_create :assign_role, :subscribe_to_basic_plan
def assign_role
self.role = 1
end
def subscribe_to_basic_plan
self.customer_id = "hello"
self.save
end
produces role: 1, customer_id: "hello". So seems like it only reads the first .save in the callbacks. I would like to understand what is the exact behaviour and why. I spent a lot of time trying to pinpoint this and wouldn't want to stumble on something similar again.
EDIT:
Maybe this is helpful. When I use self.save! in subscribe_to_basic_plan I get an error and the record is not saved at all. Putting self.save! in the assign_role doesn't change anything, so the problem is definitely with the second .save.
This answer is theoretical since I'd need to see the full model code to be sure.
Most likely your first save in assign_role is failing for some reason. When it fails and returns falls that causes rails to skip all callbacks after it. Then your second callback never runs at all.
Possible solutions in my preferred order:
Don't use callbacks. Have your controller set those values before you save the model.
Use before_create so you aren't doing 3 saves of the exact same model in a row.
Combine your two callbacks into one callback with only one save.
Save using save(validate: false) in case it is failing on validation.

Before create callback, how to validate if duplicate record exists and if so return?

Before a record gets created, I want to validate that a similiar record doesn't exist.
When I say similar, I want to compare 3-4 columns to see if they have the same value, if they do, then don't save and report back a validation message.
This only occurs on creation, there might be duplicates during updates etc. but during creation I don't want to allow duplicates.
How can I do this? i.e. which callback is best for this, and how to add to the validation errors collection?
I would do something like this:
validate :has_similar_record, :on => :create
...
private
def has_similar_record
if <Class>.find_by_column1_and_column2_and_column3(column1, column2, column3).present?
self.errors.add(:base, "record already exists with similar values")
end
end
You can use first_or_create to chain multiple where clauses and if it is not present create it.
For eg.
Post.where(author: 'tom').where(subject: 'Rails is awesome').first_or_create!
This will raise exception
More info here, (Assuming you are using Rails 3.2+)
http://guides.rubyonrails.org/v3.2.14/active_record_querying.html#first_or_create
Update
Sorry i misunderstood your question. You don't want to create the object if it already exists. So you can use exists? method which can be called on a model or relation.
For eg.
post_exists = Post.where(author: 'tom').where(subject: 'Rails is awesome').exists?
If it returns true, we can add error to self
self.errors.add(:post, "already exists") if post_exists == true
This can be wrapped in a custom validator method which will return false if post_exists is true
def validate_existence_of_post
post_exists = Post.where(author: 'tom').where(subject: 'Rails is awesome').exists?
if post_exists == true
self.errors.add(:post, "already exists")
return false
end
true
end
More info about exists here http://guides.rubyonrails.org/v3.2.14/active_record_querying.html#existence-of-objects

Rails : model.save returns false but models.errors is an empty hash

I have a model object on which .save is returning false. It subsequently has a .errors property which is an empty hash. Shouldn't the hash contain a list of what went wrong? How else can I determine why the save is not working?
TY,
Fred
This means that one of your callbacks is probably stopping the save, but isn't listing a validation error.
Check the return values, especially of any before_ callbacks and make sure that they are not returning false
If they return false, then active record will stop future callbacks and return false from the save.
You can read a little bit about it here under "canceling callbacks"
1) Disable before_create, before_save, before_update and check where it saves the day
2) If rollback was caused by one of those methods, check that those methods return true when you don't plan to rollback.
For example if you set default value for boolean field to avoid nil, you would probably do it this way
def set_defaults_before_create
self.my_boolean_field ||= false
end
In this example method set_defaults_before_create always returns false and thus rollbacks your transaction. So refactor it to return true
def set_defaults_before_create
self.my_boolean_field ||= false
true
end

how does activerecord know to perform an insert or update?

Maybe some Ruby experts out there can shed some light on how activerecord know to do an insert or update when calling save(). What is the logic behind it? Does it check to see if the primary key is blank or something and if so does an insert, if not an update?
Whilst it's fine for some people to say "RTFM" I rather the more walk-through-but-still-entirely-useless-when-Rails-3-comes-out-and-changes-everything response:
How it works in Rails 2.3 (aka "today")
save calls create_or_update which looks like this:
def create_or_update
raise ReadOnlyRecord if readonly?
result = new_record? ? create : update
result != false
end
You can ignore the first line of this method as it only raises an error if the record is readonly (it isn't usually, but in the case of joins it may be). What we are interested in here is the second and third lines inside the method.
The second line calls new_record? which is defined as this:
# Returns true if this object hasn't been saved yet -- that is, a record for the object doesn't exist yet; otherwise, returns false.
def new_record?
#new_record || false
end
And the variable #new_record is set when the initialize (new calls initialize, and gives us a new object, some background Ruby-fu here) method is called.
So if this #new_record is true it'll call create and if it's false it'll call update which brings us to what you're after, I think.
Furthermore, when you find a record it does not call initialize and therefore does not set #new_record. If you noticed, the code behind new_record? was #new_record || false, meaning it will return false if #new_record was not set.
Let's say for example you want to find the last Forum record, so you would do Forum.last.
This calls the last method on the Forum class, which inherits from ActiveRecord::Base
last calls the find class method.
find calls find_last
find_last calls find_initial
find_initial calls find_every
find_every calls find_by_sql
and find_by_sql calls instantiate
You'll see here that nowhere along this change is #new_record set and thus any record obtained by find will not be a new record.
Hope this helps you understand.
It principaly relies on the new_record? method.
This method returns true if it's a new record and false if it's not.
In fact it's not really hard.
When you get an existing record, it's not new. So new_record? can direcly return false.
When you create a new record (Model.new), new_record? will return true. It's a new record.
When you save that new record, it's not new anymore. The internal variable #new_record gets updated. new_record? won't return true anymore.
To see when it happens, go to ActiveRecord::Base, line 2911
self.id ||= new_id
#new_record = false
id
end
Check out activerecord's doc here and the source code there.

calling custom validation methods in Rails

I just upgraded my rails to 2.3.4 and I noticed this with validations:
Lets say I have a simple model Company which has a name. nothing to it.
I want to run my own validation:
class Company < ActiveRecord::Base
validate :something
def something
false
end
end
saving the model actually works in this case.
The same thing happens if i override validate() and return false.
I noticed this in a more complex model where my validation was returning false, but the object was still saving...I tried it out in an essentially empty model and the same thing applied. Is there a new practice I am missing? This doesn't seem to be the case in some of my older rails code.
Your validations are executed when you use the validate method. However rails doesn't relies on the returned value.
It relies on if there are validations errors or not. So you should add errors when your model doesn't validates.
def something
errors.add(:field, 'error message')
end
Or, if the error is not related to a field :
def something
errors.add(:base, 'error message')
end
Then your model won't be saved because there are errors.
You're getting confused between validations and callbacks.
Validations are supposed to fail if there are any errors on the object, doesn't matter what the validation returns. Callbacks fail if they return false, regardless if they add any errors to object.
Rails uses calls valid? from save calls which does not check the result of any validations.
Edit: Rails treats validate :method as a callback, but valid? still doesn't check for their results, only for errors they added to the object.
I don't think this behaviour changed at all but I could be wrong. I don't think I've ever written a validation to return false before.
Just FYI errors.add_to_base('error message') has been deprecated in rails 3 and got replaced by
errors[:base] << "Error message"
Or
errors.add(:base, "Error message")

Resources