forked from OpenNeo/impress
f997513e87
Ohh ok, without this change all of our `scope`s were just immediately evaluating the argument and fetching _all_ such matching records immediately, instead of waiting to actually be called. This led to bugs like `pet_type.as_json` returning ALL pet states in the whole db, because the `PetState.emotion_order` scope was being treated as a single predefined query, rather than a query fragment to merge into the current context.
This also explains what happened in 724ed83
: that's why things before the scope in the query were being ignored.
250 lines
8 KiB
Ruby
250 lines
8 KiB
Ruby
class PetState < ActiveRecord::Base
|
|
SwfAssetType = 'biology'
|
|
|
|
has_many :contributions, :as => :contributed,
|
|
:inverse_of => :contributed # in case of duplicates being merged
|
|
has_many :outfits
|
|
has_many :parent_swf_asset_relationships, :as => :parent,
|
|
:autosave => false
|
|
has_many :swf_assets, :through => :parent_swf_asset_relationships
|
|
|
|
belongs_to :pet_type
|
|
|
|
delegate :color, to: :pet_type
|
|
|
|
alias_method :swf_asset_ids_from_association, :swf_asset_ids
|
|
|
|
attr_writer :parent_swf_asset_relationships_to_update
|
|
|
|
# Our ideal order is: happy, sad, sick, UC, any+effects, glitched, with male
|
|
# before female within those groups for consistency. We therefore order as
|
|
# follows, listed in order of priority:
|
|
# * Send glitched states to the back
|
|
# * Bring known happy states to the front (we don't want to sort by mood_id
|
|
# DESC first because then labeled sad will appear before unlabeled happy)
|
|
# * Send states with effect assets to the back
|
|
# * Bring state with more assets forward (that is, send UC near the back)
|
|
# * Bring males forward
|
|
# * Bring states with a lower asset ID sum forward (the idea being that
|
|
# sad/female states are usually created after a happy/male base, but that's
|
|
# becoming increasingly untrue over time - this is a very last resort)
|
|
#
|
|
# Maybe someday, when most states are labeled, we can depend exclusively on
|
|
# their labels - or at least use more than is-happy and is-female. For now,
|
|
# though, this strikes a good balance of bringing default to the front for
|
|
# many pet types (the highest priority!) and otherwise doing decent sorting.
|
|
bio_effect_zone_id = 4
|
|
scope :emotion_order, -> {
|
|
joins(:parent_swf_asset_relationships).
|
|
joins("LEFT JOIN swf_assets effect_assets ON effect_assets.id = parents_swf_assets.swf_asset_id AND effect_assets.zone_id = #{bio_effect_zone_id}").
|
|
group("pet_states.id").
|
|
order("glitched ASC, (mood_id = 1) DESC, COUNT(effect_assets.remote_id) ASC, COUNT(parents_swf_assets.swf_asset_id) DESC, female ASC, SUM(parents_swf_assets.swf_asset_id) ASC")
|
|
}
|
|
|
|
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
|
|
contribution.save
|
|
end
|
|
self.outfits.each do |outfit|
|
|
outfit.pet_state = main_pet_state
|
|
outfit.save
|
|
end
|
|
ParentSwfAssetRelationship.where(ParentSwfAssetRelationship.arel_table[:parent_id].eq(self.id)).delete_all
|
|
end
|
|
|
|
def reassign_duplicates!
|
|
raise "This may only be applied to pet states that represent many duplicate entries" unless duplicate_ids
|
|
pet_states = duplicate_ids.split(',').map do |id|
|
|
PetState.find(id.to_i)
|
|
end
|
|
main_pet_state = pet_states.shift
|
|
pet_states.each do |pet_state|
|
|
pet_state.reassign_children_to!(main_pet_state)
|
|
pet_state.destroy
|
|
end
|
|
end
|
|
|
|
def sort_swf_asset_ids!
|
|
self.swf_asset_ids = swf_asset_ids_array.sort.join(',')
|
|
end
|
|
|
|
def swf_asset_ids
|
|
self['swf_asset_ids']
|
|
end
|
|
|
|
def swf_asset_ids_array
|
|
swf_asset_ids.split(',').map(&:to_i)
|
|
end
|
|
|
|
def swf_asset_ids=(ids)
|
|
self['swf_asset_ids'] = ids
|
|
end
|
|
|
|
def handle_assets!
|
|
@parent_swf_asset_relationships_to_update.each do |rel|
|
|
rel.swf_asset.save!
|
|
rel.save!
|
|
end
|
|
end
|
|
|
|
def label_by_pet(pet, username)
|
|
# If this pet is already labeled with a gender/mood, or it's unconverted
|
|
# and therefore has none, skip.
|
|
return false if self.labeled? || self.unconverted?
|
|
|
|
# Find this pet on the owner's userlookup, where we can get both its gender
|
|
# and its mood.
|
|
begin
|
|
user_pet = Neopets::User.new(username).pets.
|
|
find { |user_pet| user_pet.name.downcase == pet.name.strip.downcase }
|
|
self.female = user_pet.female?
|
|
self.mood_id = user_pet.mood.id
|
|
self.labeled = true
|
|
rescue
|
|
# If there's an error loading the userlookup data (e.g. account is
|
|
# frozen), no big deal; we just won't label the pet right now. Proceed
|
|
# as usual.
|
|
end
|
|
|
|
true
|
|
end
|
|
|
|
def mood
|
|
Neopets::Pet::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 replace_with(other)
|
|
PetState.transaction do
|
|
count = outfits.count
|
|
outfits.find_each { |outfit|
|
|
outfit.pet_state = other
|
|
outfit.save!
|
|
}
|
|
destroy
|
|
end
|
|
count
|
|
end
|
|
|
|
def artist_name
|
|
artist_neopets_username || I18n.translate("pet_states.default_artist_name")
|
|
end
|
|
|
|
def artist_url
|
|
if artist_neopets_username
|
|
"http://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 = []
|
|
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_and_swf_asset_ids(
|
|
pet_type.id,
|
|
swf_asset_ids_str
|
|
)
|
|
end
|
|
existing_swf_assets = SwfAsset.biology_assets.includes(:zone).
|
|
find_all_by_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.unconverted = (relationships.size == 1)
|
|
pet_state
|
|
end
|
|
|
|
def self.repair_all!
|
|
self.transaction do
|
|
self.all.each do |pet_state|
|
|
pet_state.sort_swf_asset_ids!
|
|
pet_state.save
|
|
end
|
|
|
|
self.
|
|
select('pet_states.pet_type_id, pet_states.swf_asset_ids, GROUP_CONCAT(DISTINCT pet_states.id) AS duplicate_ids').
|
|
joins('INNER JOIN pet_states ps2 ON pet_states.pet_type_id = ps2.pet_type_id AND pet_states.swf_asset_ids = ps2.swf_asset_ids').
|
|
group('pet_states.pet_type_id, pet_states.swf_asset_ids').
|
|
having('count(*) > 1').
|
|
all.
|
|
each do |pet_state|
|
|
pet_state.reassign_duplicates!
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|