Rails - Get old value in before_save - ruby-on-rails

I'm trying to get the old value in the before_save by adding "_was" to my value but it doesn't seem to work.
Here is my code:
before_save :get_old_title
def get_old_title
puts "old value #{self.title_was} => #{self.title}"
end
Both "title_was" and "title" got the new title just been saved.
Is it possible to get the old value inside before_save ?

The reason for you getting the same value is most probably because you check the output when creating the record. The callback before_save is called on both create() and update() but on create() both title and title_was are assigned the same, initial value. So the answer is "yes, you can get the old value inside before_save" but you have to remember that it will be different than the current value only if the record was actually changed. This means that the results you are getting are correct, because the change in question doesn't happen when the record is created.

Instead of before_save use before_update. It should work now.

So, the answer above might work but what if I wanted to get the previous value for some reason and use it to perform some task then you would need to get the previous value. In my case I used this
after_update do
if self.quantity_changed?
sku.decrement(:remaining, (self.quantity_was - self.quantity) * sku.quantity)
end
end
The _was and _changed? added to any of the columns would do the job to get the job done.

In rails 5.1.1 ActiveRecord::AttributeMethods::Dirty provides
saved_changes()
Link for saved_changes()
which returns a hash containing all the changes that were just saved.

Related

Fake a change for ActiveModel dirty or mimic 'touching' a column?

I can't think of a better way to title this Sorry!
Ultimately I have a callback that 99% of the time I only want to run when a particular list of attributes get changed. But in a couple of cases I'd love to be able to by pass my return unless previous_changes & watched_attributes.
Is there any way to mock a change to a particular attribute? Somehow set model.attribute_changed? to true?
I've been using model.touch but updated_at is a column I deliberately want to ignore.
Yes, you can check whether your attribute is changed or not by using following command
#object.column_changed? #=> true
You can get an array of changed attributes by using changed method
#object.changed #=> ['column_name']
You can try something like the following:
object.attribute_name_will_change!
object.changed << :attribute_name
puts object.attribute_name_changed?
puts object.changed?
resulting output is:
true
true
attribute_name is to be replaced with the name of your attribute.

ActiveRecord callback on when object has updated virtual attribute NOT in database

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

Save one attribute for a row in active record

There are like 5 ways to save a new value for an attribute in ActiveRecord. Unfortunately I could only get one way to work and I am not sure it is the most efficient way:
review = Review.find(id)
review.status = 'ok'
review.save!
I started with the update method, but for some strange reason that deleted my review row
review = Review.update(id, :status => 'ok)
review.save!
Any ideas?
You should use
review = Review.find_by_id(id)
review.update_column(:status, 'ok')
has you can see the docs.
Note: ActiveRecord::Base:update has been deprecated in Rails 3.2.8
The most efficient way to save a new value for an attribute is to simply do:
Review.update_attribute(id, :status => 'ok')
There is no need to call the save method after this because ActiveRecord automatically saves the value with the update_attribute method.
Now, keep in mind that this will only work if the change will only work if it passes the validations set on the object.
Check out this link for more info:
http://apidock.com/rails/ActiveRecord/Base/update/class
It should be
review.update_attributes(status: 'ok')

Track changes when updating a record

How do I check if the data is changed when I edit a record?
So before update
game.player=1
after update / edit
game.player=2
for example
how to track changes (check if changed) and do something in ruby on rails, when the data is changed.
Have a look at ActiveModel::Dirty
You can check if a model has been changed by doing:
game.changed?
Or an individual field like:
game.player_changed?
Both return a boolean value.
All changes are also kept in a hash. This comes in handy if you want to use the values.
game.changes #=> {:player => [1,2]}

Using the after_save callback to modify the same object without triggering the callback again (recursion)

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

Resources