api_bee 0.0.2 → 0.0.3

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/README.mkd CHANGED
@@ -61,19 +61,33 @@ api.get('/my/resources').each do |r|
61
61
  end
62
62
  ```
63
63
 
64
+ ## Delegate to adapter
65
+
66
+ Lazy-loading and paginating resources is great for GET requests, but you might want to still use your adapter's other methods.
67
+
68
+ api = ApiBee.setup(MyCustomAdapter) do |config|
69
+ config.expose :delete, :post
70
+ end
71
+
72
+ # This still wraps your adapter's get() method and adds lazy-loading and pagination
73
+ api.get('/products').first[:title]
74
+
75
+ # This delegates directory to MyCustomAdapter#post()
76
+ api.post('/products', :title => 'Foo', :price => 100.0)
77
+
64
78
  ## finding a single resource
65
79
 
66
- There's a special find_one method that you can call on lists. It delegates to the adapter and it's useful for finding a single resource in the context of a paginated list.
80
+ There's a special get_one method that you can call on lists. It delegates to the adapter and it's useful for finding a single resource in the context of a paginated list.
67
81
 
68
82
  resources = api.get('/my/resources')
69
- resource = resources.find_one('foobar')
83
+ resource = resources.get_one('foobar')
70
84
 
71
- That delegates to Adapter#find_one passing 2 arguments: the list's href and the passed name or identifier, so:
85
+ That delegates to Adapter#get_one passing 2 arguments: the list's href and the passed name or identifier, so:
72
86
 
73
87
  ```ruby
74
88
  class ApiBee::Adapters::Special
75
89
  # ...
76
- def find_one(href, id)
90
+ def get_one(href, id)
77
91
  get "#{href}/#{id}"
78
92
  end
79
93
  end
@@ -1,14 +1,15 @@
1
1
  $LOAD_PATH.unshift '../lib'
2
2
  require 'api_bee'
3
- #require 'net/http'
4
3
  require 'net/https'
5
4
  require 'uri'
6
5
  require 'json'
7
6
 
7
+ # Github adapter wraps raw Github API response and decorates collections with
8
+ # pagination parameters needed by APIBee
9
+ #
8
10
  class GithubAdapter
9
11
 
10
12
  def initialize
11
- ApiBee.config.uri_property_name = :url
12
13
  @url = URI.parse('https://api.github.com')
13
14
  @http = Net::HTTP.new(@url.host, @url.port)
14
15
  @http.use_ssl = true
@@ -16,8 +17,6 @@ class GithubAdapter
16
17
  end
17
18
 
18
19
  def get(path, options = {})
19
- per_page = (options[:per_page] || 20).to_i
20
- page = (options[:page] || 1).to_i
21
20
 
22
21
  q = options.map{|k,v| "#{k}=#{v}"}.join('&')
23
22
 
@@ -28,21 +27,9 @@ class GithubAdapter
28
27
 
29
28
  if response.kind_of?(Net::HTTPOK)
30
29
  results = JSON.parse response.body
31
- if results.is_a?(Array)
30
+ results = if results.is_a?(Array)
32
31
  # decorate returned array so it complies with APiBee's pagination params
33
- results = {
34
- :entries => results,
35
- :page => page,
36
- :per_page => per_page,
37
- :url => path
38
- }
39
- # Extract last page number and entries count. Github uses a 'Link' header
40
- if link = response["link"]
41
- last_page = extract_last_page(link) || page # if no 'last' link, we're on the last page
42
- results.update(
43
- :total_entries => last_page.to_i * per_page
44
- )
45
- end
32
+ paginate results, options, path, response
46
33
  else
47
34
  results
48
35
  end
@@ -54,12 +41,32 @@ class GithubAdapter
54
41
 
55
42
  end
56
43
 
57
- def find_one(href, id)
44
+ def get_one(href, id)
58
45
  get id
59
46
  end
60
47
 
61
48
  protected
62
49
 
