Let us say we have the following scenario:
class MyModel < ActiveRecord::Base
after_save :throw_after_save
after_commit :throw_after_commit
private
def throw_after_save
raise "raising on after_save"
end
def throw_after_commit
raise "raising on after_commit"
end
end
class MyController < ApplicationController
def callback
begin
MyModel.new(params).save
rescue
flash[:alert] = "Failed persisting to external system. Try again."
Airbrake.notify(
error_class: "External System Persistence",
error_message: "External System Persistence: Failed to persist data",
parameters: params
)
end
redirect_to root_path
end
end
We get a callback from an external system (where the user fills out some data and creates a temporary set of attributes for an account).
Let us assume that we want to persist some data locally after the callback to us is made. After this data is persisted, we want to make a call to the external system to complete the account creation process. The external system shall return a result informing us of success with some additional data that we need to persist locally. We also know that in some exceptional cases, persistence on the remote system will not occur successfully (let us say system is not available, or something goes wrong on their end).
The objective is to capture external persistence exceptions as well as successes and act accordingly. In the case of successes, everything is hunky dory: the additional data is stored locally, the redirect_to root_path happens. In the case of exceptions, however, we’d like to indicate this to the user (perhaps set a flash[:alert] to be displayed in the view).
We had tried to use ActiveRecord::Callbacks to throw an exception from the model after_save and after_commit and handle that exception at the controller by setting an alert and possibly passing the exception to some exception notification system (like Airbrake). In the case of after_save, the exception is thrown by the model and caught by the controller, but the record is not saved (and we have the requirement of storing the partial data even in the case of exceptions with the external system – this is not acceptable). In the case of after_commit, the exception is not thrown and picked up by the controller, but the partial record is persisted. This means we cannot notify the user of the exception (unless we implement some notification pushing mechanism – which is overkill).
As it turns out, we can set errors on the model on after_save, which is nice. But, is this a good general pattern to handle this sort of scenario?
You don’t have a lot of options in there. Since the logic itself of what you are asking is complex, I’d move the code to save the record to somewhere else (maybe a class method or another class, representing a service) and when someone wanted to save this specific object it would have to go through this service model.
For instance:
Services are not that common in Ruby/Rails projects but they are a nice fit for this kind of usage (and abusing AR callbacks usually leads to hard to test objects).