Merge branch 'globalized_search' into i18n

Conflicts:
	Gemfile
	app/controllers/application_controller.rb
	app/controllers/items_controller.rb
	app/controllers/outfits_controller.rb
	app/helpers/application_helper.rb
	app/views/items/show.html.haml
	config/locales/en-MEEP.yml
	config/locales/en.yml
	public/stylesheets/compiled/screen.css
This commit is contained in:
Emi Matchu 2013-01-25 11:09:07 -06:00
commit d3b449a8f9
107 changed files with 1557 additions and 650 deletions

View file

@ -43,13 +43,20 @@ gem 'neopets', :git => 'git://github.com/matchu/neopets.git'
gem "mini_magick", "~> 3.4"
gem "fog", "~> 1.1.2"
gem "fog", "~> 1.8.0"
gem "carrierwave", "~> 0.5.8"
gem "parallel", "~> 0.5.17"
gem "http_accept_language", :git => "git://github.com/iain/http_accept_language.git"
gem "globalize3"
# My flex branch fixes a minor pagination bug. Once it's merged into the
# original gem, we can switch back.
gem "flex", :require => "flex/rails", :git => "git@github.com:matchu/flex.git"
gem "patron", "~> 0.4.18"
group :development do
gem "bullet", "~> 4.1.5"
end

View file

@ -1,8 +1,8 @@
GIT
remote: git://github.com/eventmachine/eventmachine.git
revision: 69151c3ebb3e4ecf2bb9b6e2fab2022dc34f8541
revision: d7c8a14dc494193a775add4b16c1e303cab5b285
specs:
eventmachine (1.0.0.beta.4)
eventmachine (1.0.0)
GIT
remote: git://github.com/iain/http_accept_language.git
@ -12,9 +12,9 @@ GIT
GIT
remote: git://github.com/igrigorik/em-http-request.git
revision: ce50f322ce08d43a4a747cf333ea576765d764c4
revision: 322f2273fa7ea07c1eeb92755bf67f1f05058e54
specs:
em-http-request (1.0.1)
em-http-request (1.0.3)
addressable (>= 2.2.3)
cookiejar
em-socksify
@ -30,21 +30,21 @@ GIT
GIT
remote: git://github.com/igrigorik/em-synchrony.git
revision: c7209a58f9eb92e1dc81fb141297f9f257c2fdcb
revision: fe592a4b9b5345bca329477cb8f2f8d186b6fc7f
specs:
em-synchrony (1.0.0)
em-synchrony (1.0.2)
eventmachine (>= 1.0.0.beta.1)
GIT
remote: git://github.com/matchu/neopets.git
revision: fe694681a302243d2937e811355473f358035403
revision: d33aaf63d4617d9236ef0d99452b3bdc577cbc8e
specs:
neopets (0.0.2)
neopets (0.1.0)
nokogiri (~> 1.5.2)
GIT
remote: git://github.com/oldmoe/mysqlplus.git
revision: 3dbaa7c00ff0bb75ad9538cdef176c72de35d231
revision: f07936d2eb9b0893994ed99fe82267b5e7770d06
specs:
mysqlplus (0.1.1)
@ -54,6 +54,15 @@ GIT
specs:
RocketAMF (1.0.0)
GIT
remote: git@github.com:matchu/flex.git
revision: d62f508f795ecdbb383f406865daa72368b43ba5
specs:
flex (0.4.1)
multi_json (~> 1.3.4)
progressbar (~> 0.11.0)
prompter (~> 0.1.5)
GEM
remote: http://rubygems.org/
specs:
@ -88,17 +97,17 @@ GEM
activemodel (= 3.0.19)
activesupport (= 3.0.19)
activesupport (3.0.19)
addressable (2.2.6)
addressable (2.3.2)
arel (2.0.10)
bcrypt-ruby (2.1.4)
builder (2.1.2)
bullet (4.1.5)
bullet (4.1.6)
uniform_notifier (~> 1.0.0)
carrierwave (0.5.8)
activesupport (~> 3.0)
character-encodings (0.4.1)
chronic (0.6.7)
closure-compiler (1.1.4)
closure-compiler (1.1.8)
compass (0.10.6)
haml (>= 3.0.4)
cookiejar (0.3.0)
@ -106,27 +115,32 @@ GEM
bcrypt-ruby (~> 2.1.2)
warden (~> 1.0.2)
diff-lcs (1.1.3)
em-socksify (0.1.0)
eventmachine
dye (0.1.4)
em-socksify (0.2.1)
eventmachine (>= 1.0.0.beta.4)
erubis (2.6.6)
abstract (>= 1.0.0)
excon (0.9.6)
factory_girl (2.3.2)
activesupport
factory_girl_rails (1.4.0)
factory_girl (~> 2.3.0)
excon (0.16.10)
factory_girl (2.6.4)
activesupport (>= 2.3.9)
factory_girl_rails (1.7.0)
factory_girl (~> 2.6.0)
railties (>= 3.0.0)
fog (1.1.2)
fog (1.8.0)
builder
excon (~> 0.9.0)
excon (~> 0.14)
formatador (~> 0.2.0)
mime-types
multi_json (~> 1.0.3)
multi_json (~> 1.0)
net-scp (~> 1.0.4)
net-ssh (>= 2.1.3)
nokogiri (~> 1.5.0)
ruby-hmac
formatador (0.2.1)
formatador (0.2.4)
globalize3 (0.3.0)
activemodel (>= 3.0.0)
activerecord (>= 3.0.0)
paper_trail (~> 2)
haml (3.0.25)
hoptoad_notifier (2.4.11)
activesupport
@ -146,20 +160,28 @@ GEM
mime-types (1.19)
mini_magick (3.4)
subexec (~> 0.2.1)
msgpack (0.4.6)
multi_json (1.0.4)
mysql2 (0.2.6)
msgpack (0.4.7)
multi_json (1.3.7)
mysql2 (0.2.18)
net-scp (1.0.4)
net-ssh (>= 1.99.1)
net-ssh (2.3.0)
newrelic_rpm (3.5.3.25)
nokogiri (1.5.3)
net-ssh (2.6.3)
newrelic_rpm (3.5.5.38)
nokogiri (1.5.6)
open4 (1.3.0)
openneo-auth-signatory (0.1.0)
ruby-hmac
parallel (0.5.17)
paper_trail (2.7.0)
activerecord (~> 3.0)
railties (~> 3.0)
parallel (0.5.21)
patron (0.4.18)
polyglot (0.3.3)
rack (1.2.6)
progressbar (0.11.0)
prompter (0.1.5)
dye (>= 0.1.1)
yard (>= 0.6.3)
rack (1.2.7)
rack-fiber_pool (0.9.2)
rack-mount (0.6.14)
rack (>= 1.0.0)
@ -183,9 +205,9 @@ GEM
rdiscount (1.6.8)
rdoc (3.12)
json (~> 1.4)
redis (2.2.2)
redis-namespace (1.1.0)
redis (< 3.0.0)
redis (3.0.2)
redis-namespace (1.2.1)
redis (~> 3.0.0)
resque (1.15.0)
json (~> 1.4.6)
redis-namespace (>= 0.10.0)
@ -214,14 +236,14 @@ GEM
rspec-rails (2.0.1)
rspec (~> 2.0.0)
ruby-hmac (0.4.0)
rufus-scheduler (2.0.16)
rufus-scheduler (2.0.17)
tzinfo (>= 0.3.23)
sanitize (2.0.3)
nokogiri (>= 1.4.4, < 1.6)
sinatra (1.2.8)
rack (~> 1.1)
tilt (>= 1.2.2, < 2.0)
subexec (0.2.1)
subexec (0.2.2)
swf_converter (0.0.3)
thor (0.14.6)
tilt (1.3.3)
@ -230,14 +252,15 @@ GEM
polyglot (>= 0.3.1)
tzinfo (0.3.35)
uniform_notifier (1.0.2)
vegas (0.1.8)
vegas (0.1.11)
rack (>= 1.0.0)
warden (1.0.6)
rack (>= 1.0)
whenever (0.7.3)
activesupport (>= 2.3.4)
chronic (~> 0.6.3)
will_paginate (3.0.2)
will_paginate (3.0.4)
yard (0.8.3)
yui-compressor (0.9.6)
POpen4 (>= 0.1.4)
@ -257,7 +280,9 @@ DEPENDENCIES
em-synchrony!
eventmachine!
factory_girl_rails (~> 1.0)
fog (~> 1.1.2)
flex!
fog (~> 1.8.0)
globalize3
haml (~> 3.0.18)
hoptoad_notifier
http_accept_language!
@ -272,6 +297,7 @@ DEPENDENCIES
nokogiri (~> 1.5.2)
openneo-auth-signatory (~> 0.1.0)
parallel (~> 0.5.17)
patron (~> 0.4.18)
rack-fiber_pool
rails (= 3.0.19)
rdiscount (~> 1.6.5)

View file

@ -57,7 +57,7 @@ class ApplicationController < ActionController::Base
end
def valid_locale?(locale)
locale && I18n.available_locales.include?(locale.to_sym)
locale && I18n.usable_locales.include?(locale.to_sym)
end
end

View file

@ -188,15 +188,17 @@ class ClosetHangersController < ApplicationController
def find_closet_lists_by_owned(closet_lists)
return {} if closet_lists == []
closet_lists.alphabetical.includes(:hangers => :item).
closet_lists.alphabetical.includes(:hangers => {:item => :translations}).
group_by(&:hangers_owned)
end
def find_unlisted_closet_hangers_by_owned(visible_groups)
unless visible_groups.empty?
@user.closet_hangers.unlisted.
owned_before_wanted.alphabetical_by_item_name.includes(:item).
where(:owned => [visible_groups]).group_by(&:owned)
owned_before_wanted.alphabetical_by_item_name.
includes(:item => :translations).
where(:owned => [visible_groups]).
group_by(&:owned)
else
{}
end

View file

@ -1,7 +1,5 @@
class ItemZoneSetsController < ApplicationController
caches_page :index
def index
render :json => Zone::ItemZoneSets.keys.sort.as_json
render :json => Zone.all_plain_labels
end
end

View file

@ -1,5 +1,6 @@
class ItemsController < ApplicationController
before_filter :set_query
rescue_from Item::Search::Error, :with => :search_error
def index
if params.has_key?(:q)
@ -8,25 +9,21 @@ class ItemsController < ApplicationController
per_page = params[:per_page].to_i
per_page = 50 if per_page && per_page > 50
else
per_page = nil
per_page = 30
end
@items = Item.search(@query, current_user).alphabetize.paginate :page => params[:page], :per_page => per_page
# Note that we sort by name by hand, since we might have to use
# fallbacks after the fact
@items = Item::Search::Query.from_text(@query, current_user).
paginate(:page => params[:page], :per_page => per_page)
assign_closeted!
respond_to do |format|
format.html { render }
format.json { render :json => {:items => @items, :total_pages => @items.total_pages} }
format.js { render :json => {:items => @items, :total_pages => @items.total_pages}, :callback => params[:callback] }
end
rescue Item::SearchError
@items = []
respond_to do |format|
format.html { flash.now[:alert] = $!.message }
format.json { render :json => {:error => $!.message} }
format.js { render :json => {:error => $!.message}, :callback => params[:callback] }
end
end
elsif params.has_key?(:ids) && params[:ids].is_a?(Array)
@items = Item.find(params[:ids])
@items = Item.includes(:translations).find(params[:ids])
assign_closeted!
respond_to do |format|
format.json { render :json => @items }
@ -35,7 +32,7 @@ class ItemsController < ApplicationController
respond_to do |format|
format.html {
unless localized_fragment_exist?('items#index newest_items')
@newest_items = Item.newest.limit(18)
@newest_items = Item.newest.includes(:translations).limit(18)
end
}
format.js { render :json => {:error => '$q required'}}
@ -48,6 +45,15 @@ class ItemsController < ApplicationController
respond_to do |format|
format.html do
unless localized_fragment_exist?("items/#{@item.id} info")
@occupied_zones = @item.occupied_zones(
:scope => Zone.includes_translations.alphabetical
)
@restricted_zones = @item.restricted_zones(
:scope => Zone.includes_translations.alphabetical
)
end
unless localized_fragment_exist?("items/#{@item.id} contributors")
@contributors_with_counts = @item.contributors_with_counts
end
@ -96,7 +102,8 @@ class ItemsController < ApplicationController
raise ActiveRecord::RecordNotFound, 'Pet type not found'
end
@items = @pet_type.needed_items.alphabetize
@items = @pet_type.needed_items.includes(:translations).
alphabetize_by_translations
assign_closeted!
respond_to do |format|
@ -111,6 +118,16 @@ class ItemsController < ApplicationController
current_user.assign_closeted_to_items!(@items) if user_signed_in?
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
def set_query
@query = params[:q]
end

View file

@ -42,12 +42,13 @@ class OutfitsController < ApplicationController
def new
unless localized_fragment_exist?(:action_suffix => 'start_from_scratch_form_content')
@colors = Color.all_ordered_by_name
@species = Species.all_ordered_by_name
@colors = Color.alphabetical
@species = Species.alphabetical
end
unless localized_fragment_exist?('outfits#new newest_items')
@newest_items = Item.newest.select([:id, :name, :thumbnail_url]).limit(9)
@newest_items = Item.newest.select([:id, :name, :thumbnail_url]).
includes(:translations).limit(9)
end
unless localized_fragment_exist?('outfits#new latest_contribution')
@ -65,8 +66,13 @@ class OutfitsController < ApplicationController
end
def start
# Start URLs are always in English, so let's make sure we search in
# English.
I18n.locale = I18n.default_locale
@species = Species.find_by_name params[:species_name]
@color = Color.find_by_name params[:color_name]
if @species && @color
redirect_to wardrobe_path(:species => @species.id, :color => @color.id)
else

