The Four Way Model
We had a problem at work this week that needed a lot of help. We had to write a program in two weeks that would accept credit card payments, track user-submitted scripts, assign each script to a reader who would fill out a scorecard and return it to a dispatcher, who would issue payment to the readers after a certain number of scorecards were done. There were more rules than that, but already you can see that the models and associations in our Rails app were going to be complicated.
We searched online for a while and could only find three-actor models, ie. Rails models that involved only three overlapping Classes at most. Here’s an example — the example, pretty much — because it’s straight from the docs:
Makes sense, but in this model everything goes through a single lynchpin (appointments) which have both patients and physicians . We had to link up four separate models — Writers, Scripts, Scorecards and Readers — so that it made sense and provided all the necessary associations. Here’s how we broke it down:
We decided to break it down into a parallel four method structure — our outside classes, User and Reader, represent the different types of people who will use the site while the inner classes, Script and Scorecard, represent the materials they will be submitting to our site, which are themselves interconnected.
First, we started with the User, who will be the main customer of our site. A user has_many :scripts and also has_many :scorecards, through: :scripts. Here, we connect our User (who we want to mainly keep separated from our application since they are an outside customer) to the scripts which they will submit, and the scorecards for those scripts.
The Script class is pretty simple: a Script belongs to the User who submitted it and has_many Scorecards that our company gives it as part of our evaluation process. The Script is really the lynchpin object here, similar to Appointments in the Rails docs example. The Script touches all other models, although we left Readers out of it because the Script doesn’t need to know who its Readers are if it knows who its Scorecards are and the Scorecards know their readers.
On that note, Scorecards is a good one — it belongs to both :script and :reader. It seems counterintutive but belonging to two models is a great way to tie those models together. We often use Scorecard as the central point for a query about either Scripts or Readers since it contains both a script_id and a reader_id in its datatable.
Finally, Readers are the inverse of Users. They have many :scorecards (when we create new scorecards we automatically assign each one a :reader_id so no scorecard can exist without a reader) and they also have script through scorecards.
The irony is that ultimately, we ended up folding Users and Readers into a superclass called User which we divided into roles (writer, reader, admin) in order to take advantage of ActiveAdmin rails permissions. However, I am still convinced that this four-model structure is a good way to conceptualize an application that has two separate types of content and two separate types of users who need wildly different data tables, information and parameters.