From 3f449310d667d7242482cc91320a3192fcd576df Mon Sep 17 00:00:00 2001 From: Emi Matchu Date: Fri, 23 Feb 2024 10:44:50 -0800 Subject: [PATCH] Refactor item search JSON, add appearances Preparing to finally move wardrobe-2020's item search to use the main app's API endpoints instead! One blocker I forgot about here: Impress 2020 has actual support for knowing an item's true appearance, like by reading the manifest and stuff, that we haven't really ported over. I feel like maybe I should pause and work on the changes to manifest-archiving that I'd been planning anyway? I'll think about it. --- app/controllers/items_controller.rb | 30 +++++++++++------- app/models/item.rb | 8 ++--- app/models/item/search/query.rb | 47 +++++++++++++---------------- app/models/pet_type.rb | 12 ++++++++ app/models/swf_asset.rb | 5 ++- 5 files changed, 60 insertions(+), 42 deletions(-) diff --git a/app/controllers/items_controller.rb b/app/controllers/items_controller.rb index 6b19bc6d..c2e2d6ee 100644 --- a/app/controllers/items_controller.rb +++ b/app/controllers/items_controller.rb @@ -11,9 +11,11 @@ class ItemsController < ApplicationController else per_page = 30 end + @items = @query.results.paginate( page: params[:page], per_page: per_page) assign_closeted! + respond_to do |format| format.html { @campaign = Fundraising::Campaign.current rescue nil @@ -24,13 +26,14 @@ class ItemsController < ApplicationController end } format.json { - render json: {items: @items, total_pages: @items.total_pages, - query: @query.to_s} - } - format.js { - render json: {items: @items, total_pages: @items.total_pages, - query: @query.to_s}, - callback: params[:callback] + render json: { + items: @items.as_json( + methods: [:nc?, :pb?, :owned?, :wanted?], + ), + appearances: load_appearances, + total_pages: @items.total_pages, + query: @query.to_s, + } } end end @@ -46,7 +49,6 @@ class ItemsController < ApplicationController @campaign = Fundraising::Campaign.current rescue nil @newest_items = Item.newest.limit(18) } - format.js { render json: {error: '$q required'}} end end end @@ -100,14 +102,20 @@ class ItemsController < ApplicationController def assign_closeted! current_user.assign_closeted_to_items!(@items) if user_signed_in? end + + def load_appearances + pet_type_name = params[:with_appearances_for] + return {} if pet_type_name.blank? + + pet_type = Item::Search::Query.load_pet_type_by_name(pet_type_name) + pet_type.appearances_for(@items.map(&:id)) + end def search_error(e) @items = [] respond_to do |format| format.html { flash.now[:alert] = e.message; render } format.json { render :json => {error: e.message} } - format.js { render :json => {error: e.message}, - :callback => params[:callback] } end end @@ -122,7 +130,7 @@ class ItemsController < ApplicationController @query = params[:q] raise end - elsif q.is_a?(Hash) + elsif q.is_a?(ActionController::Parameters) @query = Item::Search::Query.from_params(q, current_user) end end diff --git a/app/models/item.rb b/app/models/item.rb index 2298fb87..af030b0a 100644 --- a/app/models/item.rb +++ b/app/models/item.rb @@ -153,11 +153,11 @@ class Item < ApplicationRecord end def owned? - @owned + @owned || false end def wanted? - @wanted + @wanted || false end def restricted_zones(options={}) @@ -445,8 +445,8 @@ class Item < ApplicationRecord @parent_swf_asset_relationships_to_update = rels end - Body = Struct.new(:id, :species) Appearance = Struct.new(:body, :swf_assets) + Appearance::Body = Struct.new(:id, :species) def appearances all_swf_assets = swf_assets.to_a @@ -469,7 +469,7 @@ class Item < ApplicationRecord swf_assets_by_body_id.map do |body_id, body_specific_assets| swf_assets_for_body = body_specific_assets + swf_assets_for_all_bodies species = Species.with_body_id(body_id).first! - body = Body.new(body_id, species) + body = Appearance::Body.new(body_id, species) Appearance.new(body, swf_assets_for_body) end end diff --git a/app/models/item/search/query.rb b/app/models/item/search/query.rb index b1f5a186..4f235837 100644 --- a/app/models/item/search/query.rb +++ b/app/models/item/search/query.rb @@ -46,17 +46,10 @@ class Item Filter.restricts(value) : Filter.not_restricts(value)) when 'fits' - color_name, species_name = value.split('-') - begin - pet_type = PetType.matching_name(color_name, species_name).first! - rescue ActiveRecord::RecordNotFound - message = I18n.translate('items.search.errors.not_found.pet_type', - name1: color_name.capitalize, name2: species_name.capitalize) - raise Item::Search::Error, message - end + pet_type = load_pet_type_by_name(value) filters << (is_positive ? - Filter.fits(pet_type.body_id, color_name, species_name) : - Filter.not_fits(pet_type.body_id, color_name, species_name)) + Filter.fits(pet_type.body_id, value.downcase) : + Filter.not_fits(pet_type.body_id, value.downcase)) when 'species' begin species = Species.find_by_name!(value) @@ -139,9 +132,11 @@ class Item pet_type = PetType.find(value) color_name = pet_type.color.name species_name = pet_type.species.name + # NOTE: Some color syntaxes are weird, like `fits:"polka dot-aisha"`! + value = "#{color_name}-#{species_name}" filters << (is_positive ? - Filter.fits(pet_type.body_id, color_name, species_name) : - Filter.not_fits(pet_type.body_id, color_name, species_name)) + Filter.fits(pet_type.body_id, value) : + Filter.not_fits(pet_type.body_id, value)) when 'user_closet_hanger_ownership' case value when 'true' @@ -160,6 +155,18 @@ class Item self.new(filters, user) end + + def self.load_pet_type_by_name(pet_type_string) + color_name, species_name = pet_type_string.split("-") + + begin + PetType.matching_name(color_name, species_name).first! + rescue ActiveRecord::RecordNotFound + message = I18n.translate('items.search.errors.not_found.pet_type', + name1: color_name.capitalize, name2: species_name.capitalize) + raise Item::Search::Error, message + end + end end class Error < Exception @@ -211,15 +218,11 @@ class Item self.new Item.not_restricts(value), "-restricts:#{q value}" end - def self.fits(body_id, color_name, species_name) - # NOTE: Some color syntaxes are weird, like `fits:"polka dot-aisha"`! - value = "#{color_name.downcase}-#{species_name.downcase}" + def self.fits(body_id, value) self.new Item.fits(body_id), "fits:#{q value}" end - def self.not_fits(body_id, color_name, species_name) - # NOTE: Some color syntaxes are weird, like `fits:"polka dot-aisha"`! - value = "#{color_name.downcase}-#{species_name.downcase}" + def self.not_fits(body_id, value) self.new Item.not_fits(body_id), "-fits:#{q value}" end @@ -277,14 +280,6 @@ class Item def self.q(value) /\s/.match(value) ? '"' + value + '"' : value end - - def self.build_fits_filter_text(color_name, species_name) - # NOTE: Colors like "Polka Dot" must be written as - # `fits:"polka dot-aisha"`. - value = "#{color_name.downcase}-#{species_name.downcase}" - value = '"' + value + '"' if value.include? ' ' - "fits:#{value}" - end end end end diff --git a/app/models/pet_type.rb b/app/models/pet_type.rb index a10dc3e7..2353cf4a 100644 --- a/app/models/pet_type.rb +++ b/app/models/pet_type.rb @@ -178,6 +178,18 @@ class PetType < ApplicationRecord }.first end + def appearances_for(item_ids) + # First, load all the relationships for these items that also fit this + # body. + relationships = ParentSwfAssetRelationship.includes(:swf_asset). + where(parent_type: "Item", parent_id: item_ids). + where(swf_asset: {body_id: [body_id, 0]}) + + # Then, convert this into a hash from item ID to SWF assets. + assets_by_item_id = relationships.group_by(&:parent_id). + transform_values { |rels| rels.map(&:swf_asset) } + end + def self.all_by_ids_or_children(ids, pet_states) pet_states_by_pet_type_id = {} pet_states.each do |pet_state| diff --git a/app/models/swf_asset.rb b/app/models/swf_asset.rb index 07f5728c..3f7db65b 100644 --- a/app/models/swf_asset.rb +++ b/app/models/swf_asset.rb @@ -82,7 +82,10 @@ class SwfAsset < ApplicationRecord scope :object_assets, -> { where(:type => Item::SwfAssetType) } scope :for_item_ids, ->(item_ids) { joins(:parent_swf_asset_relationships). - where(ParentSwfAssetRelationship.arel_table[:parent_id].in(item_ids)) + where(parent_swf_asset_relationships: { + parent_type: "Item", + parent_id: item_ids, + }) } scope :with_parent_ids, -> { select('swf_assets.*, parents_swf_assets.parent_id')