Skip to the content.

Home

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