View file

@ -1,10 +1,8 @@
class PetAttributesController < ApplicationController
caches_page :index
def index
render :json => {
:color => Color.all_ordered_by_name,
:species => Species.all_ordered_by_name
:color => Color.alphabetical,
:species => Species.alphabetical
}
end
end

View file

@ -10,13 +10,16 @@ class PetsController < ApplicationController
redirect_to roulette_path
else
raise Pet::PetNotFound unless params[:name]
@pet = Pet.load(params[:name])
@pet = Pet.load(params[:name], :item_scope => Item.includes(:translations))
if user_signed_in?
points = current_user.contribute! @pet
else
@pet.save
points = true
end
@pet.translate_items
respond_to do |format|
format.html do
path = destination + @pet.wardrobe_query

View file

@ -3,7 +3,7 @@ class SitemapController < ApplicationController
def index
respond_to do |format|
format.xml { @items = Item.sitemap }
format.xml { @items = Item.includes(:translations).sitemap }
end
end
end

View file

@ -2,7 +2,7 @@ class SwfAssetsController < ApplicationController
def index
if params[:item_id]
item = Item.find(params[:item_id])
@swf_assets = item.swf_assets
@swf_assets = item.swf_assets.includes_depth
if params[:body_id]
@swf_assets = @swf_assets.fitting_body_id(params[:body_id])
else
@ -15,33 +15,32 @@ class SwfAssetsController < ApplicationController
end
elsif params[:pet_type_id] && params[:item_ids]
pet_type = PetType.find(params[:pet_type_id], :select => [:body_id, :species_id])
items = Item.find(params[:item_ids], :select => [:id, :species_support_ids])
compatible_items = items.select { |i| i.support_species?(pet_type.species) }
compatible_item_ids = compatible_items.map(&:id)
@swf_assets = SwfAsset.object_assets.
@swf_assets = SwfAsset.object_assets.includes_depth.
fitting_body_id(pet_type.body_id).
for_item_ids(compatible_item_ids).
for_item_ids(params[:item_ids]).
with_parent_ids
json = @swf_assets.map { |a| a.as_json(:parent_id => a.parent_id.to_i, :for => 'wardrobe') }
elsif params[:pet_state_id]
@swf_assets = PetState.find(params[:pet_state_id]).swf_assets.all
@swf_assets = PetState.find(params[:pet_state_id]).swf_assets.
includes_depth.all
pet_state_id = params[:pet_state_id].to_i
json = @swf_assets.map { |a| a.as_json(:parent_id => pet_state_id, :for => 'wardrobe') }
elsif params[:pet_type_id]
@swf_assets = PetType.find(params[:pet_type_id]).pet_states.emotion_order.first.swf_assets
@swf_assets = PetType.find(params[:pet_type_id]).pet_states.emotion_order
.first.swf_assets.includes_depth
elsif params[:ids]
@swf_assets = []
if params[:ids][:biology]
@swf_assets += SwfAsset.biology_assets.where(:remote_id => params[:ids][:biology]).all
@swf_assets += SwfAsset.includes_depth.biology_assets.where(:remote_id => params[:ids][:biology]).all
end
if params[:ids][:object]
@swf_assets += SwfAsset.object_assets.where(:remote_id => params[:ids][:object]).all
@swf_assets += SwfAsset.includes_depth.object_assets.where(:remote_id => params[:ids][:object]).all
end
elsif params[:body_id] && params[:item_ids]
# DEPRECATED in favor of pet_type_id and item_ids
swf_assets = SwfAsset.arel_table
@swf_assets = SwfAsset.object_assets.
@swf_assets = SwfAsset.includes_depth.object_assets.
select('swf_assets.*, parents_swf_assets.parent_id').
fitting_body_id(params[:body_id]).
for_item_ids(params[:item_ids])

18
app/flex/flex_search.rb Normal file
View file

@ -0,0 +1,18 @@
# inspect the methods loaded in this module and their usage
# in the rails console by just typing:
# >> puts FlexSearch.flex.info
# you can eventually restrict the info to a single method by pasing its name:
# >> puts FlexSearch.flex.info :search
# see the detailed doc for this feature at https://github.com/ddnexus/flex/wiki/Selfdoc
module FlexSearch
extend self
include Flex::Loader
flex.load_search_source
# you may need to add more method here, usually wrapper methods
# that use one of the autogenerated methods from the loaded templates
end

83
app/flex/flex_search.yml Normal file
View file

@ -0,0 +1,83 @@
# Add here your search queries
# see the detailed Source documentation at https://github.com/ddnexus/flex/wiki/Sources
# ANCHORS litheral key: it will not be used as template
# you can store here fragments of queries to reuse in the templates below
ANCHORS:
- &name_partial
multi_match:
query: <<name>>
fields: <<fields>>
type: phrase
- &species_support_id_partial
term:
species_support_id: <<species_support_id>>
- &occupied_zone_id_partial
terms:
occupied_zone_id: <<occupied_zone_id>>
- &restricted_zone_id_partial
terms:
restricted_zone_id: <<restricted_zone_id>>
- &user_closet_hangers_ownership_partial
has_child:
type: closet_hanger
query:
bool:
must:
- term:
user_id: <<user_id>>
- term:
owned: <<user_closet_hanger_ownership>>
_names:
*name_partial
_negative_names:
*name_partial
_species_support_ids:
*species_support_id_partial
_negative_species_support_ids:
*species_support_id_partial
_occupied_zone_ids:
*occupied_zone_id_partial
_negative_occupied_zone_ids:
*occupied_zone_id_partial
_restricted_zone_ids:
*restricted_zone_id_partial
_negative_restricted_zone_ids:
*restricted_zone_id_partial
_user_closet_hanger_ownerships:
*user_closet_hangers_ownership_partial
_negative_user_closet_hanger_ownerships:
*user_closet_hangers_ownership_partial
item_search:
- query:
bool:
must:
- term:
is_nc: <<is_nc= ~>>
- term:
is_pb: <<is_pb= ~>>
- <<_names= ~>>
- <<_species_support_ids= ~>>
- <<_occupied_zone_ids= ~>>
- <<_restricted_zone_ids= ~>>
- <<_user_closet_hanger_ownerships= ~>>
must_not:
- <<_negative_names= ~>>
- <<_negative_species_support_ids= ~>>
- <<_negative_occupied_zone_ids= ~>>
- <<_negative_restricted_zone_ids= ~>>
- <<_negative_user_closet_hanger_ownerships= ~>>
sort:
- name.<<locale>>.untouched

View file

@ -0,0 +1,36 @@
# see the detailed Extenders documentation at https://github.com/ddnexus/flex/wiki/Extenders
module FlexSearchExtender
# set this method to restrict this extender to certain types of results
# see the other Flex extenders for reference (https://github.com/ddnexus/flex/tree/master/lib/flex/result)
def self.should_extend?(response)
true
end
def scoped_loaded_collection(options)
options[:scopes] ||= {}
@loaded_collection ||= begin
records = []
# returns a structure like {Comment=>[{"_id"=>"123", ...}, {...}], BlogPost=>[...]}
h = Flex::Utils.group_array_by(collection) do |d|
d.mapped_class(should_raise=true)
end
h.each do |klass, docs|
scope = options[:scopes][klass.name] || klass.scoped
records |= scope.find(docs.map(&:_id))
end
class_ids = collection.map { |d| [d.mapped_class.to_s, d._id] }
# Reorder records to preserve order from search results
records = class_ids.map do |class_str, id|
records.detect do |record|
record.class.to_s == class_str && record.id.to_s == id.to_s
end
end
records.extend Flex::Result::Collection
records.setup(self['hits']['total'], variables)
records
end
end
end

View file

@ -43,7 +43,10 @@ module ApplicationHelper
end
def canonical_path(resource)
original_locale = I18n.locale
I18n.locale = I18n.default_locale
content_for :meta, tag(:link, :rel => 'canonical', :href => url_for(resource))
I18n.locale = original_locale
end
def contact_email
@ -100,9 +103,18 @@ module ApplicationHelper
end
def locale_options
I18n.available_locales.map do |available_locale|
current_locale_is_public = false
options = I18n.public_locales.map do |available_locale|
current_locale_is_public = true if I18n.locale == available_locale
[translate('locale_name', :locale => available_locale), available_locale]
end
unless current_locale_is_public
name = translate('locale_name', :locale => I18n.locale) + ' (alpha)'
options << [name, I18n.locale]
end
options
end
def localized_cache(key={}, &block)

View file

@ -20,7 +20,7 @@ module ItemsHelper
end
def standard_species_search_links
build_on_pet_types(Species.all) do |pet_type|
build_on_pet_types(Species.alphabetical) do |pet_type|
image = pet_type_image(pet_type, :happy, :zoom)
query = "species:#{pet_type.species.name}"
link_to(image, items_path(:q => query))
@ -73,7 +73,7 @@ module ItemsHelper
end
def list_zones(zones, method=:label)
zones.sort { |x,y| x.label <=> y.label }.map(&method).join(', ')
zones.map(&method).join(', ')
end
def nc_icon
@ -125,7 +125,8 @@ module ItemsHelper
def build_on_pet_types(species, special_color=nil, &block)
species_ids = species.map(&:id)
pet_types = special_color ?
PetType.where(:color_id => special_color.id, :species_id => species_ids).order(:species_id) :
PetType.where(:color_id => special_color.id, :species_id => species_ids).
order(:species_id).includes_child_translations :
PetType.random_basic_per_species(species.map(&:id))
pet_types.map(&block).join.html_safe
end

View file

@ -1,4 +1,6 @@
class ClosetHanger < ActiveRecord::Base
include Flex::Model
belongs_to :item
belongs_to :list, :class_name => 'ClosetList'
belongs_to :user
@ -30,6 +32,17 @@ class ClosetHanger < ActiveRecord::Base
before_validation :merge_quantities, :set_owned_by_list
flex.parent :item, 'item' => 'closet_hanger'
flex.sync self
def flex_source
{
:user_id => user_id,
:item_id => item_id,
:owned => owned?
}.to_json
end
def verb(subject=:someone)
self.class.verb(subject, owned?)
end
@ -56,23 +69,28 @@ class ClosetHanger < ActiveRecord::Base
end
hanger = self.where(conditions).first
unless hanger
hanger = self.new
hanger.user_id = conditions[:user_id]
hanger.item_id = conditions[:item_id]
# One of the following will be nil, and that's okay. If owned is nil,
# we'll cover for it before validation, as always.
hanger.owned = conditions[:owned]
hanger.list_id = conditions[:list_id]
end
unless quantity == 0
Rails.logger.debug("Logging to #{hanger.id} quantity #{quantity}")
if quantity > 0
# If quantity is non-zero, create/update the corresponding hanger.
unless hanger
hanger = self.new
hanger.user_id = conditions[:user_id]
hanger.item_id = conditions[:item_id]
# One of the following will be nil, and that's okay. If owned is nil,
# we'll cover for it before validation, as always.
hanger.owned = conditions[:owned]
hanger.list_id = conditions[:list_id]
end
hanger.quantity = quantity
hanger.save!
else
hanger.destroy if hanger
elsif hanger
# If quantity is zero and there's a hanger, destroy it.
hanger.destroy
end
# If quantity is zero and there's no hanger, good. Do nothing.
end
protected

View file

@ -1,15 +1,16 @@
class Color < PetAttribute
fetch_objects!
class Color < ActiveRecord::Base
translates :name
Basic = %w(blue green red yellow).map { |name| find_by_name(name) }
BasicIds = Basic.map(&:id)
scope :alphabetical, lambda { includes(:translations).order(Color::Translation.arel_table[:name]) }
scope :basic, where(:basic => true)
scope :standard, where(:standard => true)
scope :nonstandard, where(:standard => false)
def self.basic_ids
BasicIds
def as_json(options={})
{:id => id, :name => human_name}
end
def self.nonstandard_ids
@nonstandard_ids ||= File.read(Rails.root.join('config', 'nonstandard_colors.txt')).
chomp.split("\n").map { |name| Color.find_by_name(name).id }
def human_name
name.split(' ').map { |word| word.capitalize }.join(' ')
end
end

View file

