Skip to content

Commit 9af0782

Browse files
author
Arjan van der Gaag
committed
Exploring gem setup and implemented basic Request
1 parent baed54b commit 9af0782

26 files changed

+525
-148
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
(unreleased)

Guardfile

-5
This file was deleted.

README.md

+45-3
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,60 @@
1+
**Currently a work in progress**
2+
3+
A Ruby wrapper around the [bol.com developers API][docs], that will be made
4+
available as a Gem. Currently in pre-alpha stage.
5+
6+
[docs]: http://developers.bol.com
7+
8+
## Available operations
9+
10+
Here's an overview of all the operations that should be implemented:
11+
112
```ruby
213
Bol::Category.find(params[:id]).products
314
Bol::Category.find(params[:id]).subcategories
415
Bol::Category.find(params[:id]).top_products
5-
Bol::Category.find(params[:id]).search()
16+
Bol::Category.find(params[:id]).search(params[:query])
617
Bol::Product.find(params[:id])
718
Bol::Product.search(params[:query]).limit(10).offset(10).order('sales_ranking ASC')
819
Bol::Product.search(params[:query]).page(params[:page])
920
```
1021

22+
These simply map to operations provided by the API to search, load lists of
23+
products or load a single product by ID. I do aim to a add a little sugar
24+
to make working with Ruby objects a little easier:
25+
26+
* Add `page` helper method to combine `limit` and `offset`
27+
* Scope operations by category in a ActiveRecord association style
28+
* Delay API calls until explicitly requested or triggered by looping over
29+
results
30+
31+
## Configuration
32+
1133
```ruby
1234
Bol.configure do |c|
13-
c.key = '...'
14-
c.secret = ''
35+
c.key = 'your public access key'
36+
c.secret = 'your private secret'
1537
c.per_page = 10
1638
c.default_order = 'sales_ranking'
1739
end
1840
```
41+
42+
## Note on Patches/Pull Requests
43+
44+
* Fork the project.
45+
* Make your feature addition or bug fix.
46+
* Add tests for it. This is important so I don't break it in a future version
47+
unintentionally.
48+
* Commit, do not mess with rakefile, version, or history. (if you want to have
49+
your own version, that is fine but bump version in a commit by itself I can
50+
ignore when I pull)
51+
* Send me a pull request. Bonus points for topic branches.
52+
53+
## History
54+
55+
For a full list of changes, please see CHANGELOG.md
56+
57+
## License
58+
59+
Copyright (C) 2011 by Arjan van der Gaag. Published under the MIT license. See
60+
LICENSE.md for details.

Rakefile

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
require 'bundler/gem_tasks'
2-
32
require 'rake/testtask'
3+
44
Rake::TestTask.new do |t|
5-
t.libs << 'test'
6-
t.test_files = FileList['test/test_*.rb']
5+
t.libs << 'spec'
6+
t.test_files = FileList['spec/**/*_spec.rb']
77
t.verbose = true
88
end
99

bol.gemspec

+1-6
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ Gem::Specification.new do |s|
77
s.version = Bol::VERSION
88
s.authors = ['Arjan van der Gaag']
99
s.email = ['[email protected]']
10-
s.homepage = ""
10+
s.homepage = "https://github.com/avdgaag/bol"
1111
s.summary = %q{Simple Ruby wrapper around the bol.com developer API}
1212
s.description = %q{Access the bol.com product catalog from a Ruby project.}
1313

@@ -17,11 +17,6 @@ Gem::Specification.new do |s|
1717
s.require_paths = ['lib']
1818

1919
s.add_development_dependency 'rake'
20-
s.add_development_dependency 'turn'
21-
s.add_development_dependency 'guard'
22-
s.add_development_dependency 'guard-minitest'
2320
s.add_development_dependency 'vcr'
2421
s.add_development_dependency 'fakeweb'
25-
s.add_development_dependency 'rb-fsevent'
26-
s.add_development_dependency 'growl'
2722
end

lib/bol.rb

+9
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,13 @@ module Bol
77
autoload :Query, 'bol/query'
88
autoload :Request, 'bol/request'
99
autoload :Requests, 'bol/requests'
10+
11+
def self.configuration
12+
@configuration ||= Configuration.new
13+
end
14+
15+
def self.configure(options = nil)
16+
@configuration = Configuration.new(options)
17+
yield @configuration if options.nil?
18+
end
1019
end

lib/bol/category.rb

+21
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,25 @@
11
module Bol
22
class Category
3+
def self.search(*args)
4+
new(0).search(*args)
5+
end
6+
7+
attr_reader :id
8+
9+
def initialize(id = 0)
10+
@id = id
11+
end
12+
13+
def search(terms)
14+
request = Requests::Search.new(Query.new(id, terms)).query
15+
end
16+
17+
def subcategories
18+
Requests::Category.new(Query.new(id))
19+
end
20+
21+
def top
22+
Requests::List.new(Query.new(id)).query
23+
end
324
end
425
end

lib/bol/configuration.rb

