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.
This commit is contained in:
Emi Matchu 2024-02-23 10:44:50 -08:00
parent 1a1615e0ad
commit 3f449310d6
5 changed files with 60 additions and 42 deletions

View file

@ -11,9 +11,11 @@ class ItemsController < ApplicationController
else else
per_page = 30 per_page = 30
end end
@items = @query.results.paginate( @items = @query.results.paginate(
page: params[:page], per_page: per_page) page: params[:page], per_page: per_page)
assign_closeted! assign_closeted!
respond_to do |format| respond_to do |format|
format.html { format.html {
@campaign = Fundraising::Campaign.current rescue nil @campaign = Fundraising::Campaign.current rescue nil
@ -24,13 +26,14 @@ class ItemsController < ApplicationController
end end
} }
format.json { format.json {
render json: {items: @items, total_pages: @items.total_pages, render json: {
query: @query.to_s} items: @items.as_json(
} methods: [:nc?, :pb?, :owned?, :wanted?],
format.js { ),
render json: {items: @items, total_pages: @items.total_pages, appearances: load_appearances,
query: @query.to_s}, total_pages: @items.total_pages,
callback: params[:callback] query: @query.to_s,
}
} }
end end
end end
@ -46,7 +49,6 @@ class ItemsController < ApplicationController
@campaign = Fundraising::Campaign.current rescue nil @campaign = Fundraising::Campaign.current rescue nil
@newest_items = Item.newest.limit(18) @newest_items = Item.newest.limit(18)
} }
format.js { render json: {error: '$q required'}}
end end
end end
end end
@ -100,14 +102,20 @@ class ItemsController < ApplicationController
def assign_closeted! def assign_closeted!
current_user.assign_closeted_to_items!(@items) if user_signed_in? current_user.assign_closeted_to_items!(@items) if user_signed_in?
end 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) def search_error(e)
@items = [] @items = []
respond_to do |format| respond_to do |format|
format.html { flash.now[:alert] = e.message; render } format.html { flash.now[:alert] = e.message; render }
format.json { render :json => {error: e.message} } format.json { render :json => {error: e.message} }
format.js { render :json => {error: e.message},
:callback => params[:callback] }
end end
end end
@ -122,7 +130,7 @@ class ItemsController < ApplicationController
@query = params[:q] @query = params[:q]
raise raise
end end
elsif q.is_a?(Hash) elsif q.is_a?(ActionController::Parameters)
@query = Item::Search::Query.from_params(q, current_user) @query = Item::Search::Query.from_params(q, current_user)
end end
end end

View file

@ -153,11 +153,11 @@ class Item < ApplicationRecord
end end
def owned? def owned?
@owned @owned || false
end end
def wanted? def wanted?
@wanted @wanted || false
end end
def restricted_zones(options={}) def restricted_zones(options={})
@ -445,8 +445,8 @@ class Item < ApplicationRecord
@parent_swf_asset_relationships_to_update = rels @parent_swf_asset_relationships_to_update = rels
end end
Body = Struct.new(:id, :species)
Appearance = Struct.new(:body, :swf_assets) Appearance = Struct.new(:body, :swf_assets)
Appearance::Body = Struct.new(:id, :species)
def appearances def appearances
all_swf_assets = swf_assets.to_a 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_by_body_id.map do |body_id, body_specific_assets|
swf_assets_for_body = body_specific_assets + swf_assets_for_all_bodies swf_assets_for_body = body_specific_assets + swf_assets_for_all_bodies
species = Species.with_body_id(body_id).first! 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) Appearance.new(body, swf_assets_for_body)
end end
end end

View file

@ -46,17 +46,10 @@ class Item
Filter.restricts(value) : Filter.restricts(value) :
Filter.not_restricts(value)) Filter.not_restricts(value))
when 'fits' when 'fits'
color_name, species_name = value.split('-') pet_type = load_pet_type_by_name(value)
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
filters << (is_positive ? filters << (is_positive ?
Filter.fits(pet_type.body_id, color_name, species_name) : Filter.fits(pet_type.body_id, value.downcase) :
Filter.not_fits(pet_type.body_id, color_name, species_name)) Filter.not_fits(pet_type.body_id, value.downcase))
when 'species' when 'species'
begin begin
species = Species.find_by_name!(value) species = Species.find_by_name!(value)
@ -139,9 +132,11 @@ class Item
pet_type = PetType.find(value) pet_type = PetType.find(value)
color_name = pet_type.color.name color_name = pet_type.color.name
species_name = pet_type.species.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 ? filters << (is_positive ?
Filter.fits(pet_type.body_id, color_name, species_name) : Filter.fits(pet_type.body_id, value) :
Filter.not_fits(pet_type.body_id, color_name, species_name)) Filter.not_fits(pet_type.body_id, value))
when 'user_closet_hanger_ownership' when 'user_closet_hanger_ownership'
case value case value
when 'true' when 'true'
@ -160,6 +155,18 @@ class Item
self.new(filters, user) self.new(filters, user)
end 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 end
class Error < Exception class Error < Exception
@ -211,15 +218,11 @@ class Item
self.new Item.not_restricts(value), "-restricts:#{q value}" self.new Item.not_restricts(value), "-restricts:#{q value}"
end end
def self.fits(body_id, color_name, species_name) def self.fits(body_id, value)
# NOTE: Some color syntaxes are weird, like `fits:"polka dot-aisha"`!
value = "#{color_name.downcase}-#{species_name.downcase}"
self.new Item.fits(body_id), "fits:#{q value}" self.new Item.fits(body_id), "fits:#{q value}"
end end
def self.not_fits(body_id, color_name, species_name) def self.not_fits(body_id, value)
# NOTE: Some color syntaxes are weird, like `fits:"polka dot-aisha"`!
value = "#{color_name.downcase}-#{species_name.downcase}"
self.new Item.not_fits(body_id), "-fits:#{q value}" self.new Item.not_fits(body_id), "-fits:#{q value}"
end end
@ -277,14 +280,6 @@ class Item
def self.q(value) def self.q(value)
/\s/.match(value) ? '"' + value + '"' : value /\s/.match(value) ? '"' + value + '"' : value
end 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 end
end end

View file

@ -178,6 +178,18 @@ class PetType < ApplicationRecord
}.first }.first
end 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) def self.all_by_ids_or_children(ids, pet_states)
pet_states_by_pet_type_id = {} pet_states_by_pet_type_id = {}
pet_states.each do |pet_state| pet_states.each do |pet_state|

View file

@ -82,7 +82,10 @@ class SwfAsset < ApplicationRecord
scope :object_assets, -> { where(:type => Item::SwfAssetType) } scope :object_assets, -> { where(:type => Item::SwfAssetType) }
scope :for_item_ids, ->(item_ids) { scope :for_item_ids, ->(item_ids) {
joins(:parent_swf_asset_relationships). 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, -> { scope :with_parent_ids, -> {
select('swf_assets.*, parents_swf_assets.parent_id') select('swf_assets.*, parents_swf_assets.parent_id')