50
+ def paginate(results, options, path, response)
51
+ per_page = (options[:per_page] || 20).to_i
52
+ page = (options[:page] || 1).to_i
53
+
54
+ results = {
55
+ :entries => results,
56
+ :page => page,
57
+ :per_page => per_page,
58
+ :url => path
59
+ }
60
+ # Extract last page number and entries count. Github uses a 'Link' header
61
+ if link = response["link"]
62
+ last_page = extract_last_page(link) || page # if no 'last' link, we're on the last page
63
+ results.update(
64
+ :total_entries => last_page.to_i * per_page
65
+ )
66
+ end
67
+ results
68
+ end
69
+
63
70
  def extract_last_page(link)
64
71
  aa = link.split('<https')
65
72
  last_link = aa.find{|e| e=~ /rel="last"/}
@@ -69,12 +76,17 @@ class GithubAdapter
69
76
 
70
77
  end
71
78
 
79
+ ########### USAGE ##################################
80
+
72
81
  ## Instantiate your wrapped API
73
82
 
74
- api = ApiBee.setup(GithubAdapter)
83
+ api = ApiBee.setup(GithubAdapter) do |config|
84
+ config.uri_property_name = :url
85
+ end
75
86
 
76
87
  repos = api.get('/users/ismasan/repos')
77
88
 
89
+ # Recursive method prints results for each page
78
90
  def show(data, c)
79
91
  puts "+++++++++++++++ Page #{data.current_page} of #{data.total_pages} (#{data.total_entries} entries). #{data.size} now. ++++++++"
80
92
  puts
@@ -97,7 +109,7 @@ show repos, 1
97
109
  puts "First created at is: #{repos.first[:created_at]}"
98
110
 
99
111
  # Fetch a single node
100
- one = repos.find_one('https://api.github.com/repos/ismasan/websockets_examples')
112
+ one = repos.get_one('https://api.github.com/repos/ismasan/websockets_examples')
101
113
 
102
114
  # one[:owner][:public_repos] will trigger a new request to the resource URL because that property is not available in the excerpt
103
115
  #
@@ -25,7 +25,7 @@ module ApiBee
25
25
  found
26
26
  end
27
27
 
28
- def find_one(href, id)
28
+ def get_one(href, id)
29
29
  get("#{href}/#{id}")
30
30
  end
31
31
 
@@ -57,7 +57,7 @@ module ApiBee
57
57
  end
58
58
 
59
59
  def is_paginated?(hash)
60
- hash[ApiBee.config.uri_property_name] && hash[:total_entries]
60
+ hash[:href] && hash[:total_entries]
61
61
  end
62
62
 
63
63
  def paginate(list, page, per_page)
data/lib/api_bee/node.rb CHANGED
@@ -9,20 +9,21 @@ module ApiBee
9
9
  end
10
10
  end
11
11
 
12
- def self.resolve(adapter, attrs, href = nil)
12
+ def self.resolve(adapter, config, attrs, href = nil)
13
13
  attrs = simbolized(attrs)
14
14
  keys = attrs.keys.map{|k| k.to_sym}
15
- if keys.include?(:total_entries) && keys.include?(ApiBee.config.uri_property_name.to_sym) # is a paginator
16
- List.new adapter, attrs, href
15
+ if keys.include?(config.total_entries_property_name) && keys.include?(config.uri_property_name.to_sym) # is a paginator
16
+ List.new adapter, config, attrs, href
17
17
  else
18
- Single.new adapter, attrs, href
18
+ Single.new adapter, config, attrs, href
19
19
  end
20
20
  end
21
21
 
22
22
  attr_reader :adapter
23
23
 
24
- def initialize(adapter, attrs, href)
24
+ def initialize(adapter, config, attrs, href)
25
25
  @adapter = adapter
26
+ @config = config
26
27
  @attributes = {}
27
28
  @href = href
28
29
  update_attributes attrs
@@ -54,7 +55,7 @@ module ApiBee
54
55
  def resolve_values_to_nodes(value)
55
56
  case value
56
57
  when ::Hash
57
- Node.resolve @adapter, value
58
+ Node.resolve @adapter, @config, value
58
59
  when ::Array
59
60
  value.map {|v| resolve_values_to_nodes(v)} # recurse
60
61
  else
@@ -63,11 +64,11 @@ module ApiBee
63
64
  end
64
65
 
65
66
  def has_more?
66
- !@complete && @attributes[ApiBee.config.uri_property_name]
67
+ !@complete && @attributes[@config.uri_property_name]
67
68
  end
