desk_api 0.6.0 → 0.6.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.
- checksums.yaml +4 -4
- data/LICENSE.txt +27 -0
- data/README.md +97 -61
- data/lib/desk.rb +29 -1
- data/lib/desk_api.rb +57 -5
- data/lib/desk_api/client.rb +104 -47
- data/lib/desk_api/configuration.rb +201 -108
- data/lib/desk_api/default.rb +109 -52
- data/lib/desk_api/error.rb +90 -40
- data/lib/desk_api/error/bad_gateway.rb +29 -1
- data/lib/desk_api/error/bad_request.rb +29 -1
- data/lib/desk_api/error/client_error.rb +31 -2
- data/lib/desk_api/error/configuration_error.rb +29 -1
- data/lib/desk_api/error/conflict.rb +29 -1
- data/lib/desk_api/error/follow_redirect_error.rb +42 -0
- data/lib/desk_api/error/forbidden.rb +29 -1
- data/lib/desk_api/error/gateway_timeout.rb +29 -1
- data/lib/desk_api/error/internal_server_error.rb +29 -1
- data/lib/desk_api/error/method_not_allowed.rb +29 -1
- data/lib/desk_api/error/not_acceptable.rb +29 -1
- data/lib/desk_api/error/not_found.rb +29 -1
- data/lib/desk_api/error/parser_error.rb +29 -1
- data/lib/desk_api/error/server_error.rb +29 -1
- data/lib/desk_api/error/service_unavailable.rb +29 -1
- data/lib/desk_api/error/too_many_requests.rb +29 -1
- data/lib/desk_api/error/unauthorized.rb +29 -1
- data/lib/desk_api/error/unprocessable_entity.rb +29 -1
- data/lib/desk_api/error/unsupported_media_type.rb +29 -1
- data/lib/desk_api/rate_limit.rb +63 -21
- data/lib/desk_api/request/encode_json.rb +49 -7
- data/lib/desk_api/request/oauth.rb +62 -14
- data/lib/desk_api/request/retry.rb +108 -34
- data/lib/desk_api/resource.rb +402 -192
- data/lib/desk_api/response/follow_redirects.rb +99 -0
- data/lib/desk_api/response/parse_dates.rb +63 -21
- data/lib/desk_api/response/parse_json.rb +47 -5
- data/lib/desk_api/response/raise_error.rb +51 -10
- data/lib/desk_api/version.rb +30 -2
- data/spec/cassettes/DeskApi_Resource/_update/can_handle_action_params.yml +110 -104
- data/spec/cassettes/DeskApi_Resource/_update/can_handle_links.yml +426 -0
- data/spec/desk_api/client_spec.rb +28 -0
- data/spec/desk_api/configuration_spec.rb +28 -0
- data/spec/desk_api/default_spec.rb +28 -0
- data/spec/desk_api/error_spec.rb +29 -1
- data/spec/desk_api/rate_limit_spec.rb +28 -0
- data/spec/desk_api/request/encode_json_spec.rb +28 -0
- data/spec/desk_api/request/oauth_spec.rb +28 -0
- data/spec/desk_api/request/retry_spec.rb +29 -1
- data/spec/desk_api/resource_spec.rb +49 -12
- data/spec/desk_api/response/follow_redirects_spec.rb +95 -0
- data/spec/desk_api/response/parse_dates_spec.rb +28 -0
- data/spec/desk_api/response/parse_json_spec.rb +56 -9
- data/spec/desk_api/response/raise_error_spec.rb +28 -0
- data/spec/desk_api_spec.rb +28 -0
- data/spec/spec_helper.rb +28 -0
- metadata +84 -24
- data/LICENSE +0 -7
@@ -1,11 +1,53 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
1
|
+
# Copyright (c) 2013-2014, Salesforce.com, Inc.
|
2
|
+
# All rights reserved.
|
3
|
+
#
|
4
|
+
# Redistribution and use in source and binary forms, with or without modification,
|
5
|
+
# are permitted provided that the following conditions are met:
|
6
|
+
#
|
7
|
+
# * Redistributions of source code must retain the above copyright notice, this
|
8
|
+
# list of conditions and the following disclaimer.
|
9
|
+
#
|
10
|
+
# * Redistributions in binary form must reproduce the above copyright notice,
|
11
|
+
# this list of conditions and the following disclaimer in the documentation
|
12
|
+
# and/or other materials provided with the distribution.
|
13
|
+
#
|
14
|
+
# * Neither the name of Salesforce.com nor the names of its contributors may be
|
15
|
+
# used to endorse or promote products derived from this software without
|
16
|
+
# specific prior written permission.
|
17
|
+
#
|
18
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
19
|
+
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
20
|
+
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
21
|
+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
22
|
+
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
23
|
+
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
24
|
+
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
25
|
+
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
26
|
+
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
27
|
+
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
4
28
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
29
|
+
module DeskApi
|
30
|
+
module Request
|
31
|
+
# {DeskApi::Request::EncodeJson} is the Faraday middleware
|
32
|
+
# that dumps a json string from whatever is specified in
|
33
|
+
# the request body. It also sets the "Content-Type" header.
|
34
|
+
#
|
35
|
+
# @author Thomas Stachl <tstachl@salesforce.com>
|
36
|
+
# @copyright Copyright (c) 2013-2014 Salesforce.com
|
37
|
+
# @license BSD 3-Clause License
|
38
|
+
class EncodeJson < Faraday::Middleware
|
39
|
+
dependency 'json'
|
40
|
+
|
41
|
+
# Changes the request before it gets sent
|
42
|
+
#
|
43
|
+
# @param env [Hash] the request hash
|
44
|
+
def call(env)
|
45
|
+
env[:request_headers]['Content-Type'] = 'application/json'
|
46
|
+
if env[:body] && !env[:body].to_s.empty?
|
47
|
+
env[:body] = ::JSON.dump(env[:body])
|
48
|
+
end
|
49
|
+
@app.call env
|
50
|
+
end
|
9
51
|
end
|
10
52
|
end
|
11
53
|
end
|
@@ -1,20 +1,68 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
1
|
+
# Copyright (c) 2013-2014, Salesforce.com, Inc.
|
2
|
+
# All rights reserved.
|
3
|
+
#
|
4
|
+
# Redistribution and use in source and binary forms, with or without modification,
|
5
|
+
# are permitted provided that the following conditions are met:
|
6
|
+
#
|
7
|
+
# * Redistributions of source code must retain the above copyright notice, this
|
8
|
+
# list of conditions and the following disclaimer.
|
9
|
+
#
|
10
|
+
# * Redistributions in binary form must reproduce the above copyright notice,
|
11
|
+
# this list of conditions and the following disclaimer in the documentation
|
12
|
+
# and/or other materials provided with the distribution.
|
13
|
+
#
|
14
|
+
# * Neither the name of Salesforce.com nor the names of its contributors may be
|
15
|
+
# used to endorse or promote products derived from this software without
|
16
|
+
# specific prior written permission.
|
17
|
+
#
|
18
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
19
|
+
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
20
|
+
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
21
|
+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
22
|
+
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
23
|
+
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
24
|
+
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
25
|
+
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
26
|
+
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
27
|
+
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
4
28
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
29
|
+
module DeskApi
|
30
|
+
module Request
|
31
|
+
# {DeskApi::Request::OAuth} is the Faraday middleware to
|
32
|
+
# sign requests with an OAuth header.
|
33
|
+
#
|
34
|
+
# @author Thomas Stachl <tstachl@salesforce.com>
|
35
|
+
# @copyright Copyright (c) 2013-2014 Salesforce.com
|
36
|
+
# @license BSD 3-Clause License
|
37
|
+
class OAuth < Faraday::Middleware
|
38
|
+
dependency 'simple_oauth'
|
9
39
|
|
10
|
-
|
11
|
-
|
12
|
-
@app
|
13
|
-
|
40
|
+
# Initializies the middleware and sets options
|
41
|
+
#
|
42
|
+
# @param app [Hash] the faraday environment hash
|
43
|
+
# @param options [Hash] additional options
|
44
|
+
def initialize(app, options)
|
45
|
+
super(app)
|
46
|
+
@options = options
|
47
|
+
end
|
48
|
+
|
49
|
+
# Changes the request before it gets sent
|
50
|
+
#
|
51
|
+
# @param env [Hash] the request hash
|
52
|
+
def call(env)
|
53
|
+
env[:request_headers]['Authorization'] = oauth(env).to_s
|
54
|
+
@app.call env
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
14
58
|
|
15
|
-
|
16
|
-
|
17
|
-
|
59
|
+
# Returns the OAuth header
|
60
|
+
#
|
61
|
+
# @param env [Hash] the request hash
|
62
|
+
# @return [String]
|
63
|
+
def oauth(env)
|
64
|
+
SimpleOAuth::Header.new env[:method], env[:url].to_s, {}, @options
|
65
|
+
end
|
18
66
|
end
|
19
67
|
end
|
20
68
|
end
|
@@ -1,47 +1,121 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
1
|
+
# Copyright (c) 2013-2014, Salesforce.com, Inc.
|
2
|
+
# All rights reserved.
|
3
|
+
#
|
4
|
+
# Redistribution and use in source and binary forms, with or without modification,
|
5
|
+
# are permitted provided that the following conditions are met:
|
6
|
+
#
|
7
|
+
# * Redistributions of source code must retain the above copyright notice, this
|
8
|
+
# list of conditions and the following disclaimer.
|
9
|
+
#
|
10
|
+
# * Redistributions in binary form must reproduce the above copyright notice,
|
11
|
+
# this list of conditions and the following disclaimer in the documentation
|
12
|
+
# and/or other materials provided with the distribution.
|
13
|
+
#
|
14
|
+
# * Neither the name of Salesforce.com nor the names of its contributors may be
|
15
|
+
# used to endorse or promote products derived from this software without
|
16
|
+
# specific prior written permission.
|
17
|
+
#
|
18
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
19
|
+
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
20
|
+
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
21
|
+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
22
|
+
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
23
|
+
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
24
|
+
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
25
|
+
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
26
|
+
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
27
|
+
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
8
28
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
29
|
+
module DeskApi
|
30
|
+
module Request
|
31
|
+
# {DeskApi::Request::Retry} is a Faraday middleware that
|
32
|
+
# retries failed requests up to 3 times. It also includes
|
33
|
+
# desk.com's rate limiting which are retried only once.
|
34
|
+
#
|
35
|
+
# @author Thomas Stachl <tstachl@salesforce.com>
|
36
|
+
# @copyright Copyright (c) 2013-2014 Salesforce.com
|
37
|
+
# @license BSD 3-Clause License
|
38
|
+
class Retry < Faraday::Middleware
|
39
|
+
class << self
|
40
|
+
# Returns an array of errors that should be retried.
|
41
|
+
#
|
42
|
+
# @return [Array]
|
43
|
+
def errors
|
44
|
+
@exceptions ||= [
|
45
|
+
Errno::ETIMEDOUT,
|
46
|
+
'Timeout::Error',
|
47
|
+
Faraday::Error::TimeoutError,
|
48
|
+
DeskApi::Error::TooManyRequests
|
49
|
+
]
|
20
50
|
end
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
51
|
+
end
|
52
|
+
|
53
|
+
# Initializies the middleware and sets options
|
54
|
+
#
|
55
|
+
# @param app [Hash] the faraday environment hash
|
56
|
+
# @param options [Hash] additional options
|
57
|
+
def initialize(app, options = {})
|
58
|
+
@max = options[:max] || 3
|
59
|
+
@interval = options[:interval] || 10
|
60
|
+
super(app)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Rescues exceptions and retries the request
|
64
|
+
#
|
65
|
+
# @param env [Hash] the request hash
|
66
|
+
def call(env)
|
67
|
+
retries = @max
|
68
|
+
request_body = env[:body]
|
69
|
+
begin
|
70
|
+
env[:body] = request_body
|
71
|
+
@app.call(env)
|
72
|
+
rescue exception_matcher => err
|
73
|
+
raise unless calc(err, retries) { |x| retries = x } > 0
|
74
|
+
sleep interval(err)
|
26
75
|
retry
|
27
76
|
end
|
28
|
-
raise
|
29
77
|
end
|
30
|
-
end
|
31
78
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
79
|
+
# Calculates the retries based on the error
|
80
|
+
#
|
81
|
+
# @param err [StandardError] the error that has been thrown
|
82
|
+
# @param retries [Integer] current retry count
|
83
|
+
# @return [Integer]
|
84
|
+
def calc(err, retries, &block)
|
85
|
+
# retry only once
|
86
|
+
if err.kind_of?(DeskApi::Error::TooManyRequests) && retries == @max - 1
|
87
|
+
block.call(0)
|
88
|
+
else
|
89
|
+
block.call(retries - 1)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Returns the interval for the specific error
|
94
|
+
#
|
95
|
+
# @param err [StandardError] the error that has been thrown
|
96
|
+
# @return [Integer]
|
97
|
+
def interval(err)
|
98
|
+
if err.kind_of?(DeskApi::Error::TooManyRequests)
|
99
|
+
err.rate_limit.reset_in
|
100
|
+
else
|
101
|
+
@interval
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# Returns an exception matcher
|
106
|
+
#
|
107
|
+
# @return [Module]
|
108
|
+
def exception_matcher
|
109
|
+
matcher = Module.new
|
110
|
+
(class << matcher; self; end).class_eval do
|
111
|
+
define_method(:===) do |error|
|
112
|
+
Retry.errors.any? do |ex|
|
113
|
+
ex.is_a?(Module) ? error.is_a?(ex) : error.class.to_s == ex.to_s
|
40
114
|
end
|
41
115
|
end
|
42
116
|
end
|
117
|
+
matcher
|
43
118
|
end
|
44
|
-
matcher
|
45
119
|
end
|
46
120
|
end
|
47
121
|
end
|
data/lib/desk_api/resource.rb
CHANGED
@@ -1,250 +1,460 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
1
|
+
# Copyright (c) 2013-2014, Salesforce.com, Inc.
|
2
|
+
# All rights reserved.
|
3
|
+
#
|
4
|
+
# Redistribution and use in source and binary forms, with or without modification,
|
5
|
+
# are permitted provided that the following conditions are met:
|
6
|
+
#
|
7
|
+
# * Redistributions of source code must retain the above copyright notice, this
|
8
|
+
# list of conditions and the following disclaimer.
|
9
|
+
#
|
10
|
+
# * Redistributions in binary form must reproduce the above copyright notice,
|
11
|
+
# this list of conditions and the following disclaimer in the documentation
|
12
|
+
# and/or other materials provided with the distribution.
|
13
|
+
#
|
14
|
+
# * Neither the name of Salesforce.com nor the names of its contributors may be
|
15
|
+
# used to endorse or promote products derived from this software without
|
16
|
+
# specific prior written permission.
|
17
|
+
#
|
18
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
19
|
+
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
20
|
+
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
21
|
+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
22
|
+
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
23
|
+
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
24
|
+
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
25
|
+
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
26
|
+
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
27
|
+
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
28
|
+
|
29
|
+
module DeskApi
|
30
|
+
# {DeskApi::Resource} holds most of the magic of this wrapper. Basically
|
31
|
+
# everything that comes back from Desk.com's API is a Resource, it keeps
|
32
|
+
# track of all the data, connects you to other resources through links
|
33
|
+
# and allows you access to embedded resources.
|
34
|
+
#
|
35
|
+
# @author Thomas Stachl <tstachl@salesforce.com>
|
36
|
+
# @copyright Copyright (c) 2013-2014 Salesforce.com
|
37
|
+
# @license BSD 3-Clause License
|
38
|
+
#
|
39
|
+
# @example get a cases {DeskApi::Resource}
|
40
|
+
# cases_resource = DeskApi.cases
|
41
|
+
class Resource
|
42
|
+
extend Forwardable
|
43
|
+
def_delegator :@_client, :by_url, :by_url
|
44
|
+
|
45
|
+
class << self
|
46
|
+
# Returns a {DeskApi::Resource} definition with a self link
|
47
|
+
#
|
48
|
+
# @param link [String/Hash] the self href as string or hash
|
49
|
+
# @return [Hash]
|
50
|
+
def build_self_link(link, params = {})
|
51
|
+
link = {'href'=>link} if link.kind_of?(String)
|
52
|
+
{'_links'=>{'self'=>link}}
|
53
|
+
end
|
10
54
|
end
|
11
|
-
end
|
12
|
-
|
13
|
-
def initialize(client, definition = {}, loaded = false)
|
14
|
-
reset!
|
15
|
-
@_client, @_definition, @_loaded = client, definition, loaded
|
16
|
-
# better default
|
17
|
-
end
|
18
|
-
|
19
|
-
def create(params = {})
|
20
|
-
new_resource(@_client.post(clean_base_url, params).body, true)
|
21
|
-
end
|
22
|
-
|
23
|
-
def update(params = {})
|
24
|
-
changes = filter_update_actions params
|
25
|
-
params.each_pair{ |key, value| send("#{key}=", value) if respond_to?("#{key}=") }
|
26
|
-
changes.merge!(@_changed.clone)
|
27
|
-
@_changed = {}
|
28
|
-
@_definition = @_client.patch(href, changes).body
|
29
|
-
end
|
30
|
-
|
31
|
-
def delete
|
32
|
-
@_client.delete(href).status === 204
|
33
|
-
end
|
34
55
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
56
|
+
# Initializes a new {DeskApi::Resource} object
|
57
|
+
#
|
58
|
+
# @param client [DeskApi::Client] the client to be used
|
59
|
+
# @param definition [Hash] a defintion for the resource
|
60
|
+
# @param loaded [Boolean] indicator of the loading state
|
61
|
+
# @return [DeskApi::Resource] the new resource
|
62
|
+
def initialize(client, definition = {}, loaded = false)
|
63
|
+
reset!
|
64
|
+
@_client, @_definition, @_loaded = client, definition, loaded
|
65
|
+
end
|
41
66
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
67
|
+
# This method will POST to the Desk.com API and create a
|
68
|
+
# new resource
|
69
|
+
#
|
70
|
+
# @param params [Hash] the params to create the resource
|
71
|
+
# @return [DeskApi::Resource] the newly created resource
|
72
|
+
def create(params = {})
|
73
|
+
new_resource(@_client.post(clean_base_url, params).body, true)
|
74
|
+
end
|
48
75
|
|
49
|
-
|
50
|
-
|
51
|
-
|
76
|
+
# Use this method to update a {DeskApi::Resource}, it'll
|
77
|
+
# PATCH changes to the Desk.com API
|
78
|
+
#
|
79
|
+
# @param params [Hash] the params to update the resource
|
80
|
+
# @return [DeskApi::Resource] the updated resource
|
81
|
+
def update(params = {})
|
82
|
+
changes = filter_update_actions params
|
83
|
+
changes.merge!(filter_links(params)) # quickfix
|
84
|
+
params.each_pair{ |key, value| send("#{key}=", value) if respond_to?("#{key}=") }
|
85
|
+
changes.merge!(@_changed.clone)
|
86
|
+
|
87
|
+
reset!
|
88
|
+
@_definition, @_loaded = [@_client.patch(href, changes).body, true]
|
89
|
+
|
90
|
+
self
|
91
|
+
end
|
52
92
|
|
53
|
-
|
54
|
-
|
55
|
-
|
93
|
+
# Deletes the {DeskApi::Resource}.
|
94
|
+
#
|
95
|
+
# @return [Boolean] has the resource been deleted?
|
96
|
+
def delete
|
97
|
+
@_client.delete(href).status === 204
|
56
98
|
end
|
57
99
|
|
58
|
-
|
100
|
+
# Using this method allows you to hit the search endpoint
|
101
|
+
#
|
102
|
+
# @param params [Hash] the search params
|
103
|
+
# @return [DeskApi::Resource] the search page resource
|
104
|
+
def search(params = {})
|
105
|
+
params = { q: params } if params.kind_of?(String)
|
106
|
+
url = Addressable::URI.parse(clean_base_url + '/search')
|
107
|
+
url.query_values = params
|
108
|
+
new_resource(self.class.build_self_link(url.to_s))
|
109
|
+
end
|
59
110
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
111
|
+
# Returns a {DeskApi::Resource} based on the given id
|
112
|
+
#
|
113
|
+
# @param id [String/Integer] the id of the resource
|
114
|
+
# @param options [Hash] additional options (currently only embed is supported)
|
115
|
+
# @return [DeskApi::Resource] the requested resource
|
116
|
+
def find(id, options = {})
|
117
|
+
res = new_resource(self.class.build_self_link("#{clean_base_url}/#{id}"))
|
118
|
+
res.embed(*(options[:embed].kind_of?(Array) ? options[:embed] : [options[:embed]])) if options[:embed]
|
119
|
+
res.exec!
|
120
|
+
end
|
121
|
+
alias_method :by_id, :find
|
122
|
+
|
123
|
+
# Change self to the next page
|
124
|
+
#
|
125
|
+
# @return [Desk::Resource] self
|
126
|
+
def next!
|
127
|
+
self.load
|
128
|
+
next_page = @_definition['_links']['next']
|
129
|
+
|
130
|
+
if next_page
|
131
|
+
@_definition = self.class.build_self_link(next_page)
|
132
|
+
self.reset!
|
133
|
+
end
|
64
134
|
end
|
65
|
-
end
|
66
135
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
136
|
+
# Paginate through all the resources on a give page {DeskApi::Resource}
|
137
|
+
#
|
138
|
+
# @raise [NoMethodError] if self is not a page resource
|
139
|
+
# @raise [ArgumentError] if no block is given
|
140
|
+
# @yield [DeskApi::Resource] the current resource
|
141
|
+
# @yield [Integer] the current page number
|
142
|
+
def all
|
143
|
+
raise ArgumentError, "Block must be given for #all" unless block_given?
|
144
|
+
each_page do |page, page_num|
|
145
|
+
page.entries.each { |resource| yield resource, page_num }
|
146
|
+
end
|
147
|
+
end
|
76
148
|
|
149
|
+
# Paginate through each page on a give page {DeskApi::Resource}
|
150
|
+
#
|
151
|
+
# @raise [NoMethodError] if self is not a page resource
|
152
|
+
# @raise [ArgumentError] if no block is given
|
153
|
+
# @yield [DeskApi::Resource] the current page resource
|
154
|
+
# @yield [Integer] the current page number
|
155
|
+
def each_page
|
156
|
+
raise ArgumentError, "Block must be given for #each_page" unless block_given?
|
157
|
+
|
158
|
+
begin
|
159
|
+
page = self.first.per_page(self.query_params['per_page'] || 1000).dup
|
160
|
+
rescue NoMethodError => err
|
161
|
+
raise NoMethodError, "#each_page and #all are only available on resources which offer pagination"
|
162
|
+
end
|
77
163
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
end
|
164
|
+
begin
|
165
|
+
yield page, page.page
|
166
|
+
end while page.next!
|
167
|
+
end
|
83
168
|
|
84
|
-
|
85
|
-
|
86
|
-
|
169
|
+
# Allows you to embed/sideload resources
|
170
|
+
#
|
171
|
+
# @example embed customers with their cases
|
172
|
+
# my_cases = client.cases.embed(:customers)
|
173
|
+
# @example embed assigned_user and assigned_group
|
174
|
+
# my_cases = client.cases.embed(:assigned_user, :assigned_group)
|
175
|
+
# @param embedds [Symbol/String] whatever you want to embed
|
176
|
+
# @return [Desk::Resource] self
|
177
|
+
def embed(*embedds)
|
178
|
+
# make sure we don't try to embed anything that's not defined
|
179
|
+
# add it to the query
|
180
|
+
self.tap{ |res| res.query_params = { embed: embedds.join(',') } }
|
181
|
+
end
|
87
182
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
183
|
+
# Returns the self link hash
|
184
|
+
#
|
185
|
+
# @return [Hash] self link hash
|
186
|
+
def get_self
|
187
|
+
@_definition['_links']['self']
|
188
|
+
end
|
92
189
|
|
93
|
-
|
94
|
-
|
95
|
-
|
190
|
+
# Returns the self link href
|
191
|
+
#
|
192
|
+
# @return [String] self link href
|
193
|
+
def href
|
194
|
+
get_self['href']
|
195
|
+
end
|
196
|
+
alias_method :get_href, :href
|
197
|
+
|
198
|
+
# Set the self link href
|
199
|
+
#
|
200
|
+
# @return [DeskApi::Resource] self
|
201
|
+
def href=(value)
|
202
|
+
@_definition['_links']['self']['href'] = value
|
203
|
+
self
|
204
|
+
end
|
96
205
|
|
97
|
-
|
98
|
-
|
206
|
+
# Returns a hash based on the current definition of the resource
|
207
|
+
#
|
208
|
+
# @return [Hash] definition hash
|
209
|
+
def to_hash
|
210
|
+
self.load
|
99
211
|
|
100
|
-
|
101
|
-
|
102
|
-
|
212
|
+
{}.tap do |hash|
|
213
|
+
@_definition.each do |k, v|
|
214
|
+
hash[k] = v
|
215
|
+
end
|
103
216
|
end
|
104
217
|
end
|
105
|
-
end
|
106
218
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
define_method(method) do |value = nil|
|
113
|
-
unless value
|
114
|
-
self.exec! if self.query_params_include?(method.to_s) == nil
|
115
|
-
return self.query_params_include?(method.to_s).to_i
|
116
|
-
end
|
117
|
-
self.tap{ |res| res.query_params = Hash[method.to_s, value.to_s] }
|
219
|
+
# Returns the given resource type
|
220
|
+
#
|
221
|
+
# @return [String] resource type/class
|
222
|
+
def resource_type
|
223
|
+
get_self['class']
|
118
224
|
end
|
119
|
-
end
|
120
225
|
|
121
|
-
def query_params
|
122
|
-
Addressable::URI.parse(href).query_values || {}
|
123
|
-
end
|
124
226
|
|
125
|
-
|
126
|
-
|
127
|
-
|
227
|
+
# Get/set the page and per_page query params
|
228
|
+
#
|
229
|
+
# @param value [Integer/Nil] the value to use
|
230
|
+
# @return [Integer/DeskApi::Resource]
|
231
|
+
%w(page per_page).each do |method|
|
232
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
233
|
+
def #{method}(value = nil)
|
234
|
+
unless value
|
235
|
+
exec! if query_params_include?('#{method}') == nil
|
236
|
+
return query_params_include?('#{method}').to_i
|
237
|
+
end
|
238
|
+
tap{ |res| res.query_params = Hash['#{method}', value.to_s] }
|
239
|
+
end
|
240
|
+
RUBY
|
241
|
+
end
|
128
242
|
|
129
|
-
|
130
|
-
|
243
|
+
# Converts the current self href query params to a hash
|
244
|
+
#
|
245
|
+
# @return [Hash] current self href query params
|
246
|
+
def query_params
|
247
|
+
Addressable::URI.parse(href).query_values || {}
|
248
|
+
end
|
131
249
|
|
132
|
-
|
250
|
+
# Checks if the specified param is included
|
251
|
+
#
|
252
|
+
# @param param [String] the param to check for
|
253
|
+
# @return [Boolean]
|
254
|
+
def query_params_include?(param)
|
255
|
+
query_params.include?(param) ? query_params[param] : nil
|
256
|
+
end
|
133
257
|
|
134
|
-
|
135
|
-
|
258
|
+
# Sets the query params based on the provided hash
|
259
|
+
#
|
260
|
+
# @param params [Hash] the query params
|
261
|
+
# @return [String] the generated href
|
262
|
+
def query_params=(params = {})
|
263
|
+
return href if params.empty?
|
136
264
|
|
137
|
-
|
265
|
+
params.keys.each{ |key| params[key] = params[key].join(',') if params[key].is_a?(Array) }
|
138
266
|
|
139
|
-
|
140
|
-
|
141
|
-
end
|
267
|
+
uri = Addressable::URI.parse(href)
|
268
|
+
params = (uri.query_values || {}).merge(params)
|
142
269
|
|
143
|
-
|
144
|
-
self.load
|
145
|
-
meth = method.to_s
|
270
|
+
@_loaded = false unless params == uri.query_values
|
146
271
|
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
return true if is_field?(meth)
|
272
|
+
uri.query_values = params
|
273
|
+
self.href = uri.to_s
|
274
|
+
end
|
151
275
|
|
152
|
-
|
153
|
-
|
276
|
+
# Checks if this resource responds to a specific method
|
277
|
+
#
|
278
|
+
# @param method [String/Symbol]
|
279
|
+
# @return [Boolean]
|
280
|
+
def respond_to?(method)
|
281
|
+
self.load
|
282
|
+
meth = method.to_s
|
154
283
|
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
284
|
+
return true if is_embedded?(meth)
|
285
|
+
return true if is_link?(meth)
|
286
|
+
return true if meth.end_with?('=') and is_field?(meth[0...-1])
|
287
|
+
return true if is_field?(meth)
|
159
288
|
|
160
|
-
|
161
|
-
|
162
|
-
end
|
289
|
+
super
|
290
|
+
end
|
163
291
|
|
164
|
-
|
165
|
-
|
166
|
-
|
292
|
+
# Reloads the current resource
|
293
|
+
#
|
294
|
+
# @return [DeskApi::Resource] self
|
295
|
+
def load!
|
296
|
+
self.exec! true
|
297
|
+
end
|
298
|
+
alias_method :reload!, :load!
|
167
299
|
|
168
|
-
|
300
|
+
# Only loads the current resource if it isn't loaded yet
|
301
|
+
#
|
302
|
+
# @return [DeskApi::Resource] self
|
303
|
+
def load
|
304
|
+
self.exec! unless @_loaded
|
305
|
+
end
|
169
306
|
|
170
|
-
|
171
|
-
|
172
|
-
|
307
|
+
# Is the current resource loaded?
|
308
|
+
#
|
309
|
+
# @return [Boolean]
|
310
|
+
def loaded?
|
311
|
+
@_loaded
|
312
|
+
end
|
173
313
|
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
314
|
+
protected
|
315
|
+
|
316
|
+
# Returns a clean base url
|
317
|
+
#
|
318
|
+
# @example removes the search if called from a search resource
|
319
|
+
# '/api/v2/cases/search' => '/api/v2/cases'
|
320
|
+
# @example removes the id if your on a specific resource
|
321
|
+
# '/api/v2/cases/1' => '/api/v2/cases'
|
322
|
+
# @return [String] the clean base url
|
323
|
+
def clean_base_url
|
324
|
+
Addressable::URI.parse(href).path.gsub(/\/(search|\d+)$/, '')
|
325
|
+
end
|
179
326
|
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
327
|
+
# Executes the request to the Desk.com API if the resource
|
328
|
+
# is not loaded yet
|
329
|
+
#
|
330
|
+
# @param reload [Boolean] should reload the resource
|
331
|
+
# @return [DeskApi::Resource] self
|
332
|
+
def exec!(reload = false)
|
333
|
+
return self if @_loaded and !reload
|
334
|
+
@_definition, @_loaded = @_client.get(href).body, true
|
335
|
+
self
|
336
|
+
end
|
337
|
+
|
338
|
+
# Resets a {DeskApi::Resource} to an empty state
|
339
|
+
#
|
340
|
+
# @return [DeskApi::Resource] self
|
341
|
+
def reset!
|
342
|
+
@_links, @_embedded, @_changed, @_loaded = {}, {}, {}, false
|
343
|
+
self
|
344
|
+
end
|
184
345
|
|
185
|
-
private
|
186
|
-
|
346
|
+
private
|
347
|
+
attr_accessor :_client, :_loaded, :_changed, :_embedded, :_links, :_definition
|
187
348
|
|
188
|
-
|
189
|
-
|
190
|
-
|
349
|
+
# Filters update actions from the params
|
350
|
+
#
|
351
|
+
# @see http://dev.desk.com/API/customers/#update
|
352
|
+
# @param params [Hash]
|
353
|
+
# @return [Hash]
|
354
|
+
def filter_update_actions(params = {})
|
355
|
+
params.select{ |key, _| key.to_s.include?('_action') }
|
356
|
+
end
|
191
357
|
|
192
|
-
|
193
|
-
|
194
|
-
|
358
|
+
# Filters the links
|
359
|
+
#
|
360
|
+
# @param params [Hash]
|
361
|
+
# @return [Hash]
|
362
|
+
def filter_links(params = {})
|
363
|
+
params.select{ |key, _| key.to_s == '_links' }
|
364
|
+
end
|
195
365
|
|
196
|
-
|
197
|
-
|
198
|
-
|
366
|
+
# Checks if the given `method` is a field on the current
|
367
|
+
# resource definition
|
368
|
+
#
|
369
|
+
# @param method [String/Symbol]
|
370
|
+
# @return [Boolean]
|
371
|
+
def is_field?(method)
|
372
|
+
@_definition.key?(method)
|
373
|
+
end
|
199
374
|
|
200
|
-
|
201
|
-
|
202
|
-
|
375
|
+
# Checks if the given `method` is a link on the current
|
376
|
+
# resource definition
|
377
|
+
#
|
378
|
+
# @param method [String/Symbol]
|
379
|
+
# @return [Boolean]
|
380
|
+
def is_link?(method)
|
381
|
+
@_definition.key?('_links') and @_definition['_links'].key?(method)
|
382
|
+
end
|
203
383
|
|
204
|
-
|
205
|
-
|
206
|
-
|
384
|
+
# Checks if the given `method` is embedded in the current
|
385
|
+
# resource definition
|
386
|
+
#
|
387
|
+
# @param method [String/Symbol]
|
388
|
+
# @return [Boolean]
|
389
|
+
def is_embedded?(method)
|
390
|
+
@_definition.key?('_embedded') and @_definition['_embedded'].key?(method)
|
391
|
+
end
|
207
392
|
|
208
|
-
|
209
|
-
|
210
|
-
@
|
393
|
+
# Returns the field value from the changed or definition hash
|
394
|
+
#
|
395
|
+
# @param method [String/Symbol]
|
396
|
+
# @return [Mixed]
|
397
|
+
def get_field_value(method)
|
398
|
+
@_changed.key?(method) ? @_changed[method] : @_definition[method]
|
399
|
+
end
|
211
400
|
|
212
|
-
|
213
|
-
|
214
|
-
|
401
|
+
# Returns the embedded resource
|
402
|
+
#
|
403
|
+
# @param method [String/Symbol]
|
404
|
+
# @return [DeskApi::Resource]
|
405
|
+
def get_embedded_resource(method)
|
406
|
+
return @_embedded[method] if @_embedded.key?(method)
|
407
|
+
@_embedded[method] = @_definition['_embedded'][method]
|
408
|
+
|
409
|
+
if @_embedded[method].kind_of?(Array)
|
410
|
+
@_embedded[method].tap do |ary|
|
411
|
+
ary.map!{ |definition| new_resource(definition, true) } unless ary.first.kind_of?(self.class)
|
412
|
+
end
|
413
|
+
else
|
414
|
+
@_embedded[method] = new_resource(@_embedded[method], true)
|
215
415
|
end
|
216
|
-
else
|
217
|
-
@_embedded[method] = new_resource(@_embedded[method], true)
|
218
416
|
end
|
219
|
-
end
|
220
417
|
|
221
|
-
|
222
|
-
|
223
|
-
@
|
418
|
+
# Returns the linked resource
|
419
|
+
#
|
420
|
+
# @param method [String/Symbol]
|
421
|
+
# @return [DeskApi::Resource]
|
422
|
+
def get_linked_resource(method)
|
423
|
+
return @_links[method] if @_links.key?(method)
|
424
|
+
@_links[method] = @_definition['_links'][method]
|
224
425
|
|
225
|
-
|
226
|
-
|
426
|
+
if @_links[method] and not @_links[method].kind_of?(self.class)
|
427
|
+
@_links[method] = new_resource(self.class.build_self_link(@_links[method]))
|
428
|
+
end
|
227
429
|
end
|
228
|
-
end
|
229
430
|
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
431
|
+
# Creates a new resource
|
432
|
+
#
|
433
|
+
# @param definition [Hash]
|
434
|
+
# @param loaded [Boolean]
|
435
|
+
# @param client [DeskApi::Client]
|
436
|
+
def new_resource(definition, loaded = false, client = @_client)
|
437
|
+
self.class.new(client, definition, loaded)
|
438
|
+
end
|
237
439
|
|
238
|
-
|
239
|
-
|
440
|
+
# Returns the requested embedded resource, linked resource or field value,
|
441
|
+
# also allows to set a new field value
|
442
|
+
#
|
443
|
+
# @param method [String/Symbol]
|
444
|
+
# @param args [Mixed]
|
445
|
+
# @param block [Proc]
|
446
|
+
# @return [Mixed]
|
447
|
+
def method_missing(method, *args, &block)
|
448
|
+
self.load
|
240
449
|
|
241
|
-
|
450
|
+
meth = method.to_s
|
242
451
|
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
452
|
+
return get_embedded_resource(meth) if is_embedded?(meth)
|
453
|
+
return get_linked_resource(meth) if is_link?(meth)
|
454
|
+
return @_changed[meth[0...-1]] = args.first if meth.end_with?('=') and is_field?(meth[0...-1])
|
455
|
+
return get_field_value(meth) if is_field?(meth)
|
247
456
|
|
248
|
-
|
457
|
+
super(method, *args, &block)
|
458
|
+
end
|
249
459
|
end
|
250
460
|
end
|