closet page, closet hanger
This commit is contained in:
parent
d0dd797cdf
commit
1c84a4bef9
10 changed files with 159 additions and 7 deletions
2
Gemfile
2
Gemfile
|
@ -33,6 +33,8 @@ gem 'right_aws', '~> 2.1.0'
|
||||||
|
|
||||||
gem "character-encodings", "~> 0.4.1", :platforms => :ruby_18
|
gem "character-encodings", "~> 0.4.1", :platforms => :ruby_18
|
||||||
|
|
||||||
|
gem "nokogiri", "~> 1.5.0"
|
||||||
|
|
||||||
group :development_async do
|
group :development_async do
|
||||||
# async wrappers
|
# async wrappers
|
||||||
gem 'eventmachine', :git => 'git://github.com/eventmachine/eventmachine.git'
|
gem 'eventmachine', :git => 'git://github.com/eventmachine/eventmachine.git'
|
||||||
|
|
|
@ -107,6 +107,7 @@ GEM
|
||||||
mime-types (1.16)
|
mime-types (1.16)
|
||||||
msgpack (0.4.4)
|
msgpack (0.4.4)
|
||||||
mysql2 (0.2.6)
|
mysql2 (0.2.6)
|
||||||
|
nokogiri (1.5.0)
|
||||||
openneo-auth-signatory (0.1.0)
|
openneo-auth-signatory (0.1.0)
|
||||||
ruby-hmac
|
ruby-hmac
|
||||||
polyglot (0.3.1)
|
polyglot (0.3.1)
|
||||||
|
@ -204,6 +205,7 @@ DEPENDENCIES
|
||||||
msgpack (~> 0.4.3)
|
msgpack (~> 0.4.3)
|
||||||
mysql2
|
mysql2
|
||||||
mysqlplus!
|
mysqlplus!
|
||||||
|
nokogiri (~> 1.5.0)
|
||||||
openneo-auth-signatory (~> 0.1.0)
|
openneo-auth-signatory (~> 0.1.0)
|
||||||
rack-fiber_pool
|
rack-fiber_pool
|
||||||
rails (= 3.0.4)
|
rails (= 3.0.4)
|
||||||
|
|
7
app/models/closet_hanger.rb
Normal file
7
app/models/closet_hanger.rb
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
class ClosetHanger < ActiveRecord::Base
|
||||||
|
belongs_to :item
|
||||||
|
belongs_to :user
|
||||||
|
|
||||||
|
scope :alphabetical_by_item_name, joins(:item).order(Item.arel_table[:name])
|
||||||
|
end
|
||||||
|
|
110
app/models/closet_page.rb
Normal file
110
app/models/closet_page.rb
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
require 'yaml'
|
||||||
|
|
||||||
|
class ClosetPage
|
||||||
|
SELECTORS = {
|
||||||
|
:items => "form[action=\"process_closet.phtml\"] tr[bgcolor!=silver][bgcolor!=\"#E4E4E4\"]",
|
||||||
|
:item_thumbnail => "img",
|
||||||
|
:item_name => "td:nth-child(2)",
|
||||||
|
:item_quantity => "td:nth-child(5)",
|
||||||
|
:item_remove => "input",
|
||||||
|
:page_select => "select[name=page]",
|
||||||
|
:selected => "option[selected]"
|
||||||
|
}
|
||||||
|
|
||||||
|
attr_reader :hangers, :index, :total_pages, :unknown_item_names
|
||||||
|
|
||||||
|
def initialize(user)
|
||||||
|
raise ArgumentError, "Expected #{user.inspect} to be a User", caller unless user.is_a?(User)
|
||||||
|
@user = user
|
||||||
|
end
|
||||||
|
|
||||||
|
def save_hangers!
|
||||||
|
@hangers.each(&:save!)
|
||||||
|
end
|
||||||
|
|
||||||
|
def source=(source)
|
||||||
|
parse_source!(source)
|
||||||
|
end
|
||||||
|
|
||||||
|
protected
|
||||||
|
|
||||||
|
def element(selector_name, parent)
|
||||||
|
parent.at_css(SELECTORS[selector_name]) ||
|
||||||
|
raise(ParseError, "Closet #{selector_name} element not found in #{parent.inspect}")
|
||||||
|
end
|
||||||
|
|
||||||
|
def elements(selector_name, parent)
|
||||||
|
parent.css(SELECTORS[selector_name])
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse_source!(source)
|
||||||
|
doc = Nokogiri::HTML(source)
|
||||||
|
|
||||||
|
page_selector = element(:page_select, doc)
|
||||||
|
@total_pages = page_selector.children.size
|
||||||
|
@index = element(:selected, page_selector)['value']
|
||||||
|
|
||||||
|
items_data = {
|
||||||
|
:id => {},
|
||||||
|
:thumbnail_url => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Go through the items, and find the ID/thumbnail for each and data with it
|
||||||
|
elements(:items, doc).each do |row|
|
||||||
|
# For normal items, the td contains essentially:
|
||||||
|
# <b>NAME<br/><span>OPTIONAL ADJECTIVE</span></b>
|
||||||
|
# For PB items, the td contains:
|
||||||
|
# NAME<br/><span>OPTIONAL ADJECTIVE</span>
|
||||||
|
# So, we want the first text node. If it's a PB item, that's the first
|
||||||
|
# child. If it's a normal item, it's the first child <b>'s child.
|
||||||
|
name_el = element(:item_name, row).children[0]
|
||||||
|
name_el = name_el.children[0] if name_el.name == 'b'
|
||||||
|
|
||||||
|
data = {
|
||||||
|
:name => name_el.text,
|
||||||
|
:quantity => element(:item_quantity, row).text.to_i
|
||||||
|
}
|
||||||
|
|
||||||
|
if id = element(:item_remove, row)['name']
|
||||||
|
id = id.to_i
|
||||||
|
items_data[:id][id] = data
|
||||||
|
else # if this is a pb item, which does not give ID, go by thumbnail
|
||||||
|
thumbnail_url = element(:item_thumbnail, row)['src']
|
||||||
|
items_data[:thumbnail_url][thumbnail_url] = data
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Find items with either a matching ID or matching thumbnail URL
|
||||||
|
# Check out that single-query beauty :)
|
||||||
|
i = Item.arel_table
|
||||||
|
items = Item.where(
|
||||||
|
i[:id].in(items_data[:id].keys).
|
||||||
|
or(
|
||||||
|
i[:thumbnail_url].in(items_data[:thumbnail_url].keys)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create closet hanger from each item, and remove them from the reference
|
||||||
|
# lists
|
||||||
|
@hangers = items.map do |item|
|
||||||
|
data = items_data[:id].delete(item.id) ||
|
||||||
|
items_data[:thumbnail_url].delete(item.thumbnail_url)
|
||||||
|
hanger = @user.closet_hangers.build
|
||||||
|
hanger.item = item
|
||||||
|
hanger.quantity = data[:quantity]
|
||||||
|
hanger
|
||||||
|
end
|
||||||
|
|
||||||
|
# Take the names of the items remaining in the reference lists, meaning
|
||||||
|
# that they weren't found
|
||||||
|
@unknown_item_names = []
|
||||||
|
items_data.each do |type, data_by_key|
|
||||||
|
data_by_key.each do |key, data|
|
||||||
|
@unknown_item_names << data[:name]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class ParseError < RuntimeError;end
|
||||||
|
end
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
class Item < ActiveRecord::Base
|
class Item < ActiveRecord::Base
|
||||||
SwfAssetType = 'object'
|
SwfAssetType = 'object'
|
||||||
|
|
||||||
|
has_many :closet_hangers
|
||||||
has_one :contribution, :as => :contributed
|
has_one :contribution, :as => :contributed
|
||||||
has_many :parent_swf_asset_relationships, :foreign_key => 'parent_id',
|
has_many :parent_swf_asset_relationships, :foreign_key => 'parent_id',
|
||||||
:conditions => {:swf_asset_type => SwfAssetType}
|
:conditions => {:swf_asset_type => SwfAssetType}
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
class User < ActiveRecord::Base
|
class User < ActiveRecord::Base
|
||||||
DefaultAuthServerId = 1
|
DefaultAuthServerId = 1
|
||||||
PreviewTopContributorsCount = 3
|
PreviewTopContributorsCount = 3
|
||||||
|
|
||||||
|
has_many :closet_hangers
|
||||||
has_many :contributions
|
has_many :contributions
|
||||||
has_many :outfits
|
has_many :outfits
|
||||||
|
|
||||||
scope :top_contributors, order('points DESC').where(arel_table[:points].gt(0))
|
scope :top_contributors, order('points DESC').where(arel_table[:points].gt(0))
|
||||||
|
|
||||||
devise :rememberable
|
devise :rememberable
|
||||||
|
|
||||||
def contribute!(pet)
|
def contribute!(pet)
|
||||||
new_contributions = []
|
new_contributions = []
|
||||||
new_points = 0
|
new_points = 0
|
||||||
|
@ -38,7 +39,7 @@ class User < ActiveRecord::Base
|
||||||
end
|
end
|
||||||
new_points
|
new_points
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.find_or_create_from_remote_auth_data(user_data)
|
def self.find_or_create_from_remote_auth_data(user_data)
|
||||||
user = find_or_initialize_by_remote_id_and_auth_server_id(
|
user = find_or_initialize_by_remote_id_and_auth_server_id(
|
||||||
user_data['id'],
|
user_data['id'],
|
||||||
|
@ -50,9 +51,10 @@ class User < ActiveRecord::Base
|
||||||
end
|
end
|
||||||
user
|
user
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.points_required_to_pass_top_contributor(offset)
|
def self.points_required_to_pass_top_contributor(offset)
|
||||||
user = User.top_contributors.select(:points).limit(1).offset(offset).first
|
user = User.top_contributors.select(:points).limit(1).offset(offset).first
|
||||||
user ? user.points : 0
|
user ? user.points : 0
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
15
db/migrate/20110712232259_create_closet_hangers.rb
Normal file
15
db/migrate/20110712232259_create_closet_hangers.rb
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
class CreateClosetHangers < ActiveRecord::Migration
|
||||||
|
def self.up
|
||||||
|
create_table :closet_hangers do |t|
|
||||||
|
t.integer :item_id
|
||||||
|
t.integer :user_id
|
||||||
|
t.integer :quantity
|
||||||
|
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.down
|
||||||
|
drop_table :closet_hangers
|
||||||
|
end
|
||||||
|
end
|
10
db/schema.rb
10
db/schema.rb
|
@ -10,7 +10,7 @@
|
||||||
#
|
#
|
||||||
# It's strongly recommended to check this file into your version control system.
|
# It's strongly recommended to check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema.define(:version => 20110626202605) do
|
ActiveRecord::Schema.define(:version => 20110712232259) do
|
||||||
|
|
||||||
create_table "auth_servers", :force => true do |t|
|
create_table "auth_servers", :force => true do |t|
|
||||||
t.string "short_name", :limit => 10, :null => false
|
t.string "short_name", :limit => 10, :null => false
|
||||||
|
@ -20,6 +20,14 @@ ActiveRecord::Schema.define(:version => 20110626202605) do
|
||||||
t.string "secret", :limit => 64, :null => false
|
t.string "secret", :limit => 64, :null => false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
create_table "closet_hangers", :force => true do |t|
|
||||||
|
t.integer "item_id"
|
||||||
|
t.integer "user_id"
|
||||||
|
t.integer "quantity"
|
||||||
|
t.datetime "created_at"
|
||||||
|
t.datetime "updated_at"
|
||||||
|
end
|
||||||
|
|
||||||
create_table "contributions", :force => true do |t|
|
create_table "contributions", :force => true do |t|
|
||||||
t.string "contributed_type", :limit => 8, :null => false
|
t.string "contributed_type", :limit => 8, :null => false
|
||||||
t.integer "contributed_id", :null => false
|
t.integer "contributed_id", :null => false
|
||||||
|
|
5
spec/models/closet_hanger_spec.rb
Normal file
5
spec/models/closet_hanger_spec.rb
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe ClosetHanger do
|
||||||
|
pending "add some examples to (or delete) #{__FILE__}"
|
||||||
|
end
|
BIN
vendor/cache/nokogiri-1.5.0.gem
vendored
Normal file
BIN
vendor/cache/nokogiri-1.5.0.gem
vendored
Normal file
Binary file not shown.
Loading…
Reference in a new issue