desk_api 0.5.8 → 0.6.0

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.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +2 -2
  3. data/README.md +274 -120
  4. data/lib/desk_api.rb +2 -2
  5. data/lib/desk_api/client.rb +6 -1
  6. data/lib/desk_api/configuration.rb +34 -26
  7. data/lib/desk_api/request/encode_json.rb +11 -0
  8. data/lib/desk_api/request/oauth.rb +20 -0
  9. data/lib/desk_api/request/retry.rb +37 -42
  10. data/lib/desk_api/resource.rb +86 -22
  11. data/lib/desk_api/response/parse_dates.rb +28 -0
  12. data/lib/desk_api/response/parse_json.rb +9 -0
  13. data/lib/desk_api/response/raise_error.rb +11 -16
  14. data/lib/desk_api/version.rb +1 -1
  15. data/spec/cassettes/DeskApi_Resource/_all/iterates_over_each_resource_on_each_page.yml +1953 -0
  16. data/spec/cassettes/DeskApi_Resource/_each_page/iterates_over_each_page.yml +1953 -0
  17. data/spec/cassettes/DeskApi_Resource/_each_page/raises_NoMethodError_is_called_on_non-page_resources.yml +207 -0
  18. data/spec/cassettes/DeskApi_Resource/_each_page/uses_a_default_per_page_of_1000.yml +1953 -0
  19. data/spec/cassettes/DeskApi_Resource/_load/loads_the_resource_if_not_already_loaded.yml +205 -0
  20. data/spec/cassettes/DeskApi_Resource/_loaded_/returns_true_if_the_resource_is_loaded.yml +205 -0
  21. data/spec/cassettes/DeskApi_Resource/_next_/changes__definition_to_next_page.yml +407 -0
  22. data/spec/cassettes/DeskApi_Resource/_next_/returns_nil_on_the_last_page.yml +315 -0
  23. data/spec/cassettes/DeskApi_Resource/_request/sends_request_through_a_client_and_returns_Faraday_Response.yml +207 -0
  24. data/spec/cassettes/DeskApi_Resource/_reset_/sets__links__embedded__changed_and__loaded_to_default_values.yml +253 -0
  25. data/spec/cassettes/DeskApi_Resource/_to_hash/converts_embedded_resources_to_hashes.yml +251 -0
  26. data/spec/cassettes/DeskApi_Resource/_to_hash/returns_a_hash_for_a_desk_resource.yml +66 -0
  27. data/spec/cassettes/DeskApi_Resource/_update/can_handle_action_params.yml +427 -0
  28. data/spec/desk_api/client_spec.rb +23 -17
  29. data/spec/desk_api/configuration_spec.rb +49 -41
  30. data/spec/desk_api/default_spec.rb +3 -3
  31. data/spec/desk_api/error_spec.rb +9 -7
  32. data/spec/desk_api/rate_limit_spec.rb +1 -1
  33. data/spec/desk_api/request/encode_json_spec.rb +33 -0
  34. data/spec/desk_api/request/oauth_spec.rb +31 -0
  35. data/spec/desk_api/request/retry_spec.rb +4 -4
  36. data/spec/desk_api/resource_spec.rb +247 -71
  37. data/spec/desk_api/response/parse_dates_spec.rb +34 -0
  38. data/spec/desk_api/response/parse_json_spec.rb +34 -0
  39. data/spec/desk_api/response/raise_error_spec.rb +31 -0
  40. data/spec/desk_api_spec.rb +6 -6
  41. data/spec/spec_helper.rb +2 -2
  42. data/spec/stubs/article.json +51 -0
  43. data/spec/stubs/to_hash_embed.json +1 -0
  44. metadata +78 -28
  45. data/.coveralls.yml +0 -1
  46. data/.gitignore +0 -23
  47. data/.rspec +0 -1
  48. data/.travis.yml +0 -4
  49. data/.yardopts +0 -3
  50. data/Gemfile +0 -10
  51. data/Guardfile +0 -5
  52. data/Rakefile +0 -32
  53. data/desk_api.gemspec +0 -32
data/lib/desk_api.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  require 'uri'
2
- require 'json'
2
+ require 'faraday'
3
3
  require 'forwardable'
4
4
  require 'addressable/uri'
5
5
 
