Simplify modeling code for biology data
We're leaning more into Rails collection management and autosave stuff!
This commit is contained in:
parent
40765c729e
commit
13ceec8fcc
3 changed files with 49 additions and 105 deletions
|
@ -15,41 +15,16 @@ class Pet < ApplicationRecord
|
||||||
raise UnexpectedDataFormat unless pet_data[:color_id]
|
raise UnexpectedDataFormat unless pet_data[:color_id]
|
||||||
raise UnexpectedDataFormat unless pet_data[:body_id]
|
raise UnexpectedDataFormat unless pet_data[:body_id]
|
||||||
|
|
||||||
has_alt_style = pet_data[:alt_style].present?
|
@pet_state = Pet.pet_state_from_pet_data(pet_data)
|
||||||
|
self.pet_type = @pet_state.pet_type
|
||||||
self.pet_type = PetType.find_or_initialize_by(
|
|
||||||
species_id: pet_data[:species_id].to_i,
|
|
||||||
color_id: pet_data[:color_id].to_i
|
|
||||||
)
|
|
||||||
|
|
||||||
begin
|
begin
|
||||||
new_image_hash = Neopets::CustomPets.fetch_image_hash(self.name)
|
pet_type.consider_pet_image(name)
|
||||||
rescue => error
|
rescue => error
|
||||||
Rails.logger.warn "Failed to load image hash: #{error.full_message}"
|
Rails.logger.warn "Failed to load pet image: #{error.full_message}"
|
||||||
end
|
|
||||||
self.pet_type.image_hash = new_image_hash if new_image_hash.present?
|
|
||||||
|
|
||||||
# With an alt style, `body_id` in the biology data refers to the body ID of
|
|
||||||
# the *alt* style, not the usual pet type. (We have `original_biology` for
|
|
||||||
# *some* of the pet type's situation, but not it's body ID!)
|
|
||||||
#
|
|
||||||
# So, in the alt style case, don't update `body_id` - but if this is our
|
|
||||||
# first time seeing this pet type and it doesn't *have* a `body_id` yet,
|
|
||||||
# let's not be creating it without one. We'll need to model it without the
|
|
||||||
# alt style first. (I don't bother with a clear error message though 😅)
|
|
||||||
self.pet_type.body_id = pet_data[:body_id] unless has_alt_style
|
|
||||||
if self.pet_type.body_id.nil?
|
|
||||||
raise UnexpectedDataFormat,
|
|
||||||
"can't process alt style on first occurrence of pet type"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
pet_state_biology = has_alt_style ? pet_data[:original_biology] :
|
if pet_data[:alt_style]
|
||||||
pet_data[:biology_by_zone]
|
|
||||||
raise UnexpectedDataFormat if pet_state_biology.empty?
|
|
||||||
pet_state_biology[0] = nil # remove effects if present
|
|
||||||
@pet_state = self.pet_type.add_pet_state_from_biology! pet_state_biology
|
|
||||||
|
|
||||||
if has_alt_style
|
|
||||||
raise UnexpectedDataFormat unless pet_data[:alt_color]
|
raise UnexpectedDataFormat unless pet_data[:alt_color]
|
||||||
raise UnexpectedDataFormat if pet_data[:biology_by_zone].empty?
|
raise UnexpectedDataFormat if pet_data[:biology_by_zone].empty?
|
||||||
|
|
||||||
|
@ -89,10 +64,7 @@ class Pet < ApplicationRecord
|
||||||
|
|
||||||
before_validation do
|
before_validation do
|
||||||
pet_type.save!
|
pet_type.save!
|
||||||
if @pet_state
|
@pet_state.save! if @pet_state
|
||||||
@pet_state.save!
|
|
||||||
@pet_state.handle_assets!
|
|
||||||
end
|
|
||||||
|
|
||||||
if @items
|
if @items
|
||||||
@items.each do |item|
|
@items.each do |item|
|
||||||
|
@ -112,6 +84,39 @@ class Pet < ApplicationRecord
|
||||||
pet
|
pet
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.pet_state_from_pet_data(pet_data)
|
||||||
|
# First, set up the pet type.
|
||||||
|
pet_type = PetType.find_or_initialize_by(
|
||||||
|
species_id: pet_data[:species_id],
|
||||||
|
color_id: pet_data[:color_id],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Apply the pet's body ID to the pet type, unless it's wearing an alt
|
||||||
|
# style, in which case ignore it, because it's the *alt style*'s body ID.
|
||||||
|
# (This can theoretically cause a problem saving a new pet type when
|
||||||
|
# there's an alt style too!)
|
||||||
|
pet_type.body_id = pet_data[:body_id] unless pet_data[:alt_style]
|
||||||
|
if pet_type.body_id.nil?
|
||||||
|
raise UnexpectedDataFormat,
|
||||||
|
"can't process alt style on first occurrence of pet type"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Then, set up the biology assets.
|
||||||
|
pet_state_biology = pet_data[:alt_style].present? ?
|
||||||
|
pet_data[:original_biology] :
|
||||||
|
pet_data[:biology_by_zone]
|
||||||
|
body_id = pet_data[:body_id].to_i
|
||||||
|
swf_assets = pet_state_biology.values.map do |biology_data|
|
||||||
|
SwfAsset.from_biology_data(body_id, biology_data)
|
||||||
|
end
|
||||||
|
swf_asset_ids = swf_assets.map(&:remote_id).sort.join(",")
|
||||||
|
|
||||||
|
# Finally, set up the pet state, and use these biology assets.
|
||||||
|
pet_type.pet_states.find_or_initialize_by(swf_asset_ids:).tap do |pet_state|
|
||||||
|
pet_state.swf_assets = swf_assets
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
class UnexpectedDataFormat < RuntimeError;end
|
class UnexpectedDataFormat < RuntimeError;end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -6,8 +6,7 @@ class PetState < ApplicationRecord
|
||||||
has_many :contributions, :as => :contributed,
|
has_many :contributions, :as => :contributed,
|
||||||
:inverse_of => :contributed # in case of duplicates being merged
|
:inverse_of => :contributed # in case of duplicates being merged
|
||||||
has_many :outfits
|
has_many :outfits
|
||||||
has_many :parent_swf_asset_relationships, :as => :parent,
|
has_many :parent_swf_asset_relationships, :as => :parent
|
||||||
:autosave => false
|
|
||||||
has_many :swf_assets, :through => :parent_swf_asset_relationships
|
has_many :swf_assets, :through => :parent_swf_asset_relationships
|
||||||
|
|
||||||
belongs_to :pet_type
|
belongs_to :pet_type
|
||||||
|
@ -16,8 +15,6 @@ class PetState < ApplicationRecord
|
||||||
|
|
||||||
alias_method :swf_asset_ids_from_association, :swf_asset_ids
|
alias_method :swf_asset_ids_from_association, :swf_asset_ids
|
||||||
|
|
||||||
attr_writer :parent_swf_asset_relationships_to_update
|
|
||||||
|
|
||||||
# A simple ordering that tries to bring reliable pet states to the front.
|
# A simple ordering that tries to bring reliable pet states to the front.
|
||||||
scope :emotion_order, -> {
|
scope :emotion_order, -> {
|
||||||
order(Arel.sql(
|
order(Arel.sql(
|
||||||
|
@ -135,71 +132,10 @@ class PetState < ApplicationRecord
|
||||||
self['swf_asset_ids'] = ids
|
self['swf_asset_ids'] = ids
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_assets!
|
|
||||||
@parent_swf_asset_relationships_to_update.each do |rel|
|
|
||||||
rel.swf_asset.save!
|
|
||||||
rel.save!
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_param
|
def to_param
|
||||||
"#{id}-#{pose.split('_').map(&:capitalize).join('-')}"
|
"#{id}-#{pose.split('_').map(&:capitalize).join('-')}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.from_pet_type_and_biology_info(pet_type, info)
|
|
||||||
swf_asset_ids = []
|
|
||||||
info.each do |zone_id, asset_info|
|
|
||||||
if zone_id.present? && asset_info
|
|
||||||
swf_asset_ids << asset_info[:part_id].to_i
|
|
||||||
end
|
|
||||||
end
|
|
||||||
swf_asset_ids_str = swf_asset_ids.sort.join(',')
|
|
||||||
if pet_type.new_record?
|
|
||||||
pet_state = self.new :swf_asset_ids => swf_asset_ids_str
|
|
||||||
else
|
|
||||||
pet_state = self.find_or_initialize_by(
|
|
||||||
pet_type_id: pet_type.id,
|
|
||||||
swf_asset_ids: swf_asset_ids_str
|
|
||||||
)
|
|
||||||
end
|
|
||||||
existing_swf_assets = SwfAsset.biology_assets.includes(:zone).
|
|
||||||
where(remote_id: swf_asset_ids)
|
|
||||||
existing_swf_assets_by_id = {}
|
|
||||||
existing_swf_assets.each do |swf_asset|
|
|
||||||
existing_swf_assets_by_id[swf_asset.remote_id] = swf_asset
|
|
||||||
end
|
|
||||||
existing_relationships_by_swf_asset_id = {}
|
|
||||||
unless pet_state.new_record?
|
|
||||||
pet_state.parent_swf_asset_relationships.each do |relationship|
|
|
||||||
existing_relationships_by_swf_asset_id[relationship.swf_asset_id] = relationship
|
|
||||||
end
|
|
||||||
end
|
|
||||||
pet_state.pet_type = pet_type # save the second case from having to look it up by ID
|
|
||||||
relationships = []
|
|
||||||
info.each do |zone_id, asset_info|
|
|
||||||
if zone_id.present? && asset_info
|
|
||||||
swf_asset_id = asset_info[:part_id].to_i
|
|
||||||
swf_asset = existing_swf_assets_by_id[swf_asset_id]
|
|
||||||
unless swf_asset
|
|
||||||
swf_asset = SwfAsset.new
|
|
||||||
swf_asset.remote_id = swf_asset_id
|
|
||||||
end
|
|
||||||
swf_asset.origin_biology_data = asset_info
|
|
||||||
swf_asset.origin_pet_type = pet_type
|
|
||||||
relationship = existing_relationships_by_swf_asset_id[swf_asset.id]
|
|
||||||
unless relationship
|
|
||||||
relationship ||= ParentSwfAssetRelationship.new
|
|
||||||
relationship.parent = pet_state
|
|
||||||
relationship.swf_asset_id = swf_asset.id
|
|
||||||
end
|
|
||||||
relationship.swf_asset = swf_asset
|
|
||||||
relationships << relationship
|
|
||||||
end
|
|
||||||
end
|
|
||||||
pet_state.parent_swf_asset_relationships_to_update = relationships
|
|
||||||
pet_state
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
# A helper for the `pose=` method.
|
# A helper for the `pose=` method.
|
||||||
|
|
|
@ -57,6 +57,14 @@ class PetType < ApplicationRecord
|
||||||
basic_image_hash || self['image_hash'] || 'deadbeef'
|
basic_image_hash || self['image_hash'] || 'deadbeef'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def consider_pet_image(pet_name)
|
||||||
|
# If we already have a basic image hash, don't worry about it!
|
||||||
|
return if basic_image_hash?
|
||||||
|
|
||||||
|
# Otherwise, use this as the new image hash for this pet type.
|
||||||
|
self.image_hash = Neopets::CustomPets.fetch_image_hash(pet_name)
|
||||||
|
end
|
||||||
|
|
||||||
def possibly_new_color
|
def possibly_new_color
|
||||||
self.color || Color.new(id: self.color_id)
|
self.color || Color.new(id: self.color_id)
|
||||||
end
|
end
|
||||||
|
@ -71,11 +79,6 @@ class PetType < ApplicationRecord
|
||||||
species_human_name: possibly_new_species.human_name)
|
species_human_name: possibly_new_species.human_name)
|
||||||
end
|
end
|
||||||
|
|
||||||
def add_pet_state_from_biology!(biology)
|
|
||||||
pet_state = PetState.from_pet_type_and_biology_info(self, biology)
|
|
||||||
pet_state
|
|
||||||
end
|
|
||||||
|
|
||||||
def canonical_pet_state
|
def canonical_pet_state
|
||||||
# For consistency (randomness is always scary!), we use the PetType ID to
|
# For consistency (randomness is always scary!), we use the PetType ID to
|
||||||
# determine which gender to prefer, if it's not built into the color. That
|
# determine which gender to prefer, if it's not built into the color. That
|
||||||
|
|
Loading…
Reference in a new issue