68
69
 
69
70
  def load_more!
70
- more_data = @adapter.get(@attributes[ApiBee.config.uri_property_name])
71
+ more_data = @adapter.get(@attributes[@config.uri_property_name])
71
72
  update_attributes Node.simbolized(more_data) if more_data
72
73
  @complete = true
73
74
  end
@@ -80,9 +81,9 @@ module ApiBee
80
81
 
81
82
  DEFAULT_PER_PAGE = 100
82
83
 
83
- def find_one(id)
84
- data = @adapter.find_one(@href, id)
85
- data.nil? ? nil : Node.resolve(@adapter, data, @href)
84
+ def get_one(id)
85
+ data = @adapter.get_one(@href, id)
86
+ data.nil? ? nil : Node.resolve(@adapter, @config, data, @href)
86
87
  end
87
88
 
88
89
  def total_entries
@@ -143,14 +144,14 @@ module ApiBee
143
144
  end
144
145
 
145
146
  def paginate(options = {})
146
- data = @adapter.get(@attributes[ApiBee.config.uri_property_name], options)
147
- Node.resolve @adapter, data, @href
147
+ data = @adapter.get(@attributes[@config.uri_property_name], options)
148
+ Node.resolve @adapter, @config, data, @href
148
149
  end
149
150
 
150
151
  protected
151
152
 
152
153
  def __entries
153
- @entries ||= (self[:entries] || [])
154
+ @entries ||= (self[@config.entries_property_name] || [])
154
155
  end
155
156
 
156
157
  end
data/lib/api_bee/proxy.rb CHANGED
@@ -4,15 +4,16 @@ module ApiBee
4
4
 
5
5
  attr_reader :adapter
6
6
 
7
- def initialize(adapter, href = nil, opts = nil)
7
+ def initialize(adapter, config, href = nil, opts = nil)
8
8
  @adapter = adapter
9
+ @config = config
9
10
  @href = href
10
11
  @opts = opts
11
12
  end
12
13
 
13
14
  def get(href, opts = {})
14
15
  # Just delegate. No API calls at this point. We only load data when we need it.
15
- Proxy.new @adapter, href, opts
16
+ Proxy.new @adapter, @config, href, opts
16
17
  end
17
18
 
18
19
  def [](key)
@@ -24,20 +25,32 @@ module ApiBee
24
25
  end
25
26
 
26
27
  def paginate(*args)
27
- @list ||= Node::List.new(@adapter, {ApiBee.config.uri_property_name => @href}, @href)
28
+ @list ||= Node::List.new(@adapter, @config, {@config.uri_property_name => @href}, @href)
28
29
  @list.paginate *args
29
30
  end
30
31
 
32
+ def ==(other)
33
+ _node.to_data
34
+ end
35
+
31
36
  protected
32
37
 
33
38
  def method_missing(method_name, *args, &block)
34
- _node.send(method_name, *args, &block)
39
+ if _adapter_delegators.include?(method_name)
40
+ @adapter.send(method_name, *args, &block)
41
+ else
42
+ _node.send(method_name, *args, &block)
43
+ end
44
+ end
45
+
46
+ def _adapter_delegators
47
+ @adapter_delegators ||= @config.adapter_delegators || []
35
48
  end
36
49
 
37
50
  def _node
38
51
  @node ||= (
39
52
  data = @adapter.get(@href, @opts)
40
- Node.resolve @adapter, data, @href
53
+ Node.resolve @adapter, @config, data, @href
41
54
  )
42
55
  end
43
56
 
@@ -1,3 +1,3 @@
1
1
  module ApiBee
2
- VERSION = "0.0.2"
2
+ VERSION = "0.0.3"
3
3
  end
data/lib/api_bee.rb CHANGED
@@ -6,8 +6,6 @@ module ApiBee
6
6
 
7
7
  def setup(adapter_klass, *args)
8
8
 
9
- yield config if block_given?
10
-
11
9
  adapter = if adapter_klass.is_a?(::Symbol)
12
10
  require File.join('api_bee', 'adapters', adapter_klass.to_s)
13
11
  klass = adapter_klass.to_s.gsub(/(^.{1})/){$1.upcase}
