forked from OpenNeo/impress
Emi Matchu
666394de25
I've moved the support secret into the encrypted credentials file, and moved the origin into a top-level custom config value in the environment files, with different defaults per environment but still the ability to override it. (I don't use this, but it feels polite to not actually *demand* that people use port 4000, y'know?)
194 lines
5.7 KiB
Ruby
194 lines
5.7 KiB
Ruby
class Outfit < ApplicationRecord
|
|
has_many :item_outfit_relationships, :dependent => :destroy
|
|
has_many :worn_item_outfit_relationships, -> { where(is_worn: true) },
|
|
class_name: 'ItemOutfitRelationship'
|
|
has_many :worn_items, through: :worn_item_outfit_relationships, source: :item
|
|
|
|
belongs_to :alt_style, optional: true
|
|
belongs_to :pet_state, optional: true # We validate presence below!
|
|
belongs_to :user, optional: true
|
|
|
|
validates :name, :presence => {:if => :user_id}, :uniqueness => {:scope => :user_id, :if => :user_id}
|
|
validates :pet_state, presence: {
|
|
message: ->(object, _) do
|
|
if object.biology
|
|
"does not exist for " +
|
|
"species ##{object.biology[:species_id]}, " +
|
|
"color ##{object.biology[:color_id]}, " +
|
|
"pose #{object.biology[:pose]}"
|
|
else
|
|
"must exist"
|
|
end
|
|
end
|
|
}
|
|
|
|
before_validation :ensure_unique_name, if: :user_id?
|
|
|
|
attr_reader :biology
|
|
delegate :color, to: :pet_state
|
|
|
|
scope :wardrobe_order, -> { order('starred DESC', :name) }
|
|
|
|
class OutfitImage
|
|
def initialize(image_versions)
|
|
@image_versions = image_versions
|
|
end
|
|
|
|
def url
|
|
@image_versions[:large]
|
|
end
|
|
|
|
def large
|
|
Version.new(@image_versions[:large])
|
|
end
|
|
|
|
def medium
|
|
Version.new(@image_versions[:medium])
|
|
end
|
|
|
|
def small
|
|
Version.new(@image_versions[:small])
|
|
end
|
|
|
|
Version = Struct.new(:url)
|
|
end
|
|
|
|
def image?
|
|
true
|
|
end
|
|
|
|
def image
|
|
OutfitImage.new(image_versions)
|
|
end
|
|
|
|
IMAGE_URL_TEMPLATE = Addressable::Template.new(
|
|
Rails.configuration.impress_2020_origin +
|
|
"/api/outfitImage{?id,size,updatedAt}"
|
|
)
|
|
def image_versions
|
|
if Rails.env.production?
|
|
# Now, instead of using the saved outfit to S3, we're using out the
|
|
# DTI 2020 API + CDN cache version. We use openneo-assets.net to get
|
|
# around a bug on Neopets petpages with openneo.net URLs.
|
|
base_url = "https://outfits.openneo-assets.net/outfits" +
|
|
"/#{CGI.escape id.to_s}" +
|
|
"/v/#{CGI.escape updated_at.to_i.to_s}"
|
|
{
|
|
large: "#{base_url}/600.png",
|
|
medium: "#{base_url}/300.png",
|
|
small: "#{base_url}/150.png",
|
|
}
|
|
else
|
|
# In development, just talk to our local Impress 2020 directly.
|
|
build_url = -> size {
|
|
IMAGE_URL_TEMPLATE.expand(
|
|
id: id, size: size, updatedAt: updated_at.to_i
|
|
).to_s
|
|
}
|
|
{
|
|
large: build_url.call("600"),
|
|
medium: build_url.call("300"),
|
|
small: build_url.call("150"),
|
|
}
|
|
end
|
|
|
|
# NOTE: Below is the previous code that uses the saved outfits!
|
|
# {}.tap do |versions|
|
|
# versions[:large] = image.url
|
|
# image.versions.each { |name, version| versions[name] = version.url }
|
|
# end
|
|
end
|
|
|
|
def as_json(more_options={})
|
|
serializable_hash(
|
|
only: [:id, :name, :pet_state_id, :starred, :created_at, :updated_at,
|
|
:alt_style_id],
|
|
methods: [:color_id, :species_id, :pose, :item_ids, :user]
|
|
)
|
|
end
|
|
|
|
def color_id
|
|
pet_state.pet_type.color_id
|
|
end
|
|
|
|
def species_id
|
|
pet_state.pet_type.species_id
|
|
end
|
|
|
|
def pose
|
|
pet_state.pose
|
|
end
|
|
|
|
def biology=(biology)
|
|
@biology = biology.slice(:species_id, :color_id, :pose, :pet_state_id)
|
|
|
|
begin
|
|
if @biology[:pet_state_id]
|
|
self.pet_state = PetState.find(@biology[:pet_state_id])
|
|
else
|
|
pet_type = PetType.where(
|
|
species_id: @biology[:species_id],
|
|
color_id: @biology[:color_id],
|
|
).first!
|
|
self.pet_state = pet_type.pet_states.with_pose(@biology[:pose]).
|
|
emotion_order.first!
|
|
end
|
|
rescue ActiveRecord::RecordNotFound
|
|
# If there's no such pet state (which shouldn't happen normally in-app),
|
|
# we don't set `pet_state` but we keep `@biology` for validation.
|
|
end
|
|
end
|
|
|
|
def item_ids
|
|
rels = item_outfit_relationships
|
|
{
|
|
worn: rels.filter { |r| r.is_worn? }.map { |r| r.item_id },
|
|
closeted: rels.filter { |r| !r.is_worn? }.map { |r| r.item_id }
|
|
}
|
|
end
|
|
|
|
def item_ids=(item_ids)
|
|
# Ensure there are no duplicates between the worn/closeted IDs. If an ID is
|
|
# present in both, it's kept in `worn` and removed from `closeted`.
|
|
worn_item_ids = item_ids.fetch(:worn, []).uniq
|
|
closeted_item_ids = item_ids.fetch(:closeted, []).uniq
|
|
closeted_item_ids.reject! { |id| worn_item_ids.include?(id) }
|
|
|
|
# Set the worn and closeted item outfit relationships. If there are any
|
|
# others attached to this outfit, they are implicitly deleted.
|
|
new_relationships = []
|
|
new_relationships += worn_item_ids.map do |item_id|
|
|
ItemOutfitRelationship.new(item_id: item_id, is_worn: true)
|
|
end
|
|
new_relationships += closeted_item_ids.map do |item_id|
|
|
ItemOutfitRelationship.new(item_id: item_id, is_worn: false)
|
|
end
|
|
self.item_outfit_relationships = new_relationships
|
|
end
|
|
|
|
def ensure_unique_name
|
|
# If no name was provided, start with "Untitled outfit".
|
|
self.name = "Untitled outfit" if name.blank?
|
|
|
|
# Strip whitespace from the name.
|
|
self.name.strip!
|
|
|
|
# Get the base name of the provided name, without any "(1)" suffixes.
|
|
base_name = name.sub(/\s*\([0-9]+\)$/, '')
|
|
|
|
# Find the user's other outfits that start with the same base name, and get
|
|
# *their* names, with whitespace stripped.
|
|
existing_outfits = self.user.outfits.
|
|
where("name LIKE ?", Outfit.sanitize_sql_like(base_name) + "%")
|
|
existing_outfits = existing_outfits.where("id != ?", id) unless id.nil?
|
|
existing_names = existing_outfits.map(&:name).map(&:strip)
|
|
|
|
# Try the provided name first, but if it's taken, add a "(1)" suffix and
|
|
# keep incrementing it until it's not.
|
|
i = 1
|
|
while existing_names.include?(name)
|
|
self.name = "#{base_name} (#{i})"
|
|
i += 1
|
|
end
|
|
end
|
|
end
|