desk_api 0.1.2 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (30) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +24 -1
  3. data/lib/desk_api/action/embeddable.rb +47 -0
  4. data/lib/desk_api/action/update.rb +5 -0
  5. data/lib/desk_api/error/not_embeddable.rb +8 -0
  6. data/lib/desk_api/error/not_updateable.rb +10 -0
  7. data/lib/desk_api/error/unprocessable_entity.rb +1 -0
  8. data/lib/desk_api/error.rb +7 -9
  9. data/lib/desk_api/resource/case.rb +2 -0
  10. data/lib/desk_api/resource/filter.rb +7 -0
  11. data/lib/desk_api/resource/inbound_mailbox.rb +7 -0
  12. data/lib/desk_api/resource/job.rb +2 -0
  13. data/lib/desk_api/resource/page.rb +10 -27
  14. data/lib/desk_api/resource/reply.rb +2 -0
  15. data/lib/desk_api/resource.rb +28 -3
  16. data/lib/desk_api/version.rb +1 -1
  17. data/spec/cassettes/DeskApi_Error/on_validation_error/allows_access_to_error_hash.yml +54 -0
  18. data/spec/cassettes/DeskApi_Resource_Case/once_closed/can_not_be_updated.yml +124 -0
  19. data/spec/cassettes/DeskApi_Resource_Page/{_by_id/loads_the_requested_resource.yml → _find/has_an_alias_by_id.yml} +6 -6
  20. data/spec/cassettes/DeskApi_Resource_Page/_find/loads_the_requested_resource.yml +54 -0
  21. data/spec/desk_api/error_spec.rb +11 -0
  22. data/spec/desk_api/request/retry_spec.rb +9 -3
  23. data/spec/desk_api/resource/case_spec.rb +28 -0
  24. data/spec/desk_api/{resources → resource}/page_spec.rb +9 -5
  25. data/spec/desk_api/resource_spec.rb +68 -0
  26. data/spec/spec_helper.rb +1 -0
  27. data/spec/stubs/case_embed_customer.json +115 -0
  28. data/spec/stubs/cases_embed_assigned_user.json +105 -0
  29. metadata +23 -7
  30. data/lib/desk_api/action/embedded.rb +0 -12
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: bd267e380566167aae73f9d0b26d8d84e72b1749
4
- data.tar.gz: deaee7be024ea5238866cb2e8fab1e8e579cbe25
3
+ metadata.gz: e26e977b6cb1ed65a5dee9ccd93a3f4617fb71e0
4
+ data.tar.gz: 46a9845c1399f0a4644cde8f614c9ea38bcfc1e1
5
5
  SHA512:
6
- metadata.gz: 0319000b143117a540561e855ee3f03adcb46a85db1ff87567d0702dd0cc9a7ac9978bca858a642e677ca39f22d8d60dc513f7c452b8a3b4788725db431f7a09
7
- data.tar.gz: abde08faf56067f3fbea77028ef954dd04cf8f6dbcab698d6deb07fe13711570f0742f07dfa9d60a716d611bf0a7bd040e84f1ad973f3fe98db6e6f38bfc8821
6
+ metadata.gz: 3c2e40fa8dbff7ac13c88e43233c155123efe5d4cd4c87c173e6b4428f725d76e4da01d49481c9d7ddfc6039f2aa25ad328da34d8c3ba19a664bed74c35309d5
7
+ data.tar.gz: 146cccebb60d61ce7c779a9fe35fe0f26284922270ffe97efbbe4f6c927db15d1bd54fc00844187a8d7ebfad4a0647f6c44ca068c93899c736899868a9a6fa50
data/README.md CHANGED
@@ -54,6 +54,8 @@ The API supports RESTful resources and so does this wrapper. Those resources are
54
54
  found_case = DeskApi.users.first.by_url '/api/v2/cases/1'
55
55
 
56
56
  # find a case by case number
57
+ found_case = DeskApi.cases.find 1
58
+ # the old #by_id still works
57
59
  found_case = DeskApi.cases.by_id 1
58
60
  ```
59
61
 
@@ -108,6 +110,27 @@ end
108
110
  DeskApi.cases.page == 1
109
111
  ```
110
112
 