@@ -25,4 +25,4 @@ module DeskApi
25
25
  end
26
26
 
27
27
  setup
28
- end
28
+ end
@@ -34,6 +34,11 @@ class DeskApi::Client
34
34
  request(:patch, path, params)
35
35
  end
36
36
 
37
+ # allow lookup by url
38
+ def by_url(url)
39
+ DeskApi::Resource.new(self, DeskApi::Resource.build_self_link(url))
40
+ end
41
+
37
42
  private
38
43
  # If the method is missing create a resource
39
44
  def method_missing(method, params = {}, &block)
@@ -53,4 +58,4 @@ private
53
58
  def connection
54
59
  @connection ||= Faraday.new endpoint, connection_options, &middleware
55
60
  end
56
- end
61
+ end
@@ -1,9 +1,11 @@
1
- require 'faraday_middleware'
2
- require 'faraday_middleware/version'
3
- require 'faraday_middleware/response/parse_dates'
1
+ require 'faraday'
4
2
 
5
3
  require 'desk_api/default'
6
4
  require 'desk_api/request/retry'
5
+ require 'desk_api/request/oauth'
6
+ require 'desk_api/request/encode_json'
7
+ require 'desk_api/response/parse_dates'
8
+ require 'desk_api/response/parse_json'
7
9
  require 'desk_api/response/raise_error'
8
10
  require 'desk_api/error/configuration_error'
9
11
  require 'desk_api/error/client_error'
@@ -29,6 +31,24 @@ module DeskApi::Configuration
29
31
  :connection_options
30
32
  ]
31
33
  end
34
+
35
+ def included(base)
36
+ if Gem::Version.new(Faraday::VERSION) >= Gem::Version.new('0.9.0')
37
+ Faraday::Request.register_middleware desk_encode_json: DeskApi::Request::EncodeJson
38
+ Faraday::Request.register_middleware desk_oauth: DeskApi::Request::OAuth
39
+ Faraday::Request.register_middleware desk_retry: DeskApi::Request::Retry
40
+ Faraday::Response.register_middleware desk_parse_dates: DeskApi::Response::ParseDates
41
+ Faraday::Response.register_middleware desk_parse_json: DeskApi::Response::ParseJson
42
+ Faraday::Response.register_middleware desk_raise_error: DeskApi::Response::RaiseError
43
+ else
44
+ Faraday.register_middleware :request, desk_encode_json: DeskApi::Request::EncodeJson
45
+ Faraday.register_middleware :request, desk_oauth: DeskApi::Request::OAuth
46
+ Faraday.register_middleware :request, desk_retry: DeskApi::Request::Retry
47
+ Faraday.register_middleware :response, desk_parse_dates: DeskApi::Response::ParseDates
48
+ Faraday.register_middleware :response, desk_parse_json: DeskApi::Response::ParseJson
49
+ Faraday.register_middleware :response, desk_raise_error: DeskApi::Response::RaiseError
50
+ end
51
+ end
32
52
  end
33
53
 
34
54
  # if subdomain is set make sure endpoint is correct
@@ -38,28 +58,16 @@ module DeskApi::Configuration
38
58
 
39
59
  def middleware
40
60
  @middleware ||= Proc.new do |builder|
41
- if Gem::Version.new(FaradayMiddleware::VERSION) >= Gem::Version.new('0.9.0')
42
- builder.request :json
43
- builder.request :basic_authentication, @username, @password if basic_auth.values.all?
44
- builder.request :oauth, oauth if oauth.values.all?
45
- builder.request :retry
46
-
47
- builder.response :dates
48
- builder.response :raise_desk_error, DeskApi::Error::ClientError
49
- builder.response :raise_desk_error, DeskApi::Error::ServerError
50
- builder.response :json, content_type: /application\/json/
51
- else
52
- builder.use FaradayMiddleware::EncodeJson
53
- builder.use Faraday::Request::BasicAuthentication, @username, @password if basic_auth.values.all?
54
- builder.use FaradayMiddleware::OAuth, oauth if oauth.values.all?
55
- builder.use DeskApi::Request::Retry
56
-
57
- builder.use FaradayMiddleware::ParseDates
58
- builder.use DeskApi::Response::RaiseError, DeskApi::Error::ClientError
59
- builder.use DeskApi::Response::RaiseError, DeskApi::Error::ServerError
60
- builder.use FaradayMiddleware::ParseJson, content_type: /application\/json/
61
- end
62
-
61
+ builder.request :desk_encode_json
62
+ builder.request :basic_auth, @username, @password if basic_auth.values.all?
63
+ builder.request :desk_oauth, oauth if oauth.values.all?
64
+ builder.request :desk_retry
65
+
66
+ builder.response :desk_parse_dates
67
+ builder.response :desk_raise_error, DeskApi::Error::ClientError
68
+ builder.response :desk_raise_error, DeskApi::Error::ServerError
69
+ builder.response :desk_parse_json
70
+
63
71
  builder.adapter Faraday.default_adapter
