restforce 1.2.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of restforce might be problematic. Click here for more details.

data/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ ## 1.3.0 (Apr 6, 2013)
2
+
3
+ * Added support for lazily traversing paginated collections #61 by @nahiluhmot.
4
+
5
+ ## 1.2.0 (Mar 30, 2013)
6
+
7
+ * Added support for proxies #60 by @wazoo.
8
+
1
9
  ## 1.1.0 (Mar 3, 2013)
2
10
 
3
11
  * Added ability to download attachments easily.
data/README.md CHANGED
@@ -395,7 +395,7 @@ end
395
395
 
396
396
  * * *
397
397
 
398
- ### Logging/Debugging
398
+ ### Logging/Debugging/Instrumenting
399
399
 
400
400
  You can easily inspect what Restforce is sending/receiving by setting
401
401
  `Restforce.log = true`.
@@ -407,16 +407,19 @@ client = Restforce.new.query('select Id, Name from Account')
407
407
 
408
408
  Another awesome feature about restforce is that, because it is based on
409
409
  Faraday, you can insert your own middleware. For example, if you were using
410
- Restforce in a rails app, you can setup custom logging using
411
- ActiveSupport::Notifications:
410
+ Restforce in a rails app, you can setup custom reporting to
411
+ [Librato](https://github.com/librato/librato-rails) using ActiveSupport::Notifications:
412
412
 
413
413
  ```ruby
414
414
  client = Restforce.new
415
- client.middleware.insert_after Restforce::Middleware::InstanceURL, FaradayMiddleware::Instrumentation
415
+ client.middleware.insert_after Restforce::Middleware::InstanceURL,
416
+ FaradayMiddleware::Instrumentation, name: 'request.salesforce'
416
417
 
417
418
  # config/initializers/notifications.rb
418
- ActiveSupport::Notifications.subscribe('request.faraday') do |name, start, finish, id, payload|
419
- Rails.logger.debug(['notification:', name, "#{(finish - start) * 1000}ms", payload[:status]].join(" "))
419
+ ActiveSupport::Notifications.subscribe('request.salesforce') do |*args|
420
+ event = ActiveSupport::Notifications::Event.new(*args)
421
+ Librato.increment 'api.salesforce.request.total'
422
+ Librato.timing 'api.salesforce.request.time', event.duration
420
423
  end
421
424
  ```
422
425
 
@@ -16,10 +16,6 @@ module Restforce
16
16
  include Restforce::Client::Canvas
17
17
  include Restforce::Client::API
18
18
 
19
- OPTIONS = [:username, :password, :security_token, :client_id, :client_secret, :host, :compress,
20
- :api_version, :oauth_token, :refresh_token, :instance_url, :cache, :authentication_retries,
21
- :timeout, :proxy_uri]
22
-
23
19
  # Public: Creates a new client instance
24
20
  #
25
21
  # opts - A hash of options to be passed in (default: {}).
@@ -76,7 +72,7 @@ module Restforce
76
72
  #
77
73
  def initialize(opts = {})
78
74
  raise 'Please specify a hash of options' unless opts.is_a?(Hash)
79
- @options = Hash[OPTIONS.map { |option| [option, Restforce.configuration.send(option)] }]
75
+ @options = Hash[Restforce.configuration.options.map { |option| [option, Restforce.configuration.send(option)] }]
80
76
  @options.merge! opts
81
77
  end
82
78
 
@@ -1,18 +1,40 @@
1
1
  module Restforce
2
- class Collection < Array
3
- attr_reader :total_size, :next_page_url
2
+ class Collection
3
+ include Enumerable
4
4
 
5
+ # Given a hash and client, will create an Enumerator that will lazily
6
+ # request Salesforce for the next page of results.
5
7
  def initialize(hash, client)
6
- @client = client
7
- @total_size = hash['totalSize']
8
- @next_page_url = hash['nextRecordsUrl']
9
- super(Restforce::Mash.build(hash['records'], @client))
8
+ @client = client
9
+ @raw_page = hash
10
10
  end
11
11
 
12
- def next_page
13
- response = @client.get next_page_url
14
- response.body
12
+ # Yield each value on each page.
13
+ def each
14
+ @raw_page['records'].each { |record| yield Restforce::Mash.build(record, @client) }
15
+
16
+ next_page.each { |record| yield record } if has_next_page?
17
+ end
18
+
19
+ # Return the size of the Collection without making any additional requests.
20
+ def size
21
+ @raw_page['totalSize']
22
+ end
23
+ alias_method :length, :size
24
+
25
+ # Return the current and all of the following pages.
26
+ def pages
27
+ [self] + (has_next_page? ? next_page.pages : [])
28
+ end
29
+
30
+ # Returns true if there is a pointer to the next page.
31
+ def has_next_page?
32
+ !@raw_page['nextRecordsUrl'].nil?
15
33
  end
16
34
 
35
+ # Returns the next page as a Restforce::Collection if it's available, nil otherwise.
36
+ def next_page
37
+ @next_page ||= @client.get(@raw_page['nextRecordsUrl']).body if has_next_page?
38
+ end
17
39
  end
18
40
  end
@@ -37,85 +37,70 @@ module Restforce
37
37
  end
38
38
 
39
39
  class Configuration
40
- attr_accessor :api_version
40
+ class << self
41
+ attr_accessor :options
42
+
43
+ def option(name, options = {})
44
+ default = options.fetch(:default, nil)
45
+ attr_accessor name
46
+ define_method name do
47
+ instance_variable_get(:"@#{name}") ||
48
+ instance_variable_set(:"@#{name}", default.respond_to?(:call) ? default.call : default)
49
+ end if default
50
+ self.options ||= []
51
+ self.options << name
52
+ end
53
+ end
54
+
55
+ option :api_version, :default => '26.0'
56
+
41
57
  # The username to use during login.
42
- attr_accessor :username
58
+ option :username, :default => lambda { ENV['SALESFORCE_USERNAME'] }
59
+
43
60
  # The password to use during login.
44
- attr_accessor :password
61
+ option :password, :default => lambda { ENV['SALESFORCE_PASSWORD'] }
62
+
45
63
  # The security token to use during login.
46
- attr_accessor :security_token
64
+ option :security_token, :default => lambda { ENV['SALESFORCE_SECURITY_TOKEN'] }
65
+
47
66
  # The OAuth client id
48
- attr_accessor :client_id
67
+ option :client_id, :default => lambda { ENV['SALESFORCE_CLIENT_ID'] }
68
+
49
69
  # The OAuth client secret
50
- attr_accessor :client_secret
70
+ option :client_secret, :default => lambda { ENV['SALESFORCE_CLIENT_SECRET'] }
71
+
51
72
  # Set this to true if you're authenticating with a Sandbox instance.
52
73
  # Defaults to false.
53
- attr_accessor :host
74
+ option :host, :default => 'login.salesforce.com'
54
75
 
55
- attr_accessor :oauth_token
56
- attr_accessor :refresh_token
57
- attr_accessor :instance_url
76
+ option :oauth_token
77
+ option :refresh_token
78
+ option :instance_url
58
79
 
59
80
  # Set this to an object that responds to read, write and fetch and all GET
60
81
  # requests will be cached.
61
- attr_accessor :cache
82
+ option :cache
62
83
 
63
84
  # The number of times reauthentication should be tried before failing.
64
- attr_accessor :authentication_retries
85
+ option :authentication_retries, :default => 3
65
86
 
66
87
  # Set to true if you want responses from Salesforce to be gzip compressed.
67
- attr_accessor :compress
88
+ option :compress
68
89
 
69
90
  # Faraday request read/open timeout.
70
- attr_accessor :timeout
91
+ option :timeout
71
92
 
72
93
  # Faraday adapter to use. Defaults to Faraday.default_adapter.
73
- attr_accessor :adapter
74
-
75
- attr_accessor :proxy_uri
76
-
77
- def api_version
78
- @api_version ||= '26.0'
79
- end
80
-
81
- def username
82
- @username ||= ENV['SALESFORCE_USERNAME']
83
- end
84
-
85
- def password
86
- @password ||= ENV['SALESFORCE_PASSWORD']
87
- end
94
+ option :adapter, :default => lambda { Faraday.default_adapter }
88
95
 
89
- def security_token
90
- @security_token ||= ENV['SALESFORCE_SECURITY_TOKEN']
91
- end
92
-
93
- def client_id
94
- @client_id ||= ENV['SALESFORCE_CLIENT_ID']
95
- end
96
-
97
- def client_secret
98
- @client_secret ||= ENV['SALESFORCE_CLIENT_SECRET']
99
- end
100
-
101
- def proxy_uri
102
- @proxy_uri ||= ENV['PROXY_URI']
103
- end
104
-
105
- def host
106
- @host ||= 'login.salesforce.com'
107
- end
108
-
109
- def authentication_retries
110
- @authentication_retries ||= 3
111
- end
112
-
113
- def adapter
114
- @adapter ||= Faraday.default_adapter
115
- end
96
+ option :proxy_uri, :default => lambda { ENV['PROXY_URI'] }
116
97
 
117
98
  def logger
118
99
  @logger ||= ::Logger.new STDOUT
119
100
  end
101
+
102
+ def options
103
+ self.class.options
104
+ end
120
105
  end
121
106
  end
@@ -1,3 +1,3 @@
1
1
  module Restforce
2
- VERSION = '1.2.0'
2
+ VERSION = '1.3.0'
3
3
  end
@@ -97,7 +97,7 @@ shared_examples_for 'methods' do
97
97
  requests 'query\?q=SELECT%20some,%20fields%20FROM%20object', :fixture => 'sobject/query_success_response'
98
98
 
99
99
  subject { client.query('SELECT some, fields FROM object') }
100
- it { should be_an Array }
100
+ it { should be_an Enumerable }
101
101
  end
102
102
 
103
103
  describe '.search' do
@@ -364,7 +364,7 @@ shared_examples_for 'methods' do
364
364
 
365
365
  let(:cache) { MockCache.new }
366
366
  subject { client.without_caching { client.query('SELECT some, fields FROM object') } }
367
- it { should be_an Array }
367
+ it { should be_an Enumerable }
368
368
  end
369
369
 
370
370
  unless RUBY_PLATFORM == 'java'
@@ -453,7 +453,7 @@ shared_examples_for 'methods' do
453
453
  end
454
454
 
455
455
  subject { client.query('SELECT some, fields FROM object') }
456
- it { should be_an Array }
456
+ it { should be_an Enumerable }
457
457
  end
458
458
  end
459
459
 
@@ -11,10 +11,9 @@ describe Restforce::Collection do
11
11
  described_class.new(JSON.parse(fixture('sobject/query_success_response')), client)
12
12
  end
13
13
 
14
- it { should respond_to :each }
15
- its(:size) { should eq 1 }
16
- its(:total_size) { should eq 1 }
17
- its(:next_page_url) { should be_nil }
14
+ it { should respond_to :each }
15
+ its(:size) { should eq 1 }
16
+ its(:has_next_page?) { should be_false }
18
17
  specify { expect(subject.instance_variable_get(:@client)).to eq client }
19
18
 
20
19
  describe 'each record' do
@@ -24,23 +23,29 @@ describe Restforce::Collection do
24
23
  end
25
24
 
26
25
  context 'with pagination' do
27
- let(:records) do
28
- described_class.new(JSON.parse(fixture('sobject/query_paginated_first_page_response')), client)
29
- end
26
+ let(:first_page) { JSON.parse(fixture('sobject/query_paginated_first_page_response')) }
27
+ let(:next_page) { JSON.parse(fixture('sobject/query_paginated_last_page_response')) }
28
+ let(:records) { described_class.new(first_page, client) }
30
29
 
31
- it { should respond_to :each }
32
- its(:size) { should eq 1 }
33
- its(:total_size) { should eq 2 }
34
- its(:next_page_url) { should eq "/services/data/v#{Restforce.configuration.api_version}/query/01gD" }
30
+ it { should respond_to :each }
35
31
  specify { expect(subject.instance_variable_get(:@client)).to eq client }
36
32
 
37
- describe '.next_page' do
33
+ context 'when only values from the first page are being requested' do
34
+ before { client.should_not_receive(:get) }
35
+
36
+ its(:size) { should eq 2 }
37
+ its(:first) { should be_an_instance_of Restforce::SObject }
38
+ end
39
+
40
+ context 'when all of the values are being requested' do
38
41
  before do
39
- client.should_receive(:get).and_return(Faraday::Response.new(:body => Restforce::Collection.new({'records' => []}, client)))
42
+ client.stub(:get).and_return(Faraday::Response.new(:body => Restforce::Collection.new(next_page, client)))
40
43
  end
41
44
 
42
- subject { records.next_page }
43
- it { should be_a Restforce::Collection }
45
+ its(:pages) { should be_all { |page| expect(page).to be_a Restforce::Collection } }
46
+ its(:has_next_page?) { should be_true }
47
+ it { should be_all { |record| expect(record).to be_a Restforce::SObject } }
48
+ specify { subject.next_page.should be_a Restforce::Collection }
44
49
  end
45
50
  end
46
51
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: restforce
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.3.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-03-30 00:00:00.000000000 Z
12
+ date: 2013-04-06 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: faraday
@@ -252,7 +252,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
252
252
  version: '0'
253
253
  segments:
254
254
  - 0
255
- hash: 1651043006991876016
255
+ hash: 2514062890750763667
256
256
  required_rubygems_version: !ruby/object:Gem::Requirement
257
257
  none: false
258
258
  requirements:
@@ -261,7 +261,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
261
261
  version: '0'
262
262
  segments:
263
263
  - 0
264
- hash: 1651043006991876016
264
+ hash: 2514062890750763667
265
265
  requirements: []
266
266
  rubyforge_project:
267
267
  rubygems_version: 1.8.23