Observers are not used anymore in Rails 4, although you can use the rails-observers gem. But in one of my controllers I have some actions that need to be performed, for which an observer would be ideal in my opinion:
The creation of the notification and storing the aggregated activities should not belong in this controller method, but can be moved to an observer. What would be a good solution in your opinion?
First move the logic to âmark a notification as approvedâ into Notification and Timesheet.
def approve
authorize! :approve, :timesheet_assessment
@timesheet = Timesheet.find(params[:timesheet_assessment_id])
@timesheet.approve!
Notification.approve_subject(@timesheet)
Notifier.new(@timesheet.user).timesheet_approved(@timesheet, current_user)
Aggregator.new(@timesheet).store_aggregated_activities
redirect_to admin_timesheet_assessments_path, notice: I18n.t('.timesheet.message_approve')
end
class Timesheet < ActiveRecord::Base
# TODO: Probably could be an `ApprovalConcern` mix-in
def approve!
update_attribute(:status, "approved")
end
end
class Notification < ActiveRecord::Base
def self.approve_subject(subject)
notification = where(subject_id: subject.id).first
notification.approve! unless notification.blank?
end
# TODO: Probably could be an `ApprovalConcern` mix-in
def approve!
update_attribute(:status, "approved")
end
end
Then, you can create a TimesheetApprovalService object to encapsulate the extra logic.
def approve
authorize! :approve, :timesheet_assessment
@timesheet = Timesheet.find(params[:timesheet_assessment_id])
@timesheet.approve!
TimesheetApprovalService.new(@timesheet, current_user).approve!
redirect_to admin_timesheet_assessments_path, notice: I18n.t('.timesheet.message_approve')
end
class TimesheetApprovalService
def initialize(timesheet, approved_by)
@timesheet = timesheet
@approved_by = approved_by
end
def approve!
approve_notification
send_notifier
store_aggregated_activities
end
private
def approve_notification
Notification.approve_subject(@timesheet)
end
def send_notifier
Notifier.new(@timesheet.user).timesheet_approved(@timesheet, @approved_by)
end
def store_aggregated_activities
Aggregator.new(@timesheet).store_aggregated_activities
end
end
I donât know much about your domain needs, so take the naming with a grain of salt. But, overall the idea to encapsulate that logic so that if anything changes you can update it in one place. Also, you now have the benefit of being able to use the approval service in places other than the controller such as background jobs or rake tasks.
Additionally, it helps testing. Write unit test for the service and stub it out in the controller test. This saves you from stubbing out three collaborators when testing the approve controller action.