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.
@@ -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