@ -1,8 +1,13 @@
class Item < ActiveRecord::Base
include Flex::Model
include PrettyParam
set_inheritance_column 'inheritance_type' # PHP Impress used "type" to describe category
SwfAssetType = 'object'
translates :name, :description, :rarity
has_many :closet_hangers
has_one :contribution, :as => :contributed
has_many :parent_swf_asset_relationships, :as => :parent
@ -15,18 +20,16 @@ class Item < ActiveRecord::Base
SPECIAL_COLOR_DESCRIPTION_REGEX =
/This item is only wearable by Neopets painted ([a-zA-Z]+)\.|WARNING: This [a-zA-Z]+ can be worn by ([a-zA-Z]+) [a-zA-Z]+ ONLY!/
SPECIAL_PAINTBRUSH_COLORS_PATH = Rails.root.join('config', 'colors_with_unique_bodies.txt')
SPECIAL_PAINTBRUSH_COLORS = File.read(SPECIAL_PAINTBRUSH_COLORS_PATH).split("\n").map { |name| Color.find_by_name(name) }
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 :alphabetize, order(arel_table[:name])
scope :alphabetize_by_translations, lambda {
it = Item::Translation.arel_table
order(it[:name])
}
scope :join_swf_assets, joins(:swf_assets).group('objects.id')
scope :join_swf_assets, joins(:swf_assets).group(arel_table[:id])
scope :newest, order(arel_table[:created_at].desc) if arel_table[:created_at]
@ -35,10 +38,32 @@ class Item < ActiveRecord::Base
scope :sold_in_mall, where(:sold_in_mall => true)
scope :not_sold_in_mall, where(:sold_in_mall => false)
scope :sitemap, select([:id, :name]).order(:id).limit(49999)
scope :sitemap, select([arel_table[:id], arel_table[:name]]).
order(arel_table[:id]).limit(49999)
scope :with_closet_hangers, joins(:closet_hangers)
flex.sync self
def flex_source
indexed_attributes = {
:is_nc => self.nc?,
:is_pb => self.pb?,
:species_support_id => self.species_support_ids,
:occupied_zone_id => self.occupied_zone_ids,
:restricted_zone_id => self.restricted_zone_ids,
:name => {}
}
I18n.usable_locales_with_neopets_language_code.each do |locale|
I18n.with_locale(locale) do
indexed_attributes[:name][locale] = self.name
end
end
indexed_attributes.to_json
end
def closeted?
@owned || @wanted
end
@ -73,6 +98,10 @@ class Item < ActiveRecord::Base
NCRarities.include?(rarity_index)
end
def pb?
(self.description == PAINTBRUSH_SET_DESCRIPTION)
end
def owned?
@owned
end
@ -81,17 +110,27 @@ class Item < ActiveRecord::Base
@wanted
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
def restricted_zones(options={})
options[:scope] ||= Zone.scoped
options[:scope].find(restricted_zone_ids)
end
def occupied_zones
def restricted_zone_ids
unless @restricted_zone_ids
@restricted_zone_ids = []
zones_restrict.split(//).each_with_index do |switch, id|
@restricted_zone_ids << (id.to_i + 1) if switch == '1'
end
end
@restricted_zone_ids
end
def occupied_zone_ids
occupied_zones.map(&:id)
end
def occupied_zones(options={})
options[:scope] ||= Zone.scoped
all_body_ids = []
zone_body_ids = {}
selected_assets = swf_assets.select('body_id, zone_id').each do |swf_asset|
@ -100,12 +139,11 @@ class Item < ActiveRecord::Base
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 = []
zones = options[:scope].find(zone_body_ids.keys)
zones_by_id = zones.inject({}) { |h, z| h[z.id] = z; h }
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
zones_by_id[zone_id].sometimes = true if body_ids.size < total_body_ids
end
zones
end
@ -120,17 +158,21 @@ class Item < ActiveRecord::Base
protected
def determine_special_color
if description.include?(PAINTBRUSH_SET_DESCRIPTION)
downcased_name = name.downcase
SPECIAL_PAINTBRUSH_COLORS.each do |color|
return color if downcased_name.include?(color.name)
I18n.with_locale(I18n.default_locale) do
# Rather than go find the special description in all locales, let's just
# run this logic in English.
if description.include?(PAINTBRUSH_SET_DESCRIPTION)
downcased_name = name.downcase
Color.nonstandard.each do |color|
return color if downcased_name.include?(color.name)
end
end
end
match = description.match(SPECIAL_COLOR_DESCRIPTION_REGEX)
if match
color = match[1] || match[2]
return Color.find_by_name(color.downcase)
match = description.match(SPECIAL_COLOR_DESCRIPTION_REGEX)
if match
color = match[1] || match[2]
return Color.find_by_name(color.downcase)
end
end
end
public
@ -146,45 +188,18 @@ class Item < ActiveRecord::Base
end
def supported_species
@supported_species ||= species_support_ids.blank? ? Species.all : species_support_ids.sort.map { |id| Species.find(id) }
body_ids = swf_assets.select([:body_id]).map(&:body_id)
return Species.all if body_ids.include?(0)
pet_types = PetType.where(:body_id => body_ids).select([:species_id])
species_ids = pet_types.map(&:species_id).uniq
Species.find(species_ids)
end
def support_species?(species)
species_support_ids.blank? || species_support_ids.include?(species.id)
end
def self.search(query, user=nil)
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, user)
end
end
def as_json(options = {})
{
:description => description,
@ -231,6 +246,12 @@ class Item < ActiveRecord::Base
end
end
def body_specific?
# If there are species support IDs (it's not empty), the item is
# body-specific. If it's empty, it fits everyone the same.
!species_support_ids.empty?
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)
@ -253,6 +274,12 @@ class Item < ActiveRecord::Base
@parent_swf_asset_relationships_to_update = rels
end
def needed_translations
translatable_locales = Set.new(I18n.locales_with_neopets_language_code)
translated_locales = Set.new(translations.map(&:locale))
translatable_locales - translated_locales
end
def self.all_by_ids_or_children(ids, swf_assets)
swf_asset_ids = []
swf_assets_by_id = {}
@ -284,7 +311,7 @@ class Item < ActiveRecord::Base
end
end
def self.collection_from_pet_type_and_registries(pet_type, info_registry, asset_registry)
def self.collection_from_pet_type_and_registries(pet_type, info_registry, asset_registry, scope=Item.scoped)
# bear in mind that registries are arrays with many nil elements,
# due to how the parser works
@ -299,7 +326,7 @@ class Item < ActiveRecord::Base
# Collect existing relationships
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 = scope.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 = {}
@ -656,214 +683,4 @@ class Item < ActiveRecord::Base
class SpiderHTTPError < SpiderError;end
class SpiderJSONError < SpiderError;end
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, &block)
Proc.new { |str, user, scope|
condition = block.arity == 1 ? block.call(str) : block.call(str, user)
unless positive
condition = condition.to_sql if condition.respond_to?(:to_sql)
condition = "!(#{condition})"
end
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
def self.adjective_filters
@adjective_filters ||= {
'nc' => arel_table[:rarity_index].in(NCRarities),
'pb' => arel_table[:description].eq(PAINTBRUSH_SET_DESCRIPTION)
}
end
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
USER_ADJECTIVES = {
'own' => true,
'owns' => true,
'owned' => true,
'want' => false,
'wants' => false,
'wanted' => false,
'all' => nil,
'items' => nil
}
def self.parse_user_adjective(adjective, user)
unless USER_ADJECTIVES.has_key?(adjective)
raise SearchError, "We don't understand user:#{adjective}. " +
"Find items you own with user:owns, items you want with user:wants, or " +
"both with user:all"
end
unless user
raise SearchError, "It looks like you're not logged in, so you don't own any items."
end
USER_ADJECTIVES[adjective]
end
search_filter :user do |adjective, user|
# Though joins may seem more efficient here for the positive case, we need
# to be able to handle cases like "user:owns user:wants", which breaks on
# the JOIN approach. Just have to look up the IDs in advance.
owned_value = parse_user_adjective(adjective, user)
hangers = ClosetHanger.arel_table
items = user.closeted_items
items = items.where(ClosetHanger.arel_table[:owned].eq(owned_value)) unless owned_value.nil?
item_ids = items.map(&:id)
# Though it's best to do arel_table[:id].in(item_ids), it breaks in this
# version of Arel, and other conditions will overwrite this one. Since IDs
# are guaranteed to be integers, let's just build our own string condition
# and be done with it.
if item_ids.empty?
raise SearchError, "You don't #{ClosetHanger.verb :you, owned_value} " +
"any items yet. Head to Your Items to add some!"
end
arel_table[:id].in(item_ids)
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.find_set(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, user, 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[:parent_type].eq(self.name).
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, user)
if SearchFilterScopes.include?(filter)
polarized_filter = @positive ? filter : "not_#{filter}"
Item.send("search_filter_#{polarized_filter}", self, user, 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

View file

@ -0,0 +1,8 @@
class Item
module Search
def self.error(key, *args)
message = I18n.translate("items.search.errors.#{key}", *args)
raise Item::Search::Error, message
end
end
end

View file

@ -0,0 +1,7 @@
class Item
module Search
class Contradiction < Error
end
end
end

View file

@ -0,0 +1,7 @@
class Item
module Search
class Error < RuntimeError
end
end
end

View file

@ -0,0 +1,11 @@
class Item
module Search
class Field
attr_reader :key
def initialize(key)
@key = key
end
end
end
end

View file

@ -0,0 +1,20 @@
class Item
module Search
module Fields
class Flag < Field
def <<(filter)
if @value.nil?
@value = filter.positive?
elsif @value != filter.positive?
raise Item::Search::Contradiction,
"flag #{key} both positive and negative"
end
end
def to_flex_params
{key => @value}
end
end
end
end
end

View file

@ -0,0 +1,34 @@
class Item
module Search
module Fields
class SetField < Field
def initialize(*args)
super(*args)
@values = {true => Set.new, false => Set.new}
end
def <<(filter)
if @values[!filter.positive?].include?(filter.value)
raise Item::Search::Contradiction,
"positive #{key} and negative #{key} both contain #{filter.value}"
end
@values[filter.positive?] << filter.value
end
def to_flex_params
{
:"_#{key}s" => nil_if_empty(@values[true]),
:"_negative_#{key}s" => nil_if_empty(@values[false])
}
end
private
def nil_if_empty(set)
set.map { |value| {key => value} } unless set.empty?
end
end
end
end
end

View file

@ -0,0 +1,29 @@
class Item
module Search
module Fields
class TextField < Field
def initialize(*args)
super(*args)
@values = {true => '', false => ''}
end
def <<(filter)
@values[filter.positive?] << (filter.value + ' ')
end
def to_flex_params
{
key => nil_if_empty(@values[true]),
:"negative_#{key}" => nil_if_empty(@values[false])
}
end
private
def nil_if_empty(str)
str unless str.empty?
end
end
end
end
end

View file

@ -0,0 +1,17 @@
class Item
module Search
class Filter
attr_reader :key, :value
def initialize(key, value, is_positive)
@key = key
@value = value
@is_positive = is_positive
end
def positive?
@is_positive
end
end
end
end

View file

@ -0,0 +1,197 @@
class Item
module Search
class Query
FIELD_CLASSES = {
:is_nc => Fields::Flag,
:is_pb => Fields::Flag,
:species_support_id => Fields::SetField,
:occupied_zone_id => Fields::SetField,
:restricted_zone_id => Fields::SetField,
:name => Fields::SetField,
:user_closet_hanger_ownership => Fields::SetField
}
def initialize(filters, user)
@filters = filters
@user = user
end
def fields
initial_fields.tap do |fields|
@filters.each { |filter| fields[filter.key] << filter }
end
end
def to_flex_params
fields.values.map(&:to_flex_params).inject(&:merge)
end
def paginate(options={})
begin
flex_params = self.to_flex_params
rescue Item::Search::Contradiction
# If we have a contradictory query, no need to raise a stink about
# it, but no need to actually run a search, either.
return []
end
final_flex_params = {
:page => (options[:page] || 1),
:size => (options[:per_page] || 30),
:type => 'item'
}.merge(flex_params)
locales = I18n.fallbacks[I18n.locale] &
I18n.locales_with_neopets_language_code
final_flex_params[:locale] = locales.first
# Extend the names/negative_names queries with the corresponding
# localalized field names.
if final_flex_params[:_names] || final_flex_params[:_negative_names]
locale_entries = locales.map do |locale|
boost = (locale == I18n.locale) ? 4 : 1
"name.#{locale}^#{boost}"
end
# We *could* have set _name_locales once as a partial, but Flex won't
# let us call partials from inside other partials. Whatever. Assign
# it to each name entry instead. I also feel bad doing this
# afterwards, since it's kinda the field's job to return proper flex
# params, but that's a refactor for another day.
[:_names, :_negative_names].each do |key|
if final_flex_params[key]
final_flex_params[key].each do |name_query|
name_query[:fields] = locale_entries
end
end
end
end
# Okay, yeah, looks like this really does deserve a refactor, like
# _names and _negative_names do. (Or Flex could just make all variables
# accessible from partials... hint, hint)
[:_user_closet_hanger_ownerships, :_negative_user_closet_hanger_ownerships].each do |key|
if final_flex_params[key]
Item::Search.error 'not_logged_in' unless @user
final_flex_params[key].each do |entry|
entry[:user_id] = @user.id
end
end
end
result = FlexSearch.item_search(final_flex_params)
result.scoped_loaded_collection(
:scopes => {'Item' => Item.includes(:translations)}
)
end
# Load the text query labels from I18n, so that when we see, say,
# the filter "species:acara", we know it means species_support_id.
TEXT_KEYS_BY_LABEL = {}
OWNERSHIP_KEYWORDS = {}
I18n.available_locales.each do |locale|
TEXT_KEYS_BY_LABEL[locale] = {}
OWNERSHIP_KEYWORDS[locale] = {}
FIELD_CLASSES.keys.each do |key|
# A locale can specify multiple labels for a key by separating by
# commas: "occupies,zone,type"
labels = I18n.translate("items.search.labels.#{key}",
:locale => locale).split(',')
labels.each { |label| TEXT_KEYS_BY_LABEL[locale][label] = key }
{:owns => true, :wants => false}.each do |key, value|
translated_key = I18n.translate("items.search.labels.user_#{key}",
:locale => locale)
OWNERSHIP_KEYWORDS[locale][translated_key] = value
end
end
end
TEXT_QUERY_RESOURCE_FINDERS = {
:species => lambda { |name|
species = Species.find_by_name(name)
unless species
Item::Search.error 'not_found.species', :species_name => name
end
species.id
},
:zone => lambda { |label|
zone_set = Zone.with_plain_label(label)
unless zone_set
Item::Search.error 'not_found.zone', :zone_name => name
end
zone_set.map(&:id)
},
:ownership => lambda { |keyword|
OWNERSHIP_KEYWORDS[I18n.locale][keyword].tap do |value|
if value.nil?
Item::Search.error 'not_found.ownership', :keyword => keyword
end
end
}
}
TEXT_QUERY_RESOURCE_TYPES_BY_KEY = {
:species_support_id => :species,
:occupied_zone_id => :zone,
:restricted_zone_id => :zone,
:user_closet_hanger_ownership => :ownership
}
TEXT_FILTER_EXPR = /([+-]?)(?:([a-z]+):)?(?:"([^"]+)"|(\S+))/
def self.from_text(text, user=nil)
filters = []
is_keyword = I18n.translate('items.search.flag_keywords.is')
text.scan(TEXT_FILTER_EXPR) do |sign, label, quoted_value, unquoted_value|
label ||= 'name'
raw_value = quoted_value || unquoted_value
is_positive = (sign != '-')
if label == is_keyword
# is-filters are weird. "-is:nc" is transposed to something more
# like "-nc:<nil>", then it's translated into a negative "is_nc"
# flag. Fun fact: "nc:foobar" and "-nc:foobar" also work. A bonus,
# I guess. There's probably a good way to refactor this to avoid
# the unintended bonus syntax, but this is a darn good cheap
# technique for the time being.
label = raw_value
raw_value = nil
end
key = TEXT_KEYS_BY_LABEL[I18n.locale][label]
if key.nil?
message = I18n.translate('items.search.errors.not_found.label',
:label => label)
raise Item::Search::Error, message
end
if TEXT_QUERY_RESOURCE_TYPES_BY_KEY.has_key?(key)
resource_type = TEXT_QUERY_RESOURCE_TYPES_BY_KEY[key]
finder = TEXT_QUERY_RESOURCE_FINDERS[resource_type]
value = finder.call(raw_value)
else
value = raw_value
end
filters << Filter.new(key, value, is_positive)
end
self.new(filters, user)
end
private
# The fields start out empty, then have the filters inserted into 'em,
# so that the fields can validate and aggregate their requirements.
def initial_fields
{}.tap do |fields|
FIELD_CLASSES.map do |key, klass|
fields[key] = klass.new(key)
end
end
end
end
end
end

View file

@ -84,7 +84,7 @@ class Outfit < ActiveRecord::Base
# ordered from bottom to top. Careful: this method is memoized, so if the
# image layers change after its first call we'll get bad results.
def image_layers
@image_layers ||= visible_assets_with_images.sort { |a, b| a.zone.depth <=> b.zone.depth }
@image_layers ||= visible_assets_with_images.sort { |a, b| a.depth <=> b.depth }
end
# Creates and writes the thumbnail images for this outfit iff the new image
@ -177,9 +177,10 @@ class Outfit < ActiveRecord::Base
end
def visible_assets
biology_assets = pet_state.swf_assets
biology_assets = pet_state.swf_assets.includes(:zone)
object_assets = SwfAsset.object_assets.
fitting_body_id(pet_state.pet_type.body_id).for_item_ids(worn_item_ids)
fitting_body_id(pet_state.pet_type.body_id).for_item_ids(worn_item_ids).
includes(:zone)
# Now for fun with bitmasks! Rather than building a bunch of integer arrays
# here, we instead go low-level and use bit-level operations. Build the

View file

@ -2,8 +2,8 @@ require 'rocketamf/remote_gateway'
class Pet < ActiveRecord::Base
GATEWAY_URL = 'http://www.neopets.com/amfphp/gateway.php'
AMF_SERVICE_NAME = 'CustomPetService'
PET_VIEWER_METHOD = 'getViewerData'
PET_VIEWER = RocketAMF::RemoteGateway.new(GATEWAY_URL).
service('CustomPetService').action('getViewerData')
PET_NOT_FOUND_REMOTE_ERROR = 'PHP: Unable to retrieve records from the database.'
WARDROBE_PATH = '/wardrobe'
@ -16,11 +16,22 @@ class Pet < ActiveRecord::Base
joins(:pet_type).where(PetType.arel_table[:id].in(color_ids))
}
def load!
def load!(options={})
options[:item_scope] ||= Item.scoped
options[:locale] ||= I18n.default_locale
original_locale = I18n.locale
I18n.locale = options[:locale]
require 'ostruct'
begin
envelope = Pet.amf_service.request(PET_VIEWER_METHOD, name, nil).
fetch(:timeout => 2)
neopets_language_code = I18n.neopets_language_code_for(options[:locale])
envelope = PET_VIEWER.request([name, 0]).post(
:timeout => 2,
:headers => {
'Cookie' => "lang=#{neopets_language_code}"
}
)
rescue RocketAMF::RemoteGateway::AMFError => e
if e.message == PET_NOT_FOUND_REMOTE_ERROR
raise PetNotFound, "Pet #{name.inspect} does not exist"
@ -31,18 +42,27 @@ class Pet < ActiveRecord::Base
end
contents = OpenStruct.new(envelope.messages[0].data.body)
pet_data = OpenStruct.new(contents.custom_pet)
self.pet_type = PetType.find_or_initialize_by_species_id_and_color_id(
pet_data.species_id.to_i,
pet_data.color_id.to_i
)
self.pet_type.body_id = pet_data.body_id
self.pet_type.origin_pet = self
biology = pet_data.biology_by_zone
biology[0] = nil # remove effects if present
@pet_state = self.pet_type.add_pet_state_from_biology! biology
@pet_state.label_by_pet(self, pet_data.owner)
@items = Item.collection_from_pet_type_and_registries(self.pet_type,
contents.object_info_registry, contents.object_asset_registry)
# in case this is running in a thread, explicitly grab an ActiveRecord
# connection, to avoid connection conflicts
Pet.connection_pool.with_connection do
self.pet_type = PetType.find_or_initialize_by_species_id_and_color_id(
pet_data.species_id.to_i,
pet_data.color_id.to_i
)
self.pet_type.body_id = pet_data.body_id
self.pet_type.origin_pet = self
biology = pet_data.biology_by_zone
biology[0] = nil # remove effects if present
@pet_state = self.pet_type.add_pet_state_from_biology! biology
@pet_state.label_by_pet(self, pet_data.owner)
@items = Item.collection_from_pet_type_and_registries(self.pet_type,
contents.object_info_registry, contents.object_asset_registry,
options[:item_scope])
end
I18n.locale = original_locale
true
end
@ -65,6 +85,44 @@ class Pet < ActiveRecord::Base
contributables
end
def item_translation_candidates
{}.tap do |candidates|
if @items
@items.each do |item|
item.needed_translations.each do |locale|
candidates[locale] ||= []
candidates[locale] << item
end
end
end
end
end
def translate_items
candidates = self.item_translation_candidates
until candidates.empty?
last_pet_loaded = nil
reloaded_pets = Parallel.map(candidates.keys, :in_threads => 8) do |locale|
Rails.logger.info "Reloading #{name} in #{locale}"
reloaded_pet = Pet.load(name, :item_scope => Item.includes(:translations),
:locale => locale)
Pet.connection_pool.with_connection { reloaded_pet.save! }
last_pet_loaded = reloaded_pet
end
previous_candidates = candidates
candidates = last_pet_loaded.item_translation_candidates
if previous_candidates == candidates
# This condition should never happen if Neopets responds with correct
# data, but, if Neopets somehow responds with incorrect data, this
# condition could throw us into an infinite loop if uncaught. Better
# safe than sorry when working with external services.
raise "No change when reloading #{name} for #{candidates}"
end
end
end
before_validation do
pet_type.save!
if @pet_state
@ -80,25 +138,12 @@ class Pet < ActiveRecord::Base
end
end
def self.load(name)
def self.load(name, options={})
pet = Pet.find_or_initialize_by_name(name)
pet.load!
pet.load!(options)
pet
end
private
def self.amf_service
@amf_service ||= gateway.service AMF_SERVICE_NAME
end
def self.gateway
unless @gateway
@gateway = RocketAMF::RemoteGateway.new(GATEWAY_URL)
end
@gateway
end
class PetNotFound < Exception;end
class DownloadError < Exception;end
end

