pipeline_dealers 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/.rspec +1 -0
- data/Gemfile +4 -0
- data/Guardfile +5 -0
- data/LICENSE.txt +22 -0
- data/README.md +64 -0
- data/Rakefile +1 -0
- data/examples/companies_create_and_destroy.rb +6 -0
- data/examples/companies_list_all.rb +9 -0
- data/examples/company_details.rb +19 -0
- data/examples/company_notes.rb +17 -0
- data/examples/company_people.rb +15 -0
- data/examples/config.rb +54 -0
- data/examples/find_person.rb +9 -0
- data/examples/list_custom_fields.rb +19 -0
- data/examples/people_create_and_destroy.rb +6 -0
- data/examples/people_list_all.rb +9 -0
- data/lib/pipeline_dealers/backend/base/backend.rb +13 -0
- data/lib/pipeline_dealers/backend/base/collection.rb +111 -0
- data/lib/pipeline_dealers/backend/base.rb +2 -0
- data/lib/pipeline_dealers/backend/http/backend.rb +64 -0
- data/lib/pipeline_dealers/backend/http/collection.rb +32 -0
- data/lib/pipeline_dealers/backend/http/connection.rb +69 -0
- data/lib/pipeline_dealers/backend/http/fetcher.rb +78 -0
- data/lib/pipeline_dealers/backend/http.rb +5 -0
- data/lib/pipeline_dealers/backend/test/backend.rb +34 -0
- data/lib/pipeline_dealers/backend/test/collection.rb +34 -0
- data/lib/pipeline_dealers/backend/test.rb +3 -0
- data/lib/pipeline_dealers/client.rb +23 -0
- data/lib/pipeline_dealers/delegator.rb +61 -0
- data/lib/pipeline_dealers/error/connection.rb +17 -0
- data/lib/pipeline_dealers/error/custom_field.rb +12 -0
- data/lib/pipeline_dealers/error/invalid_attribute.rb +29 -0
- data/lib/pipeline_dealers/error.rb +12 -0
- data/lib/pipeline_dealers/limits.rb +7 -0
- data/lib/pipeline_dealers/model/company/custom_field.rb +9 -0
- data/lib/pipeline_dealers/model/company.rb +46 -0
- data/lib/pipeline_dealers/model/custom_field.rb +94 -0
- data/lib/pipeline_dealers/model/has_custom_fields.rb +62 -0
- data/lib/pipeline_dealers/model/note.rb +37 -0
- data/lib/pipeline_dealers/model/person/custom_field.rb +9 -0
- data/lib/pipeline_dealers/model/person.rb +63 -0
- data/lib/pipeline_dealers/model.rb +158 -0
- data/lib/pipeline_dealers/test.rb +10 -0
- data/lib/pipeline_dealers/version.rb +3 -0
- data/lib/pipeline_dealers.rb +24 -0
- data/pipeline_dealers.gemspec +39 -0
- data/spec/acceptance/companies/creation_spec.rb +30 -0
- data/spec/acceptance/companies/custom_fields_spec.rb +8 -0
- data/spec/acceptance/companies/updating_spec.rb +36 -0
- data/spec/acceptance/people/creation_spec.rb +20 -0
- data/spec/pipeline_dealers/backend/http/backend_spec.rb +48 -0
- data/spec/pipeline_dealers/backend/http/collection_spec.rb +158 -0
- data/spec/pipeline_dealers/model/custom_field_spec.rb +129 -0
- data/spec/pipeline_dealers/model/has_custom_fields_spec.rb +115 -0
- data/spec/pipeline_dealers/model_spec.rb +33 -0
- data/spec/pipeline_dealers/test_client_spec.rb +80 -0
- data/spec/support/test_model.rb +6 -0
- data/todo.md +13 -0
- metadata +291 -0
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--colour
|
data/Gemfile
ADDED
data/Guardfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Maarten Hoogendoorn
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
# Pipelinedealers
|
2
|
+
|
3
|
+
API client for pipelinedeals.com
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add it to your application's Gemfile:
|
8
|
+
|
9
|
+
```gem 'pipeline_dealers'```
|
10
|
+
|
11
|
+
## Usage
|
12
|
+
|
13
|
+
The client's API is modelled after ActiveRecord.
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
require 'pipeline_dealers'
|
17
|
+
|
18
|
+
client = Pipelinedealers::Client.new(api_key: "z3kr3tp@zzw0rd")
|
19
|
+
|
20
|
+
# Get all companies
|
21
|
+
client.companies.all.each do |company|
|
22
|
+
puts company.name
|
23
|
+
end
|
24
|
+
|
25
|
+
# Find company by ID
|
26
|
+
my_company = client.companies.find(id)
|
27
|
+
|
28
|
+
my_company.name = "foobar"
|
29
|
+
my_company.save
|
30
|
+
```
|
31
|
+
|
32
|
+
For more details and how to use people and notes, see [the examples](examples/)
|
33
|
+
|
34
|
+
## Test your application's usage of pipeline\_dealers.
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
require 'pipeline_dealers'
|
38
|
+
require 'pipeline_dealers/test'
|
39
|
+
|
40
|
+
describe "MyClass" do
|
41
|
+
let(:client) { Pipelinedealers::TestClient.new }
|
42
|
+
|
43
|
+
it "should create a company" do
|
44
|
+
expect do
|
45
|
+
client.companies.create(name: "AwesomeCompany")
|
46
|
+
end.to change(client.companies, :length).from(0).to(1)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
```
|
50
|
+
*Note:* Be sure to stub the client in your code. This only works if you use the same client reference in both the specs and your application.
|
51
|
+
|
52
|
+
## Contributing
|
53
|
+
|
54
|
+
1. Fork it
|
55
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
56
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
57
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
58
|
+
5. Create new Pull Request
|
59
|
+
|
60
|
+
## Thanks to
|
61
|
+
|
62
|
+
The awesome people at
|
63
|
+
|
64
|
+
[![Springest](http://static-1.cdnhub.nl/images/logo-springest.jpg "Logo springest.com")](http://www.springest.com/about-springest)
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,19 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require_relative "config"
|
3
|
+
|
4
|
+
client = PipelineDealers::Client.new(api_key: YOUR_API_KEY)
|
5
|
+
|
6
|
+
id = ask_id("Enter the ID of the company")
|
7
|
+
|
8
|
+
company = client.companies.find(id)
|
9
|
+
|
10
|
+
attributes = PipelineDealers::Model::Company.attributes.collect do |attribute, options|
|
11
|
+
[attribute, options, company.attributes[attribute]]
|
12
|
+
end
|
13
|
+
|
14
|
+
print_table ["Name", "Options", "Value"], attributes
|
15
|
+
|
16
|
+
puts
|
17
|
+
|
18
|
+
puts "Custom fields of this company:"
|
19
|
+
print_table ["Name", "Value"], company.custom_fields.collect { |k,v| [k,v] }
|
@@ -0,0 +1,17 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require_relative "config"
|
3
|
+
|
4
|
+
client = PipelineDealers::Client.new(api_key: YOUR_API_KEY)
|
5
|
+
|
6
|
+
client.companies.each do |company|
|
7
|
+
puts "Company: " + company.name
|
8
|
+
puts "Notes:"
|
9
|
+
|
10
|
+
company.notes.each do |note|
|
11
|
+
puts "Title : #{note.title}"
|
12
|
+
puts " - Person : #{note.person.full_name}" if note.person_id != nil
|
13
|
+
puts " - Content: #{note.content.gsub("\n","\n ")}"
|
14
|
+
end
|
15
|
+
|
16
|
+
puts
|
17
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require_relative "config"
|
3
|
+
|
4
|
+
client = PipelineDealers::Client.new(api_key: YOUR_API_KEY)
|
5
|
+
|
6
|
+
client.companies.each do |company|
|
7
|
+
puts "Company: " + company.name
|
8
|
+
puts "People:"
|
9
|
+
|
10
|
+
company.people.each do |person|
|
11
|
+
puts " - " + person.full_name
|
12
|
+
end
|
13
|
+
|
14
|
+
puts
|
15
|
+
end
|
data/examples/config.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "bundler"
|
3
|
+
|
4
|
+
Bundler.setup
|
5
|
+
|
6
|
+
require "pipeline_dealers"
|
7
|
+
|
8
|
+
YOUR_API_KEY = ENV["API_KEY"]
|
9
|
+
|
10
|
+
if YOUR_API_KEY.nil?
|
11
|
+
puts "Run these examples like: "
|
12
|
+
puts " $ API_KEY=<YOUR_API_KEY> bundle exec ./<EXAMPLE>.rb"
|
13
|
+
exit
|
14
|
+
end
|
15
|
+
|
16
|
+
def ask_id question
|
17
|
+
puts question
|
18
|
+
while true
|
19
|
+
id = gets.chomp.strip.to_i
|
20
|
+
if id < 1
|
21
|
+
puts "Invalid id!"
|
22
|
+
puts question
|
23
|
+
else
|
24
|
+
return id
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def print_table header, rows
|
30
|
+
rows = [header] +rows.map { |row| row.map { |item| item.inspect} }
|
31
|
+
maximums = {}
|
32
|
+
rows.each do |row|
|
33
|
+
row.each_with_index do |val, idx|
|
34
|
+
maximums[idx] = 0 if maximums[idx].nil?
|
35
|
+
|
36
|
+
length = val.length
|
37
|
+
maximums[idx] = length if maximums[idx] < length
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
format = "|"
|
42
|
+
separator="+"
|
43
|
+
maximums.keys.sort.each do |idx|
|
44
|
+
format += " %-#{maximums[idx]}s |"
|
45
|
+
separator += "-#{'-'*maximums[idx] }-+"
|
46
|
+
end
|
47
|
+
|
48
|
+
header = rows.shift
|
49
|
+
puts separator
|
50
|
+
puts(format % header)
|
51
|
+
puts separator
|
52
|
+
rows.each { |row| puts(format % row) }
|
53
|
+
puts separator
|
54
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require_relative "config"
|
3
|
+
|
4
|
+
client = PipelineDealers::Client.new(api_key: YOUR_API_KEY)
|
5
|
+
|
6
|
+
puts "Custom fields for companies:"
|
7
|
+
custom_fields = client.companies.custom_fields.all.collect do |field|
|
8
|
+
[field.name, field.is_required, field.field_type]
|
9
|
+
end
|
10
|
+
print_table ["Name", "Required?", "Field type"], custom_fields
|
11
|
+
|
12
|
+
puts
|
13
|
+
|
14
|
+
puts "Custom fields for people:"
|
15
|
+
custom_fields = client.people.custom_fields.all.collect do |field|
|
16
|
+
[field.name, field.is_required, field.field_type]
|
17
|
+
end
|
18
|
+
print_table ["Name", "Required?", "Field type"], custom_fields
|
19
|
+
|
@@ -0,0 +1,111 @@
|
|
1
|
+
module PipelineDealers
|
2
|
+
module Backend
|
3
|
+
class Base
|
4
|
+
class Collection
|
5
|
+
extend Delegator
|
6
|
+
|
7
|
+
Identity = lambda { |x| x }
|
8
|
+
|
9
|
+
delegate :connection, to: :backend
|
10
|
+
delegate :collection_url, to: :model_klass
|
11
|
+
|
12
|
+
attr_reader :model_klass, :backend, :client
|
13
|
+
|
14
|
+
def initialize(backend, options)
|
15
|
+
@options = options
|
16
|
+
@backend = backend
|
17
|
+
@client = options[:client] || raise("No client given")
|
18
|
+
@model_klass = options[:model_klass] || raise("No :model_klass given")
|
19
|
+
end
|
20
|
+
|
21
|
+
def custom_fields
|
22
|
+
if @options[:custom_fields]
|
23
|
+
@custom_fields ||= @backend.collection(@options[:custom_fields].merge(client: @client))
|
24
|
+
else
|
25
|
+
raise "Collection of #{@model_klass} doesn not have any custom fields"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def all
|
30
|
+
if use_cache?
|
31
|
+
@backend.cache(@options[:cache_key]) { self.to_a }
|
32
|
+
else
|
33
|
+
self
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def select &block
|
38
|
+
results = []
|
39
|
+
|
40
|
+
self.each do |result|
|
41
|
+
results << result if block.call(result)
|
42
|
+
end
|
43
|
+
|
44
|
+
results
|
45
|
+
end
|
46
|
+
|
47
|
+
def collect &operation
|
48
|
+
results = []
|
49
|
+
|
50
|
+
self.each do
|
51
|
+
end
|
52
|
+
|
53
|
+
results
|
54
|
+
end
|
55
|
+
|
56
|
+
def where(condition)
|
57
|
+
refine(where: condition)
|
58
|
+
end
|
59
|
+
|
60
|
+
def limit(to)
|
61
|
+
refine(limit: to)
|
62
|
+
end
|
63
|
+
|
64
|
+
def first
|
65
|
+
limit(1).collect { |x| return x }
|
66
|
+
end
|
67
|
+
|
68
|
+
def to_a
|
69
|
+
collect &Identity
|
70
|
+
end
|
71
|
+
|
72
|
+
def count
|
73
|
+
@backend.count(self)
|
74
|
+
end
|
75
|
+
|
76
|
+
def new(attributes = {})
|
77
|
+
if @options[:new_defaults]
|
78
|
+
attributes = @options[:new_defaults].merge(attributes)
|
79
|
+
end
|
80
|
+
|
81
|
+
model_klass.new(collection: self, record_new: true, attributes: attributes)
|
82
|
+
end
|
83
|
+
|
84
|
+
def create(attributes = {})
|
85
|
+
save(new(attributes))
|
86
|
+
end
|
87
|
+
|
88
|
+
def save(model)
|
89
|
+
@backend.save(self, model)
|
90
|
+
model
|
91
|
+
end
|
92
|
+
|
93
|
+
def destroy(model)
|
94
|
+
@backend.destroy(self, model)
|
95
|
+
model
|
96
|
+
end
|
97
|
+
|
98
|
+
protected
|
99
|
+
|
100
|
+
# Refine the criterea
|
101
|
+
def refine(refinements)
|
102
|
+
self.class.new(@backend, @options.merge(refinements))
|
103
|
+
end
|
104
|
+
|
105
|
+
def use_cache?
|
106
|
+
@options[:cached] == true
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module PipelineDealers
|
2
|
+
module Backend
|
3
|
+
class Http < Base
|
4
|
+
attr_reader :connection
|
5
|
+
|
6
|
+
def initialize(options)
|
7
|
+
@cache = {}
|
8
|
+
@options = options
|
9
|
+
@connection = Connection.new(@options)
|
10
|
+
end
|
11
|
+
|
12
|
+
def collection(options)
|
13
|
+
Collection.new(self, options)
|
14
|
+
end
|
15
|
+
|
16
|
+
def save(collection, model)
|
17
|
+
model_attr_name = model.class.attribute_name
|
18
|
+
|
19
|
+
if model_attr_name.nil?
|
20
|
+
raise "Model #{model.class} doesn't have a attribute_name field"
|
21
|
+
end
|
22
|
+
|
23
|
+
# Use :post for new records, :put for existing ones
|
24
|
+
method = model.new_record? ? :post : :put
|
25
|
+
|
26
|
+
status, response = @connection.send(method, model_url(collection, model), model_attr_name => model.save_attrs)
|
27
|
+
|
28
|
+
case status
|
29
|
+
when 200 then
|
30
|
+
model.send(:import_attributes!, response) # Update with server side copy
|
31
|
+
model.send(:instance_variable_set, :@persisted, true) # Mark as persisted
|
32
|
+
when 422 then handle_errors_during_save(response)
|
33
|
+
else; raise "Unexepcted response status #{status.inspect}"
|
34
|
+
end
|
35
|
+
|
36
|
+
self
|
37
|
+
end
|
38
|
+
|
39
|
+
def destroy(collection, model)
|
40
|
+
state, response = @connection.delete(model_url(collection, model), {})
|
41
|
+
if (state == 200)
|
42
|
+
model.send(:instance_variable_set, :@persisted, false) # Mark as persisted
|
43
|
+
model
|
44
|
+
else
|
45
|
+
raise "Could not remove"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def model_url(collection, model)
|
50
|
+
if model.persisted?
|
51
|
+
collection.send(:collection_url) + "/" + model.id.to_s + ".json"
|
52
|
+
else
|
53
|
+
collection.send(:collection_url) + ".json"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
protected
|
58
|
+
|
59
|
+
def handle_errors_during_save(response)
|
60
|
+
raise response.collect { |error| "field '#{error["field"]}' #{error["msg"]}" }.join(" and ").capitalize
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module PipelineDealers
|
2
|
+
module Backend
|
3
|
+
class Http
|
4
|
+
class Collection < Backend::Base::Collection
|
5
|
+
def find id
|
6
|
+
status, result = @backend.connection.get(collection_url + "/#{id}.json", {})
|
7
|
+
if status == 200
|
8
|
+
model_klass.new(collection: self, persisted: true, attributes: result)
|
9
|
+
else
|
10
|
+
raise Error::NotFound
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def each &operation
|
15
|
+
Fetcher.new(@backend.connection, self, model_klass, @options).each do |result|
|
16
|
+
operation.call(result)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def collect &operation
|
21
|
+
results = []
|
22
|
+
|
23
|
+
Fetcher.new(@backend.connection, self, model_klass, @options).each do |result|
|
24
|
+
results << operation.call(result)
|
25
|
+
end
|
26
|
+
|
27
|
+
results
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require "faraday"
|
2
|
+
|
3
|
+
module PipelineDealers
|
4
|
+
module Backend
|
5
|
+
class Http
|
6
|
+
class Connection
|
7
|
+
ENDPOINT = "https://api.pipelinedeals.com/api/v3/"
|
8
|
+
|
9
|
+
def initialize(options)
|
10
|
+
@key = options.delete(:api_key)
|
11
|
+
@options = options
|
12
|
+
@http = build_connection
|
13
|
+
end
|
14
|
+
|
15
|
+
def build_connection
|
16
|
+
Faraday.new(url: ENDPOINT) do |faraday|
|
17
|
+
faraday.request :url_encoded
|
18
|
+
faraday.response :logger if @options[:http_debug]
|
19
|
+
faraday.adapter Faraday.default_adapter
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# HTTP methods
|
24
|
+
{get: :get_style, post: :post_style, put: :post_style, delete: :get_style}.each do |name, style|
|
25
|
+
class_eval <<-RUBY
|
26
|
+
def #{name} url, params = {}
|
27
|
+
request_#{style}(:#{name}, url, params)
|
28
|
+
end
|
29
|
+
RUBY
|
30
|
+
end
|
31
|
+
|
32
|
+
protected
|
33
|
+
|
34
|
+
# get / delete
|
35
|
+
def request_get_style method, request_url, params
|
36
|
+
request_params = params.merge(api_key: @key)
|
37
|
+
query = Faraday::Utils.build_nested_query(request_params)
|
38
|
+
server_response = @http.send(method, request_url + "?" + query)
|
39
|
+
|
40
|
+
# Bwelch. Only a delete doesn't return valid json :(
|
41
|
+
raw = (method == :delete)
|
42
|
+
|
43
|
+
build_response_from server_response, raw: raw
|
44
|
+
end
|
45
|
+
|
46
|
+
# post / put
|
47
|
+
def request_post_style method, request_url, params
|
48
|
+
request_url += "?api_key=#{@key}"
|
49
|
+
request_body = Faraday::Utils.build_nested_query(params)
|
50
|
+
build_response_from @http.send(method, request_url, request_body)
|
51
|
+
end
|
52
|
+
|
53
|
+
DEFAULT_REQUEST_OPTIONS = { raw: false }
|
54
|
+
|
55
|
+
def build_response_from response, options = DEFAULT_REQUEST_OPTIONS
|
56
|
+
if options[:raw]
|
57
|
+
return [response.status, JSON.parse('{ "msg": "' + response.body + '" }')]
|
58
|
+
else
|
59
|
+
return [response.status, JSON.parse(response.body)]
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def debug?
|
64
|
+
ENV['DEBUG'] == "true"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module PipelineDealers
|
2
|
+
module Backend
|
3
|
+
class Http
|
4
|
+
class Fetcher
|
5
|
+
attr_reader :options
|
6
|
+
|
7
|
+
# This struct captures the state we need to implement an transparent API
|
8
|
+
Cursor = Struct.new(:params, :results_yielded, :done)
|
9
|
+
|
10
|
+
def initialize(connection, collection, model_klass, options)
|
11
|
+
@connection = connection
|
12
|
+
@collection = collection
|
13
|
+
@model_klass = model_klass
|
14
|
+
@options = options
|
15
|
+
end
|
16
|
+
|
17
|
+
def each(&block)
|
18
|
+
cursor = Cursor.new(build_params, 0, false)
|
19
|
+
if @collection.collection_url.nil?
|
20
|
+
raise "Model #{@model_klass.to_s} doesn't have 'collection_url' value"
|
21
|
+
end
|
22
|
+
|
23
|
+
while not cursor.done
|
24
|
+
status, result = @connection.get(@collection.collection_url + ".json", cursor.params)
|
25
|
+
|
26
|
+
if status == 200
|
27
|
+
yield_results(cursor, result, &block)
|
28
|
+
else
|
29
|
+
raise "Unexpected status! #{status.inspect}. Expected 200"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def build_params
|
35
|
+
params = { page: 1, per_page: Limits::MAX_RESULTS_PER_PAGE }
|
36
|
+
|
37
|
+
# Improve efficiency by only fetching as much as we need.
|
38
|
+
if requesting_to_many_results?
|
39
|
+
params[:per_page] = @options[:limit]
|
40
|
+
end
|
41
|
+
|
42
|
+
# Additional condition for filtering
|
43
|
+
if @options.has_key?(:where)
|
44
|
+
params.merge!(@options[:where])
|
45
|
+
end
|
46
|
+
|
47
|
+
params
|
48
|
+
end
|
49
|
+
|
50
|
+
def requesting_to_many_results?
|
51
|
+
@options[:limit] && @options[:limit] < Limits::MAX_RESULTS_PER_PAGE
|
52
|
+
end
|
53
|
+
|
54
|
+
def yield_results cursor, result, &block
|
55
|
+
result["entries"].each do |entry|
|
56
|
+
block.call(@model_klass.new(client: @collection.client, collection: @collection, persisted: true, attributes: entry))
|
57
|
+
cursor.results_yielded += 1
|
58
|
+
|
59
|
+
# Have we reached the limit?
|
60
|
+
if options[:limit] && cursor.results_yielded >= options[:limit]
|
61
|
+
cursor.done = true
|
62
|
+
return
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Are we on the last page?
|
67
|
+
if result["pagination"]["page"] < result["pagination"]["pages"]
|
68
|
+
# No, go to next page
|
69
|
+
cursor.params[:page] += 1
|
70
|
+
else
|
71
|
+
# Yes, we're done
|
72
|
+
cursor.done = true
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|