64
72
  end
65
73
  end
@@ -136,4 +144,4 @@ private
136
144
  raise(DeskApi::Error::ConfigurationError, "Invalid endpoint specified: `#{endpoint}` must be a valid url.")
137
145
  end
138
146
  end
139
- end
147
+ end
@@ -0,0 +1,11 @@
1
+ module DeskApi::Request
2
+ class EncodeJson < Faraday::Middleware
3
+ dependency 'json'
4
+
5
+ def call(env)
6
+ env[:request_headers]['Content-Type'] = 'application/json'
7
+ env[:body] = ::JSON.dump(env[:body]) if env[:body] and not env[:body].to_s.empty?
8
+ @app.call env
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,20 @@
1
+ module DeskApi::Request
2
+ class OAuth < Faraday::Middleware
3
+ dependency 'simple_oauth'
4
+
5
+ def initialize(app, options)
6
+ super(app)
7
+ @options = options
8
+ end
9
+
10
+ def call(env)
11
+ env[:request_headers]['Authorization'] = oauth(env).to_s
12
+ @app.call env
13
+ end
14
+
15
+ private
16
+ def oauth(env)
17
+ SimpleOAuth::Header.new env[:method], env[:url].to_s, {}, @options
18
+ end
19
+ end
20
+ end
@@ -1,52 +1,47 @@
1
- module DeskApi
2
- module Request
3
- class Retry < Faraday::Request::Retry
4
- def initialize(app, options = {})
5
- @max = options[:max] || 3
6
- @interval = options[:interval] || 10
7
- super(app)
8
- end
1
+ module DeskApi::Request
2
+ class Retry < Faraday::Middleware
3
+ def initialize(app, options = {})
4
+ @max = options[:max] || 3
5
+ @interval = options[:interval] || 10
6
+ super(app)
7
+ end
9
8
 
10
- def call(env)
11
- retries = @retries
12
- env_clone = env.clone
13
- begin
14
- @app.call(env)
15
- rescue DeskApi::Error::TooManyRequests => e
16
- if retries > 0 and e.rate_limit.reset_in
17
- retries = 0
18
- sleep e.rate_limit.reset_in
19
- env = env_clone
20
- retry
21
- end
22
- raise
23
- rescue exception_matcher
24
- if retries > 0
25
- retries -= 1
26
- sleep @interval
27
- env = env_clone
28
- retry
29
- end
30
- raise
9
+ def call(env)
10
+ retries = @max
11
+ request_body = env[:body]
12
+ begin
13
+ env[:body] = request_body
14
+ @app.call(env)
15
+ rescue DeskApi::Error::TooManyRequests => e
16
+ if retries > 0
17
+ retries = 0
18
+ sleep e.rate_limit.reset_in
19
+ retry
31
20
  end
21
+ raise
22
+ rescue exception_matcher
23
+ if retries > 0
24
+ retries -= 1
25
+ sleep @interval
26
+ retry
27
+ end
28
+ raise
32
29
  end
30
+ end
33
31
 
34
- def exception_matcher
35
- exceptions = [Errno::ETIMEDOUT, 'Timeout::Error', Faraday::Error::TimeoutError]
36
- matcher = Module.new
37
- (class << matcher; self; end).class_eval do
38
- define_method(:===) do |error|
39
- exceptions.any? do |ex|
40
- if ex.is_a? Module then error.is_a? ex
41
- else error.class.to_s == ex.to_s
42
- end
32
+ def exception_matcher
33
+ exceptions = [Errno::ETIMEDOUT, 'Timeout::Error', Faraday::Error::TimeoutError]
34
+ matcher = Module.new
35
+ (class << matcher; self; end).class_eval do
36
+ define_method(:===) do |error|
37
+ exceptions.any? do |ex|
38
+ if ex.is_a? Module then error.is_a? ex
39
+ else error.class.to_s == ex.to_s
43
40
  end
