Handle new colors/species in the Rainbow Pool

Oh right, yeah, we like to do things gracefully around here when
there's no corresponding color/species record yet!

Paying more attention to this, I'm thinking like… it could be a cool
idea to, in modeling, *create* the new color/species record, and just
not have all the attributes filled in yet? Especially now that we're
less dependent on attributes like `standard` to be set for correct
functioning.

But for now, we follow the same strategy we do elsewhere in the app: a
pet type can have `color_id` and `species_id` that don't correspond to
a real record, and we cover over that smoothly.
This commit is contained in:
Emi Matchu 2024-11-15 19:56:07 -08:00
parent dd213e8078
commit 217d25edab
12 changed files with 171 additions and 17 deletions

View file

@ -17,7 +17,7 @@ class PetStatesController < ApplicationController
protected
def find_pet_state
@pet_type = PetType.matching_name_param(params[:pet_type_name]).first!
@pet_type = PetType.find_by_param!(params[:pet_type_name])
@pet_state = @pet_type.pet_states.find(params[:id])
end

View file

@ -70,9 +70,7 @@ class PetTypesController < ApplicationController
color_id: params[:color_id],
)
elsif params[:name]
color_name, _, species_name = params[:name].rpartition("-")
raise ActiveRecord::RecordNotFound if species_name.blank?
PetType.matching_name(color_name, species_name).first!
PetType.find_by_param!(params[:name])
else
raise "expected params: species_id and color_id, or name"
end

View file

@ -21,6 +21,10 @@ class Color < ApplicationRecord
end
end
def to_param
name? ? human_name : id.to_s
end
def example_pet_type(preferred_species: nil)
preferred_species ||= Species.first
pet_types.order([Arel.sql("species_id = ? DESC"), preferred_species.id],
@ -36,4 +40,8 @@ class Color < ApplicationRecord
nil
end
end
def self.param_to_id(param)
param.match?(/\A\d+\Z/) ? param.to_i : find_by_name!(param).id
end
end

View file

@ -15,10 +15,6 @@ class PetType < ApplicationRecord
species = Species.find_by_name!(species_name)
where(color_id: color.id, species_id: species.id)
}
scope :matching_name_param, ->(name_param) {
color_name, _, species_name = name_param.rpartition("-")
matching_name(color_name, species_name)
}
scope :preferring_species, ->(species_id) {
joins(:species).order([Arel.sql("species_id = ? DESC"), species_id])
}
@ -116,7 +112,7 @@ class PetType < ApplicationRecord
end
def to_param
"#{color.human_name}-#{species.human_name}"
"#{possibly_new_color.to_param}-#{possibly_new_species.to_param}"
end
def fully_labeled?
@ -136,6 +132,15 @@ class PetType < ApplicationRecord
pet_states.count { |ps| ps.pose == "UNKNOWN" }
end
def self.find_by_param!(param)
raise ActiveRecord::RecordNotFound unless param.include?("-")
color_param, _, species_param = param.rpartition("-")
where(
color_id: Color.param_to_id(color_param),
species_id: Species.param_to_id(species_param),
).first!
end
def self.basic_body_ids
PetType.basic.distinct.pluck(:body_id)
end

View file

@ -16,6 +16,10 @@ class Species < ApplicationRecord
end
end
def to_param
name? ? human_name : id.to_s
end
# Given a list of body IDs, return a hash from body ID to Species.
# (We assume that each body ID belongs to just one species; if not, which
# species we return for that body ID is undefined.)
@ -26,4 +30,8 @@ class Species < ApplicationRecord
to_h { |s| [s.id, s] }
species_ids_by_body_id.transform_values { |id| species_by_id[id] }
end
def self.param_to_id(param)
param.match?(/\A\d+\Z/) ? param.to_i : find_by_name!(param).id
end
end

View file

@ -5,11 +5,11 @@
%li
= link_to "Rainbow Pool", pet_types_path
%li
= link_to @pet_type.color.human_name,
pet_types_path(color: @pet_type.color.human_name)
= link_to @pet_type.possibly_new_color.human_name,
pet_types_path(color: @pet_type.possibly_new_color.human_name)
%li{"data-relation-to-prev": "sibling"}
= link_to @pet_type.species.human_name,
pet_types_path(species: @pet_type.species.human_name)
= link_to @pet_type.possibly_new_species.human_name,
pet_types_path(species: @pet_type.possibly_new_species.human_name)
%li
= link_to "Appearances", @pet_type
%li

View file

