has_many_callbacks¶ ↑
Adds additional callback hooks to ActiveRecord has_many relations.
Motivation¶ ↑
Callback hooks are a common pattern in software development, used when modeling event driven behavior.
Rails has_many relations provide hooks to child events, but they have several shortcomings. This gem provides additional callbacks, that are built on ActiveRecord model callbacks and work in the same way.
The main goal is ensuring the code that expresses the relation parent behavior when handling events triggered by child objects is kept in the parent code base, where it belongs. If the parent has a lot of relations, this prevents that class’ code to be scattered across all the child class definitions.
Usage¶ ↑
Just add a reference to Gemfile:
gem install "has_many_callbacks"
As an example, consider the following classes:
class TodoList < ActiveRecord::Base # name, completed has_many :tasks end class Task < ActiveRecord::Base # name, completed belongs_to :todo_list end
Imagine that you want a To-Do list to be marked completed if it’s not empty and all the tasks in it are completed.
class TodoList < ActiveRecord::Base has_many :tasks, :inverse_of => :todo_list, :after_save => lambda { |todo_list, task| todo_list.completed = !task.completed ? false : todo_list.tasks.where(:completed => false).count == 0 todo_list.save! if todo_list.completed_changed? }, :after_destroy => lambda { |todo_list, task| todo_list.completed = (todo_list.tasks.any? and todo_list.tasks.where(:completed => false).count == 0) todo_list.save! if todo_list.completed_changed? } end
Please note that the inverse_of
option is required. Three callbacks are available, after_create
, after_save
and after_destroy
.
You can also pass callbacks as symbols refering to the instance methods to call:
class TodoList < ActiveRecord::Base has_many :tasks, :inverse_of => :todo_list, :after_save => :handle_task_save, :after_destroy => :handle_task_destroy private def handle_task_save(task) self.completed = !task.completed ? false : tasks.where(:completed => false).count == 0 save! if completed_changed? end def handle_task_destroy(task) self.completed = (tasks.any? and tasks.where(:completed => false).count == 0) save! if completed_changed? end end
License¶ ↑
This software is available under the MIT License. See MIT-LICENSE for details.