0

I work at a large company with a somewhat large Ruby on Rails application. We have internally debated how to handle event callbacks (e.g. Model 1 changes from state A -> B, how should Model 2 "find out").

In our codebase, we've implemented a few different approaches. We'd like to consolidate on a single approach. We're curious what companies with even larger / more mature Rails applications have gone with (e.g. what have the Stripes and Shopifys of the world done).

The approaches currently used in our codebase are:

  1. Observers (which I see were apparently removed from Rails in 4.0)
  2. Commands, which are basically functions that encapsulate a series of state changes across multiple models. In this approach, we ban models from directly calling one another (and instead only allow calling model functions from the top-level command). So a command might look like Model1.do_a; Model2.do_b; Model3.do_c.
  3. Callback functions (e.g. on_a) on models themselves.

For what it's worth, some of the problems we've observed with each approach are:

  1. Observers: Because these are "implicit," they are not as discoverable. At times, these have been a footgun (make it easy to accidentally trigger dangerous callback by accident).
  2. Commands: This approach seems more non-standard but so far these have led to less bugs.
  3. Callback functions: These have also at times been a footgun because they make it possible accidentally introduce circular callbacks (e.g. Model 1 calls on_a on Model 2, Model 2 calls on_b on Model 3, Model 3 calls on_c on Model 1).
Nolan H
  • 6,205
  • 1
  • 5
  • 19
Ben Sandler
  • 2,223
  • 5
  • 26
  • 36
  • 2
    This is an excellent question - its just not suited for a Q&A site like Stackoverflow since it is primarily opinion based and more of an architectural question then a practical programming question. I would suggest somewhere else like [/r/rubyonrails](https://www.reddit.com/r/rubyonrails/) which is a format thats open for discussions. – max Apr 23 '21 at 17:22
  • 2
    The answer is "it depends". They all have their uses. We would need to know more about the models, how they are related, and why and what they need to know about each other. For example "Item is sold so Inventory must be updated". Note there's a fourth option: make a class to manage the whole process. `manager = InventoryManager.new(inventory); manager.sell(item)`. Inventory and Item don't need to know anything about each other, they are not coupled, InventoryManager manages the business rules about what happens when an item is sold. – Schwern Apr 23 '21 at 18:36
  • 1
    @Schwern would that be an example of the Interactor pattern? – max Apr 24 '21 at 10:26
  • @max Yes, more or less. [Interactors](https://github.com/collectiveidea/interactor) generally do one thing and have no state. So it would be more like `SellItem.call(inventory: inventory, item: item)`. Interactors generally sit between the Model and the Controller to keep the Models from getting fat and interwoven. – Schwern Apr 24 '21 at 20:11

0 Answers0