View file

@ -3,6 +3,8 @@ class PetType < ActiveRecord::Base
IMAGE_CP_LOCATION_REGEX = %r{^/cp/(.+?)/1/1\.png$};
IMAGE_CPN_ACCEPTABLE_NAME = /^[a-z0-9_]+$/
belongs_to :species
belongs_to :color
has_one :contribution, :as => :contributed
has_many :pet_states
has_many :pets
@ -13,17 +15,20 @@ class PetType < ActiveRecord::Base
# Returns all pet types of a single standard color. The caller shouldn't care
# which, though, in this implemention, it's always Blue. Don't depend on that.
scope :single_standard_color, where(:color_id => Color::BasicIds[0])
scope :single_standard_color, lambda { where(:color_id => Color.standard.first) }
scope :nonstandard_colors, where(:color_id => Color.nonstandard_ids)
scope :nonstandard_colors, lambda { where(:color_id => Color.nonstandard) }
scope :includes_child_translations,
lambda { includes({:color => :translations, :species => :translations}) }
def self.standard_pet_types_by_species_id
@standard_pet_types_by_species_id ||=
PetType.where(:color_id => Color::BasicIds).group_by(&:species_id)
PetType.where(:color_id => Color.basic).includes_child_translations.
group_by(&:species_id)
end
def self.standard_body_ids
@standard_body_ids ||= [].tap do |body_ids|
[].tap do |body_ids|
standard_pet_types_by_species_id.each do |species_id, pet_types|
body_ids.concat(pet_types.map(&:body_id))
end
@ -32,8 +37,9 @@ class PetType < ActiveRecord::Base
def self.random_basic_per_species(species_ids)
random_pet_types = []
standards = self.standard_pet_types_by_species_id
species_ids.each do |species_id|
pet_types = standard_pet_types_by_species_id[species_id]
pet_types = standards[species_id]
random_pet_types << pet_types[rand(pet_types.size)] if pet_types
end
random_pet_types
@ -51,40 +57,21 @@ class PetType < ActiveRecord::Base
end
end
def color_id=(new_color_id)
@color = nil
write_attribute('color_id', new_color_id)
end
def color=(new_color)
@color = new_color
write_attribute('color_id', @color.id)
end
def color
@color ||= Color.find(color_id)
end
def species_id=(new_species_id)
@species = nil
write_attribute('species_id', new_species_id)
end
def species=(new_species)
@species = new_species
write_attribute('species_id', @species.id)
end
def species
@species ||= Species.find(species_id)
end
def image_hash
self['image_hash'] || basic_image_hash
end
def basic_image_hash
BasicHashes[species.name][color.name]
I18n.with_locale(I18n.default_locale) do
# Probably should move the basic hashes into the database someday.
# Until then, access the hash using the English color/species names.
unless BasicHashes[species.name] && BasicHashes[species.name][color.name]
raise "basic image hash for #{species.name}, #{color.name} not found"
end
BasicHashes[species.name][color.name]
end
end
def human_name

View file

@ -1,11 +1,13 @@
class Species < PetAttribute
fetch_objects!
class Species < ActiveRecord::Base
translates :name
def self.require_by_name(name)
species = Species.find_by_name(name)
raise NotFound, "Species \"#{name.humanize}\" does not exist" unless species
species
scope :alphabetical, lambda { includes(:translations).order(Species::Translation.arel_table[:name]) }
def as_json(options={})
{:id => id, :name => human_name}
end
class NotFound < ArgumentError;end
def human_name
name.capitalize
end
end

View file

@ -5,11 +5,21 @@ class StaticResource
@objects
end
def self.find(id)
@objects[id-1]
def self.find(id_or_ids)
if id_or_ids.is_a?(Array)
id_or_ids.uniq.map { |id| find_one(id) }
else
find_one(id_or_ids)
end
end
def self.count
@objects.size
end
private
def self.find_one(id)
@objects[id - 1]
end
end

View file

@ -24,6 +24,10 @@ class SwfAsset < ActiveRecord::Base
include SwfConverter
converts_swfs :size => IMAGE_SIZES[:large], :output_sizes => IMAGE_SIZES.values
belongs_to :zone
scope :includes_depth, lambda { includes(:zone) }
def local_swf_path
LOCAL_ASSET_DIR.join(local_path_within_outfit_swfs)
end
@ -197,15 +201,7 @@ class SwfAsset < ActiveRecord::Base
end
def body_specific?
# If we already have assigned this a non-zero body id, or if the asset is
# in a body-specific zone, or if the item is explicitly labeled as
# body-specific (like Encased In Ice, which is body-specific but whose
# assets occupy Background Item), then this asset is body-specific.
(body_id? && body_id > 0) || self.zone.type_id < 3 || (@item && @item.explicitly_body_specific?)
end
def zone
Zone.find(zone_id)
self.zone.type_id < 3 || (@item && @item.body_specific?)
end
def origin_pet_type=(pet_type)

View file

