Callback chain management with attribute accessors
ActiveRecord callbacks like after_create, after_update etc can be used to abstract hooks related to creating or updating different objects that are logically affected by the object being saved. However being able to control a trigger inside a callback when deciding to stop or continue firing callbacks is important in some cases too.
Let's continue with our example domain of vehicles and wheels. A vehicle is an object that has many wheels. A vehicle is also an abstract class where automobiles, bicycles etc inherit.
When a vehicle is created a default wheel is created through an after_create callback. No problem there. But if we imagine that we would like to also add another wheel on each wheel creation, this will trigger a chain of callbacks which we will surely like to finalize at some point.
At this point we get an error (obviously):
One solution to this is to use a
skip_callback statement just after the
after_create statement in the wheel model:
This is not very DRY and personally I think it can be made better. Another solution is to explicitly turn off the callback trigger inside the callback, halting the chain:
The problems here are:
- This is not threadsafe as the callback is disabled and then enabled on class level
- You explicitly need to enable the callback back after you exit the chain which is just annoying.
The cleanest solution so far for this problem is to define a boolean flag using
attr_accessor and pass it into the instance just as it gets created, to either halt or continue the callback chain:
This way, once we have enough wheels a boolean flag is set during the next chain link that determines whether we continue with another callback or not.
The only problem here is that when a vehicle is created and the
after_create callback for Vehicle is fired to create the first wheel, we have to also now supply a
wheel_needed attribute to start creating wheels, otherwise only a single wheel will be created as the if check above for
create_other_wheel will fail.
To overcome this the following minor change will suffice:
Now we assign by default true as an initial value, if the
wheel_limit is larger than 1 (why not support unicycles?)