@@ -17,18 +15,37 @@ module ApiBee
17
15
  end
18
16
 
19
17
  raise NoMethodError, "Adapter must implement #get(path, *args) method" unless adapter.respond_to?(:get)
20
- Proxy.new adapter
18
+
19
+ config = new_config
20
+ yield config if block_given?
21
+ Proxy.new adapter, config
22
+ end
23
+
24
+ # new config object with defaults
25
+ def new_config
26
+ Config.new(
27
+ # This field is expected in API responses
28
+ # and should point to an individual resource with more data
29
+ :uri_property_name => :href,
30
+ # Total number of entries
31
+ # Used to paginate lists
32
+ :total_entries_property_name => :total_entries,
33
+ # Name of array property
34
+ # that contains cureent page's entries
35
+ :entries_property_name => :entries
36
+ )
21
37
  end
22
38
 
23
- def config
24
- @config ||= OpenStruct.new
39
+ class Config < OpenStruct
40
+
41
+ def expose(*fields)
42
+ self.adapter_delegators = fields
43
+ end
44
+
25
45
  end
26
46
 
27
47
  end
28
48
 
29
- # Defaults
30
- self.config.uri_property_name = :href
31
-
32
49
  module Adapters
33
50
 
34
51
  end
data/spec/node_spec.rb CHANGED
@@ -86,17 +86,18 @@ describe ApiBee do
86
86
 
87
87
  before do
88
88
  @adapter = mock('Adapter')
89
+ @config = ApiBee.new_config
89
90
  end
90
91
 
91
92
  it 'should resolve single nodes' do
92
- node = ApiBee::Node.resolve(@adapter, {:title => 'Blah', :foo => [1,2,3]})
93
+ node = ApiBee::Node.resolve(@adapter, @config, {:title => 'Blah', :foo => [1,2,3]})
93
94
  node[:title].should == 'Blah'
94
95
  node.adapter.should == @adapter
95
96
  node.should be_kind_of(ApiBee::Node::Single)
96
97
  end
97
98
 
98
99
  it 'should symbolize hash keys' do