44
41
  end
45
42
  end
46
- matcher
47
43
  end
44
+ matcher
48
45
  end
49
-
50
- Faraday.register_middleware :request, retry: lambda { Retry }
51
46
  end
52
- end
47
+ end
@@ -1,4 +1,8 @@
1
1
  class DeskApi::Resource
2
+ # by_url is deprecated on resources
3
+ extend Forwardable
4
+ def_delegator :@_client, :by_url, :by_url
5
+
2
6
  class << self
3
7
  def build_self_link(link, params = {})
4
8
  link = {'href'=>link} if link.kind_of?(String)
@@ -7,11 +11,13 @@ class DeskApi::Resource
7
11
  end
8
12
 
9
13
  def initialize(client, definition = {}, loaded = false)
10
- @_client, @_definition, @_loaded, @_changed = client, definition, loaded, {}
14
+ reset!
15
+ @_client, @_definition, @_loaded = client, definition, loaded
16
+ # better default
11
17
  end
12
18
 
13
19
  def create(params = {})
14
- self.class.new(@_client, @_client.post(clean_base_url, params).body, true)
20
+ new_resource(@_client.post(clean_base_url, params).body, true)
15
21
  end
16
22
 
17
23
  def update(params = {})
@@ -30,26 +36,51 @@ class DeskApi::Resource
30
36
  params = { q: params } if params.kind_of?(String)
31
37
  url = Addressable::URI.parse(clean_base_url + '/search')
32
38
  url.query_values = params
33
- self.class.new(@_client, self.class.build_self_link(url.to_s))
39
+ new_resource(self.class.build_self_link(url.to_s))
34
40
  end
35
41
 
36
42
  def find(id, options = {})
37
- res = self.class.new(@_client, self.class.build_self_link("#{clean_base_url}/#{id}"))
43
+ res = new_resource(self.class.build_self_link("#{clean_base_url}/#{id}"))
38
44
  res.embed(*(options[:embed].kind_of?(Array) ? options[:embed] : [options[:embed]])) if options[:embed]
39
45
  res.exec!
40
46
  end
41
47
  alias_method :by_id, :find
42
48
 
49
+ def next!
50
+ self.load
51
+ next_page = @_definition['_links']['next']
52
+
53
+ if next_page
54
+ @_definition = self.class.build_self_link(next_page)
55
+ self.reset!
56
+ end
57
+
58
+ end
59
+
60
+ def all(&block)
61
+ raise ArgumentError, "Block must be given for #all" unless block_given?
62
+ each_page do |page, page_num|
63
+ page.entries.each { |resource| yield resource, page_num }
64
+ end
65
+ end
66
+
67
+ def each_page
68
+ raise ArgumentError, "Block must be given for #each_page" unless block_given?
69
+ page = self.first.per_page(self.query_params['per_page'] || 1000).dup
70
+ begin
71
+ yield page, page.page
72
+ end while page.next!
73
+ rescue NoMethodError => err
74
+ raise NoMethodError, "#each_page and #all are only available on resources which offer pagination"
75
+ end
76
+
77
+
43
78
  def embed(*embedds)
44
79
  # make sure we don't try to embed anything that's not defined
45
80
  # add it to the query
46
81
  self.tap{ |res| res.query_params = { embed: embedds.join(',') } }
47
82
  end
48
83
 
49
- def by_url(url)
50
- self.class.new(@_client, self.class.build_self_link(url))
51
- end
52
-
53
84
  def get_self
54
85
  @_definition['_links']['self']
55
86
  end
@@ -63,6 +94,16 @@ class DeskApi::Resource
63
94
  @_definition['_links']['self']['href'] = value
64
95
  end
65
96
 
97
+ def to_hash
98
+ self.load
99
+
100
+ {}.tap do |hash|
101
+ @_definition.each do |k, v|
102
+ hash[k] = v
103
+ end
104
+ end
105
+ end
106
+
66
107
  def resource_type
67
108
  get_self['class']
68
109
  end