113
+ ### Side loading
114
+
115
+ APIv2 has a lot of great new features but the one I'm most excited about is side loading or embedding resources. You basically request one resource and tell the API to embed sub resources, eg. you need cases but also want to have the `assigned_user` - instead of requesting all cases and the `assigned_user` for each of those cases (30 cases = 31 API requests) you can now embed `assigned_user` into your cases list view (1 API REQUEST!!!!).
116
+
117
+ Of course we had to bring this awesomeness into the API wrapper as soon as possible, so here you go:
118
+
119
+ ```ruby
120
+ # fetch cases with their respective customers
121
+ cases = DeskApi.cases.embed(:customer)
122
+ customer = cases.first.customer
123
+
124
+ # you can use this feature in finders too
125
+ my_case = DeskApi.cases.find(1, embed: :customer)
126
+ # OR
127
+ my_case = DeskApi.cases.find(1, embed: [:customer, :assigned_user, :assigned_group])
128
+
129
+ customer = my_case.customer
130
+ assigned_user = my_case.assigned_user
131
+ assigned_group = my_case.assigned_group
132
+ ```
133
+
111
134
  ### Create, Update and Delete
112
135
 
113
136
  Of course we support creating, updating and deleting resources but not all resources can be deleted or updated or created, if that's the case for the resource you're trying to update, it'll throw a `DeskApi::Error::MethodNotSupported` error. The specific method won't be defined on the resource either `DeskApi.cases.first.respond_to?(:delete) == false`.
