Previous: Chapter 5 - The Mutation
📝 As an app user, I want to see all rounds and selections, so that I can see selection history.
To get started, we’ll need “round” and “selection” concepts. There’s a few things to keep in mind as we architect the models. We want selections to be completely random. We should not select users twice in a row. Similarly, we should exhaust the list of available users before choosing a user again. If I were a new engineer, I wouldn’t want to host right away, so let’s prevent new engineers from being selected for the current round. The general approach will be: start a round, select engineers to host until we’ve exhausted the list of available engineers, then start a new round. We’ll only select from engineers that were created before the round was created. We have our existing users table. We’ll need rounds and selections tables. selections will join users with rounds. When we create the Round model, we won’t need any fields besides the ID and timestamps. That’s perfectly okay.
Generate a Round model.
Terminal
bin/rails g model round
Run the migration.
Terminal
bin/rails db:migrate
Generate a Selection model.
Terminal
bin/rails g model selection round:belongs_to user:belongs_to
This time we’re going to make some very specific changes to the generated migration. We won’t allow adding users to a round more than once. To enforce this at the database level, we’ll add a multicolumn index on round_id and user_id with a uniqueness constraint. Since the database will be able to use this index for queries using just the leftmost column of our index, round_id, another index on round_id would be redundant. We’ll actually tell Rails not to create one by using index: false.
Make those changes.
db/migrate/[timestamp]_create_selections.rb
class CreateSelections < ActiveRecord::Migration[7.0]
def change
create_table :selections do |t|
t.belongs_to :round, null: false, foreign_key: true, index: false
t.belongs_to :user, null: false, foreign_key: true
t.timestamps
t.index [:round_id, :user_id], unique: true
end
end
end
Run the migration.
Terminal
bin/rails db:migrate
Check the changes to db/schema.rb to make sure things worked as expected. Just like with User, we now want to add in validations to Selection to match our database constraints. Our required validation can be summed up as “validate that the user is unique to the round.” Unlike with User, we won’t need presence validations for round and user since belongs_to will handle those for us.
Add the validation.
app/models/selection.rb
class Selection < ApplicationRecord
belongs_to :round
belongs_to :user
validates :user_id, uniqueness: { scope: :round_id }
end
Rails has generated some fixtures for us but the defaults won’t quite work.
Improve Round fixture names.
test/fixtures/rounds.yml
full: {}
empty: {}
Make sure the Selection fixtures match.
test/fixtures/selections.yml
full_chantel:
round: full
user: chantel
full_sarah:
round: full
user: sarah
full_daniel:
round: full
user: daniel
full_adrian:
round: full
user: adrian
We still need a few tests. My personal approach is to open the app file and the test file side by side to make sure I cover everything.
Write tests for the Selection model.
test/models/selection_test.rb
require "test_helper"
class SelectionTest < ActiveSupport::TestCase
context "associations" do
should belong_to(:round)
should belong_to(:user)
end
context "validations" do
should validate_uniqueness_of(:user_id).scoped_to(:round_id)
end
end
Run the new tests.
Terminal
bin/rails t test/models/selection_test.rb
Check for regressions by running all tests.
Terminal
bin/rails t
Success!
âś… Make a commit
Next: Chapter 7 - The History