pipeline_dealers 0.0.1
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 +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
|
+
[](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
|