Previous: Chapter 11 - The Reaper
📝 As an app user, I want to see the archived status of users, so that I can know who is available.
Up until now we’ve assumed that users will be around forever, but there are many reasons why we’d want to archive a user. For this we’ll need to store someone’s archived status. Some options are a string/integer/enum field named state, a timestamp field named archived_at, or a boolean field named archived. state may be a great choice when there are multiple states required. archived_at would be useful as well because not only does a timestamp tell you whether a flag has been set, it tells you when. For our app, though, we don’t anticipate any further state complexity or timestamp requirements. Let’s just go with the simplest and smallest of the three, a boolean field named archived.
Generate a migration for the new column.
Terminal
bin/rails g migration AddArchivedToUser archived:boolean
We’ll make a couple additions to the migration. First, we’ll add a not-null constraint. That will help us Avoid the Three-state Boolean Problem. Second, we’ll add a default value. PostgreSQL will use the default for any existing columns. Keep in mind that this can be dangerous with a large number of existing rows. We don’t have to worry about that yet. The default will also be used for any new instances of User where archived is not specified.
Make those changes to the migration.
db/migrate/[timestamp]_add_archived_to_user.rb
class AddArchivedToUser < ActiveRecord::Migration[7.0]
def change
add_column :users, :archived, :boolean, null: false, default: false
end
end
Run the migration.
Terminal
bin/rails db:migrate
Add a validation for the new column.
app/models/user.rb
class User < ApplicationRecord
validates :name, :email, presence: true
validates :email, uniqueness: { case_sensitive: false }
validates :archived, exclusion: [nil]
end
Add tests for the changes.
test/models/user_test.rb
require "test_helper"
class UserTest < ActiveSupport::TestCase
context "validations" do
should validate_presence_of(:name)
should validate_presence_of(:email)
should validate_uniqueness_of(:email).case_insensitive
should validate_exclusion_of(:archived).in_array([nil])
end
end
Run the tests.
Terminal
bin/rails t test/models/user_test.rb
Expose the new field to the API.
app/graphql/types/user_type.rb
module Types
class UserType < Types::BaseObject
field :id, ID, null: false
field :name, String, null: false
field :email, String, null: false
field :created_at, GraphQL::Types::ISO8601DateTime, null: false
field :updated_at, GraphQL::Types::ISO8601DateTime, null: false
field :archived, Boolean, null: false
end
end
Add the new field to relevant tests.
test/integration/types/query_type/user_test.rb
require "test_helper"
class Types::QueryType::UserTest < ActionDispatch::IntegrationTest
setup do
@query = <<~GRAPHQL
query User($id: ID!) {
user(id: $id) {
id
name
email
createdAt
updatedAt
archived
}
}
GRAPHQL
end
test "user" do
user = users(:daniel)
post graphql_path, params: { query: @query, variables: { id: user.id } }
assert_equal(
{
"data" => {
"user" => {
"id" => user.id.to_s,
"name" => user.name,
"email" => user.email,
"createdAt" => user.created_at.iso8601,
"updatedAt" => user.updated_at.iso8601,
"archived" => user.archived,
},
},
},
@response.parsed_body
)
end
test "user not found" do
post graphql_path, params: { query: @query, variables: { id: "0" } }
assert_equal(
{
"data" => nil,
"errors" => [
{
"message" => "User not found",
"locations" => [{ "line" => 2, "column" => 3 }],
"path" => ["user"],
},
],
},
@response.parsed_body
)
end
end
Run the tests.
Terminal
bin/rails t test/integration/types/query_type/user_test.rb
Add the new field to one more test.
test/integration/types/query_type/users_test.rb
require "test_helper"
class Types::QueryType::UsersTest < ActionDispatch::IntegrationTest
test "users" do
query = <<~GRAPHQL
{
users {
id
name
email
createdAt
updatedAt
archived
}
}
GRAPHQL
post graphql_path, params: { query: query }
users_in_email_order = [
users(:adrian),
users(:chantel),
users(:daniel),
users(:sarah),
]
assert_equal(
{
"data" => {
"users" => users_in_email_order.map { |user|
{
"id" => user.id.to_s,
"name" => user.name,
"email" => user.email,
"createdAt" => user.created_at.iso8601,
"updatedAt" => user.updated_at.iso8601,
"archived" => user.archived,
}
},
},
},
@response.parsed_body
)
end
end
Run the tests.
Terminal
bin/rails t test/integration/types/query_type/users_test.rb
Check for regressions by running all tests.
Terminal
bin/rails t
Success!
âś… Make a commit
âś… As an app user, I want to see the archived status of users, so that I can know who is available.
Next: Chapter 13 - The Filter