@@ -191,7 +214,7 @@ Please also have a look at all [desk.com API errors](http://dev.desk.com/API/usi
191
214
 
192
215
  (The MIT License)
193
216
 
194
- Copyright (c) 2013 Thomas Stachl <tom@desk.com>
217
+ Copyright (c) 2013 Thomas Stachl <thomas@desk.com>
195
218
 
196
219
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
197
220
 
@@ -0,0 +1,47 @@
1
+ require 'desk_api/error/not_embeddable'
2
+
3
+ module DeskApi
4
+ module Action
5
+ module Embeddable
6
+ attr_reader :embedded
7
+
8
+ def embed(*embedds)
9
+ # make sure we don't try to embed anything that's not defined
10
+ # add it to the query
11
+ self.query_params = { embed: embedds.each{ |embed|
12
+ unless self.base_class.embeddable?(embed)
13
+ raise DeskApi::Error::NotEmbeddable.new("`#{embed.to_s}' can not be embedded.")
14
+ end
15
+ }.join(',') }
16
+ # return self
17
+ self
18
+ end
19
+
20
+ def setup_embedded(embedds)
21
+ if embedds.entries?
22
+ @records = embedds['entries'].map do |record|
23
+ resource(record._links.self['class']).new(client, record, true)
24
+ end
25
+ else
26
+ embedds.each_pair do |key, definition|
27
+ @_links[key]['resource'] = resource(definition['class']).new @client, definition, true
28
+ end
29
+ end
30
+ end
31
+
32
+ module ClassMethods
33
+ def embeddable(*embeddables)
34
+ @embeddables = embeddables
35
+ end
36
+
37
+ def embeddable?(key)
38
+ (@embeddables || []).include?(key.to_sym)
39
+ end
40
+ end
41
+
42
+ def self.included(base)
43
+ base.extend(ClassMethods)
44
+ end
45
+ end
46
+ end
47
+ end
@@ -2,9 +2,14 @@ module DeskApi
2
2
  module Action
3
3
  module Update
4
4
  def update(params = {})
5
+ if kind_of?(resource('case')) and status == 'closed'
6
+ raise DeskApi::Error::NotUpdateable
7
+ end
8
+
5
9
  params.each_pair do |key, value|
6
10
  send("#{key}=", value) if respond_to?("#{key}=")
7
11
  end
12
+
8
13
  setup(client.patch(@_links.self.href, @_changed).body)
9
14
  end
10
15
  end
@@ -0,0 +1,8 @@
1
+ require 'desk_api/error'
2
+
3
+ module DeskApi
4
+ class Error
5
+ class NotEmbeddable < DeskApi::Error
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,10 @@
1
+ require 'desk_api/error'
2
+
3
+ module DeskApi
4
+ class Error
5
+ # Raised when an updateable resources becomes non-updateable
6
+ # and you try to update it
7
+ class NotUpdateable < DeskApi::Error
8
+ end
9
+ end
10
+ end
@@ -5,6 +5,7 @@ module DeskApi
5
5
  # Raised when desk.com returns the HTTP status code 422
6
6
  class UnprocessableEntity < DeskApi::Error::ClientError
7
7
  HTTP_STATUS_CODE = 422
8
+ attr_reader :errors
8
9
  end
9
10
  end
10
11
  end
@@ -11,10 +11,9 @@ module DeskApi
11
11
  # @param response_headers [Hash]
12
12
  # @param code [Integer]
13
13
  # @return [DeskApi::Error]
14
- def initialize(exception=$!, response_headers={}, code = nil)
14
+ def initialize(exception=$!, response_headers={}, code = nil, err_hash = nil)
15
15
  @rate_limit = DeskApi::RateLimit.new(response_headers)
16
- @wrapped_exception = exception
17
- @code = code
16
+ @wrapped_exception, @code, @errors = exception, code, err_hash
18
17
  exception.respond_to?(:backtrace) ? super(exception.message) : super(exception.to_s)
19
18
  end
20
19
 
@@ -28,8 +27,8 @@ module DeskApi
28
27
  # @param response [Hash]
29
28
  # @return [DeskApi::Error]
30
29
  def from_response(response = {})
31
- error, code = parse_error(response[:body]), response[:status]
32
- new(error, response[:response_headers], code)
30
+ err_hash, error, code = parse_body(response[:body]).push(response[:status])
31
+ new(error, response[:response_headers], code, err_hash)
33
32
  end
34
33
 
35
34
  # @return [Hash]
@@ -51,11 +50,10 @@ module DeskApi
51
50
 
52
51
  private
53
52
 
54
- def parse_error(body)
55
- if body['message']
56
- body['message']
57
- end
53
+ def parse_body(body = {})
54
+ [body['errors'] || nil, body['message'] || nil]
58
55
  end
56
+
59
57
  end
60
58
  end
61
59
  end
@@ -4,6 +4,8 @@ module DeskApi
4
4
  include DeskApi::Action::Create
5
5
  include DeskApi::Action::Update
6
6
  include DeskApi::Action::Search
7
+
8
+ embeddable :customer, :assigned_user, :assigned_group, :locked_by
7
9
  end
8
10
  end
9
11
  end
@@ -0,0 +1,7 @@
1
+ module DeskApi
2
+ class Resource
3
+ class Filter < DeskApi::Resource
4
+ embeddable :user, :group
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module DeskApi
2
+ class Resource
3
+ class InboundMailbox < DeskApi::Resource
4
+ embeddable :default_group
5
+ end
6
+ end
7
+ end
@@ -2,6 +2,8 @@ module DeskApi
2
2
  class Resource
3
3
  class Job < DeskApi::Resource
4
4
  include DeskApi::Action::Create
5
+
6
+ embeddable :user
5
7
  end
6
8
  end
7
9
  end
@@ -2,7 +2,6 @@ module DeskApi
2
2
  class Resource
3
3
  class Page < DeskApi::Resource
4
4
  include Enumerable
5
- include DeskApi::Action::Embedded
6
5
 
7
6
  [
8
7
  :all?, :any?,
@@ -31,44 +30,28 @@ module DeskApi
31
30
  [:page, :per_page].each do |method|
32
31
  define_method(method) do |value = nil|
33
32
  if not value
34
- self.exec! if self.query_params(method.to_s) == nil
35
- return self.query_params(method.to_s).to_i
33
+ self.exec! if self.query_params_include?(method.to_s) == nil
34
+ return self.query_params_include?(method.to_s).to_i
36
35
  end
37
36
  self.query_params = Hash[method.to_s, value.to_s]
38
37
  self
39
38
  end
40
39
  end
41
40
 
42
- def by_id(id)
43
- by_url("#{clean_base_url}/#{id}")
41
+ def find(id, options = {})
42
+ res = base_class.new(client, Hashie::Mash.new({ _links: { self: { href: "#{clean_base_url}/#{id}" }}}))
43
+ if options[:embed]
44
+ options[:embed] = [options[:embed]] unless options[:embed].kind_of?(Array)
45
+ res.embed(*options[:embed])
46
+ end
47
+ res.exec!
44
48
  end
49
+ alias_method :by_id, :find
45
50
 
46
51
  protected
47
52
 
48
53
  attr_reader :records
49
54
 
50
- def setup(definition)
51
- setup_embedded(definition._embedded['entries']) if definition._embedded?
52
- super(definition)
53
- end
54
-
55
- def query_params(param)
56
- params = Addressable::URI.parse(@_links.self.href).query_values || {}
57
- params.include?(param) ? params[param] : nil
58
- end
59
-
60
- def query_params=(params = {})
61
- return @_links.self.href if params.empty?
62
-
63
- uri = Addressable::URI.parse(@_links.self.href)
64
- params = (uri.query_values || {}).merge(params)
65
-
66
- @loaded = false unless params == uri.query_values
67
-
68
- uri.query_values = params
69
- @_links.self.href = uri.to_s
70
- end
71
-
72
55
  def clean_base_url
73
56
  Addressable::URI.parse(@_links.self.href).path.gsub(/\/search$/, '')
74
57
  end
@@ -3,6 +3,8 @@ module DeskApi
3
3
  class Reply < DeskApi::Resource
4
4
  include DeskApi::Action::Create
5
5
  include DeskApi::Action::Update
6
+
7
+ embeddable :case, :sent_by, :entered_by
6
8
  end
7
9
  end
8
10
  end
@@ -1,6 +1,6 @@
1
1
  require 'desk_api/action/create'
2
2
  require 'desk_api/action/delete'
3
- require 'desk_api/action/embedded'
3
+ require 'desk_api/action/embeddable'
4
4
  require 'desk_api/action/field'
5
5
  require 'desk_api/action/link'
6
6
  require 'desk_api/action/resource'
@@ -14,6 +14,7 @@ module DeskApi
14
14
  include DeskApi::Action::Resource
15
15
  include DeskApi::Action::Link
16
16
  include DeskApi::Action::Field
17
+ include DeskApi::Action::Embeddable
17
18
 
18
19
  def initialize(client, definition = {}, loaded = false)
19
20
  @client, @loaded, @_changed = client, loaded, {}
@@ -37,16 +38,41 @@ module DeskApi
37
38
 
38
39
  def exec!(reload = false)
39
40
  return self if loaded and !reload
40
- definition, @loaded = client.get(@_links.self.href).body, true
41
+ definition, @loaded = client.get(get_href).body, true
41
42
  setup(definition)
42
43
  end
43
44
 
45
+ def query_params
46
+ Addressable::URI.parse(@_links.self.href).query_values || {}
47
+ end
48
+
49
+ def query_params_include?(param)
50
+ query_params.include?(param) ? query_params[param] : nil
51
+ end
52
+
53
+ def query_params=(params = {})
54
+ return @_links.self.href if params.empty?
55
+
56
+ uri = Addressable::URI.parse(@_links.self.href)
57
+ params = (uri.query_values || {}).merge(params)
58
+
59
+ @loaded = false unless params == uri.query_values
60
+
61
+ uri.query_values = params
62
+ @_links.self.href = uri.to_s
63
+ end
64
+
65
+ def base_class
66
+ self.class
67
+ end
68
+
44
69
  private
45
70
 
46
71
  attr_accessor :client, :loaded, :_changed
47
72
 
48
73
  def setup(definition)
49
74
  setup_links(definition._links) if definition._links?
75
+ setup_embedded(definition._embedded) if definition._embedded?
50
76
  setup_fields(definition)
51
77
  self
52
78
  end
@@ -56,6 +82,5 @@ module DeskApi
56
82
  raise DeskApi::Error::MethodNotSupported unless self.respond_to?(method.to_sym)
57
83
  self.send(method, *args, &block)
58
84
  end
59
-
60
85
  end
61
86
  end
@@ -1,3 +1,3 @@
1
1
  module DeskApi
2
- VERSION = '0.1.2'
2
+ VERSION = '0.1.3'
3
3
  end
@@ -0,0 +1,54 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: post
5
+ uri: https://devel.desk.com/api/v2/articles
6
+ body:
7
+ encoding: UTF-8
8
+ string: '{"subject":"Testing","body":"Testing"}'
9
+ headers:
10
+ Accept:
11
+ - application/json
12
+ User-Agent:
13
+ - desk.com Ruby Gem v0.1.2
14
+ Content-Type:
15
+ - application/json
16
+ response:
17
+ status:
18
+ code: 422
19
+ message:
20
+ headers:
21
+ Accept-Ranges:
22
+ - bytes
23
+ Cache-Control:
24
+ - no-cache
25
+ Content-Type:
26
+ - application/json; charset=utf-8
27
+ Date:
28
+ - Wed, 04 Sep 2013 23:57:05 GMT
29
+ Status:
30
+ - 422 Unprocessable Entity
31
+ Vary:
32
+ - X-AppVersion
33
+ X-AppVersion:
34
+ - '9.7'
35
+ X-Frame-Options:
36
+ - SAMEORIGIN
37
+ X-Rate-Limit-Limit:
38
+ - '60'
39
+ X-Rate-Limit-Remaining:
40
+ - '59'
41
+ X-Rate-Limit-Reset:
42
+ - '55'
43
+ X-Request-Id:
44
+ - a902613e4a179503f1a94b35421449ec
45
+ Content-Length:
46
+ - '71'
47
+ Connection:
48
+ - keep-alive
49
+ body:
50
+ encoding: UTF-8
51
+ string: '{"message":"Validation Failed","errors":{"_links":{"topic":["blank"]}}}'
52
+ http_version:
53
+ recorded_at: Wed, 04 Sep 2013 23:57:05 GMT
54
+ recorded_with: VCR 2.5.0