diff --git a/app/controllers/pet_types_controller.rb b/app/controllers/pet_types_controller.rb index ed30e051..bcef99e7 100644 --- a/app/controllers/pet_types_controller.rb +++ b/app/controllers/pet_types_controller.rb @@ -1,4 +1,37 @@ class PetTypesController < ApplicationController + def index + color = Color.find params[:color_id] + pet_types = color.pet_types.includes(pet_states: [:swf_assets]). + includes(species: [:translations]) + + # This is a relatively big request, for relatively static data. Let's + # consider it fresh for 10min (so new pet releases show up quickly), but + # it's also okay for the client to quickly serve the cached copy then + # reload in the background, if it's been less than a day. + expires_in 10.minutes, stale_while_revalidate: 1.day, public: true + + # We dive deep to get all the right fields for appearance data, cuz this + # endpoint is primarily used to power the preview on the item page! + render json: pet_types.map { |pt| + pt.as_json( + only: [:id, :body_id], + include: { + species: {only: [:id], methods: [:name, :human_name]}, + canonical_pet_state: { + only: [:id], + methods: [:pose], + include: { + swf_assets: { + only: [:id, :known_glitches], + methods: [:zone, :restricted_zones, :urls], + }, + }, + }, + }, + ) + } + end + def show @pet_type = PetType. where(species_id: params[:species_id]). diff --git a/app/models/color.rb b/app/models/color.rb index d7c50e80..d57dcedd 100644 --- a/app/models/color.rb +++ b/app/models/color.rb @@ -1,5 +1,6 @@ class Color < ApplicationRecord translates :name + has_many :pet_types scope :alphabetical, -> { ct = Color::Translation.arel_table @@ -24,7 +25,7 @@ class Color < ApplicationRecord end def as_json(options={}) - {id: id, name: human_name, unfunny_name: unfunny_human_name, prank: prank?} + {id: id, name: name, human_name: human_name} end def human_name diff --git a/app/models/pet_state.rb b/app/models/pet_state.rb index 8ab1563f..ac80eb72 100644 --- a/app/models/pet_state.rb +++ b/app/models/pet_state.rb @@ -25,7 +25,7 @@ class PetState < ApplicationRecord } # Filter pet states using the "pose" concept we use in the editor. - scope :with_pose, ->(pose) { + scope :with_pose, -> pose { case pose when "UNCONVERTED" where(unconverted: true) @@ -71,16 +71,6 @@ class PetState < ApplicationRecord end end - def as_json(options={}) - { - id: id, - gender_mood_description: gender_mood_description, - swf_asset_ids: swf_asset_ids_array, - artist_name: artist_name, - artist_url: artist_url - } - end - def reassign_children_to!(main_pet_state) self.contributions.each do |contribution| contribution.contributed = main_pet_state @@ -127,47 +117,6 @@ class PetState < ApplicationRecord rel.save! end end - - def mood - Mood.find(self.mood_id) - end - - def gender_name - if female? - I18n.translate("pet_states.description.gender.female") - else - I18n.translate("pet_states.description.gender.male") - end - end - - def mood_name - I18n.translate("pet_states.description.mood.#{mood.name}") - end - - def gender_mood_description - if glitched? - I18n.translate('pet_states.description.glitched') - elsif unconverted? - I18n.translate('pet_states.description.unconverted') - elsif labeled? - I18n.translate('pet_states.description.main', :gender => gender_name, - :mood => mood_name) - else - I18n.translate('pet_states.description.unlabeled') - end - end - - def artist_name - artist_neopets_username || I18n.translate("pet_states.default_artist_name") - end - - def artist_url - if artist_neopets_username - "https://www.neopets.com/userlookup.phtml?user=#{artist_neopets_username}" - else - nil - end - end def self.from_pet_type_and_biology_info(pet_type, info) swf_asset_ids = [] @@ -223,31 +172,5 @@ class PetState < ApplicationRecord pet_state.unconverted = (relationships.size == 1) pet_state end - - # Copied from https://github.com/matchu/neopets/blob/5d13a720b616ba57fbbd54541f3e5daf02b3fedc/lib/neopets/pet/mood.rb - class Mood - attr_reader :id, :name - - def initialize(options) - @id = options[:id] - @name = options[:name] - end - - def self.find(id) - self.all_by_id[id.to_i] - end - - def self.all - @all ||= [ - Mood.new(:id => 1, :name => :happy), - Mood.new(:id => 2, :name => :sad), - Mood.new(:id => 4, :name => :sick) - ] - end - - def self.all_by_id - @all_by_id ||= self.all.inject({}) { |h, m| h[m.id] = m; h } - end - end end diff --git a/app/models/pet_type.rb b/app/models/pet_type.rb index feaea0b8..159d0291 100644 --- a/app/models/pet_type.rb +++ b/app/models/pet_type.rb @@ -59,15 +59,10 @@ class PetType < ApplicationRecord end def as_json(options={}) - if options[:for] == 'wardrobe' - { - :id => id, - :body_id => body_id, - :pet_states => pet_states.emotion_order.as_json - } - else - {:image_hash => image_hash} - end + super({ + only: [:id], + methods: [:color, :species] + }.merge(options)) end def image_hash @@ -157,6 +152,31 @@ class PetType < ApplicationRecord end end + def canonical_pet_state + # For consistency (randomness is always scary!), we use the PetType ID to + # determine which gender to prefer. That way, it'll be stable, but we'll + # still get the *vibes* of uniform randomness. + preferred_gender = id % 2 == 0 ? :fem : :masc + + # NOTE: If this were only being called on one pet type at a time, it would + # be more efficient to send this as a single query with an `order` part and + # just get the first record. But we most importantly call this on all the + # pet types for a single color at once, in which case it's better for the + # caller to use `includes(:pet_states)` to preload the pet states then sort + # then in Ruby here, rather than send ~50 queries. Also, there's generally + # very few pet states per pet type, so the perf difference is negligible. + pet_states.sort_by { |pet_state| + gender = pet_state.female? ? :fem : :masc + [ + pet_state.mood_id.present? ? -1 : 1, # Prefer mood is labeled + pet_state.mood_id, # Prefer mood is happy, then sad, then sick + gender == preferred_gender ? -1 : 1, # Prefer our "random" gender + -pet_state.id, # Prefer newer pet states + !pet_state.glitched? ? -1 : 1, # Prefer is not glitched + ] + }.first + end + def self.all_by_ids_or_children(ids, pet_states) pet_states_by_pet_type_id = {} pet_states.each do |pet_state| diff --git a/config/locales/en.yml b/config/locales/en.yml index 718f13bc..37d332a2 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -715,7 +715,6 @@ en: glitched: Glitched unconverted: Unconverted unlabeled: Unlabeled - default_artist_name: the OpenNeo team pet_types: human_name: "%{color_human_name} %{species_human_name}" diff --git a/config/routes.rb b/config/routes.rb index 2ff04e02..3e80b268 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -23,11 +23,14 @@ OpenneoImpressItems::Application.routes.draw do get :needed end end - resources :species do - resources :colors do + resources :species, only: [] do + resources :colors, only: [] do get :pet_type, to: 'pet_types#show' end end + resources :colors, only: [] do + resources :pet_types, only: [:index] + end # Loading and modeling pets! post '/pets/load' => 'pets#load', :as => :load_pet