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