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
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

View file

@ -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

View file

@ -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

View file

@ -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|

View file

@ -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')