@ -1,51 +1,28 @@
class Zone < StaticResource
AttributeNames = ['id', 'label', 'depth', 'type_id']
ItemZoneSets = {}
class Zone < ActiveRecord::Base
translates :label, :plain_label
attr_reader *AttributeNames
# When selecting zones that an asset occupies, we allow the zone to set
# whether or not the zone is "sometimes" occupied. This is false by default.
attr_writer :sometimes
def initialize(attributes)
AttributeNames.each do |name|
instance_variable_set "@#{name}", attributes[name]
end
end
scope :alphabetical, lambda {
includes_translations.order(Zone::Translation.arel_table[:label])
}
scope :includes_translations, lambda { includes(:translations) }
scope :with_plain_label, lambda { |label|
t = Zone::Translation.arel_table
includes(:translations).where(t[:plain_label].eq(Zone.plainify_label(label)))
}
def uncertain_label
@sometimes ? "#{label} sometimes" : label
end
def self.find_set(name)
ItemZoneSets[plain(name)]
def self.all_plain_labels
Zone.select([:id]).includes(:translations).all.map(&:plain_label).uniq.sort
end
def self.plain(name)
name.delete('\- /').downcase
end
n = 0
@objects = YAML.load_file(Rails.root.join('config', 'zones.yml')).map do |a|
a['id'] = (n += 1)
obj = new(a)
if obj.type_id == 2 || obj.type_id == 3
plain_name = plain(obj.label)
ItemZoneSets[plain_name] ||= []
ItemZoneSets[plain_name] << obj
end
obj
end
n = nil
# Add aliases to keys like "lowerforegrounditem" to "lowerforeground"
# ...unless there's already such a key, like "backgrounditem" to "background",
# in which case we don't, because that'd be silly.
ItemZoneSets.keys.each do |name|
if name.end_with?('item')
stripped_name = name[0..-5]
ItemZoneSets[stripped_name] ||= ItemZoneSets[name]
end
def self.plainify_label(label)
label.delete('\- /').downcase
end
end

View file