@ -5,11 +5,11 @@
%li
= link_to "Rainbow Pool", pet_types_path
%li
= link_to @pet_type.color.human_name,
pet_types_path(color: @pet_type.color.human_name)
= link_to @pet_type.possibly_new_color.human_name,
pet_types_path(color: @pet_type.possibly_new_color.human_name)
%li{"data-relation-to-prev": "sibling"}
= link_to @pet_type.species.human_name,
pet_types_path(species: @pet_type.species.human_name)
= link_to @pet_type.possibly_new_species.human_name,
pet_types_path(species: @pet_type.possibly_new_species.human_name)
%li
Appearances

View file

@ -10,3 +10,6 @@ robot:
striped:
id: 77
name: striped
swamp_gas:
id: 93
name: "swamp gas"

19
spec/fixtures/pet_types.yml vendored Normal file
View file

@ -0,0 +1,19 @@
blue_acara:
color_id: 8
species_id: 1
body_id: 123
newcolor_acara:
color_id: 123
species_id: 1
body_id: 123
blue_newspecies:
color_id: 8
species_id: 456
body_id: 123
newcolor_newspecies:
color_id: 123
species_id: 456
body_id: 123

38
spec/models/color_spec.rb Normal file
View file

@ -0,0 +1,38 @@
require 'rails_helper'
RSpec.describe Color do
fixtures :colors
describe '#to_param' do
it("uses name when possible") do
expect(colors(:blue).to_param).to eq "Blue"
end
it("uses spaces for multi-word names") do
expect(colors(:swamp_gas).to_param).to eq "Swamp Gas"
end
it("uses IDs for new colors") do
expect(Color.new(id: 12345).to_param).to eq "12345"
end
end
describe ".param_to_id" do
it("looks up by name") do
expect(Color.param_to_id("blue")).to eq colors(:blue).id
end
it("is case-insensitive for name") do
expect(Color.param_to_id("bLUe")).to eq colors(:blue).id
end
it("returns ID when the param is just a number, even if it doesn't exist") do
expect(Color.param_to_id("123456")).to eq 123456
end
it("raises RecordNotFound if no name matches") do
expect { Color.param_to_id("nonexistant") }.
to raise_error ActiveRecord::RecordNotFound
end
end
end

View file

@ -0,0 +1,41 @@
require 'rails_helper'
RSpec.describe PetType do
fixtures :colors, :species, :pet_types
describe '#to_param' do
it('uses color and species name when possible ("Blue-Acara")') do
expect(pet_types(:blue_acara).to_param).to eq "Blue-Acara"
end
it('uses color ID for new colors (123-Acara)') do
expect(pet_types(:newcolor_acara).to_param).to eq "123-Acara"
end
it('uses species ID for new colors (Blue-456)') do
expect(pet_types(:blue_newspecies).to_param).to eq "Blue-456"
end
it('uses color ID and species ID when both are new (123-456)') do
expect(pet_types(:newcolor_newspecies).to_param).to eq "123-456"
end
end
describe ".find_by_param!" do
it('looks up by species and color name ("Blue-Acara")') do
expect(PetType.find_by_param!("Blue-Acara")).to eq pet_types(:blue_acara)
end
it('looks up by color ID for new colors ("123-Acara")') do
expect(PetType.find_by_param!("123-Acara")).to eq pet_types(:newcolor_acara)
end
it('looks up by species ID for new species ("Blue-456")') do
expect(PetType.find_by_param!("Blue-456")).to eq pet_types(:blue_newspecies)
end
it('looks up by color ID and species ID when both are new ("123-456")') do
expect(PetType.find_by_param!("123-456")).to eq pet_types(:newcolor_newspecies)
end
end
end

View file

@ -0,0 +1,34 @@
require 'rails_helper'
RSpec.describe Species do
fixtures :species
describe '#to_param' do
it("uses name when possible") do
expect(species(:acara).to_param).to eq "Acara"
end
it("uses IDs for new species") do
expect(Species.new(id: 12345).to_param).to eq "12345"
end
end
describe ".param_to_id" do
it("looks up by name") do
expect(Species.param_to_id("acara")).to eq species(:acara).id
end
it("is case-insensitive for name") do
expect(Species.param_to_id("aCaRa")).to eq species(:acara).id
end
it("returns ID when the param is just a number, even if no record exists") do
expect(Species.param_to_id("123456")).to eq 123456
end
it("raises RecordNotFound if no name matches") do
expect { Species.param_to_id("nonexistant") }.
to raise_error ActiveRecord::RecordNotFound
end
end
end