It is fairly common to see ‘before’ and ‘after’ callbacks in Rails models to handle instructions for the model to execute prior to and after its state has changed.
However, there might be a need to execute some custom actions in the middle of this callback flow and you might not have control over the callback to make the changes as this might be from say an Engine or a gem.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class B < ActiveRecord::Base
has_many :c
belongs_to :a
end
class C < ActiveRecord::Base
belongs_to :b
end
# this module could be from a gem or engine
module Audit
def self.included(klass)
klass.extend ClassMethods
end
module ClassMethods
has_many :b
after_save :create_b
def create_b
# saves a record of type B
end
end
end
class A < ActiveRecord::Base
include Audit
# how do we create C which has a reference to B ??
end
Take the example above. We have an ActiveRecord model A, whereby after saving it, it creates a record in the database of type B.
The callback is included into A from an Audit module which is external to the application.
How do we trigger a create on C but after the first callback so that we can obtain a reference to B?
The approach I have taken is to use an around_save. I can yield to the main class callback, in this case, creating B, and then execute my custom action to create C afterwards.
The following ruby code demonstrates the idea:
1
2
3
4
5
6
7
def test_yield
puts "Hello World!"
yield
puts "Goodbye World!"
end
test_yield{ puts "Custom processing here..." } # => Hello World!; Custom processing here...; Goodbye World!
The test_yield method calls the block when it reaches yield and passes control to the code inside the block. Once thats done, control returns to the end of the original method.
I can use an around_save callback which calls yield on the main class to run its callbacks and then have it run my custom code after:
1
2
3
4
5
6
7
8
9
10
11
12
13
class A < ActiveRecord::Base
include Audit
around_save :create_c
def create_c
# calls the after_save defined in Audit module first
yield
# custom call to create C
C.create!(b: @b)
end
end
Happy Hacking!