impress/app/models/item.rb

420 lines
13 KiB
Ruby

class Item < ActiveRecord::Base
SwfAssetType = 'object'
has_one :contribution, :as => :contributed
has_many :parent_swf_asset_relationships, :foreign_key => 'parent_id',
:conditions => {:swf_asset_type => SwfAssetType}
has_many :swf_assets, :through => :parent_swf_asset_relationships, :source => :object_asset
attr_writer :current_body_id
NCRarities = [0, 500]
PaintbrushSetDescription = 'This item is part of a deluxe paint brush set!'
set_table_name 'objects' # Neo & PHP Impress call them objects, but the class name is a conflict (duh!)
set_inheritance_column 'inheritance_type' # PHP Impress used "type" to describe category
cattr_reader :per_page
@@per_page = 30
scope :alphabetize, order('name ASC')
scope :join_swf_assets, joins('INNER JOIN parents_swf_assets psa ON psa.swf_asset_type = "object" AND psa.parent_id = objects.id').
joins('INNER JOIN swf_assets ON swf_assets.id = psa.swf_asset_id').
group('objects.id')
# Not defining validations, since this app is currently read-only
def nc?
NCRarities.include?(rarity_index)
end
def restricted_zones
unless @restricted_zones
@restricted_zones = []
zones_restrict.split(//).each_with_index do |switch, id|
@restricted_zones << Zone.find(id.to_i + 1) if switch == '1'
end
end
@restricted_zones
end
def occupied_zones
all_body_ids = []
zone_body_ids = {}
selected_assets = swf_assets.select('body_id, zone_id').each do |swf_asset|
zone_body_ids[swf_asset.zone_id] ||= []
body_ids = zone_body_ids[swf_asset.zone_id]
body_ids << swf_asset.body_id unless body_ids.include?(swf_asset.body_id)
all_body_ids << swf_asset.body_id unless all_body_ids.include?(swf_asset.body_id)
end
zones = []
total_body_ids = all_body_ids.size
zone_body_ids.each do |zone_id, body_ids|
zone = Zone.find(zone_id)
zone.sometimes = true if body_ids.size < total_body_ids
zones << zone
end
zones
end
def species_support_ids
@species_support_ids_array ||= read_attribute('species_support_ids').split(',').map(&:to_i)
end
def species_support_ids=(replacement)
@species_support_ids_array = nil
replacement = replacement.join(',') if replacement.is_a?(Array)
write_attribute('species_support_ids', replacement)
end
def supported_species
@supported_species ||= species_support_ids.empty? ? Species.all : species_support_ids.sort.map { |id| Species.find(id) }
end
def self.search(query)
raise SearchError, "Please provide a search query" unless query
query = query.strip
raise SearchError, "Search queries should be at least 3 characters" if query.length < 3
query_conditions = [Condition.new]
in_phrase = false
query.each_char do |c|
if c == ' ' && !in_phrase
query_conditions << Condition.new
elsif c == '"'
in_phrase = !in_phrase
elsif c == ':' && !in_phrase
query_conditions.last.to_filter!
elsif c == '-' && !in_phrase && query_conditions.last.empty?
query_conditions.last.negate!
else
query_conditions.last << c
end
end
limited_filters_used = []
query_conditions.inject(self.scoped) do |scope, condition|
if condition.filter? && LimitedSearchFilters.include?(condition.filter)
if limited_filters_used.include?(condition.filter)
raise SearchError, "The #{condition.filter} filter is complex; please only use one per search. Thanks!"
else
limited_filters_used << condition.filter
end
end
condition.narrow(scope)
end
end
def as_json(options = {})
{
:description => description,
:id => id,
:name => name,
:thumbnail_url => thumbnail_url,
:zones_restrict => zones_restrict,
:rarity_index => rarity_index
}
end
before_create do
self.sold_in_mall = false
true
end
def handle_assets!
if @parent_swf_asset_relationships_to_update && @current_body_id
new_swf_asset_ids = @parent_swf_asset_relationships_to_update.map(&:swf_asset_id)
rels = ParentSwfAssetRelationship.arel_table
swf_assets = SwfAsset.arel_table
ids_to_delete = self.parent_swf_asset_relationships.
select(:id).
joins(:object_asset).
where(rels[:swf_asset_id].not_in(new_swf_asset_ids)).
where(swf_assets[:body_id].in([@current_body_id, 0])).
map(&:id)
unless ids_to_delete.empty?
ParentSwfAssetRelationship.
where(rels[:parent_id].eq(self.id)).
where(rels[:swf_asset_type].eq(SwfAssetType)).
where(rels[:swf_asset_id].in(ids_to_delete)).
delete_all
end
self.parent_swf_asset_relationships += @parent_swf_asset_relationships_to_update
end
end
def origin_registry_info=(info)
# bear in mind that numbers from registries are floats
self.species_support_ids = info[:species_support].map(&:to_i)
attribute_names.each do |attribute|
value = info[attribute.to_sym]
if value
value = value.to_i if value.is_a? Float
self[attribute] = value
end
end
end
def pending_swf_assets
@parent_swf_asset_relationships_to_update.inject([]) do |all_swf_assets, relationship|
all_swf_assets << relationship.swf_asset
end
end
def parent_swf_asset_relationships_to_update=(rels)
@parent_swf_asset_relationships_to_update = rels
end
def self.all_by_ids_or_children(ids, swf_assets)
swf_asset_ids = []
swf_assets_by_id = {}
swf_assets_by_parent_id = {}
swf_assets.each do |swf_asset|
id = swf_asset.id
swf_assets_by_id[id] = swf_asset
swf_asset_ids << id
end
SwfAsset.select('id, parent_id').object_assets.joins(:object_asset_relationships).
where(SwfAsset.arel_table[:id].in(swf_asset_ids)).each do |row|
item_id = row.parent_id.to_i
swf_assets_by_parent_id[item_id] ||= []
swf_assets_by_parent_id[item_id] << swf_assets_by_id[row.id.to_i]
ids << item_id
end
find(ids).tap do |items|
items.each do |item|
swf_assets = swf_assets_by_parent_id[item.id]
if swf_assets
swf_assets.each do |swf_asset|
swf_asset.item = item
end
end
end
end
end
def self.collection_from_pet_type_and_registries(pet_type, info_registry, asset_registry)
# bear in mind that registries are arrays with many nil elements,
# due to how the parser works
items = {}
item_ids = []
info_registry.each do |info|
if info && info[:is_compatible]
item_ids << info[:obj_info_id].to_i
end
end
existing_relationships_by_item_id_and_swf_asset_id = {}
existing_items = Item.find_all_by_id(item_ids, :include => :parent_swf_asset_relationships)
existing_items.each do |item|
items[item.id] = item
relationships_by_swf_asset_id = {}
item.parent_swf_asset_relationships.each do |relationship|
relationships_by_swf_asset_id[relationship.swf_asset_id] = relationship
end
existing_relationships_by_item_id_and_swf_asset_id[item.id] =
relationships_by_swf_asset_id
end
swf_asset_ids = []
asset_registry.each_with_index do |asset_data, index|
swf_asset_ids << index if asset_data
end
existing_swf_assets = SwfAsset.find_all_by_id swf_asset_ids,
:conditions => {:type => SwfAssetType}
existing_swf_assets_by_id = {}
existing_swf_assets.each do |swf_asset|
existing_swf_assets_by_id[swf_asset.id] = swf_asset
end
relationships_by_item_id = {}
asset_registry.each do |asset_data|
if asset_data
item_id = asset_data[:obj_info_id].to_i
next unless item_ids.include?(item_id) # skip incompatible
item = items[item_id]
unless item
item = Item.new
item.id = item_id
items[item_id] = item
end
item.origin_registry_info = info_registry[item.id]
item.current_body_id = pet_type.body_id
swf_asset_id = asset_data[:asset_id].to_i
swf_asset = existing_swf_assets_by_id[swf_asset_id]
unless swf_asset
swf_asset = SwfAsset.new
swf_asset.id = swf_asset_id
end
swf_asset.origin_object_data = asset_data
swf_asset.origin_pet_type = pet_type
relationship = existing_relationships_by_item_id_and_swf_asset_id[item.id][swf_asset_id] rescue nil
unless relationship
relationship = ParentSwfAssetRelationship.new
relationship.parent_id = item.id
relationship.swf_asset_type = SwfAssetType
relationship.swf_asset_id = swf_asset.id
end
relationship.object_asset = swf_asset
relationships_by_item_id[item_id] ||= []
relationships_by_item_id[item_id] << relationship
end
end
relationships_by_item_id.each do |item_id, relationships|
items[item_id].parent_swf_asset_relationships_to_update = relationships
end
items.values
end
private
SearchFilterScopes = []
LimitedSearchFilters = []
def self.search_filter(name, options={}, &block)
assume_complement = options.delete(:assume_complement) || true
name = name.to_s
SearchFilterScopes << name
LimitedSearchFilters << name if options[:limit]
(class << self; self; end).instance_eval do
if options[:full]
define_method "search_filter_#{name}", &options[:full]
else
if assume_complement
define_method "search_filter_not_#{name}", &Item.search_filter_block(options, false, &block)
end
define_method "search_filter_#{name}", &Item.search_filter_block(options, true, &block)
end
end
end
def self.single_search_filter(name, options={}, &block)
options[:assume_complement] = false
search_filter name, options, &block
end
def self.search_filter_block(options, positive)
Proc.new { |str, scope|
condition = yield(str)
condition = "!(#{condition.to_sql})" unless positive
scope = scope.send(options[:scope]) if options[:scope]
scope.where(condition)
}
end
search_filter :name do |name|
arel_table[:name].matches("%#{name}%")
end
search_filter :description do |description|
arel_table[:description].matches("%#{description}%")
end
ADJECTIVE_FILTERS = {
'nc' => arel_table[:rarity_index].in(NCRarities),
'pb' => arel_table[:description].eq(PaintbrushSetDescription)
}
search_filter :is do |adjective|
filter = ADJECTIVE_FILTERS[adjective]
unless filter
raise SearchError,
"We don't know how an item can be \"#{adjective}\". " +
"Did you mean is:nc or is:pb?"
end
filter
end
search_filter :only do |species_name|
begin
id = Species.require_by_name(species_name).id
rescue Species::NotFound => e
raise SearchError, e.message
end
arel_table[:species_support_ids].eq(id.to_s)
end
search_filter :species do |species_name|
begin
id = Species.require_by_name(species_name).id
rescue Species::NotFound => e
raise SearchError, e.message
end
ids = arel_table[:species_support_ids]
ids.eq('').or(ids.matches_any([
id,
"#{id},%",
"%,#{id},%",
"%,#{id}"
]))
end
single_search_filter :type, {:limit => true, :scope => :join_swf_assets} do |zone_set_name|
zone_set = Zone::ItemZoneSets[zone_set_name]
raise SearchError, "Type \"#{zone_set_name}\" does not exist" unless zone_set
SwfAsset.arel_table[:zone_id].in(zone_set.map(&:id))
end
single_search_filter :not_type, :full => lambda { |zone_set_name, scope|
zone_set = Zone::ItemZoneSets[zone_set_name]
raise SearchError, "Type \"#{zone_set_name}\" does not exist" unless zone_set
psa = ParentSwfAssetRelationship.arel_table.alias
sa = SwfAsset.arel_table.alias
# Join to SWF assets, including the zone condition in the join so that
# SWFs that don't match end up being NULL rows. Then we take the max SWF
# asset ID, which is NULL if and only if there are no rows that matched
# the zone requirement. If that max was NULL, return the object.
item_ids = select(arel_table[:id]).joins(
"LEFT JOIN #{ParentSwfAssetRelationship.table_name} #{psa.name} ON " +
psa[:swf_asset_type].eq(SwfAssetType)
.and(psa[:parent_id].eq(arel_table[:id]))
.to_sql
).
joins(
"LEFT JOIN #{SwfAsset.table_name} #{sa.name} ON " +
sa[:type].eq(SwfAssetType)
.and(sa[:id].eq(psa[:swf_asset_id]))
.and(sa[:zone_id].in(zone_set.map(&:id)))
.to_sql
).
group("#{table_name}.id").
having("MAX(#{sa.name}.id) IS NULL"). # SwfAsset.arel_table[:id].maximum has no #eq
map(&:id)
scope.where(arel_table[:id].in(item_ids))
}
class Condition < String
attr_accessor :filter
def initialize
@positive = true
end
def filter?
!@filter.nil?
end
def to_filter!
@filter = self.clone
self.replace ''
end
def negate!
@positive = !@positive
end
def narrow(scope)
if SearchFilterScopes.include?(filter)
polarized_filter = @positive ? filter : "not_#{filter}"
Item.send("search_filter_#{polarized_filter}", self, scope)
else
raise SearchError, "Filter #{filter} does not exist"
end
end
def filter
@filter || 'name'
end
def inspect
@filter ? "#{@filter}:#{super}" : super
end
end
class SearchError < ArgumentError;end
end