forked from OpenNeo/impress
Create endpoint to show all species appearances for a given color
Building toward replacing more of the 2020 data sources! I think this is an endpoint that benefits from bulk loading, esp with the way the item page previews work. I also like taking the concept of "canonical" out of the GQL interface, and instead just loading for each of the 50 species and letting the client decide. (And then it can fast-swap between them!)
This commit is contained in:
parent
c8de3dae63
commit
a656cd511a
6 changed files with 70 additions and 91 deletions
|
@ -1,4 +1,37 @@
|
||||||
class PetTypesController < ApplicationController
|
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
|
def show
|
||||||
@pet_type = PetType.
|
@pet_type = PetType.
|
||||||
where(species_id: params[:species_id]).
|
where(species_id: params[:species_id]).
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
class Color < ApplicationRecord
|
class Color < ApplicationRecord
|
||||||
translates :name
|
translates :name
|
||||||
|
has_many :pet_types
|
||||||
|
|
||||||
scope :alphabetical, -> {
|
scope :alphabetical, -> {
|
||||||
ct = Color::Translation.arel_table
|
ct = Color::Translation.arel_table
|
||||||
|
@ -24,7 +25,7 @@ class Color < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def as_json(options={})
|
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
|
end
|
||||||
|
|
||||||
def human_name
|
def human_name
|
||||||
|
|
|
@ -25,7 +25,7 @@ class PetState < ApplicationRecord
|
||||||
}
|
}
|
||||||
|
|
||||||
# Filter pet states using the "pose" concept we use in the editor.
|
# Filter pet states using the "pose" concept we use in the editor.
|
||||||
scope :with_pose, ->(pose) {
|
scope :with_pose, -> pose {
|
||||||
case pose
|
case pose
|
||||||
when "UNCONVERTED"
|
when "UNCONVERTED"
|
||||||
where(unconverted: true)
|
where(unconverted: true)
|
||||||
|
@ -71,16 +71,6 @@ class PetState < ApplicationRecord
|
||||||
end
|
end
|
||||||
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)
|
def reassign_children_to!(main_pet_state)
|
||||||
self.contributions.each do |contribution|
|
self.contributions.each do |contribution|
|
||||||
contribution.contributed = main_pet_state
|
contribution.contributed = main_pet_state
|
||||||
|
@ -127,47 +117,6 @@ class PetState < ApplicationRecord
|
||||||
rel.save!
|
rel.save!
|
||||||
end
|
end
|
||||||
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)
|
def self.from_pet_type_and_biology_info(pet_type, info)
|
||||||
swf_asset_ids = []
|
swf_asset_ids = []
|
||||||
|
@ -223,31 +172,5 @@ class PetState < ApplicationRecord
|
||||||
pet_state.unconverted = (relationships.size == 1)
|
pet_state.unconverted = (relationships.size == 1)
|
||||||
pet_state
|
pet_state
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
|
|
|
@ -59,15 +59,10 @@ class PetType < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def as_json(options={})
|
def as_json(options={})
|
||||||
if options[:for] == 'wardrobe'
|
super({
|
||||||
{
|
only: [:id],
|
||||||
:id => id,
|
methods: [:color, :species]
|
||||||
:body_id => body_id,
|
}.merge(options))
|
||||||
:pet_states => pet_states.emotion_order.as_json
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{:image_hash => image_hash}
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def image_hash
|
def image_hash
|
||||||
|
@ -157,6 +152,31 @@ class PetType < ApplicationRecord
|
||||||
end
|
end
|
||||||
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)
|
def self.all_by_ids_or_children(ids, pet_states)
|
||||||
pet_states_by_pet_type_id = {}
|
pet_states_by_pet_type_id = {}
|
||||||
pet_states.each do |pet_state|
|
pet_states.each do |pet_state|
|
||||||
|
|
|
@ -715,7 +715,6 @@ en:
|
||||||
glitched: Glitched
|
glitched: Glitched
|
||||||
unconverted: Unconverted
|
unconverted: Unconverted
|
||||||
unlabeled: Unlabeled
|
unlabeled: Unlabeled
|
||||||
default_artist_name: the OpenNeo team
|
|
||||||
|
|
||||||
pet_types:
|
pet_types:
|
||||||
human_name: "%{color_human_name} %{species_human_name}"
|
human_name: "%{color_human_name} %{species_human_name}"
|
||||||
|
|
|
@ -23,11 +23,14 @@ OpenneoImpressItems::Application.routes.draw do
|
||||||
get :needed
|
get :needed
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
resources :species do
|
resources :species, only: [] do
|
||||||
resources :colors do
|
resources :colors, only: [] do
|
||||||
get :pet_type, to: 'pet_types#show'
|
get :pet_type, to: 'pet_types#show'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
resources :colors, only: [] do
|
||||||
|
resources :pet_types, only: [:index]
|
||||||
|
end
|
||||||
|
|
||||||
# Loading and modeling pets!
|
# Loading and modeling pets!
|
||||||
post '/pets/load' => 'pets#load', :as => :load_pet
|
post '/pets/load' => 'pets#load', :as => :load_pet
|
||||||
|
|
Loading…
Reference in a new issue