@@ -100,7 +141,7 @@ class DeskApi::Resource
100
141
  end
101
142
 
102
143
  def respond_to?(method, include_private = false)
103
- self.exec! unless @_loaded
144
+ self.load
104
145
  meth = method.to_s
105
146
 
106
147
  return true if is_embedded?(meth)
@@ -114,6 +155,15 @@ class DeskApi::Resource
114
155
  def reload!
115
156
  self.exec! true
116
157
  end
158
+ alias_method :load!, :reload!
159
+
160
+ def load
161
+ self.exec! unless @_loaded
162
+ end
163
+
164
+ def loaded?
165
+ @_loaded
166
+ end
117
167
 
118
168
  protected
119
169
 
@@ -127,11 +177,16 @@ protected
127
177
  self
128
178
  end
129
179
 
180
+ def reset!
181
+ @_links, @_embedded, @_changed, @_loaded = {}, {}, {}, false
182
+ self
183
+ end
184
+
130
185
  private
131
- attr_accessor :_client, :_loaded, :_changed, :_definition
186
+ attr_accessor :_client, :_loaded, :_changed, :_embedded, :_links, :_definition
132
187
 
133
188
  def filter_update_actions(params = {})
134
- params.select{ |key, _| key.to_s.include?('update_action') }
189
+ params.select{ |key, _| key.to_s.include?('_action') }
135
190
  end
136
191
 
137
192
  def is_field?(method)
@@ -151,28 +206,37 @@ private
151
206
  end
152
207
 
153
208
  def get_embedded_resource(method)
154
- embedds = @_definition['_embedded']
209
+ return @_embedded[method] if @_embedded.key?(method)
210
+ @_embedded[method] = @_definition['_embedded'][method]
155
211
 
156
- if embedds[method].kind_of?(Array) and not embedds[method].first.kind_of?(self.class)
157
- embedds[method].map!{ |definition| self.class.new(@_client, definition, true) }
158
- elsif not embedds[method].kind_of?(self.class)
159
- embedds[method] = self.class.new(@_client, embedds[method], true)
212
+ if @_embedded[method].kind_of?(Array)
213
+ @_embedded[method].tap do |ary|
214
+ ary.map!{ |definition| new_resource(definition, true) } unless ary.first.kind_of?(self.class)
215
+ end
160
216
  else
161
- embedds[method]
217
+ @_embedded[method] = new_resource(@_embedded[method], true)
162
218
  end
163
219
  end
164
220
 
165
221
  def get_linked_resource(method)
166
- links = @_definition['_links']
222
+ return @_links[method] if @_links.key?(method)
223
+ @_links[method] = @_definition['_links'][method]
224
+
225
+ if @_links[method] and not @_links[method].kind_of?(self.class)
226
+ @_links[method] = new_resource(self.class.build_self_link(@_links[method]))
227
+ end
228
+ end
167
229
 
168
- return nil if links[method].nil?
169
- return links[method] if links[method].kind_of?(self.class)
230
+ def new_resource(definition, loaded=false, client=@_client)
231
+ self.class.new(client, definition, loaded)
232
+ end
170
233
 
171
- links[method] = self.class.new(@_client, self.class.build_self_link(links[method]))
234
+ def request(method, url, params={}, client=@_client)
235
+ client.send(method, url, params)
172
236
  end
173
237
 
174
238
  def method_missing(method, *args, &block)
175
- self.exec! unless @_loaded
239
+ self.load
176
240
 
177
241
  meth = method.to_s
178
242
 
@@ -0,0 +1,28 @@
1
+ module DeskApi::Response
2
+ class ParseDates < Faraday::Response::Middleware
3
+ dependency 'time'
4
+
5
+ def on_complete(env)
6
+ env[:body] = parse_dates env[:body]
7
+ end
8
+
9
+ private
10
+
11
+ def parse_dates(value)
12
+ case value
13
+ when Hash
14
+ value.each_pair do |key, element|
15
+ value[key] = parse_dates element
16
+ end
17
+ when Array
18
+ value.each_with_index do |element, index|
19
+ value[index] = parse_dates element
20
+ end
21
+ when /\A\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z\Z/m
22
+ Time.parse value
23
+ else
24
+ value
25
+ end
26
+ end
27
+ end
28
+ end