@ -1,4 +1,7 @@
@import "partials/icon"
@import url(http://fonts.googleapis.com/css?family=Droid+Sans:400,700)
@import url(http://fonts.googleapis.com/css?family=Droid+Serif:400,700,400italic)
@import url(http://fonts.googleapis.com/css?family=Calligraffitti)
/* Reset
@ -249,30 +252,3 @@ dd
font-style: italic
src: local("Delicious"), font-url("Delicious-Italic.otf")
@font-face
font-family: 'Droid Serif'
font-style: normal
font-weight: normal
src: local("Droid Serif"), url("http://themes.googleusercontent.com/font?kit=70P0G8gxVDIV6F9om0DsKg") format("truetype")
@font-face
font-family: 'Droid Serif'
font-style: normal
font-weight: bold
src: local("Droid Serif"), url("http://themes.googleusercontent.com/font?kit=QQt14e8dY39u-eYBZmppwf5Jgr8ufe5A6KahQF76Xmg") format("truetype")
@font-face
font-family: 'Droid Sans'
font-style: normal
font-weight: normal
src: local("Droid Sans"), url("http://themes.googleusercontent.com/font?kit=POVDFY-UUf0WFR9DIMCU8g") format("truetype")
@font-face
font-family: 'Calligraffitti'
font-style: normal
font-weight: normal
src: local('Calligraffiti'), url('http://themes.googleusercontent.com/font?kit=vLVN2Y-z65rVu1R7lWdvyKIZAuDcNtpCWuPSaIR0Ie8') format('woff')

View file

@ -53,13 +53,13 @@
#item-zones
%p
%strong #{t '.zones.occupied_header'}:
= list_zones @item.occupied_zones, :uncertain_label
= list_zones @occupied_zones, :uncertain_label
%p
%strong #{t '.zones.restricted_header'}:
- if @item.restricted_zones.empty?
- if @restricted_zones.empty?
= t '.zones.none'
- else
= list_zones @item.restricted_zones
= list_zones @restricted_zones
#trade-hangers
- [true, false].each do |owned|

36
config/flex.yml Normal file
View file

@ -0,0 +1,36 @@
# ANCHORS litheral key: it will not be used as template
# you can store here fragments of structures to reuse below
ANCHORS:
-
# This is a dynamic index name The settings and mapping below will work with any index.
# The default index name generated by Flex is usually <application_name>_<environment>,
# but you may have changed it in the initializers/flex.rb or you can hardcode it if you prefer.
<%= Flex::Configuration.variables[:index] %>:
settings:
number_of_shards: 5
number_of_replicas: 1
# add your custom mappings here
mappings:
item:
properties:
# Name is an object of locale fields, which are in turn multi_fields.
# First, an analyzed string for searching. Second, an untouched
# string for sorting. Elasticsearch requires that both be expliticly
# named in the mapping, but will handle the copy implicitly.
name:
type: object
properties:
<% I18n.usable_locales_with_neopets_language_code.each do |locale| %>
<%= locale %>:
type: multi_field
fields:
<%= locale %>:
type: string
index: analyzed
untouched:
type: string
index: not_analyzed
<% end %>

View file

@ -0,0 +1,44 @@
# see the detailed Configuration documentation at https://github.com/ddnexus/flex/wiki/Configuration
Flex::Configuration.configure do |config|
# you MUST add your indexed model names here
config.flex_models = %w[ Item ClosetHanger ]
# Add the your result extenders here
config.result_extenders |= [ FlexSearchExtender ]
# Add the default variables here
# see also the details Variables documentation at https://github.com/ddnexus/flex/wiki/Variables
# config.variables.add :index => 'my_index',
# :type => 'project',
# :anything => 'anything
# The custom url of your ElasticSearch server
# config.base_uri = 'http://localhost:9200'
# Set it to true to log the debug infos (true by default in development mode)
# config.debug = false
# Debug info are actually valid curl commands
# config.debug_to_curl = false
# The custom logger you want Flex to use. Default Rails.logger
# config.logger = Logger.new(STDERR)
# Custom config file path
# config.config_file = '/custom/path/to/flex.yml',
# Custom flex dir path
# config.flex_dir = '/custom/path/to/flex',
# The custom http_client you may want to implement
# config.http_client = 'Your::Client'
# The options passed to the http_client. They are client specific.
# config.http_client_options = {:timeout => 5}
# Experimental: checks the response and return a boolean (should raise?)
# config.raise_proc = proc{|response| response.status >= 400}
end

View file

@ -0,0 +1,58 @@
module LocaleMeta
PUBLIC_LOCALES = []
USABLE_LOCALES = []
NEOPETS_LANGUAGE_CODES_BY_LOCALE = {}
LOCALES_WITH_NEOPETS_LANGUAGE_CODE = []
COMPATIBLE_LOCALES = {}
end
config = YAML.load_file(Rails.root.join('config', 'locale_meta.yml'))
config.each do |locale_str, locale_meta|
locale = locale_str.to_sym
visibility = locale_meta['visibility']
if visibility == 'public'
LocaleMeta::PUBLIC_LOCALES << locale
LocaleMeta::USABLE_LOCALES << locale
elsif visibility == 'private'
LocaleMeta::USABLE_LOCALES << locale
end
if locale_meta.has_key?('neopets_language_code')
neopets_language_code = locale_meta['neopets_language_code']
LocaleMeta::NEOPETS_LANGUAGE_CODES_BY_LOCALE[locale] = neopets_language_code
LocaleMeta::LOCALES_WITH_NEOPETS_LANGUAGE_CODE << locale
elsif locale_meta.has_key?('compatible_with')
compatible_locale = locale_meta['compatible_with'].to_sym
LocaleMeta::COMPATIBLE_LOCALES[locale] = compatible_locale
else
raise "locale #{locale} must either have a neopets_language_code or " +
"be compatible_with a locale that does"
end
end
LocaleMeta::USABLE_LOCALES_WITH_NEOPETS_LANGUAGE_CODE = LocaleMeta::USABLE_LOCALES &
LocaleMeta::LOCALES_WITH_NEOPETS_LANGUAGE_CODE
module I18n
def self.public_locales
LocaleMeta::PUBLIC_LOCALES
end
def self.usable_locales
LocaleMeta::USABLE_LOCALES
end
def self.locales_with_neopets_language_code
LocaleMeta::LOCALES_WITH_NEOPETS_LANGUAGE_CODE
end
def self.usable_locales_with_neopets_language_code
LocaleMeta::USABLE_LOCALES_WITH_NEOPETS_LANGUAGE_CODE
end
def self.neopets_language_code_for(locale)
LocaleMeta::NEOPETS_LANGUAGE_CODES_BY_LOCALE[locale]
end
end

47
config/locale_meta.yml Normal file
View file

@ -0,0 +1,47 @@
en:
visibility: public
neopets_language_code: en
en-MEEP:
visibility: public
compatible_with: en
pt:
visibility: private
neopets_language_code: pt
es:
visibility: none
neopets_language_code: es
nl:
visibility: none
neopets_language_code: nl
de:
visibility: none
neopets_language_code: de
fr:
visibility: none
neopets_language_code: fr
it:
visibility: none
neopets_language_code: it
zh-CN:
visibility: none
neopets_language_code: ch
zh-TW:
visibility: none
neopets_language_code: zh
ja:
visibility: none
neopets_language_code: ja
ko:
visibility: none
neopets_language_code: ko

View file

@ -326,6 +326,30 @@ en:
header: Brought to you by
footer: Thanks!
search:
errors:
not_found:
label: Filter "%{label}" does not exist. Is it spelled correctly?
species:
Species "%{species_name}" does not exist. Is it spelled correctly?
zone: Zone "%{zone_name}" does not exist. Is it spelled correctly?
ownership:
I don't know what user:%{keyword} means. Is it spelled correctly?
not_logged_in:
The "user" filters are only available if you're logged in.
flag_keywords:
is: is
labels:
name: name
is_nc: nc
is_pb: pb
species_support_id: species
occupied_zone_id: occupies,zone,type
restricted_zone_id: restricts
user_closet_hanger_ownership: user
user_owns: owns
user_wants: wants
neopets_pages:
create:
success: Page %{index} saved!

5
config/locales/pt.yml Normal file
View file

@ -0,0 +1,5 @@
pt:
locale_name: Portuguese
# This is a placeholder Portuguese locale, so that we can test things
# involving its presence.

View file

@ -0,0 +1,17 @@
class TranslateItems < ActiveRecord::Migration
def self.up
rename_table :objects, :items
Item.create_translation_table!({
:name => :string,
:description => :text,
:rarity => :string
}, {
:migrate_data => true
})
end
def self.down
Item.drop_translation_table! :migrate_data => true
rename_table :items, :objects
end
end

View file

@ -0,0 +1,11 @@
class CreateSpecies < ActiveRecord::Migration
def self.up
create_table :species
Species.create_translation_table! :name => :string
end
def self.down
drop_table :species
Species.drop_translation_table!
end
end

View file

@ -0,0 +1,14 @@
class CreateColors < ActiveRecord::Migration
def self.up
create_table :colors do |t|
t.boolean :basic
t.boolean :standard
end
Color.create_translation_table! :name => :string
end
def self.down
drop_table :colors
Color.drop_translation_table!
end
end

View file

@ -0,0 +1,14 @@
class CreateZones < ActiveRecord::Migration
def self.up
create_table :zones do |t|
t.integer :depth
t.integer :type_id
end
Zone.create_translation_table! :label => :string, :plain_label => :string
end
def self.down
drop_table :zones
Zone.drop_translation_table!
end
end

View file

@ -1,3 +1,4 @@
# encoding: UTF-8
# This file is auto-generated from the current state of the database. Instead
# of editing this file, please use the migrations feature of Active Record to
# incrementally modify your database, and then regenerate this schema definition.
@ -10,7 +11,7 @@
#
# It's strongly recommended to check this file into your version control system.
ActiveRecord::Schema.define(:version => 20121006010446) do
ActiveRecord::Schema.define(:version => 20130121221226) do
create_table "auth_servers", :force => true do |t|
t.string "short_name", :limit => 10, :null => false
@ -20,6 +21,13 @@ ActiveRecord::Schema.define(:version => 20121006010446) do
t.string "secret", :limit => 64, :null => false
end
create_table "campaigns", :force => true do |t|
t.integer "goal_cents", :null => false
t.integer "progress_cents", :null => false
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "closet_hangers", :force => true do |t|
t.integer "item_id"
t.integer "user_id"
@ -46,9 +54,20 @@ ActiveRecord::Schema.define(:version => 20121006010446) do
add_index "closet_lists", ["user_id"], :name => "index_closet_lists_on_user_id"
create_table "color_translations", :force => true do |t|
t.integer "color_id"
t.string "locale"
t.string "name"
t.datetime "created_at"
t.datetime "updated_at"
end
add_index "color_translations", ["color_id"], :name => "index_color_translations_on_color_id"
add_index "color_translations", ["locale"], :name => "index_color_translations_on_locale"
create_table "colors", :force => true do |t|
t.string "name"
t.boolean "basic", :default => false, :null => false
t.boolean "basic"
t.boolean "standard"
end
create_table "contributions", :force => true do |t|
@ -61,6 +80,14 @@ ActiveRecord::Schema.define(:version => 20121006010446) do
add_index "contributions", ["contributed_id", "contributed_type"], :name => "index_contributions_on_contributed_id_and_contributed_type"
add_index "contributions", ["user_id"], :name => "index_contributions_on_user_id"
create_table "donations", :force => true do |t|
t.integer "amount_cents", :null => false
t.integer "campaign_id", :null => false
t.integer "user_id"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "forums", :force => true do |t|
t.string "name"
t.text "description"
@ -80,16 +107,20 @@ ActiveRecord::Schema.define(:version => 20121006010446) do
add_index "item_outfit_relationships", ["item_id"], :name => "index_item_outfit_relationships_on_item_id"
add_index "item_outfit_relationships", ["outfit_id", "is_worn"], :name => "index_item_outfit_relationships_on_outfit_id_and_is_worn"
create_table "login_cookies", :force => true do |t|
t.integer "user_id", :null => false
t.integer "series", :null => false
t.integer "token", :null => false
create_table "item_translations", :force => true do |t|
t.integer "item_id"
t.string "locale"
t.string "name"
t.text "description"
t.string "rarity"
t.datetime "created_at"
t.datetime "updated_at"
end
add_index "login_cookies", ["user_id", "series"], :name => "login_cookies_user_id_and_series"
add_index "login_cookies", ["user_id"], :name => "login_cookies_user_id"
add_index "item_translations", ["item_id"], :name => "index_item_translations_on_item_id"
add_index "item_translations", ["locale"], :name => "index_item_translations_on_locale"
create_table "objects", :force => true do |t|
create_table "items", :force => true do |t|
t.text "zones_restrict", :null => false
t.text "thumbnail_url", :limit => 16777215, :null => false
t.string "name", :limit => 100, :null => false
@ -108,8 +139,25 @@ ActiveRecord::Schema.define(:version => 20121006010446) do
t.boolean "explicitly_body_specific", :default => false, :null => false
end
add_index "objects", ["last_spidered"], :name => "objects_last_spidered"
add_index "objects", ["name"], :name => "name"
add_index "items", ["last_spidered"], :name => "objects_last_spidered"
add_index "items", ["name"], :name => "name"
create_table "login_cookies", :force => true do |t|
t.integer "user_id", :null => false
t.integer "series", :null => false
t.integer "token", :null => false
end
add_index "login_cookies", ["user_id", "series"], :name => "login_cookies_user_id_and_series"
add_index "login_cookies", ["user_id"], :name => "login_cookies_user_id"
create_table "outfit_features", :force => true do |t|
t.integer "outfit_id", :null => false
t.datetime "created_at"
t.datetime "updated_at"
t.date "frontpage_start_date"
t.date "frontpage_end_date"
end
create_table "outfits", :force => true do |t|
t.integer "pet_state_id"
@ -121,6 +169,7 @@ ActiveRecord::Schema.define(:version => 20121006010446) do
t.string "image"
t.string "image_layers_hash"
t.boolean "image_enqueued", :default => false, :null => false
t.boolean "image_dirty", :default => false, :null => false
end
add_index "outfits", ["pet_state_id"], :name => "index_outfits_on_pet_state_id"
@ -179,6 +228,20 @@ ActiveRecord::Schema.define(:version => 20121006010446) do
t.datetime "updated_at"
end
create_table "species", :force => true do |t|
end
create_table "species_translations", :force => true do |t|
t.integer "species_id"
t.string "locale"
t.string "name"
t.datetime "created_at"
t.datetime "updated_at"
end
add_index "species_translations", ["locale"], :name => "index_species_translations_on_locale"
add_index "species_translations", ["species_id"], :name => "index_species_translations_on_species_id"
create_table "swf_assets", :force => true do |t|
t.string "type", :limit => 7, :null => false
t.integer "remote_id", :limit => 3, :null => false
@ -223,11 +286,21 @@ ActiveRecord::Schema.define(:version => 20121006010446) do
t.integer "wanted_closet_hangers_visibility", :default => 1, :null => false
end
create_table "zone_translations", :force => true do |t|
t.integer "zone_id"
t.string "locale"
t.string "label"
t.string "plain_label"
t.datetime "created_at"
t.datetime "updated_at"
end
add_index "zone_translations", ["locale"], :name => "index_zone_translations_on_locale"
add_index "zone_translations", ["zone_id"], :name => "index_zone_translations_on_zone_id"
create_table "zones", :force => true do |t|
t.integer "depth", :limit => 1, :null => false
t.integer "type_id", :limit => 1, :null => false
t.string "type", :limit => 40, :null => false
t.string "label", :limit => 40, :null => false
t.integer "depth"
t.integer "type_id"
end
end

View file

@ -1,7 +1,208 @@
# This file should contain all the record creation needed to seed the database with its default values.
# The data can then be loaded with the rake db:seed (or created alongside the db with db:setup).
#
# Examples:
#
# cities = City.create([{ :name => 'Chicago' }, { :name => 'Copenhagen' }])
# Mayor.create(:name => 'Daley', :city => cities.first)
Species.create(:id => 1, :name => "acara")
Species.create(:id => 2, :name => "aisha")
Species.create(:id => 3, :name => "blumaroo")
Species.create(:id => 4, :name => "bori")
Species.create(:id => 5, :name => "bruce")
Species.create(:id => 6, :name => "buzz")
Species.create(:id => 7, :name => "chia")
Species.create(:id => 8, :name => "chomby")
Species.create(:id => 9, :name => "cybunny")
Species.create(:id => 10, :name => "draik")
Species.create(:id => 11, :name => "elephante")
Species.create(:id => 12, :name => "eyrie")
Species.create(:id => 13, :name => "flotsam")
Species.create(:id => 14, :name => "gelert")
Species.create(:id => 15, :name => "gnorbu")
Species.create(:id => 16, :name => "grarrl")
Species.create(:id => 17, :name => "grundo")
Species.create(:id => 18, :name => "hissi")
Species.create(:id => 19, :name => "ixi")
Species.create(:id => 20, :name => "jetsam")
Species.create(:id => 21, :name => "jubjub")
Species.create(:id => 22, :name => "kacheek")
Species.create(:id => 23, :name => "kau")
Species.create(:id => 24, :name => "kiko")
Species.create(:id => 25, :name => "koi")
Species.create(:id => 26, :name => "korbat")
Species.create(:id => 27, :name => "kougra")
Species.create(:id => 28, :name => "krawk")
Species.create(:id => 29, :name => "kyrii")
Species.create(:id => 30, :name => "lenny")
Species.create(:id => 31, :name => "lupe")
Species.create(:id => 32, :name => "lutari")
Species.create(:id => 33, :name => "meerca")
Species.create(:id => 34, :name => "moehog")
Species.create(:id => 35, :name => "mynci")
Species.create(:id => 36, :name => "nimmo")
Species.create(:id => 37, :name => "ogrin")
Species.create(:id => 38, :name => "peophin")
Species.create(:id => 39, :name => "poogle")
Species.create(:id => 40, :name => "pteri")
Species.create(:id => 41, :name => "quiggle")
Species.create(:id => 42, :name => "ruki")
Species.create(:id => 43, :name => "scorchio")
Species.create(:id => 44, :name => "shoyru")
Species.create(:id => 45, :name => "skeith")
Species.create(:id => 46, :name => "techo")
Species.create(:id => 47, :name => "tonu")
Species.create(:id => 48, :name => "tuskaninny")
Species.create(:id => 49, :name => "uni")
Species.create(:id => 50, :name => "usul")
Species.create(:id => 51, :name => "wocky")
Species.create(:id => 52, :name => "xweetok")
Species.create(:id => 53, :name => "yurble")
Species.create(:id => 54, :name => "zafara")
Color.create(:id => 1, :name => "alien", :basic => false, :standard => true)
Color.create(:id => 2, :name => "apple", :basic => false, :standard => false)
Color.create(:id => 3, :name => "asparagus", :basic => false, :standard => false)
Color.create(:id => 4, :name => "aubergine", :basic => false, :standard => false)
Color.create(:id => 5, :name => "avocado", :basic => false, :standard => false)
Color.create(:id => 6, :name => "baby", :basic => false, :standard => false)
Color.create(:id => 7, :name => "biscuit", :basic => false, :standard => true)
Color.create(:id => 8, :name => "blue", :basic => true, :standard => true)
Color.create(:id => 9, :name => "blueberry", :basic => false, :standard => false)
Color.create(:id => 10, :name => "brown", :basic => false, :standard => true)
Color.create(:id => 11, :name => "camouflage", :basic => false, :standard => true)
Color.create(:id => 12, :name => "carrot", :basic => false, :standard => false)
Color.create(:id => 13, :name => "checkered", :basic => false, :standard => true)
Color.create(:id => 14, :name => "chocolate", :basic => false, :standard => true)
Color.create(:id => 15, :name => "chokato", :basic => false, :standard => false)
Color.create(:id => 16, :name => "christmas", :basic => false, :standard => true)
Color.create(:id => 17, :name => "clay", :basic => false, :standard => true)
Color.create(:id => 18, :name => "cloud", :basic => false, :standard => true)
Color.create(:id => 19, :name => "coconut", :basic => false, :standard => true)
Color.create(:id => 20, :name => "custard", :basic => false, :standard => true)
Color.create(:id => 21, :name => "darigan", :basic => false, :standard => true)
Color.create(:id => 22, :name => "desert", :basic => false, :standard => true)
Color.create(:id => 23, :name => "disco", :basic => false, :standard => true)
Color.create(:id => 24, :name => "durian", :basic => false, :standard => false)
Color.create(:id => 25, :name => "electric", :basic => false, :standard => true)
Color.create(:id => 26, :name => "faerie", :basic => false, :standard => true)
Color.create(:id => 27, :name => "fire", :basic => false, :standard => true)
Color.create(:id => 28, :name => "garlic", :basic => false, :standard => true)
Color.create(:id => 29, :name => "ghost", :basic => false, :standard => true)
Color.create(:id => 30, :name => "glowing", :basic => false, :standard => true)
Color.create(:id => 31, :name => "gold", :basic => false, :standard => true)
Color.create(:id => 32, :name => "gooseberry", :basic => false, :standard => false)
Color.create(:id => 33, :name => "grape", :basic => false, :standard => false)
Color.create(:id => 34, :name => "green", :basic => true, :standard => true)
Color.create(:id => 35, :name => "grey", :basic => false, :standard => true)
Color.create(:id => 36, :name => "halloween", :basic => false, :standard => true)
Color.create(:id => 37, :name => "ice", :basic => false, :standard => true)
Color.create(:id => 38, :name => "invisible", :basic => false, :standard => true)
Color.create(:id => 39, :name => "island", :basic => false, :standard => true)
Color.create(:id => 40, :name => "jelly", :basic => false, :standard => true)
Color.create(:id => 41, :name => "lemon", :basic => false, :standard => false)
Color.create(:id => 42, :name => "lime", :basic => false, :standard => false)
Color.create(:id => 43, :name => "mallow", :basic => false, :standard => true)
Color.create(:id => 44, :name => "maraquan", :basic => false, :standard => false)
Color.create(:id => 45, :name => "msp", :basic => false, :standard => true)
Color.create(:id => 46, :name => "mutant", :basic => false, :standard => false)
Color.create(:id => 47, :name => "orange", :basic => false, :standard => false)
Color.create(:id => 48, :name => "pea", :basic => false, :standard => false)
Color.create(:id => 49, :name => "peach", :basic => false, :standard => false)
Color.create(:id => 50, :name => "pear", :basic => false, :standard => false)
Color.create(:id => 51, :name => "pepper", :basic => false, :standard => false)
Color.create(:id => 52, :name => "pineapple", :basic => false, :standard => false)
Color.create(:id => 53, :name => "pink", :basic => false, :standard => true)
Color.create(:id => 54, :name => "pirate", :basic => false, :standard => true)
Color.create(:id => 55, :name => "plum", :basic => false, :standard => false)
Color.create(:id => 56, :name => "plushie", :basic => false, :standard => true)
Color.create(:id => 57, :name => "purple", :basic => false, :standard => true)
Color.create(:id => 58, :name => "quigukiboy", :basic => false, :standard => true)
Color.create(:id => 59, :name => "quigukigirl", :basic => false, :standard => true)
Color.create(:id => 60, :name => "rainbow", :basic => false, :standard => true)
Color.create(:id => 61, :name => "red", :basic => true, :standard => true)
Color.create(:id => 62, :name => "robot", :basic => false, :standard => true)
Color.create(:id => 63, :name => "royalboy", :basic => false, :standard => true)
Color.create(:id => 64, :name => "royalgirl", :basic => false, :standard => true)
Color.create(:id => 65, :name => "shadow", :basic => false, :standard => true)
Color.create(:id => 66, :name => "silver", :basic => false, :standard => true)
Color.create(:id => 67, :name => "sketch", :basic => false, :standard => true)
Color.create(:id => 68, :name => "skunk", :basic => false, :standard => true)
Color.create(:id => 69, :name => "snot", :basic => false, :standard => true)
Color.create(:id => 70, :name => "snow", :basic => false, :standard => false)
Color.create(:id => 71, :name => "speckled", :basic => false, :standard => true)
Color.create(:id => 72, :name => "split", :basic => false, :standard => true)
Color.create(:id => 73, :name => "sponge", :basic => false, :standard => true)
Color.create(:id => 74, :name => "spotted", :basic => false, :standard => true)
Color.create(:id => 75, :name => "starry", :basic => false, :standard => true)
Color.create(:id => 76, :name => "strawberry", :basic => false, :standard => true)
Color.create(:id => 77, :name => "striped", :basic => false, :standard => true)
Color.create(:id => 78, :name => "thornberry", :basic => false, :standard => false)
Color.create(:id => 79, :name => "tomato", :basic => false, :standard => false)
Color.create(:id => 80, :name => "tyrannian", :basic => false, :standard => true)
Color.create(:id => 81, :name => "usukiboy", :basic => false, :standard => true)
Color.create(:id => 82, :name => "usukigirl", :basic => false, :standard => true)
Color.create(:id => 83, :name => "white", :basic => false, :standard => true)
Color.create(:id => 84, :name => "yellow", :basic => true, :standard => true)
Color.create(:id => 85, :name => "zombie", :basic => false, :standard => true)
Color.create(:id => 86, :name => "onion", :basic => false, :standard => false)
Color.create(:id => 87, :name => "magma", :basic => false, :standard => true)
Color.create(:id => 88, :name => "relic", :basic => false, :standard => true)
Color.create(:id => 89, :name => "woodland", :basic => false, :standard => true)
Color.create(:id => 90, :name => "transparent", :basic => false, :standard => true)
Color.create(:id => 91, :name => "maractite", :basic => false, :standard => true)
Color.create(:id => 92, :name => "8-bit", :basic => false, :standard => true)
Color.create(:id => 93, :name => "swamp gas", :basic => false, :standard => true)
Color.create(:id => 94, :name => "water", :basic => false, :standard => true)
Color.create(:id => 95, :name => "wraith", :basic => false, :standard => true)
Color.create(:id => 96, :name => "eventide", :basic => false, :standard => true)
Color.create(:id => 97, :name => "elderlyboy", :basic => false, :standard => true)
Color.create(:id => 98, :name => "elderlygirl", :basic => false, :standard => true)
Color.create(:id => 99, :name => "stealthy", :basic => false, :standard => true)
Color.create(:id => 100, :name => "dimensional", :basic => false, :standard => true)
Zone.create(:id => 1, :label => "Music", :plain_label => "music", :depth => 1, :type_id => 4)
Zone.create(:id => 2, :label => "Sound Effects", :plain_label => "soundeffects", :depth => 2, :type_id => 4)
Zone.create(:id => 3, :label => "Background", :plain_label => "background", :depth => 3, :type_id => 3)
Zone.create(:id => 4, :label => "Biology Effects", :plain_label => "biologyeffects", :depth => 6, :type_id => 1)
Zone.create(:id => 5, :label => "Hind Biology", :plain_label => "hindbiology", :depth => 7, :type_id => 1)
Zone.create(:id => 6, :label => "Markings", :plain_label => "markings", :depth => 8, :type_id => 2)
Zone.create(:id => 7, :label => "Hind Disease", :plain_label => "hinddisease", :depth => 9, :type_id => 1)
Zone.create(:id => 8, :label => "Hind Cover", :plain_label => "hindcover", :depth => 10, :type_id => 2)
Zone.create(:id => 9, :label => "Hind Transient Biology", :plain_label => "hindtransientbiology", :depth => 11, :type_id => 1)
Zone.create(:id => 10, :label => "Hind Drippings", :plain_label => "hinddrippings", :depth => 12, :type_id => 1)
Zone.create(:id => 11, :label => "Backpack", :plain_label => "backpack", :depth => 13, :type_id => 2)
Zone.create(:id => 12, :label => "Wings Transient Biology", :plain_label => "wingstransientbiology", :depth => 14, :type_id => 1)
Zone.create(:id => 13, :label => "Wings", :plain_label => "wings", :depth => 15, :type_id => 2)
Zone.create(:id => 14, :label => "Hair Back", :plain_label => "hairback", :depth => 17, :type_id => 1)
Zone.create(:id => 15, :label => "Body", :plain_label => "body", :depth => 18, :type_id => 1)
Zone.create(:id => 16, :label => "Markings", :plain_label => "markings", :depth => 19, :type_id => 2)
Zone.create(:id => 17, :label => "Body Disease", :plain_label => "bodydisease", :depth => 20, :type_id => 1)
Zone.create(:id => 18, :label => "Feet Transient Biology", :plain_label => "feettransientbiology", :depth => 21, :type_id => 1)
Zone.create(:id => 19, :label => "Shoes", :plain_label => "shoes", :depth => 22, :type_id => 2)
Zone.create(:id => 20, :label => "Lower-body Transient Biology", :plain_label => "lowerbodytransientbiology", :depth => 23, :type_id => 1)
Zone.create(:id => 21, :label => "Trousers", :plain_label => "trousers", :depth => 24, :type_id => 2)
Zone.create(:id => 22, :label => "Upper-body Transient Biology", :plain_label => "upperbodytransientbiology", :depth => 25, :type_id => 1)
Zone.create(:id => 23, :label => "Shirt/Dress", :plain_label => "shirtdress", :depth => 26, :type_id => 2)
Zone.create(:id => 24, :label => "Necklace", :plain_label => "necklace", :depth => 28, :type_id => 2)
Zone.create(:id => 25, :label => "Gloves", :plain_label => "gloves", :depth => 29, :type_id => 2)
Zone.create(:id => 26, :label => "Jacket", :plain_label => "jacket", :depth => 30, :type_id => 2)
Zone.create(:id => 27, :label => "Collar", :plain_label => "collar", :depth => 31, :type_id => 2)
Zone.create(:id => 28, :label => "Body Drippings", :plain_label => "bodydrippings", :depth => 32, :type_id => 1)
Zone.create(:id => 29, :label => "Ruff", :plain_label => "ruff", :depth => 33, :type_id => 1)
Zone.create(:id => 30, :label => "Head", :plain_label => "head", :depth => 34, :type_id => 1)
Zone.create(:id => 31, :label => "Markings", :plain_label => "markings", :depth => 35, :type_id => 2)
Zone.create(:id => 32, :label => "Head Disease", :plain_label => "headdisease", :depth => 36, :type_id => 1)
Zone.create(:id => 33, :label => "Eyes", :plain_label => "eyes", :depth => 37, :type_id => 1)
Zone.create(:id => 34, :label => "Mouth", :plain_label => "mouth", :depth => 38, :type_id => 1)
Zone.create(:id => 35, :label => "Glasses", :plain_label => "glasses", :depth => 41, :type_id => 2)
Zone.create(:id => 36, :label => "Earrings", :plain_label => "earrings", :depth => 39, :type_id => 2)
Zone.create(:id => 37, :label => "Hair Front", :plain_label => "hairfront", :depth => 40, :type_id => 1)
Zone.create(:id => 38, :label => "Head Transient Biology", :plain_label => "headtransientbiology", :depth => 42, :type_id => 1)
Zone.create(:id => 39, :label => "Head Drippings", :plain_label => "headdrippings", :depth => 43, :type_id => 1)
Zone.create(:id => 40, :label => "Hat", :plain_label => "hat", :depth => 44, :type_id => 2)
Zone.create(:id => 41, :label => "Earrings", :plain_label => "earrings", :depth => 45, :type_id => 2)
Zone.create(:id => 42, :label => "Right-hand Item", :plain_label => "righthand", :depth => 46, :type_id => 2)
Zone.create(:id => 43, :label => "Left-hand Item", :plain_label => "lefthand", :depth => 47, :type_id => 2)
Zone.create(:id => 44, :label => "Higher Foreground Item", :plain_label => "higherforeground", :depth => 49, :type_id => 3)
Zone.create(:id => 45, :label => "Lower Foreground Item", :plain_label => "lowerforeground", :depth => 50, :type_id => 3)
Zone.create(:id => 46, :label => "Static", :plain_label => "static", :depth => 48, :type_id => 3)
Zone.create(:id => 47, :label => "Thought Bubble", :plain_label => "thoughtbubble", :depth => 51, :type_id => 3)
Zone.create(:id => 48, :label => "Background Item", :plain_label => "background", :depth => 4, :type_id => 3)
Zone.create(:id => 49, :label => "Right-hand Item", :plain_label => "righthand", :depth => 5, :type_id => 2)
Zone.create(:id => 50, :label => "Hat", :plain_label => "hat", :depth => 16, :type_id => 2)
Zone.create(:id => 51, :label => "Belt", :plain_label => "belt", :depth => 27, :type_id => 2)
Zone.create(:id => 52, :label => "Foreground", :plain_label => "foreground", :depth => 52, :type_id => 3)

View file

@ -1,7 +1,6 @@
require 'net/http'
require 'rocketamf'
require File.join(File.dirname(__FILE__), 'remote_gateway', 'service')
require File.join(File.dirname(__FILE__), 'remote_gateway', 'request')
module RocketAMF
class RemoteGateway

View file

@ -0,0 +1,18 @@
require File.join(File.dirname(__FILE__), 'request')
module RocketAMF
class RemoteGateway
class Action
attr_reader :service, :name
def initialize(service, name)
@service = service
@name = name
end
def request(params)
Request.new(self, params)
end
end
end
end

View file

@ -5,18 +5,21 @@ module RocketAMF
class Request
ERROR_CODE = 'AMFPHP_RUNTIME_ERROR'
def initialize(service, method, *params)
@service = service
@method = method
def initialize(action, params)
@action = action
@params = params
end
def fetch(options={})
uri = @service.gateway.uri
def post(options={})
uri = @action.service.gateway.uri
data = envelope.serialize
req = Net::HTTP::Post.new(uri.path)
req.body = data
headers = options[:headers] || {}
headers.each do |key, value|
req[key] = value
end
res = nil
@ -68,8 +71,8 @@ module RocketAMF
def remoting_message
message = Values::RemotingMessage.new
message.source = @service.name
message.operation = @method
message.source = @action.service.name
message.operation = @action.name
message.body = @params
message
end

View file

@ -1,3 +1,5 @@
require File.join(File.dirname(__FILE__), 'action')
module RocketAMF
class RemoteGateway
class Service
@ -8,8 +10,8 @@ module RocketAMF
@name = name
end
def request(method, *params)
Request.new(self, method, *params)
def action(name)
Action.new(self, name)
end
end
end

View file

@ -1,6 +1,9 @@
@charset "UTF-8";
@import url(http://fonts.googleapis.com/css?family=Droid+Sans:400,700);
@import url(http://fonts.googleapis.com/css?family=Droid+Serif:400,700,400italic);
@import url(http://fonts.googleapis.com/css?family=Calligraffitti);
/* Reset */
/* line 5, ../../../app/stylesheets/_layout.sass */
/* line 8, ../../../app/stylesheets/_layout.sass */
html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p,
blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em,
font, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b,
@ -16,12 +19,12 @@ caption, tbody, tfoot, thead, tr, th, td {
}
/* Typography */
/* line 20, ../../../app/stylesheets/_layout.sass */
/* line 23, ../../../app/stylesheets/_layout.sass */
html, body {
height: 100%;
}
/* line 23, ../../../app/stylesheets/_layout.sass */
/* line 26, ../../../app/stylesheets/_layout.sass */
body {
background: white;
color: #004400;
@ -30,60 +33,60 @@ body {
line-height: 1.5;
}
/* line 31, ../../../app/stylesheets/_layout.sass */
/* line 34, ../../../app/stylesheets/_layout.sass */
a {
color: #226622;
}
/* line 34, ../../../app/stylesheets/_layout.sass */
/* line 37, ../../../app/stylesheets/_layout.sass */
p {
font-family: "Droid Serif", Georgia, "Times New Roman", Times, serif;
}
/* line 37, ../../../app/stylesheets/_layout.sass */
/* line 40, ../../../app/stylesheets/_layout.sass */
input, button, select {
font-family: inherit;
font-size: 100%;
}
/* line 42, ../../../app/stylesheets/_layout.sass */
/* line 45, ../../../app/stylesheets/_layout.sass */
p {
margin-bottom: 1em;
}
/* line 45, ../../../app/stylesheets/_layout.sass */
/* line 48, ../../../app/stylesheets/_layout.sass */
h1, h2, h3 {
font-family: Delicious, Helvetica, Arial, Verdana, sans-serif;
}
/* line 48, ../../../app/stylesheets/_layout.sass */
/* line 51, ../../../app/stylesheets/_layout.sass */
h1 {
font-size: 3em;
line-height: 1;
margin-bottom: 0.5em;
}
/* line 53, ../../../app/stylesheets/_layout.sass */
/* line 56, ../../../app/stylesheets/_layout.sass */
h2 {
font-size: 2em;
margin-bottom: 0.75em;
}
/* line 57, ../../../app/stylesheets/_layout.sass */
/* line 60, ../../../app/stylesheets/_layout.sass */
h3 {
font-size: 1.5em;
line-height: 1;
margin-bottom: 1em;
}
/* line 62, ../../../app/stylesheets/_layout.sass */
/* line 65, ../../../app/stylesheets/_layout.sass */
.inline-image, body.pets-bulk #bulk-pets-form ul img {
margin-right: 1em;
vertical-align: middle;
}
/* Main */
/* line 70, ../../../app/stylesheets/_layout.sass */
/* line 73, ../../../app/stylesheets/_layout.sass */
#container {
margin: 1em auto;
padding-top: 3em;
@ -91,12 +94,12 @@ h3 {
width: 800px;
}
/* line 76, ../../../app/stylesheets/_layout.sass */
/* line 79, ../../../app/stylesheets/_layout.sass */
input, button, select, label {
cursor: pointer;
}
/* line 79, ../../../app/stylesheets/_layout.sass */
/* line 82, ../../../app/stylesheets/_layout.sass */
input[type=text], body.pets-bulk #bulk-pets-form textarea, input[type=password], input[type=search], input[type=number], select, textarea {
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
@ -105,17 +108,17 @@ input[type=text], body.pets-bulk #bulk-pets-form textarea, input[type=password],
color: #448844;
padding: 0.25em;
}
/* line 85, ../../../app/stylesheets/_layout.sass */
/* line 88, ../../../app/stylesheets/_layout.sass */
input[type=text]:focus, body.pets-bulk #bulk-pets-form textarea:focus, input[type=text]:active, body.pets-bulk #bulk-pets-form textarea:active, input[type=password]:focus, input[type=password]:active, input[type=search]:focus, input[type=search]:active, input[type=number]:focus, input[type=number]:active, select:focus, select:active, textarea:focus, textarea:active {
color: inherit;
}
/* line 88, ../../../app/stylesheets/_layout.sass */
/* line 91, ../../../app/stylesheets/_layout.sass */
textarea {
font: inherit;
}
/* line 91, ../../../app/stylesheets/_layout.sass */
/* line 94, ../../../app/stylesheets/_layout.sass */
a.button, input[type=submit], button {
/* http://www.zurb.com/blog_uploads/0000/0617/buttons-03.html */
-moz-border-radius: 5px;
@ -146,7 +149,7 @@ a.button:hover, input[type=submit]:hover, button:hover {
a.button:active, input[type=submit]:active, button:active {
top: 1px;
}
/* line 93, ../../../app/stylesheets/_layout.sass */
/* line 96, ../../../app/stylesheets/_layout.sass */
a.button.loud, input[type=submit].loud, button.loud {
background: #ff5c00 url('/images/alert-overlay.png?1344550430') repeat-x;
font-size: 125%;
@ -157,21 +160,21 @@ a.button.loud:hover, input[type=submit].loud:hover, button.loud:hover {
background-color: #ee4b00;
}
/* line 96, ../../../app/stylesheets/_layout.sass */
/* line 99, ../../../app/stylesheets/_layout.sass */
ul.buttons {
margin-bottom: 1em;
}
/* line 98, ../../../app/stylesheets/_layout.sass */
/* line 101, ../../../app/stylesheets/_layout.sass */
ul.buttons li {
list-style: none;
margin: 0 0.5em;
}
/* line 101, ../../../app/stylesheets/_layout.sass */
/* line 104, ../../../app/stylesheets/_layout.sass */
ul.buttons li, ul.buttons li form {
display: inline;
}
/* line 104, ../../../app/stylesheets/_layout.sass */
/* line 107, ../../../app/stylesheets/_layout.sass */
#footer {
clear: both;
font-size: 75%;
@ -179,69 +182,69 @@ ul.buttons li, ul.buttons li form {
padding-top: 2em;
text-align: center;
}
/* line 110, ../../../app/stylesheets/_layout.sass */
/* line 113, ../../../app/stylesheets/_layout.sass */
#footer ul, #footer div {
display: inline;
margin: 0 1em;
}
/* line 113, ../../../app/stylesheets/_layout.sass */
/* line 116, ../../../app/stylesheets/_layout.sass */
#footer li, #footer div ul {
display: inline;
margin: 0 0.5em;
}
/* line 116, ../../../app/stylesheets/_layout.sass */
/* line 119, ../../../app/stylesheets/_layout.sass */
#footer #locale-form {
float: right;
}
/* line 119, ../../../app/stylesheets/_layout.sass */
/* line 122, ../../../app/stylesheets/_layout.sass */
.success, .alert, .warning {
margin-bottom: 1em;
padding: 0.25em 0.5em;
text-align: center;
}
/* line 124, ../../../app/stylesheets/_layout.sass */
/* line 127, ../../../app/stylesheets/_layout.sass */
.success {
background: #e6efc2;
border: 1px solid #c6d880;
color: #264409;
}
/* line 127, ../../../app/stylesheets/_layout.sass */
/* line 130, ../../../app/stylesheets/_layout.sass */
.alert {
background: #fbe3e4;
border: 1px solid #fbc2c4;
color: #8a1f11;
}
/* line 130, ../../../app/stylesheets/_layout.sass */
/* line 133, ../../../app/stylesheets/_layout.sass */
.warning {
background: #fff6bf;
border: 1px solid #ffd324;
color: #514721;
}
/* line 133, ../../../app/stylesheets/_layout.sass */
/* line 136, ../../../app/stylesheets/_layout.sass */
#userbar {
font-family: Delicious, Helvetica, Arial, Verdana, sans-serif;
position: absolute;
right: 0;
top: 0;
}
/* line 138, ../../../app/stylesheets/_layout.sass */
/* line 141, ../../../app/stylesheets/_layout.sass */
#userbar > * {
display: inline;
margin: 0 0.25em;
}
/* line 142, ../../../app/stylesheets/_layout.sass */
/* line 145, ../../../app/stylesheets/_layout.sass */
#userbar-image-mode {
font-weight: bold;
margin-right: 1em;
text-decoration: none;
}
/* line 146, ../../../app/stylesheets/_layout.sass */
/* line 149, ../../../app/stylesheets/_layout.sass */
#userbar-image-mode img {
bottom: -2px;
height: 16px;
@ -249,25 +252,25 @@ ul.buttons li, ul.buttons li form {
width: 16px;
}
/* line 149, ../../../app/stylesheets/_layout.sass */
/* line 152, ../../../app/stylesheets/_layout.sass */
#userbar-log-in {
text-decoration: none;
}
/* line 151, ../../../app/stylesheets/_layout.sass */
/* line 154, ../../../app/stylesheets/_layout.sass */
#userbar-log-in img {
margin-bottom: -4px;
margin-right: 0.25em;
}
/* line 155, ../../../app/stylesheets/_layout.sass */
/* line 158, ../../../app/stylesheets/_layout.sass */
#userbar-log-in span {
text-decoration: underline;
}
/* line 157, ../../../app/stylesheets/_layout.sass */
/* line 160, ../../../app/stylesheets/_layout.sass */
#userbar-log-in:hover span {
text-decoration: none;
}
/* line 160, ../../../app/stylesheets/_layout.sass */
/* line 163, ../../../app/stylesheets/_layout.sass */
.object {
display: -moz-inline-box;
-moz-box-orient: vertical;
@ -282,32 +285,32 @@ ul.buttons li, ul.buttons li form {
vertical-align: top;
width: 100px;
}
/* line 168, ../../../app/stylesheets/_layout.sass */
/* line 171, ../../../app/stylesheets/_layout.sass */
.object a {
text-decoration: none;
}
/* line 170, ../../../app/stylesheets/_layout.sass */
/* line 173, ../../../app/stylesheets/_layout.sass */
.object a img {
-moz-opacity: 0.75;
-webkit-opacity: 0.75;
-o-opacity: 0.75;
-khtml-opacity: 0.75;
}
/* line 172, ../../../app/stylesheets/_layout.sass */
/* line 175, ../../../app/stylesheets/_layout.sass */
.object img {
display: block;
height: 80px;
margin: 0 auto;
width: 80px;
}
/* line 177, ../../../app/stylesheets/_layout.sass */
/* line 180, ../../../app/stylesheets/_layout.sass */
.object:hover img, .object a:hover img {
-moz-opacity: 1;
-webkit-opacity: 1;
-o-opacity: 1;
-khtml-opacity: 1;
}
/* line 183, ../../../app/stylesheets/_layout.sass */
/* line 186, ../../../app/stylesheets/_layout.sass */
.object .nc-icon, .object .closeted-icons {
-moz-opacity: 1;
-webkit-opacity: 1;
@ -318,7 +321,7 @@ ul.buttons li, ul.buttons li form {
position: absolute;
top: 64px;
}
/* line 189, ../../../app/stylesheets/_layout.sass */
/* line 192, ../../../app/stylesheets/_layout.sass */
.object .nc-icon:hover, .object .closeted-icons:hover {
-moz-opacity: 0.5;
-webkit-opacity: 0.5;
@ -326,32 +329,32 @@ ul.buttons li, ul.buttons li form {
-khtml-opacity: 0.5;
background: transparent;
}
/* line 193, ../../../app/stylesheets/_layout.sass */
/* line 196, ../../../app/stylesheets/_layout.sass */
.object .nc-icon, .object .closeted-icons img {
display: inline;
height: 16px;
width: 16px;
}
/* line 198, ../../../app/stylesheets/_layout.sass */
/* line 201, ../../../app/stylesheets/_layout.sass */
.object .nc-icon {
right: 18px;
}
/* line 202, ../../../app/stylesheets/_layout.sass */
/* line 205, ../../../app/stylesheets/_layout.sass */
.object .closeted-icons {
left: 18px;
}
/* line 205, ../../../app/stylesheets/_layout.sass */
/* line 208, ../../../app/stylesheets/_layout.sass */
dt {
font-weight: bold;
}
/* line 208, ../../../app/stylesheets/_layout.sass */
/* line 211, ../../../app/stylesheets/_layout.sass */
dd {
margin: 0 0 1.5em 1em;
}
/* line 211, ../../../app/stylesheets/_layout.sass */
/* line 214, ../../../app/stylesheets/_layout.sass */
#home-link {
font-family: Delicious, Helvetica, Arial, Verdana, sans-serif;
font-size: 175%;
@ -362,21 +365,21 @@ dd {
position: absolute;
top: 0;
}
/* line 221, ../../../app/stylesheets/_layout.sass */
/* line 224, ../../../app/stylesheets/_layout.sass */
#home-link:hover {
background: #eeffee;
text-decoration: none;
}
/* line 224, ../../../app/stylesheets/_layout.sass */
/* line 227, ../../../app/stylesheets/_layout.sass */
#home-link span:before {
content: "<< ";
}
/* line 228, ../../../app/stylesheets/_layout.sass */
/* line 231, ../../../app/stylesheets/_layout.sass */
.pagination a, .pagination span {
margin: 0 0.5em;
}
/* line 230, ../../../app/stylesheets/_layout.sass */
/* line 233, ../../../app/stylesheets/_layout.sass */
.pagination .current {
font-weight: bold;
}
@ -400,34 +403,6 @@ dd {
src: local("Delicious"), url('/fonts/Delicious-Italic.otf');
}
@font-face {
font-family: "Droid Serif";
font-style: normal;
font-weight: normal;
src: local("Droid Serif"), url("http://themes.googleusercontent.com/font?kit=70P0G8gxVDIV6F9om0DsKg") format("truetype");
}
@font-face {
font-family: "Droid Serif";
font-style: normal;
font-weight: bold;
src: local("Droid Serif"), url("http://themes.googleusercontent.com/font?kit=QQt14e8dY39u-eYBZmppwf5Jgr8ufe5A6KahQF76Xmg") format("truetype");
}
@font-face {
font-family: "Droid Sans";
font-style: normal;
font-weight: normal;
src: local("Droid Sans"), url("http://themes.googleusercontent.com/font?kit=POVDFY-UUf0WFR9DIMCU8g") format("truetype");
}
@font-face {
font-family: "Calligraffitti";
font-style: normal;
font-weight: normal;
src: local("Calligraffiti"), url("http://themes.googleusercontent.com/font?kit=vLVN2Y-z65rVu1R7lWdvyKIZAuDcNtpCWuPSaIR0Ie8") format("woff");
}
/* line 2, ../../../app/stylesheets/partials/_jquery.jgrowl.sass */
div.jGrowl {
padding: 10px;

Binary file not shown.

BIN
vendor/cache/addressable-2.3.2.gem vendored Normal file

Binary file not shown.

Binary file not shown.

BIN
vendor/cache/bullet-4.1.6.gem vendored Normal file

Binary file not shown.

Binary file not shown.

BIN
vendor/cache/closure-compiler-1.1.8.gem vendored Normal file

Binary file not shown.

BIN
vendor/cache/dye-0.1.4.gem vendored Normal file

Binary file not shown.

Binary file not shown.

BIN
vendor/cache/em-socksify-0.2.1.gem vendored Normal file

Binary file not shown.

BIN
vendor/cache/excon-0.16.10.gem vendored Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
vendor/cache/factory_girl-2.6.4.gem vendored Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
vendor/cache/fog-1.8.0.gem vendored Normal file

Binary file not shown.

Binary file not shown.

BIN
vendor/cache/formatador-0.2.4.gem vendored Normal file

Binary file not shown.

BIN
vendor/cache/globalize3-0.3.0.gem vendored Normal file

Binary file not shown.

Binary file not shown.

BIN
vendor/cache/msgpack-0.4.7.gem vendored Normal file

Binary file not shown.

Binary file not shown.

BIN
vendor/cache/multi_json-1.3.7.gem vendored Normal file

Binary file not shown.

BIN
vendor/cache/mysql2-0.2.18.gem vendored Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
vendor/cache/net-ssh-2.6.3.gem vendored Normal file

Binary file not shown.

Binary file not shown.

BIN
vendor/cache/newrelic_rpm-3.5.5.38.gem vendored Normal file

Binary file not shown.

Binary file not shown.

BIN
vendor/cache/nokogiri-1.5.6.gem vendored Normal file

Binary file not shown.

BIN
vendor/cache/paper_trail-2.7.0.gem vendored Normal file

Binary file not shown.

Binary file not shown.

BIN
vendor/cache/parallel-0.5.21.gem vendored Normal file

Binary file not shown.

BIN
vendor/cache/patron-0.4.18.gem vendored Normal file

Binary file not shown.

BIN
vendor/cache/progressbar-0.11.0.gem vendored Normal file

Binary file not shown.

BIN
vendor/cache/prompter-0.1.5.gem vendored Normal file

Binary file not shown.

Binary file not shown.

BIN
vendor/cache/rack-1.2.7.gem vendored Normal file

Binary file not shown.

Binary file not shown.

BIN
vendor/cache/redis-3.0.2.gem vendored Normal file

Binary file not shown.

Binary file not shown.

BIN
vendor/cache/redis-namespace-1.2.1.gem vendored Normal file

Binary file not shown.

Binary file not shown.

BIN
vendor/cache/rufus-scheduler-2.0.17.gem vendored Normal file

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show more