When used correctly ActiveRecord::Callbacks provide a great way to trigger logic before and after the life cycle of your objects. One of my favorite callbacks is
As those of you who are familiar with Unix conventions are already aware,
touch is used to update a file's timestamp to the present moment. In Rails, calling touch on an ActiveRecord object triggers a modification on the
updated_at column with the current time.
Let's say we have an instance of a Receipt and we call
receipt.touch on it. The result is ActiveRecord executing a SQL statement:
UPDATE `receipts` SET `updated_at` = 'current_date_time' WHERE `receipts`.`id` = 1;
Okay, this is somewhat interesting, but why would we want to update that column without making any other modifications?
Enter the Client
Your client has called and is requesting a new feature. Her users have been adding items to their receipts, but they have been emailing her company to let someone know that the system really should be able to automatically tally up the various amounts for each item and persist that to the parent receipt itself.
This might sound like a fairly complex request; however, there is a relatively simple solution for this problem.
Touching (ActiveRecord) Base
class Item < ActiveRecord::Base belongs_to :receipt, touch: true validates_presence_of :price validates_numericality_of :price end
Here, we specify that items belong to a receipt and if an item is modified it should touch the parent receipt. We also guarantee that each item has a price and that the price is in fact numerical.
class Receipt < ActiveRecord::Base has_many :items accepts_nested_attributes_for :items, :allow_destroy => true validates_presence_of :taxes validates_numericality_of :taxes validates_length_of :items, :minimum => 1 before_save :recalculate_taxes after_touch :recalculate_items protected def recalculate_taxes self.amount = amount_total end def recalculate_items update_attribute(:amount, amount_total) end private def amount_total items.sum(:price) + self.taxes end end
Here, we see that receipts require a numerical value for taxes and at least one item in order to be considered valid. We also see the presence of two ActiveRecord::Callbacks, namely,
When the receipt itself is saved the value for taxes may have been altered, so we need to modify it's value in place before the call to save submits the changes to the database.
In this case, when a receipt is touched by an item, the receipt needs to recalculate it's amount attribute and update it without triggering further ActiveRecord::Callbacks. This is where
update_attribute comes in, performing a direct SQL
UPDATE on the specified column with the specified value. Please be aware,
update_attribute can be extremely dangerous because it does not trigger validations.
In this example, we have ensured that the parent and child attributes involved in the evaluation are already numerical which has the effect of validating the function's inputs by proxy prior to submitting them for evaluation.
Why two versions of the same method?
There are two reasons:
- You cannot use
before_savecallback or else you will trigger a stack level too deep error by recursively saving the record.
- If you use the
after_touchnothing will be persisted to the database as save is not implicitly called
Our client called and told us how happy she is with the new feature securely in place. Job well done!