forked from OpenNeo/impress
Emi Matchu
bec350e9f2
I don't have the cute fancy UI with trade list names included, but! Now the trade ratios should match DTI 2020 and that's nice!
194 lines
5.9 KiB
Ruby
194 lines
5.9 KiB
Ruby
class ClosetHanger < ApplicationRecord
|
|
belongs_to :item
|
|
belongs_to :list, class_name: 'ClosetList', optional: true
|
|
belongs_to :user
|
|
|
|
delegate :name, to: :item, prefix: true
|
|
delegate :log_trade_activity, to: :user
|
|
|
|
validates :item_id, :uniqueness => {:scope => [:user_id, :owned, :list_id]}
|
|
validates :quantity, :numericality => {:greater_than => 0}
|
|
validates_presence_of :item, :user
|
|
|
|
validate :list_belongs_to_user
|
|
|
|
scope :alphabetical_by_item_name, -> {
|
|
it = Item::Translation.arel_table
|
|
joins(:item => :translations).where(it[:locale].eq(I18n.locale)).
|
|
order(it[:name].asc)
|
|
}
|
|
scope :trading, -> {
|
|
ch = arel_table
|
|
cl = ClosetList.arel_table
|
|
u = User.arel_table
|
|
joins(:user, :list).where(
|
|
# sigh… our default-lists continue to be a pain
|
|
(
|
|
ch[:list_id].not_eq(nil).and(cl[:visibility].gteq(
|
|
ClosetVisibility[:trading].id))
|
|
).or(
|
|
(
|
|
ch[:list_id].eq(nil).and(ch[:owned].eq(true))
|
|
).and(
|
|
u[:owned_closet_hangers_visibility].gteq(
|
|
ClosetVisibility[:trading].id)
|
|
)
|
|
).or(
|
|
(
|
|
ch[:list_id].eq(nil).and(ch[:owned].eq(false))
|
|
).and(
|
|
u[:wanted_closet_hangers_visibility].gteq(
|
|
ClosetVisibility[:trading].id)
|
|
)
|
|
)
|
|
)
|
|
}
|
|
scope :newest, -> { order(arel_table[:created_at].desc) }
|
|
scope :owned_before_wanted, -> { order(arel_table[:owned].desc) }
|
|
scope :unlisted, -> { where(:list_id => nil) }
|
|
scope :user_is_active, -> {
|
|
u = User.arel_table
|
|
joins(:user).where(u[:last_trade_activity_at].gteq(6.months.ago))
|
|
}
|
|
|
|
before_validation :merge_quantities, :set_owned_by_list
|
|
|
|
after_save :log_trade_activity, if: :trading?
|
|
after_destroy :log_trade_activity, if: :trading?
|
|
|
|
def possibly_null_closet_list
|
|
list || user.null_closet_list(owned)
|
|
end
|
|
|
|
def trading?
|
|
possibly_null_closet_list.trading?
|
|
end
|
|
|
|
def wanted?
|
|
!owned?
|
|
end
|
|
|
|
def possibly_null_list_id=(list_id_or_owned)
|
|
if list_id_or_owned.to_s == 'true' || list_id_or_owned.to_s == 'false'
|
|
self.list_id = nil
|
|
self.owned = list_id_or_owned
|
|
else
|
|
self.list_id = list_id_or_owned
|
|
# owned is set in the set_owned_by_list hook
|
|
end
|
|
end
|
|
|
|
def verb(subject=:someone)
|
|
self.class.verb(subject, owned?)
|
|
end
|
|
|
|
def self.verb(subject, owned, positive=true)
|
|
base = (owned) ? 'own' : 'want'
|
|
base << 's' if positive && subject != :you && subject != :i
|
|
base
|
|
end
|
|
|
|
def self.preload_items(
|
|
hangers,
|
|
items_scope: Item.all,
|
|
item_translations_scope: Item::Translation.all
|
|
)
|
|
# Preload the records we need. (This is like `includes`, but `includes`
|
|
# always selects all fields for all records, and we give the caller the
|
|
# opportunity to specify which fields it actually wants via scope!)
|
|
items = items_scope.where(id: hangers.map(&:item_id))
|
|
translations = item_translations_scope.where(item_id: items.map(&:id))
|
|
|
|
# Group the records by relevant IDs.
|
|
translations_by_item_id = translations.group_by(&:item_id)
|
|
items_by_id = items.to_h { |i| [i.id, i] }
|
|
|
|
# Assign the preloaded records to the records they belong to. (This is like
|
|
# doing e.g. i.translations = ..., but that's a database write - we
|
|
# actually just want to set the `translations` field itself directly!
|
|
# Hacky, ripped from how `ActiveRecord::Associations::Preloader` does it!)
|
|
items.each do |item|
|
|
item.association(:translations).target = translations_by_item_id[item.id]
|
|
end
|
|
hangers.each do |hanger|
|
|
hanger.association(:item).target = items_by_id[hanger.item_id]
|
|
end
|
|
end
|
|
|
|
def self.set_quantity!(quantity, options)
|
|
quantity = quantity.to_i
|
|
conditions = {:user_id => options[:user_id].to_i,
|
|
:item_id => options[:item_id].to_i}
|
|
|
|
if options[:key] == "true"
|
|
conditions[:owned] = true
|
|
conditions[:list_id] = nil
|
|
elsif options[:key] == "false"
|
|
conditions[:owned] = false
|
|
conditions[:list_id] = nil
|
|
else
|
|
conditions[:list_id] = options[:key].to_i
|
|
end
|
|
|
|
hanger = self.where(conditions).first
|
|
|
|
if quantity > 0
|
|
# If quantity is non-zero, create/update the corresponding hanger.
|
|
|
|
unless hanger
|
|
hanger = self.new
|
|
hanger.user_id = conditions[:user_id]
|
|
hanger.item_id = conditions[:item_id]
|
|
# One of the following will be nil, and that's okay. If owned is nil,
|
|
# we'll cover for it before validation, as always.
|
|
hanger.owned = conditions[:owned]
|
|
hanger.list_id = conditions[:list_id]
|
|
end
|
|
|
|
hanger.quantity = quantity
|
|
hanger.save!
|
|
elsif hanger
|
|
# If quantity is zero and there's a hanger, destroy it.
|
|
hanger.destroy
|
|
end
|
|
|
|
# If quantity is zero and there's no hanger, good. Do nothing.
|
|
end
|
|
|
|
protected
|
|
|
|
def list_belongs_to_user
|
|
if list_id?
|
|
if list
|
|
errors.add(:list_id, "must belong to you") unless list.user_id == user_id
|
|
else
|
|
errors.add(:list, "must exist")
|
|
end
|
|
end
|
|
end
|
|
|
|
def merge_quantities
|
|
# Find a hanger that conflicts: for the same item, in the same user's
|
|
# closet, same owned status, same list. It also must not be the current
|
|
# hanger. Select enough for our logic and to update flex_source.
|
|
# TODO: We deleted flex, does this reduce what data we need here?
|
|
conflicting_hanger = self.class.select([:id, :quantity, :user_id, :item_id,
|
|
:owned]).
|
|
where(:user_id => user_id, :item_id => item_id, :owned => owned,
|
|
:list_id => list_id).where(['id != ?', self.id]).first
|
|
|
|
# If there is such a hanger, remove it and merge its quantity into this one.
|
|
if conflicting_hanger
|
|
self.quantity += conflicting_hanger.quantity
|
|
conflicting_hanger.destroy
|
|
end
|
|
|
|
true
|
|
end
|
|
|
|
def set_owned_by_list
|
|
self.owned = list.hangers_owned if list
|
|
true
|
|
end
|
|
end
|
|
|