I've been trying to migrate my codebase from rails 5 to rails 7 and there are some issues with callbacks which I'm unable to figure out
Issues:
When multiple callbacks like after_create_commit and after_update_commit are supposed to trigger the same method say foo the method doesn't get called. For example:
after_create_commit :foo
after_update_commit :foo
But it does when I slightly change the syntax as
after_create_commit -> { foo }
after_update_commit -> { foo }
If there are multiple methods to be triggered after a callback then only the last one gets called and all the ones before it doesn't get executed, For example:
after_create_commit :foo1
after_create_commit :foo2
after_create_commit :foo3
here only foo3 gets executed and foo1, foo2 doesn't.
can someone explain this behaviour and what could be the proper solution for this?
Issue 1:
after_create_commit :foo
after_update_commit :foo
The above code will not work as we cannot register same method in after_commit callback on both creation and updation. Here, the latter line of after_update_commit overrides the previous registration and callback will run after updation only.
Reason:
Rails does not maintain separate callback chains for create and update internally. We can check callback chain by using __callbacks[:commit] method.
Also, Below code will work fine because these are procs and they will be registered as two separate procs in callback chain.
after_create_commit -> { foo }
after_update_commit -> { foo }
Issue 2:
after_create_commit :foo1
after_create_commit :foo2
after_create_commit :foo3
Here, All methods should execute in the order foo3, foo2, foo1. But, If foo3 raises ActiveRecord::Rollback exception, then foo2 and foo1 will not execute. Please post the exact code that you are using.
Related
I'm getting a error in a RoR application where there's a new feature using skip_callback method.
class Foo
before_save: :some_callback
def some_callback
end
end
When there's multiples call to the class, raise Argument error because of rails functionality created on this commit.
# I using this only with the purpose of showing the error,
# I know that the block will execute without errors if calling set_callback after skip:
5.times do
Foo.skip_callback(:save, :before, :some_callback)
end
ArgumentError (Before save callback :some_callback has not been defined)
I know that I can avoid raise a error if I use the method like this
skip_callback(:save, :before, :some_callback, raise: false)
But I don't really feel 100% confident of this approach, there's a better way to skip calling the callbacks without raising a error if call the class multiple times? I'm afraid another part of the code, call the same class and don't call the callback when it's needed.
I am trying to pass parameters into my event using the aasm ruby gem and rails. However, whenever I try to follow the example in the documentation, I get the error Wrong number of arguments. Expected 0, got 2.. What am I doing wrong?
Code is below:
class Foo < ActiveRecord::Base
include AASM
aasm column: :status do
state :stopped, initial: true
# TODO: if post fails, should this remain in_progress?
state :running
event :run, aasm_fire_event: :do_something do
transitions from: :stopped, to: :running
end
end
def do_something(list_of_things)
.... #stuff here
end
end
and then the calling code
foo = Foo.new
foo.run(:running, [param1, param2])
This seems to follow the example, but I can't get it to work. Any help would be appreciated.
For any who stumble upon this problem, if you have other callbacks on your event or on the old_state or new_state that will get called on the event, the above pattern will try to apply the parameters to all of the callbacks. This is what was happening to me, so I solved the problem by allowing the args to be passed on those callbacks and then not doing anything with them.
I'm trying to run all callback methods manually inside a method. For example, I want to run all "before_destroy" methods inside my model.
Is there a way to manually trigger this? For example, something like:
def some_method
# ...
trigger(:before_destroy)
end
which will then run all methods that I have declared with "before_destroy :...."
Any ideas?
If you're happy to run both :before and :after hooks, you can try run_callbacks.
From the docs:
run_callbacks(kind, &block)
Runs the callbacks for the given event.
Calls the before and around callbacks in the order they were set, yields the block (if given one), and then runs the after callbacks in reverse order.
If the callback chain was halted, returns false. Otherwise returns the result of the block, or true if no block is given.
run_callbacks :save do
save
end
class Foo < ActiveRecord::Base
def destroy_method_1
end
def destroy_method_2
end
before_destroy :destroy_method_1, :destroy_method_2
DESTROY_METHODS = [:destroy_method_1, :destroy_method_2]
def some_method
DESTROY_METHODS.each {|m| send(m) }
end
end
I wonder if this is possible?
after_create -> { some_method_from_model }, if: :should_be_executed?
Syntax is ok, but the Proc will be called/executed or just created?
If you want to call a method on the model the best approach would be passing the model as an argument for your lambda, and then using it to invoke the desired method, like so:
after_create -> (model) { model.some_method }, if: :execution_condition_satisfied?
This is because the value of self inside the lambda isn't the model but a Proc object and, without an explicit receiver, Ruby tries to invoke the method in self.
In your example, Ruby will try to find some_model_method in the Proc object. So no, your example would not work, but that's not related to the new lambda literal syntax.
I hope it helps ;)
This seems overly complicated to me. I would just put the if test inside the method which is being called.
after_create :some_method
def some_method
if condition_satisfied
#do the stuff
end
end
I'm trying to inject an after_save callback via a mixin, but my rspec tests are telling me that the callback is being called twice when the create method is called. Why is the method being called twice?
The following rspec test fails
it 'should call callback' do
Product.any_instance.should_receive(:update_linkable_attachments).once
Product.create(:name=>'abc')
end
The failure message is:
Failure/Error: Unable to find matching line from backtrace
(#<Product:0xb7db738>).update_linkable_attachments(any args)
expected: 1 time
received: 2 times
Here's the code
module MainModuleSupport
def self.included(base)
base.instance_eval("after_save :update_linkable_attachments")
end
def update_linkable_attachments
LinkedAttachment.delay.create_from_attachment self
end
end
class Product < ActiveRecord::Base
include MainModuleSupport
...
end
The Product class has other code, but does not have any other callbacks.
after_save is part of the transaction and therefore may be called more than once provided that you have other associated objects that need to be saved as well. In cases like this I typically move from the after_save callback to the after_commit callback which runs only after the transaction has completed.