gilt 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,4 @@
1
+ .DS_Store
2
+ *.gem
3
+ .bundle
4
+ pkg/*
@@ -0,0 +1,10 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.8.7
4
+ - 1.9.2
5
+ - 1.9.3
6
+ - jruby-18mode
7
+ - jruby-19mode
8
+ - rbx-18mode
9
+ - rbx-19mode
10
+ - ree
data/Gemfile ADDED
@@ -0,0 +1,15 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in gilt-rb.gemspec
4
+ gemspec
5
+
6
+ gem "rake", "~> 0.9.2"
7
+
8
+ group :test do
9
+ gem "rspec", "~> 2.9.0"
10
+ gem "webmock", "~> 1.8.5"
11
+ end
12
+
13
+ platforms :jruby do
14
+ gem "jruby-openssl", "~> 0.7.6"
15
+ end
@@ -0,0 +1,54 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ gilt (0.1.0)
5
+ money (~> 4.0.2)
6
+ weary (~> 1.0.0)
7
+
8
+ GEM
9
+ remote: http://rubygems.org/
10
+ specs:
11
+ addressable (2.2.7)
12
+ bouncy-castle-java (1.5.0146.1)
13
+ crack (0.3.1)
14
+ diff-lcs (1.1.3)
15
+ i18n (0.6.0)
16
+ jruby-openssl (0.7.6.1)
17
+ bouncy-castle-java (>= 1.5.0146.1)
18
+ json (1.6.6)
19
+ money (4.0.2)
20
+ i18n (~> 0.4)
21
+ json
22
+ multi_json (1.1.0)
23
+ promise (0.3.0)
24
+ rack (1.4.1)
25
+ rake (0.9.2.2)
26
+ rspec (2.9.0)
27
+ rspec-core (~> 2.9.0)
28
+ rspec-expectations (~> 2.9.0)
29
+ rspec-mocks (~> 2.9.0)
30
+ rspec-core (2.9.0)
31
+ rspec-expectations (2.9.0)
32
+ diff-lcs (~> 1.1.3)
33
+ rspec-mocks (2.9.0)
34
+ simple_oauth (0.1.5)
35
+ weary (1.0.0)
36
+ addressable (~> 2.2.7)
37
+ multi_json (~> 1.1.0)
38
+ promise (~> 0.3.0)
39
+ rack (~> 1.4.0)
40
+ simple_oauth (~> 0.1.5)
41
+ webmock (1.8.5)
42
+ addressable (>= 2.2.7)
43
+ crack (>= 0.1.7)
44
+
45
+ PLATFORMS
46
+ java
47
+ ruby
48
+
49
+ DEPENDENCIES
50
+ gilt!
51
+ jruby-openssl (~> 0.7.6)
52
+ rake (~> 0.9.2)
53
+ rspec (~> 2.9.0)
54
+ webmock (~> 1.8.5)
@@ -0,0 +1,43 @@
1
+ .oooooo. ooooo ooooo ooooooooooooo
2
+ d8P' `Y8b `888' `888' 8' 888 `8
3
+ 888 888 888 888
4
+ 888 888 888 888
5
+ 888 ooooo 888 888 888
6
+ `88. .88' 888 888 o 888
7
+ `Y8bood8P' o888o o888ooood8 o888o
8
+
9
+
10
+ `gilt` is a Ruby library for v1 of the [Gilt Public API](http://dev.gilt.com/).
11
+
12
+ It's written with the [Weary](https://github.com/mwunsch/weary) framework, so it gets the features of that library out of the box, like:
13
+
14
+ * Full Rack integration. The Client to the library is a Rack application.
15
+ * Fully asynchronous. `gilt` makes liberal use of futures and Weary::Deferred.
16
+
17
+ ## Examples
18
+
19
+ ```ruby
20
+ active_sales = Gilt::Sale.active :apikey => "your-api-key"
21
+ womens_sales = sales.select {|sale| sale.store == Gilt::Stores::WOMEN }
22
+ sales.products.map(&:name)
23
+ ```
24
+
25
+ Above, the call to `sales.products` returns a list of [Weary::Deferred](https://github.com/mwunsch/weary/blob/master/lib/weary/deferred.rb) objects wrapping Gilt::Product objects. This means that fetching the product is asynchronous, and only blocks when accessed.
26
+
27
+ ### With Rack
28
+
29
+ ```ruby
30
+ # config.ru
31
+ client = Gilt::Client::Product
32
+ client.use Rack::Runtime
33
+
34
+ run client
35
+ ```
36
+
37
+ After `rackup`:
38
+
39
+ curl "http://localhost:9292/sales/active.json?apikey=my-api-key"
40
+
41
+ ## Installation
42
+
43
+ gem install gilt
@@ -0,0 +1,8 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+
4
+ task :default => :spec
5
+
6
+ RSpec::Core::RakeTask.new(:spec) do |t|
7
+ t.rspec_opts = ["--color"]
8
+ end
@@ -0,0 +1,21 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "gilt/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "gilt"
7
+ s.version = Gilt::VERSION
8
+ s.authors = ["Mark Wunsch"]
9
+ s.email = ["mwunsch@gilt.com"]
10
+ s.homepage = "http://github.com/mwunsch/gilt"
11
+ s.summary = %q{Ruby client for the Gilt public API.}
12
+ s.description = %q{Ruby client for the Gilt public API (http://dev.gilt.com). Written with the Weary framework.}
13
+
14
+ s.files = `git ls-files`.split("\n")
15
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
16
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
17
+ s.require_paths = ["lib"]
18
+
19
+ s.add_runtime_dependency "money", "~> 4.0.2"
20
+ s.add_runtime_dependency "weary", "~> 1.0.0"
21
+ end
@@ -0,0 +1,14 @@
1
+ module Gilt
2
+ module Stores
3
+ WOMEN = :women
4
+ MEN = :men
5
+ KIDS = :kids
6
+ HOME = :home
7
+ end
8
+
9
+ autoload :Sale, 'gilt/sale'
10
+ autoload :Product, 'gilt/product'
11
+ end
12
+
13
+ require "gilt/client"
14
+ require "gilt/version"
@@ -0,0 +1,18 @@
1
+ require "weary/client"
2
+
3
+ module Gilt
4
+ class Client < Weary::Client
5
+ VERSION = "v1"
6
+ FORMAT = "json"
7
+ DOMAIN = "https://api.gilt.com/#{VERSION}"
8
+
9
+ def initialize(apikey, affid=nil)
10
+ @defaults = {}
11
+ @defaults[:apikey] = apikey
12
+ @defaults[:affid] = affid unless affid.nil?
13
+ end
14
+
15
+ autoload :Sales, "gilt/client/sales"
16
+ autoload :Products, "gilt/client/products"
17
+ end
18
+ end
@@ -0,0 +1,15 @@
1
+ require "gilt/client"
2
+
3
+ module Gilt
4
+ class Client
5
+ class Products < Gilt::Client
6
+ domain DOMAIN
7
+
8
+ required :apikey
9
+
10
+ optional :affid
11
+
12
+ get :detail, "/products/:product_id/detail.#{FORMAT}"
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,41 @@
1
+ require "gilt/client"
2
+
3
+ module Gilt
4
+ class Client
5
+ class Sales < Gilt::Client
6
+ domain DOMAIN
7
+
8
+ required :apikey
9
+
10
+ optional :affid
11
+
12
+ get :active, "/sales/active.#{FORMAT}"
13
+
14
+ get :active_in_store, "/sales/:store/active.#{FORMAT}"
15
+
16
+ get :upcoming, "/sales/upcoming.#{FORMAT}"
17
+
18
+ get :upcoming_in_store, "/sales/:store/upcoming.#{FORMAT}"
19
+
20
+ get :detail, "/sales/:store/:sale_key/detail.#{FORMAT}"
21
+
22
+ [ Gilt::Stores::WOMEN,
23
+ Gilt::Stores::MEN,
24
+ Gilt::Stores::KIDS,
25
+ Gilt::Stores::HOME ].each do |store|
26
+ define_method "active_in_#{store}" do |*args, &block|
27
+ params = args.first || {}
28
+ active_in_store params.merge({:store => store}), &block
29
+ end
30
+ alias_method "#{store}_active".intern, "active_in_#{store}".intern
31
+
32
+ define_method "upcoming_in_#{store}" do |*args, &block|
33
+ params = args.first || {}
34
+ upcoming_in_store params.merge({:store => store}), &block
35
+ end
36
+ alias_method "#{store}_upcoming".intern, "upcoming_in_#{store}".intern
37
+ end
38
+
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,162 @@
1
+ module Gilt
2
+ autoload :Sku, "gilt/sku"
3
+
4
+ class Product
5
+
6
+ def self.defer(future)
7
+ require 'weary/deferred'
8
+ Weary::Deferred.new future, self, lambda {|product, response| product.new(response.parse) }
9
+ end
10
+
11
+ def self.client(apikey, affid=nil)
12
+ Gilt::Client::Products.new(apikey, affid)
13
+ end
14
+
15
+ def self.detail(product_id, apikey, affid=nil)
16
+ client(apikey, affid).detail(:product_id => product_id)
17
+ end
18
+
19
+ def self.create(product_id, apikey, affid=nil)
20
+ response = detail(product_id, apikey, affid).perform
21
+ self.new response.parse
22
+ end
23
+
24
+ def initialize(product_body)
25
+ @product = product_body
26
+ end
27
+
28
+ def name
29
+ @product["name"]
30
+ end
31
+
32
+ def product
33
+ URI(@product["product"])
34
+ end
35
+
36
+ def id
37
+ @product["id"].to_i
38
+ end
39
+
40
+ def brand
41
+ @product["brand"]
42
+ end
43
+
44
+ def url
45
+ URI(@product["url"])
46
+ end
47
+
48
+ def description
49
+ fetch_content :description
50
+ end
51
+
52
+ def fit_notes
53
+ fetch_content :fit_notes
54
+ end
55
+
56
+ def material
57
+ fetch_content :material
58
+ end
59
+
60
+ def care_instructions
61
+ fetch_content :care_instructions
62
+ end
63
+
64
+ def origin
65
+ fetch_content :origin
66
+ end
67
+
68
+ def content
69
+ keys = [:description, :fit_notes, :material, :care_instructions, :origin]
70
+ Hash[keys.map {|content| [content, self.send(content) ]}]
71
+ end
72
+
73
+ def skus
74
+ @product["skus"].map {|sku| Sku.new(sku) }
75
+ end
76
+
77
+ def min_price
78
+ sorted_price.last
79
+ end
80
+
81
+ def max_price
82
+ sorted_price.first
83
+ end
84
+
85
+ def price_range
86
+ [min_price, max_price]
87
+ end
88
+
89
+ def format_price
90
+ range = price_range
91
+ return range.first.format if range.first == range.last
92
+ price_range.map(&:format).join(" - ")
93
+ end
94
+
95
+ def images
96
+ @product["image_urls"]
97
+ end
98
+
99
+ def colors
100
+ skus.map {|sku| sku.attributes[:color] }.uniq
101
+ end
102
+
103
+ def sizes
104
+ skus.map {|sku| sku.attributes[:size] }.uniq
105
+ end
106
+
107
+ def skus_of_size(size)
108
+ skus_with_attribute :size, size
109
+ end
110
+
111
+ def skus_of_color(color)
112
+ skus_with_attribute :color, color
113
+ end
114
+
115
+ def skus_with_attribute(attribute, value)
116
+ skus.select {|sku| !!sku.attributes[attribute.to_sym].match(value) }
117
+ end
118
+
119
+ def select_sku(attributes)
120
+ attribute_map = attributes.map {|k, v| skus_with_attribute(k, v).map(&:id) }
121
+ ids = attribute_map.reduce(:&)
122
+ skus.find {|sku| sku.id == ids.first } if ids.size > 0
123
+ end
124
+
125
+ def inventory_status
126
+ sku_inventory = skus.map(&:inventory_status).uniq
127
+ if sku_inventory.include? Gilt::Sku::FOR_SALE
128
+ Gilt::Sku::FOR_SALE
129
+ elsif sku_inventory.all? {|status| status == Gilt::Sku::RESERVED}
130
+ Gilt::Sku::RESERVED
131
+ else
132
+ Gilt::Sku::SOLD_OUT
133
+ end
134
+ end
135
+
136
+ def for_sale?
137
+ inventory_status == Gilt::Sku::FOR_SALE
138
+ end
139
+
140
+ def sold_out?
141
+ inventory_status == Gilt::Sku::SOLD_OUT
142
+ end
143
+
144
+ def reserved?
145
+ inventory_status == Gilt::Sku::RESERVED
146
+ end
147
+
148
+ private
149
+
150
+ def fetch_content(key)
151
+ content = @product["content"]
152
+ content[key.to_s] unless content.nil?
153
+ end
154
+
155
+ def sorted_price
156
+ set = skus.map(&:sale_price).uniq
157
+ return set if set.size == 1
158
+ set.sort
159
+ end
160
+
161
+ end
162
+ end
@@ -0,0 +1,101 @@
1
+ require 'time'
2
+
3
+ module Gilt
4
+ class Sale
5
+ class << self
6
+ Gilt::Client::Sales.resources.keys.each do |key|
7
+ define_method key do |params, &block|
8
+ args = params || {}
9
+ apikey = args[:apikey]
10
+ affid = args[:affid]
11
+ products = product_client apikey, affid
12
+ req = client(apikey, affid).send(key.to_sym, args)
13
+ response = req.perform.parse
14
+ if response["sales"].nil?
15
+ [self.new(response, products)]
16
+ else
17
+ response["sales"].map {|sale| self.new(sale, products)}
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ def self.client(apikey, affid=nil)
24
+ Gilt::Client::Sales.new(apikey, affid)
25
+ end
26
+
27
+ def self.product_client(apikey, affid=nil)
28
+ Gilt::Client::Products.new(apikey, affid)
29
+ end
30
+
31
+ def self.create(store, sale_key, apikey, affid=nil)
32
+ detail(:store => store, :sale_key => sale_key, :apikey => apikey, :affid => affid).first
33
+ end
34
+
35
+ def initialize(sale_body, client=nil)
36
+ @sale = sale_body
37
+ @client = client
38
+ end
39
+
40
+ def name
41
+ @sale["name"]
42
+ end
43
+
44
+ def store
45
+ @sale["store"].intern
46
+ end
47
+
48
+ def sale_key
49
+ @sale["sale_key"]
50
+ end
51
+
52
+ def sale
53
+ URI(@sale["sale"])
54
+ end
55
+
56
+ def sale_url
57
+ URI(@sale["sale_url"])
58
+ end
59
+
60
+ def images
61
+ @sale["image_urls"]
62
+ end
63
+
64
+ def description
65
+ @sale["description"]
66
+ end
67
+
68
+ def begins
69
+ Time.parse @sale["begins"]
70
+ end
71
+
72
+ def ends
73
+ end_time = @sale["ends"]
74
+ Time.parse end_time unless end_time.nil?
75
+ end
76
+
77
+ def ended?
78
+ return false if ends.nil?
79
+ ends < Time.now
80
+ end
81
+
82
+ def duration
83
+ (ends - begins).ceil unless ends.nil?
84
+ end
85
+
86
+ def products
87
+ return @products unless @products.nil?
88
+ resource = Gilt::Client::Products.resources[:detail]
89
+ @products = (@sale["products"] || []).map do |product|
90
+ id = resource.url.extract(product)["product_id"]
91
+ @client.detail(:product_id => id).perform
92
+ Product.defer @client.detail(:product_id => id).perform
93
+ end
94
+ end
95
+
96
+ def length
97
+ return products.length
98
+ end
99
+
100
+ end
101
+ end