2023-08-02 16:05:02 -07:00
|
|
|
class ClosetList < ApplicationRecord
|
2011-07-26 17:27:23 -07:00
|
|
|
belongs_to :user
|
2023-10-24 15:35:21 -07:00
|
|
|
has_many :hangers, class_name: 'ClosetHanger', foreign_key: 'list_id', dependent: :destroy
|
2011-07-26 17:27:23 -07:00
|
|
|
|
|
|
|
validates :name, :presence => true, :uniqueness => {:scope => :user_id}
|
|
|
|
validates :user, :presence => true
|
|
|
|
validates :hangers_owned, :inclusion => {:in => [true, false], :message => "can't be blank"}
|
|
|
|
|
2024-01-19 00:00:46 -08:00
|
|
|
delegate :log_trade_activity, to: :user
|
|
|
|
|
2023-07-22 14:04:01 -07:00
|
|
|
scope :alphabetical, -> { order(:name) }
|
2023-07-29 11:37:46 -07:00
|
|
|
scope :publicly_visible, -> {
|
|
|
|
where(arel_table[:visibility].gteq(ClosetVisibility[:public].id))
|
|
|
|
}
|
2024-01-19 00:00:46 -08:00
|
|
|
scope :trading, -> {
|
|
|
|
where(arel_table[:visibility].gteq(ClosetVisibility[:trading].id))
|
|
|
|
}
|
2023-07-22 14:04:01 -07:00
|
|
|
scope :visible_to, ->(user) {
|
2011-07-30 19:34:27 -07:00
|
|
|
condition = arel_table[:visibility].gteq(ClosetVisibility[:public].id)
|
|
|
|
condition = condition.or(arel_table[:user_id].eq(user.id)) if user
|
|
|
|
where(condition)
|
|
|
|
}
|
2011-07-29 07:52:04 -07:00
|
|
|
|
2011-07-26 17:27:23 -07:00
|
|
|
after_save :sync_hangers_owned!
|
2024-01-19 00:00:46 -08:00
|
|
|
after_save :log_trade_activity, if: :trading?
|
|
|
|
after_destroy :log_trade_activity, if: :trading?
|
|
|
|
|
|
|
|
def trading?
|
|
|
|
visibility >= ClosetVisibility[:trading].id
|
|
|
|
end
|
2011-07-26 17:27:23 -07:00
|
|
|
|
|
|
|
def sync_hangers_owned!
|
|
|
|
if hangers_owned_changed?
|
|
|
|
hangers.each do |hanger|
|
|
|
|
hanger.owned = hangers_owned
|
|
|
|
hanger.save!
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2015-07-27 10:25:24 -07:00
|
|
|
def try_non_null(method_name)
|
|
|
|
send(method_name)
|
|
|
|
end
|
|
|
|
|
Do preloading manually on user list pages, to reduce memory usage
I used the new profiler tools on this page, and noticed a lot of
allocations in the Globalize library, which we use for translating
database records. I realized that we were loading all of the fields of
not just all of the items on the page, but all of their translation
records in all locales! We used to scrape data for lots of languages, so
that can be quite a lot!
Unfortunately, Rails's `includes` method to efficiently preload related
records always loads all fields, and simply can't be overridden.
So, in this change we write manual preloading code, to identify the
records we need, load them in big bulk queries, and assign them back to
the appropriate associations. Basically just what `includes` does, but
written out a bit more, to give us the chance to specify SELECT and
WHERE clauses!
2023-10-27 19:42:02 -07:00
|
|
|
def self.preload_items(
|
|
|
|
lists,
|
|
|
|
hangers_scope: ClosetHanger.all,
|
2024-02-20 15:36:20 -08:00
|
|
|
items_scope: Item.all
|
Do preloading manually on user list pages, to reduce memory usage
I used the new profiler tools on this page, and noticed a lot of
allocations in the Globalize library, which we use for translating
database records. I realized that we were loading all of the fields of
not just all of the items on the page, but all of their translation
records in all locales! We used to scrape data for lots of languages, so
that can be quite a lot!
Unfortunately, Rails's `includes` method to efficiently preload related
records always loads all fields, and simply can't be overridden.
So, in this change we write manual preloading code, to identify the
records we need, load them in big bulk queries, and assign them back to
the appropriate associations. Basically just what `includes` does, but
written out a bit more, to give us the chance to specify SELECT and
WHERE clauses!
2023-10-27 19:42:02 -07:00
|
|
|
)
|
|
|
|
# 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!)
|
|
|
|
hangers = hangers_scope.where(list_id: lists.map(&:id))
|
|
|
|
|
|
|
|
# Group the records by relevant IDs.
|
|
|
|
hangers_by_list_id = hangers.group_by(&:list_id)
|
|
|
|
|
|
|
|
# Assign the preloaded records to the records they belong to. (This is like
|
2024-02-20 15:36:20 -08:00
|
|
|
# doing e.g. h.item = ..., but that's a database write - we actually just
|
|
|
|
# want to set the `item` field itself directly! Hacky, ripped from how
|
|
|
|
# `ActiveRecord::Associations::Preloader` does it!)
|
Do preloading manually on user list pages, to reduce memory usage
I used the new profiler tools on this page, and noticed a lot of
allocations in the Globalize library, which we use for translating
database records. I realized that we were loading all of the fields of
not just all of the items on the page, but all of their translation
records in all locales! We used to scrape data for lots of languages, so
that can be quite a lot!
Unfortunately, Rails's `includes` method to efficiently preload related
records always loads all fields, and simply can't be overridden.
So, in this change we write manual preloading code, to identify the
records we need, load them in big bulk queries, and assign them back to
the appropriate associations. Basically just what `includes` does, but
written out a bit more, to give us the chance to specify SELECT and
WHERE clauses!
2023-10-27 19:42:02 -07:00
|
|
|
lists.each do |list|
|
|
|
|
list.association(:hangers).target = hangers_by_list_id[list.id]
|
|
|
|
end
|
|
|
|
|
|
|
|
# Then, do similar preloading for the hangers and their items.
|
|
|
|
ClosetHanger.preload_items(
|
|
|
|
hangers,
|
|
|
|
items_scope: items_scope,
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
2013-12-27 11:49:46 -08:00
|
|
|
module VisibilityMethods
|
|
|
|
delegate :trading?, to: :visibility_level
|
|
|
|
|
|
|
|
def visibility_level
|
|
|
|
ClosetVisibility.levels[visibility]
|
|
|
|
end
|
|
|
|
|
|
|
|
def trading_changed?
|
|
|
|
return false unless visibility_changed?
|
|
|
|
level_change = visibility_change.map { |v| ClosetVisibility.levels[v] }
|
|
|
|
old_trading, new_trading = level_change.map(&:trading?)
|
|
|
|
old_trading != new_trading
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2024-01-21 06:42:24 -08:00
|
|
|
def self.group_by_owned
|
2024-01-23 04:06:58 -08:00
|
|
|
{true => [], false => []}.tap do |h|
|
|
|
|
all.each { |list| h[list.hangers_owned?] << list}
|
|
|
|
end
|
2024-01-21 06:42:24 -08:00
|
|
|
end
|
|
|
|
|
2013-12-27 11:49:46 -08:00
|
|
|
include VisibilityMethods
|
|
|
|
|
|
|
|
class Null
|
|
|
|
include VisibilityMethods
|
|
|
|
attr_reader :user
|
|
|
|
|
|
|
|
def initialize(user)
|
|
|
|
@user = user
|
|
|
|
end
|
|
|
|
|
|
|
|
def hangers
|
|
|
|
user.closet_hangers.unlisted.where(owned: hangers_owned)
|
|
|
|
end
|
2015-07-27 10:25:24 -07:00
|
|
|
|
|
|
|
def hangers_owned?
|
|
|
|
hangers_owned
|
|
|
|
end
|
|
|
|
|
|
|
|
def try_non_null(method_name)
|
|
|
|
nil
|
|
|
|
end
|
2013-12-27 11:49:46 -08:00
|
|
|
end
|
|
|
|
|
|
|
|
class NullOwned < Null
|
|
|
|
def hangers_owned
|
|
|
|
true
|
|
|
|
end
|
|
|
|
|
|
|
|
def visibility
|
|
|
|
user.owned_closet_hangers_visibility
|
|
|
|
end
|
|
|
|
|
|
|
|
def visibility_changed?
|
|
|
|
user.owned_closet_hangers_visibility_changed?
|
|
|
|
end
|
|
|
|
|
|
|
|
def visibility_change
|
|
|
|
user.owned_closet_hangers_visibility_change
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
class NullWanted < Null
|
|
|
|
def hangers_owned
|
|
|
|
false
|
|
|
|
end
|
|
|
|
|
|
|
|
def visibility
|
|
|
|
user.wanted_closet_hangers_visibility
|
|
|
|
end
|
|
|
|
|
|
|
|
def visibility_changed?
|
|
|
|
user.wanted_closet_hangers_visibility_changed?
|
|
|
|
end
|
|
|
|
|
|
|
|
def visibility_change
|
|
|
|
user.wanted_closet_hangers_visibility_change
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|