desk_api 0.5.8 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
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