+37
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,41 @@
11
module Bol
22
class Configuration
3+
ALLOWED_KEYS = %w[key per_page].map(&:to_sym)
4+
5+
def initialize(options = {})
6+
raise ArgumentError unless options.nil? or options.respond_to? :each_pair
7+
8+
@options = { per_page: 10 }
9+
10+
unless options.nil?
11+
options.each_pair do |k, v|
12+
self[k] = v
13+
end
14+
end
15+
end
16+
17+
def [](key)
18+
@options[key]
19+
end
20+
21+
def []=(key, value)
22+
raise ArgumentError unless ALLOWED_KEYS.include?(key)
23+
@options[key] = value
24+
end
25+
26+
def method_missing(name, *args)
27+
return super unless respond_to? name
28+
if name.to_s =~ /=$/
29+
send(:[]=, name.to_s.sub(/=$/, '').to_sym, *args)
30+
else
31+
send(:[], name)
32+
end
33+
end
34+
35+
def respond_to?(name)
36+
super or
37+
ALLOWED_KEYS.include?(name) or
38+
ALLOWED_KEYS.include?(name.to_s.sub(/=$/, '').to_sym)
39+
end
340
end
441
end

lib/bol/product.rb

+36
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,40 @@
11
module Bol
22
class Product
3+
def self.find(id)
4+
q = Query.new(0)
5+
q.product_id = id
6+
Requests::Product.new(q)
7+
end
8+
9+
attr_reader :attributes
10+
11+
def initialize
12+
@attributes = {}
13+
end
14+
15+
def [](key)
16+
@attributes[key]
17+
end
18+
19+
def []=(key, value)
20+
@attributes[key] = value
21+
end
22+
23+
def method_missing(name, *args)
24+
if attributes.keys.include?(name)
25+
if name =~ /=$/
26+
attributes[name] = *args
27+
else
28+
attributes[name]
29+
end
30+
else
31+
super
32+
end
33+
end
34+
35+
def respond_to?(name)
36+
super or attributes.keys.include?(name) or
37+
attributes.keys.include?(name.to_s.sub(/=$/, '').to_sym)
38+
end
339
end
440
end

lib/bol/query.rb

+48
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,52 @@
11
module Bol
22
class Query
3+
attr_reader :terms, :category_id
4+
attr_accessor :product_id
5+
6+
def initialize(category_id, terms = nil)
7+
raise ArgumentError unless category_id.is_a?(Fixnum)
8+
@category_id = category_id
9+
@terms = terms
10+
end
11+
12+
def params
13+
p = {
14+
categoryId: @category_id,
15+
}
16+
p[:nrProducts] = @limit if @limit
17+
p[:offset] = @offset if @offset
18+
p[:sortingMethod] = @order_key if @order_key
19+
p[:sortingAscending] = @order_direction if @order_direction
20+
p[:productId] = @product_id if @product_id
21+
p
22+
end
23+
24+
def order(str)
25+
if str =~ /^(sales_ranking|price|title|publishing_date|customer_rating) (ASC|DESC)/
26+
@order_key = $1
27+
@order_direction = $2 == 'ASC' ? 'true' : 'false'
28+
else
29+
raise ArgumentError
30+
end
31+
self
32+
end
33+
34+
def page(n = nil)
35+
limit Bol.configuration[:per_page]
36+
offset ((n || 1).to_i - 1) * Bol.configuration[:per_page]
37+
self
38+
end
39+
40+
def offset(n = nil)
41+
return @offset if n.nil?
42+
@offset = n.to_i
43+
self
44+
end
45+
46+
def limit(n = nil)
47+
return @limit if n.nil?
48+
@limit = n.to_i
49+
self
50+
end
351
end
452
end

lib/bol/request.rb

+51
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,55 @@
1+
require 'net/https'
2+
require 'base64'
3+
require 'uri'
4+
15
module Bol
26
class Request
7+
extend Forwardable
8+
attr_reader :query, :path, :access_key, :secret, :response
9+
10+
DOMAIN = 'openapi.bol.com'
11+
12+
def initialize(query, path)
13+
@query = query
14+
@path = path
15+
end
16+
17+
def fetch
18+
uri = URI.parse("https://#{DOMAIN}#{path}")
19+
http = Net::HTTP.new(uri.host, uri.port)
20+
http.use_ssl = true
21+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
22+
request = Net::HTTP::Get.new(uri.request_uri)
23+
request.content_type = 'application/xml'
24+
request['Connection'] = 'close'
25+
request['X-OpenAPI-Authorization'] = signature(date)
26+
request['X-OpenAPI-Date'] = date
27+
@response = http.request(request)
28+
self
29+
end
30+
31+
def_delegators :response, :code, :body
32+
33+
def success?
34+
response.code == '200'
35+
end
36+
37+
private
38+
39+
def date
40+
@date ||= Time.now.utc.strftime '%a, %d %B %Y %H:%M:%S GMT'
41+
end
42+
43+
def signature
44+
msg = <<-EOS
45+
GET
46+
47+
application/xml
48+
#{date}
49+
x-openapi-date:#{date}
50+
#{path}
51+
EOS
52+
access_key + ':' + Base64.encode64(OpenSSL::HMAC.digest('sha256', secret, msg.chomp)).sub(/\n/, '')
53+
end
354
end
455
end

lib/bol/requests/category.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module Bol
22
module Requests
3-
class Category
3+
class Category < Request
44
end
55
end
66
end

lib/bol/requests/list.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module Bol
22
module Requests
3-
class List
3+
class List < Request
44
end
55
end
66
end

lib/bol/requests/product.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module Bol
22
module Requests
3-
class Product
3+
class Product < Request
44
end
55
end
66
end

lib/bol/requests/search.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module Bol
22
module Requests
3-
class Search
3+
class Search < Request
44
end
55
end
66
end

0 commit comments

Comments
 (0)