99
- node = ApiBee::Node.resolve(@adapter, {
100
+ node = ApiBee::Node.resolve(@adapter, @config, {
100
101
  'title' => 'Blah',
101
102
  'total_entries' => 4,
102
103
  'href' => '/products',
@@ -109,7 +110,7 @@ describe ApiBee do
109
110
  end
110
111
 
111
112
  it 'should resolve paginated lists' do
112
- node = ApiBee::Node.resolve(@adapter, {:title => 'Blah', :total_entries => 4, :href => '/products'})
113
+ node = ApiBee::Node.resolve(@adapter, @config, {:title => 'Blah', :total_entries => 4, :href => '/products'})
113
114
  node.total_entries.should == 4
114
115
  node.adapter.should == @adapter
115
116
  node.should be_kind_of(ApiBee::Node::List)
@@ -138,25 +139,25 @@ describe ApiBee do
138
139
  end
139
140
  end
140
141
 
141
- describe '#find_one' do
142
+ describe '#get_one' do
142
143
 
143
144
  before do
144
145
  @products = @api.get('/products', :page => 1, :per_page => 2)
145
146
  end
146
147
 
147
148
  it 'should delegate to adapter. It knows how to find individual resoruces' do
148
- @adapter.should_receive(:find_one).with('/products', 'foo-1')
149
- @products.find_one('foo-1')
149
+ @adapter.should_receive(:get_one).with('/products', 'foo-1')
150
+ @products.get_one('foo-1')
150
151
  end
151
152
 
152
153
  it 'should return a Node::Single' do
153
- node = @products.find_one('foo-1')
154
+ node = @products.get_one('foo-1')
154
155
  node.should be_kind_of(ApiBee::Node::Single)
155
156
  node[:title].should == 'Foo 1'
156
157
  end
157
158
 
158
159
  it 'should return nil if not found' do
159
- @products.find_one('foo-1000').should be_nil
160
+ @products.get_one('foo-1000').should be_nil
160
161
  end
161
162
 
162
163
  end
data/spec/setup_spec.rb CHANGED
@@ -10,23 +10,6 @@ describe 'ApiBee.setup' do
10
10
  end
11
11
  end
12
12
 
13
- describe 'config block' do
14
-
15
- it 'should have default uri_property_name field name' do
16
- ApiBee.config.uri_property_name.should == :href
17
- end
18
-
19
- it 'should set global variables' do
20
- api = ApiBee.setup(:hash, {}) do |config|
21
- config.foo = 11
22
- config.bar = 22
23
- end
24
-
25
- ApiBee.config.foo.should == 11
26
- ApiBee.config.bar.should == 22
27
- end
28
- end
29
-
30
13
  describe 'with custom adapter class' do
31
14
  before do
32
15
 
@@ -57,4 +40,96 @@ describe 'ApiBee.setup' do
57
40
  end
58
41
 
59
42
  end
43
+
44
+ context 'configuration' do
45
+
46
+ before do
47
+ @api1 = ApiBee.setup(:hash, {
48
+ :user => {
49
+ :name => 'ismael 1',
50
+ :href => '/users/ismael1'
51
+ }
52
+ })
53
+
54
+ @api2 = ApiBee.setup(:hash, {
55
+ :user => {
56
+ :name => 'ismael 2',
57
+ :url => '/users/ismael2'
58
+ }
59
+ }) do |config|
60
+ config.uri_property_name = :url
61
+ end
62
+ end
63
+
64
+ describe 'API config' do
65
+
66
+ it 'should produce a config object with default values' do
67
+ config = ApiBee.new_config
68
+ config.uri_property_name.should == :href
69
+ config.total_entries_property_name.should == :total_entries
70
+ config.entries_property_name.should == :entries
71
+ end
72
+
73
+ it 'should have default :href configured' do
74
+ user = @api1.get('/user')
75
+ user[:name].should == 'ismael 1'
76
+ @api1.adapter.should_receive(:get).with('/users/ismael1').and_return(:last_name => 'Celis')
77
+
78
+ user[:last_name].should == 'Celis'
79
+ end
80
+
81
+ it 'should overwrite config for individual apis' do
82
+ user1 = @api1.get('/user')
83
+ user1[:name].should == 'ismael 1'
84
+ @api1.adapter.should_receive(:get).with('/users/ismael1').and_return(:last_name => 'Celis 1')
85
+
86
+ user1[:last_name].should == 'Celis 1'
87
+
88
+ user2 = @api2.get('/user')
89
+ user2[:name].should == 'ismael 2'
90
+ @api2.adapter.should_receive(:get).with('/users/ismael2').and_return(:last_name => 'Celis 2')
91
+
92
+ user2[:last_name].should == 'Celis 2'
93
+ end
94
+
95
+ end
96
+
97
+ describe 'delegate to adapter' do
98
+
99
+ before do
100
+
101
+ adapter = Class.new(ApiBee::Adapters::Hash) do
102
+ def fetch(*args)
103
+ @data.fetch *args
104
+ end
105
+
106
+ def keys(*args)
107
+ @data.keys *args
108
+ end
109
+ end
110
+
111
+ @api = ApiBee.setup(adapter, {
112
+ :a => {:name => 1},
113
+ :b => {:name => 2}
114
+ }) do |config|
115
+ config.expose :fetch, :keys
116
+ end
117
+
118
+ end
119
+
120
+ it 'should still work' do
121
+ @api.get('/a').should == {:name => 1}
122
+ @api.get('/b').should == {:name => 2}
123
+ end
124
+
125
+ it 'should delegate configured methods on to adapter' do
126
+ @api.fetch(:a).should == {:name => 1}
127
+ @api.fetch(:x, 'X').should == 'X'
128
+
129
+ @api.keys.should == [:a, :b]
130
+ end
131
+
132
+ end
133
+ end
134
+
60
135
  end
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: api_bee
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 0.0.2
5
+ version: 0.0.3
6
6
  platform: ruby
7
7
  authors:
8
8
  - Ismael Celis
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2011-09-21 00:00:00 Z
13
+ date: 2011-09-22 00:00:00 Z
14
14
  dependencies: []
15
15
 
16
16
  description: API Bee is a small client / spec for a particular style of JSON API. USe Hash adapter for local data access.