Before updating the record, I need to do some checks. As a result of one of the checks, I need to destroy the record that is being updated.
I have two questions.
How good is this solution? (I need to delete a record on update because a similar record was found among the old ones)
What is the correct way to implement cancellation of an update after a record has been destroyed?
I wrote this simple code:
return yield unless title_changed?
tmp_destroyed = false
# some code
tmp_destroyed = true if destroy!
# some code
return if tmp_destroyed
yield
But I'm not sure if this is the right decision.
Can you please tell me if I'm doing everything right? And did I choose the right way for the solution?
I would suggest, before updating the record, check your conditions in controller and if your conditions is true then delete the record instead of checking in model callback methods.
Related
CODE
# Item Model
class Item < ActiveRecord::Base
attr_accessor :paid_amount
after_save :amount_processed?
def amount_processed?
if self.try(:paid_amount)
return true
else
return false
end
end
end
# Controller snippet
...
# params = {"paid_amount" => 10}
#item.assign_attributes(params)
if #item.valid?
#item.save
end
...
Currently the callback is not running, i.e., the code never checks amount_processed?. The reason this is happening is because paid_amount isn't a db attribute for Item. But that is by design. The question is ASSUMING this design were to stay, would there be a way for me to run a callback to check amount_processed? simply based on the fact that the attribute was passed? (i.e., if you run #item.paid_amount you'd get "10" after the #item.assign_attributes).
Note that the following callbacks will not work:
after_save or after_touch because as above, the paid_amount is never saved so the #item is never updated
after_find because this runs, by definition, before the attribute assignment. So with this validation, even though amount_processed? is checked, when it is checked, #item.paid_amount = nil
Would love to combine the two...
Since the question asks how to do this GIVEN current design, a perfectly acceptable answer is to say in the current design, it's not possible. The callback will only work if the attribute is actually updated. In that case, I already have 2 strategies to tackle this, the easiest of which being moving amount_processed? to the controller level so I can check the paid_amount after the assign_attributes. The other strategy is to have a Child of Item, but this is dependent on other info about the code that, for simplicity's sake, I have withheld.
Thanks!
Ook I think I have the answer here, thanks for the comments. Willem is right, in the current design, I can ensure amount_processed? is run by using a custom validation, changing the callback to:
validate :amount_processed?
However, doing so then makes the code a bit hacky, since I'm co-opting a validation to do the work of a callback. In other words, I would have to ensure amount_processed? always returned true (at end of the if statement; obviously other work would be done with paid_amount). There are some other considerations as well looking holistically at my code.
Given that, may change the design... but this was still a very helpful exercise
I am still new to Ruby on Rails and in my models I often do this:
def activate
update_column(:activated, true)
update_column(:activated_at, Time.zone.now)
update_column(:activation_token, nil)
end
What difference does it make if I instead do this?
def activate
self.activated = true
self.activated_at = Time.zone.now
self.activation_token = nil
save!(:validate => false)
end
I still don't unterstand the difference between these two approaches. Which one is faster or more efficient from a database point of view?
When you call update_column as soon as the call is made, a query is generated and executed on the database. So you end up with three update queries.
But when you change the object's attributes using the second method, and then finally call save, a single query will be generated and executed to make all the changes made on the object (representing the record).
So in terms of effeciency you should always go with the second one. Remember the lesser the number of writes or queries on the database the better!
update_column :
Updates a single attribute of an object, without calling save.
Validation is skipped.
Callbacks are skipped.
updated_at/updated_on column is not updated if that column is available.
will fire 1 query for each update_column call.
where as
save :
will perform validations, callbacks, update updated_at column and will fire a single query.
For your case second option is definitely the better one and yes, you should remove :validate => false form save to avoid any validation issues.
I call first_or_create like so:
collection = Collection.first_or_create(:title => title)
Is there a way to determine if the result is an existing entry or a freshly created one? So far the best solution I've come up with is to use first_or_initialize:
collection = Collection.first_or_initialize(:title => title)
if collection.id.nil?
<process>
collection.save
end
But this feels a bit hacky. Is there a way to get this information directly from first_or_create?
first_or_create takes a block that'll only be executed if a new record is created, you can set some flag inside that block to indicate it's a new record, for example
MyObject.where(attr: "value").first_or_create do |obj|
#my_object_created = true
end
As far as I know you can't know. Two options are to check the created_at time (unreliable), or instead use first_or_initialize, then check to see if new_record? is true, and if so, do your other operations and then call save!. This may be the best approach for you anyway, since you may very well not want to finalize the save until the other relations are saved, and you probably want to do all of that in the same database transaction.
Using first_or_create you can't know for sure is it a newly created object or one from the database. Possible tricky solution is to compare created_at value with current time. This works if you don't create objects often.
Btw, why you need to know is it the newly created object or not?
So there is
record.new_record?
To check if something is new
I need to check if something is on it's way out.
record = some_magic
record.destroy
record.is_destroyed? # => true
Something like that. I know destroying freezes the object, so frozen? sort of works, but is there something explicitly for this task?
Just do it:
record.destroyed?
Details are here ActiveRecord::Persistence
You can do this.
Record.exists?(record.id)
However that will do a hit on the database which isn't always necessary. The only other solution I know is to do a callback as theIV mentioned.
attr_accessor :destroyed
after_destroy :mark_as_destroyed
def mark_as_destroyed
self.destroyed = true
end
And then check record.destroyed.
This is coming very soon. In the latest Riding Rails post, it says this:
And finally, it's not necessarily
BugMash-related, but José Valim -
among dozens of other commits - added
model.destroyed?. This nifty method
will return true only if the instance
you're currently looking at has been
successfully destroyed.
So there you go. Coming soon!
destroying an object doesn't return anything other than a call to freeze (as far as I know) so I think frozen? is your best bet. Your other option is to rescue from ActiveRecord::RecordNotFound if you did something like record.reload.
I think Mike's tactic above could be best, or you could write a wrapper for these cases mentioned if you want to start 'making assumptions'.
Cheers.
While record.destroyed? works fine, and does return true or false, you can also DRY this up a little bit and create the if condition on the line you call destroy on in your controller.
record = Object.find(params[:id])
if record.destroy
... happy path
else
... sad path
end
Realize this post is a bit late in the game. But should anyone want to discuss this more, i'm game!
Side note: I also had an after_destroy validation on my model and while it worked, a separate method for something like this seems like overkill ;)
Without knowing more of the logic of your app, I think that frozen? is your best bet.
Failing that, you could certainly add a "destroyed" attribute to your models that you trigger in the callbacks and that could be checked against if you want a more precise solution.
The short answer is:
record.destroyed?
# or...
Record.exists?(record) # also very fast!
You'd think that record.destroyed? is better because it doesn't send an extra database request but actually Record.exists? is so extremely fast, that this typically isn't a reason to prefer one over the other.
Don't use the return value of record.destroy, which will always return a frozen instance of the record, whether it's deleted or not, from right before you tried to deleted it. See here: https://apidock.com/rails/v5.2.3/ActiveRecord/Persistence/destroy
# Assuming no issues when destroying the record...
x = record_one.destroy
x.destroyed? # Would return false! (even though the record no longer exists in the db)
Record.exists?(x) # Would correctly return false
# vs.
record_two.destroy
record_two.destroyed? # Would correctly return true
Record.exists?(record_two) # Would correctly return false
If you're in a controller, destroy! will throw a ActiveRecord::RecordNotDestroyed, which you can catch, based on before_destroy callbacks.
def destroy
render json: #record.destroy!
rescue ActiveRecord::RecordNotDestroyed
# #record will work fine down here, it still exists.
render json: { errors: ["Record not destroyed"] }
end
If I add an after_save callback to an ActiveRecord model, and on that callback I use update_attribute to change the object, the callback is called again, and so a 'stack overflow' occurs (hehe, couldn't resist).
Is it possible to avoid this behavior, maybe disabling the callback during it's execution? Or is there another approach?
Thanks!
One workaround is to set a variable in the class, and check its value in the after_save.
Check it first. (if var)
Assign it to a 'false' value before calling update_attribute.
call update_attribute.
Assign it to a 'true' value.
end
This way, it'll only attempt to save twice. This will likely hit your database twice, which may or may not be desirable.
I have a vague feeling that there's something built in, but this is a fairly foolproof way to prevent a specific point of recursion in just about any application.
I would also recommend looking at the code again, as it's likely that whatever you're doing in the after_save should be done in before_save. There are times that this isn't true, but they're fairly rare.
Could you use the before_save callback instead?
I didn't see this answer, so I thought I'd add it in case it helps anyone searching on this topic. (ScottD's without_callbacks suggestion is close.)
ActiveRecord provides update_without_callbacks for this situation, but it is a private method. Use send to get access to it anyway. Being inside a callback for the object you are saving is exactly the reason to use this.
Also there is another SO thread here that covers this pretty well:
How can I avoid running ActiveRecord callbacks?
Also you can look at the plugin Without_callbacks. It adds a method to AR that lets you skip certain call backs for a given block.
Example:
def your_after_save_func
YourModel.without_callbacks(:your_after_save_func) do
Your updates/changes
end
end
Check out how update_attribute is implemented. Use the send method instead:
send(name.to_s + '=', value)
If you use before_save, you can modify any additional parameters before the save is completed, meaning you won't have to explicitly call save.
This code doesn't even attempt to address threading or concurrency issues, much like Rails proper. If you need that feature, take heed!
Basically, the idea is to keep a count at what level of recursive calls of "save" you are, and only allow after_save when you are exiting the topmost level. You'll want to add in exception handling, too.
def before_save
#attempted_save_level ||= 0
#attempted_save_level += 1
end
def after_save
if (#attempted_save_level == 1)
#fill in logic here
save #fires before_save, incrementing save_level to 2, then after_save, which returns without taking action
#fill in logic here
end
#attempted_save_level -= 1 # reset the "prevent infinite recursion" flag
end
Thanks guys, the problem is that I update other objects too (siblings if you will)... forgot to mention that part...
So before_save is out of the question, because if the save fails all the modifications to the other objects would have to be reverted and that could get messy :)
The trick is just to use #update_column:
Validations are skipped.
Callbacks are skipped.
updated_at/updated_on are not updated.
Additionally, it simply issues a single quick update query to the db.
http://apidock.com/rails/ActiveRecord/Persistence/update_columns
I had this problem too. I need to save an attribute which depends upon the object id. I solved it by using conditional invocation for the callback ...
Class Foo << ActiveRecord::Base
after_save :init_bar_attr, :if => "bar_attr.nil?" # just make sure this is false after the callback runs
def init_bar_attr
self.bar_attr = "my id is: #{self.id}"
# careful now, let's save only if we're sure the triggering condition will fail
self.save if bar_attr
end
Sometimes this is because of not specifying attr_accessible in models. When update_attribute wants to edit the attributes, if finds out they are not accessible and create new objects instead.On saving the new objects, it will get into an unending loop.
I had a need to gsub the path names in a block of text when its record was copied to a different context:
attr_accessor :original_public_path
after_save :replace_public_path, :if => :original_public_path
private
def replace_public_path
self.overview = overview.gsub(original_public_path, public_path)
self.original_public_path = nil
save
end
The key to stop the recursion was to assign the value from the attribute and then set the attribute to nil so that the :if condition isn't met on the subsequent save.
You can use after_save in association with if as follows:
after_save :after_save_callback, if: Proc.new {
//your logic when to call the callback
}
or
after_save :after_save_callback, if: :call_if_condition
def call_if_condition
//condition for when to call the :after_save_callback method
end
call_if_condition is a method. Define the scenario when to call the after_save_callback in that method