gilt 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +4 -0
- data/.travis.yml +10 -0
- data/Gemfile +15 -0
- data/Gemfile.lock +54 -0
- data/README.md +43 -0
- data/Rakefile +8 -0
- data/gilt.gemspec +21 -0
- data/lib/gilt.rb +14 -0
- data/lib/gilt/client.rb +18 -0
- data/lib/gilt/client/products.rb +15 -0
- data/lib/gilt/client/sales.rb +41 -0
- data/lib/gilt/product.rb +162 -0
- data/lib/gilt/sale.rb +101 -0
- data/lib/gilt/sku.rb +68 -0
- data/lib/gilt/version.rb +3 -0
- data/spec/fixtures/active.json +12 -0
- data/spec/fixtures/product.json +12 -0
- data/spec/fixtures/sale_detail.json +12 -0
- data/spec/gilt/client/products_spec.rb +33 -0
- data/spec/gilt/client/sales_spec.rb +90 -0
- data/spec/gilt/product_spec.rb +212 -0
- data/spec/gilt/sale_spec.rb +146 -0
- data/spec/gilt/sku_spec.rb +147 -0
- data/spec/gilt_spec.rb +1 -0
- data/spec/spec_helper.rb +12 -0
- metadata +109 -0
data/.gitignore
ADDED
data/.travis.yml
ADDED
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
|
data/Gemfile.lock
ADDED
@@ -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)
|
data/README.md
ADDED
@@ -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
|
data/Rakefile
ADDED
data/gilt.gemspec
ADDED
@@ -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
|
data/lib/gilt.rb
ADDED
data/lib/gilt/client.rb
ADDED
@@ -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,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
|
data/lib/gilt/product.rb
ADDED
@@ -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
|
data/lib/gilt/sale.rb
ADDED
@@ -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
|