discourse_zendesk_api 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +176 -0
- data/lib/zendesk_api/actions.rb +334 -0
- data/lib/zendesk_api/association.rb +195 -0
- data/lib/zendesk_api/associations.rb +212 -0
- data/lib/zendesk_api/client.rb +243 -0
- data/lib/zendesk_api/collection.rb +474 -0
- data/lib/zendesk_api/configuration.rb +79 -0
- data/lib/zendesk_api/core_ext/inflection.rb +3 -0
- data/lib/zendesk_api/delegator.rb +5 -0
- data/lib/zendesk_api/error.rb +49 -0
- data/lib/zendesk_api/helpers.rb +24 -0
- data/lib/zendesk_api/lru_cache.rb +39 -0
- data/lib/zendesk_api/middleware/request/encode_json.rb +26 -0
- data/lib/zendesk_api/middleware/request/etag_cache.rb +52 -0
- data/lib/zendesk_api/middleware/request/raise_rate_limited.rb +31 -0
- data/lib/zendesk_api/middleware/request/retry.rb +59 -0
- data/lib/zendesk_api/middleware/request/upload.rb +86 -0
- data/lib/zendesk_api/middleware/request/url_based_access_token.rb +26 -0
- data/lib/zendesk_api/middleware/response/callback.rb +21 -0
- data/lib/zendesk_api/middleware/response/deflate.rb +17 -0
- data/lib/zendesk_api/middleware/response/gzip.rb +19 -0
- data/lib/zendesk_api/middleware/response/logger.rb +44 -0
- data/lib/zendesk_api/middleware/response/parse_iso_dates.rb +30 -0
- data/lib/zendesk_api/middleware/response/parse_json.rb +23 -0
- data/lib/zendesk_api/middleware/response/raise_error.rb +26 -0
- data/lib/zendesk_api/middleware/response/sanitize_response.rb +11 -0
- data/lib/zendesk_api/resource.rb +208 -0
- data/lib/zendesk_api/resources.rb +971 -0
- data/lib/zendesk_api/sideloading.rb +58 -0
- data/lib/zendesk_api/silent_mash.rb +8 -0
- data/lib/zendesk_api/track_changes.rb +85 -0
- data/lib/zendesk_api/trackie.rb +13 -0
- data/lib/zendesk_api/verbs.rb +65 -0
- data/lib/zendesk_api/version.rb +3 -0
- data/lib/zendesk_api.rb +4 -0
- data/util/resource_handler.rb +74 -0
- data/util/verb_handler.rb +16 -0
- metadata +166 -0
@@ -0,0 +1,195 @@
|
|
1
|
+
require 'zendesk_api/helpers'
|
2
|
+
|
3
|
+
module ZendeskAPI
|
4
|
+
# Represents an association between two resources
|
5
|
+
# @private
|
6
|
+
class Association
|
7
|
+
class << self
|
8
|
+
def namespaces
|
9
|
+
[ZendeskAPI] + ZendeskAPI::DataNamespace.descendants
|
10
|
+
end
|
11
|
+
|
12
|
+
def class_from_namespace(klass_as_string)
|
13
|
+
namespaces.each do |ns|
|
14
|
+
if module_defines_class?(ns, klass_as_string)
|
15
|
+
return ns.const_get(klass_as_string)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
nil
|
20
|
+
end
|
21
|
+
|
22
|
+
# 1.9+ changed default to search ancestors, added flag to disable behavior.
|
23
|
+
def module_defines_class?(mod, klass_as_string)
|
24
|
+
if RUBY_VERSION < '1.9'
|
25
|
+
mod.const_defined?(klass_as_string)
|
26
|
+
else
|
27
|
+
mod.const_defined?(klass_as_string, false)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# @return [Hash] Options passed into the association
|
33
|
+
attr_reader :options
|
34
|
+
|
35
|
+
# Options to pass in
|
36
|
+
# * class - Required
|
37
|
+
# * parent - Parent instance
|
38
|
+
# * path - Optional path instead of resource name
|
39
|
+
def initialize(options = {})
|
40
|
+
@options = SilentMash.new(options)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Generate a path to the resource.
|
44
|
+
# id and <parent>_id attributes will be deleted from passed in options hash if they are used in the built path.
|
45
|
+
# Arguments that can be passed in:
|
46
|
+
# An instance, any resource instance
|
47
|
+
# Hash Options:
|
48
|
+
# * with_parent - Include the parent path (false by default)
|
49
|
+
# * with_id - Include the instance id, if possible (true)
|
50
|
+
def generate_path(*args)
|
51
|
+
options = SilentMash.new(:with_id => true)
|
52
|
+
if args.last.is_a?(Hash)
|
53
|
+
original_options = args.pop
|
54
|
+
options.merge!(original_options)
|
55
|
+
end
|
56
|
+
|
57
|
+
instance = args.first
|
58
|
+
|
59
|
+
namespace = @options[:class].to_s.split("::")
|
60
|
+
namespace[-1] = @options[:class].resource_path
|
61
|
+
|
62
|
+
# Remove components without path information
|
63
|
+
ignorable_namespace_strings.each { |ns| namespace.delete(ns) }
|
64
|
+
has_parent = namespace.size > 1 || (options[:with_parent] && @options.parent)
|
65
|
+
|
66
|
+
if has_parent
|
67
|
+
parent_class = @options.parent ? @options.parent.class : Association.class_from_namespace(ZendeskAPI::Helpers.modulize_string(namespace[0]))
|
68
|
+
parent_namespace = build_parent_namespace(parent_class, instance, options, original_options)
|
69
|
+
namespace[1..1] = parent_namespace if parent_namespace
|
70
|
+
namespace[0] = parent_class.resource_path
|
71
|
+
else
|
72
|
+
namespace[0] = @options.path || @options[:class].resource_path
|
73
|
+
end
|
74
|
+
|
75
|
+
if id = extract_id(instance, options, original_options)
|
76
|
+
namespace << id
|
77
|
+
end
|
78
|
+
|
79
|
+
namespace.join("/")
|
80
|
+
end
|
81
|
+
|
82
|
+
# Tries to place side loads onto given resources.
|
83
|
+
def side_load(resources, side_loads)
|
84
|
+
key = "#{options.name}_id"
|
85
|
+
plural_key = "#{Inflection.singular options.name.to_s}_ids"
|
86
|
+
|
87
|
+
resources.each do |resource|
|
88
|
+
if resource.key?(plural_key) # Grab associations from child_ids field on resource
|
89
|
+
side_load_from_child_ids(resource, side_loads, plural_key)
|
90
|
+
elsif resource.key?(key) || options.singular
|
91
|
+
side_load_from_child_or_parent_id(resource, side_loads, key)
|
92
|
+
else # Grab associations from parent_id field from multiple child resources
|
93
|
+
side_load_from_parent_id(resource, side_loads, key)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
# @return [Array<String>] ['ZendeskAPI', 'Voice', etc.. ]
|
101
|
+
def ignorable_namespace_strings
|
102
|
+
ZendeskAPI::DataNamespace.descendants.map { |klass| klass.to_s.split('::') }.flatten.uniq
|
103
|
+
end
|
104
|
+
|
105
|
+
def _side_load(resource, side_loads)
|
106
|
+
side_loads.map! do |side_load|
|
107
|
+
resource.send(:wrap_resource, side_load, options)
|
108
|
+
end
|
109
|
+
|
110
|
+
ZendeskAPI::Collection.new(resource.client, options[:class]).tap do |collection|
|
111
|
+
collection.replace(side_loads)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def side_load_from_parent_id(resource, side_loads, key)
|
116
|
+
key = "#{resource.class.singular_resource_name}_id"
|
117
|
+
|
118
|
+
resource.send("#{options.name}=", _side_load(resource, side_loads.select {|side_load|
|
119
|
+
side_load[key] == resource.id
|
120
|
+
}))
|
121
|
+
end
|
122
|
+
|
123
|
+
def side_load_from_child_ids(resource, side_loads, plural_key)
|
124
|
+
ids = resource.send(plural_key)
|
125
|
+
|
126
|
+
resource.send("#{options.name}=", _side_load(resource, side_loads.select {|side_load|
|
127
|
+
ids.include?(side_load[options.include_key])
|
128
|
+
}))
|
129
|
+
end
|
130
|
+
|
131
|
+
def side_load_from_child_or_parent_id(resource, side_loads, key)
|
132
|
+
# Either grab association from child_id field on resource or parent_id on child resource
|
133
|
+
if resource.key?(key)
|
134
|
+
id = resource.send(key)
|
135
|
+
include_key = options.include_key
|
136
|
+
else
|
137
|
+
id = resource.id
|
138
|
+
include_key = "#{resource.class.singular_resource_name}_id"
|
139
|
+
end
|
140
|
+
|
141
|
+
return unless id
|
142
|
+
|
143
|
+
side_load = side_loads.detect do |side_load|
|
144
|
+
id == side_load[include_key]
|
145
|
+
end
|
146
|
+
|
147
|
+
resource.send("#{options.name}=", side_load) if side_load
|
148
|
+
end
|
149
|
+
|
150
|
+
def build_parent_namespace(parent_class, instance, options, original_options)
|
151
|
+
path = @options.path
|
152
|
+
|
153
|
+
association_on_parent = parent_class.associations.detect { |a| a[:name] == @options[:name] }
|
154
|
+
association_on_parent ||= parent_class.associations.detect do |a|
|
155
|
+
!a[:inline] && a[:class] == @options[:class]
|
156
|
+
end
|
157
|
+
|
158
|
+
if association_on_parent
|
159
|
+
path ||= association_on_parent[:path]
|
160
|
+
path ||= association_on_parent[:name].to_s
|
161
|
+
end
|
162
|
+
|
163
|
+
path ||= @options[:class].resource_path
|
164
|
+
|
165
|
+
[
|
166
|
+
extract_parent_id(parent_class, instance, options, original_options),
|
167
|
+
path
|
168
|
+
]
|
169
|
+
end
|
170
|
+
|
171
|
+
def extract_parent_id(parent_class, instance, options, original_options)
|
172
|
+
parent_id_column = "#{parent_class.singular_resource_name}_id"
|
173
|
+
|
174
|
+
if @options.parent
|
175
|
+
@options.parent.id
|
176
|
+
elsif instance
|
177
|
+
instance.send(parent_id_column)
|
178
|
+
elsif options[parent_id_column]
|
179
|
+
original_options.delete(parent_id_column) || original_options.delete(parent_id_column.to_sym)
|
180
|
+
else
|
181
|
+
raise ArgumentError.new("#{@options[:class].resource_name} requires #{parent_id_column} or parent")
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
def extract_id(instance, options, original_options)
|
186
|
+
if options[:with_id] && !@options[:class].ancestors.include?(SingularResource)
|
187
|
+
if instance && instance.id
|
188
|
+
instance.id
|
189
|
+
elsif options[:id]
|
190
|
+
original_options.delete(:id) || original_options.delete("id")
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
@@ -0,0 +1,212 @@
|
|
1
|
+
require 'zendesk_api/helpers'
|
2
|
+
|
3
|
+
module ZendeskAPI
|
4
|
+
# This module holds association method for resources.
|
5
|
+
# Associations can be loaded in three ways:
|
6
|
+
# * Commonly used resources are automatically side-loaded server side and sent along with their parent object.
|
7
|
+
# * Associated resource ids are sent and are then loaded one-by-one into the parent collection.
|
8
|
+
# * The association is represented with Rails' nested association urls (such as tickets/:id/groups) and are loaded that way.
|
9
|
+
#
|
10
|
+
# @private
|
11
|
+
module Associations
|
12
|
+
def self.included(base)
|
13
|
+
base.extend ClassMethods
|
14
|
+
end
|
15
|
+
|
16
|
+
def wrap_resource(resource, class_level_association, options = {})
|
17
|
+
instance_association = Association.new(class_level_association.merge(:parent => self))
|
18
|
+
klass = class_level_association[:class]
|
19
|
+
|
20
|
+
case resource
|
21
|
+
when Hash
|
22
|
+
klass.new(@client, resource.merge(:association => instance_association))
|
23
|
+
when String, Integer
|
24
|
+
klass.new(@client, (options[:include_key] || :id) => resource, :association => instance_association)
|
25
|
+
else
|
26
|
+
resource.association = instance_association
|
27
|
+
resource
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# @private
|
32
|
+
module ClassMethods
|
33
|
+
def self.extended(klass)
|
34
|
+
klass.extend Has
|
35
|
+
klass.extend HasMany
|
36
|
+
end
|
37
|
+
|
38
|
+
def associations
|
39
|
+
@associations ||= []
|
40
|
+
end
|
41
|
+
|
42
|
+
def associated_with(name)
|
43
|
+
associations.inject([]) do |associated_with, association|
|
44
|
+
if association[:include] == name.to_s
|
45
|
+
associated_with.push(Association.new(association))
|
46
|
+
end
|
47
|
+
|
48
|
+
associated_with
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def build_association(klass, resource_name, options)
|
55
|
+
{
|
56
|
+
:class => klass,
|
57
|
+
:name => resource_name,
|
58
|
+
:inline => options.delete(:inline),
|
59
|
+
:path => options.delete(:path),
|
60
|
+
:include => (options.delete(:include) || klass.resource_name).to_s,
|
61
|
+
:include_key => (options.delete(:include_key) || :id).to_s,
|
62
|
+
:singular => options.delete(:singular),
|
63
|
+
:extensions => Array(options.delete(:extend))
|
64
|
+
}
|
65
|
+
end
|
66
|
+
|
67
|
+
def define_used(association)
|
68
|
+
define_method "#{association[:name]}_used?" do
|
69
|
+
!!instance_variable_get("@#{association[:name]}")
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
module Has
|
74
|
+
# Represents a parent-to-child association between resources. Options to pass in are: class, path.
|
75
|
+
# @param [Symbol] resource_name_or_class The underlying resource name or a class to get it from
|
76
|
+
# @param [Hash] class_level_options The options to pass to the method definition.
|
77
|
+
def has(resource_name_or_class, class_level_options = {})
|
78
|
+
if klass = class_level_options.delete(:class)
|
79
|
+
resource_name = resource_name_or_class
|
80
|
+
else
|
81
|
+
klass = resource_name_or_class
|
82
|
+
resource_name = klass.singular_resource_name
|
83
|
+
end
|
84
|
+
|
85
|
+
class_level_association = build_association(klass, resource_name, class_level_options)
|
86
|
+
class_level_association.merge!(:singular => true, :id_column => "#{resource_name}_id")
|
87
|
+
|
88
|
+
associations << class_level_association
|
89
|
+
|
90
|
+
define_used(class_level_association)
|
91
|
+
define_has_getter(class_level_association)
|
92
|
+
define_has_setter(class_level_association)
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
def define_has_getter(association)
|
98
|
+
klass = association[:class] # shorthand
|
99
|
+
|
100
|
+
define_method association[:name] do |*args|
|
101
|
+
instance_options = args.last.is_a?(Hash) ? args.pop : {}
|
102
|
+
|
103
|
+
# return if cached
|
104
|
+
cached = instance_variable_get("@#{association[:name]}")
|
105
|
+
return cached if cached && !instance_options[:reload]
|
106
|
+
|
107
|
+
# find and cache association
|
108
|
+
instance_association = Association.new(association.merge(:parent => self))
|
109
|
+
resource = if klass.respond_to?(:find) && resource_id = method_missing(association[:id_column])
|
110
|
+
klass.find(@client, :id => resource_id, :association => instance_association)
|
111
|
+
elsif found = method_missing(association[:name].to_sym)
|
112
|
+
wrap_resource(found, association, :include_key => association[:include_key])
|
113
|
+
elsif klass.superclass == DataResource && !association[:inline]
|
114
|
+
response = @client.connection.get(instance_association.generate_path(:with_parent => true))
|
115
|
+
klass.new(@client, response.body[klass.singular_resource_name].merge(:association => instance_association))
|
116
|
+
end
|
117
|
+
|
118
|
+
send("#{association[:id_column]}=", resource.id) if resource && has_key?(association[:id_column])
|
119
|
+
instance_variable_set("@#{association[:name]}", resource)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def define_has_setter(association)
|
124
|
+
define_method "#{association[:name]}=" do |resource|
|
125
|
+
resource = wrap_resource(resource, association)
|
126
|
+
send("#{association[:id_column]}=", resource.id) if has_key?(association[:id_column])
|
127
|
+
instance_variable_set("@#{association[:name]}", resource)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
module HasMany
|
133
|
+
# Represents a parent-to-children association between resources. Options to pass in are: class, path.
|
134
|
+
# @param [Symbol] resource_name_or_class The underlying resource name or class to get it from
|
135
|
+
# @param [Hash] class_level_options The options to pass to the method definition.
|
136
|
+
def has_many(resource_name_or_class, class_level_options = {})
|
137
|
+
if klass = class_level_options.delete(:class)
|
138
|
+
resource_name = resource_name_or_class
|
139
|
+
else
|
140
|
+
klass = resource_name_or_class
|
141
|
+
resource_name = klass.resource_name
|
142
|
+
end
|
143
|
+
|
144
|
+
class_level_association = build_association(klass, resource_name, class_level_options)
|
145
|
+
class_level_association.merge!(:singular => false, :id_column => "#{resource_name}_ids")
|
146
|
+
|
147
|
+
associations << class_level_association
|
148
|
+
|
149
|
+
define_used(class_level_association)
|
150
|
+
define_has_many_getter(class_level_association)
|
151
|
+
define_has_many_setter(class_level_association)
|
152
|
+
end
|
153
|
+
|
154
|
+
private
|
155
|
+
|
156
|
+
def define_has_many_getter(association)
|
157
|
+
klass = association[:class]
|
158
|
+
|
159
|
+
define_method association[:name] do |*args|
|
160
|
+
instance_opts = args.last.is_a?(Hash) ? args.pop : {}
|
161
|
+
|
162
|
+
# return if cached
|
163
|
+
cached = instance_variable_get("@#{association[:name]}")
|
164
|
+
return cached if cached && !instance_opts[:reload]
|
165
|
+
|
166
|
+
# find and cache association
|
167
|
+
instance_association = Association.new(association.merge(:parent => self))
|
168
|
+
singular_resource_name = Inflection.singular(association[:name].to_s)
|
169
|
+
|
170
|
+
resources = if (ids = method_missing("#{singular_resource_name}_ids")) && ids.any?
|
171
|
+
ids.map do |id|
|
172
|
+
klass.find(@client, :id => id, :association => instance_association)
|
173
|
+
end.compact
|
174
|
+
elsif (resources = method_missing(association[:name].to_sym)) && resources.any?
|
175
|
+
resources.map { |res| wrap_resource(res, association) }
|
176
|
+
else
|
177
|
+
[]
|
178
|
+
end
|
179
|
+
|
180
|
+
collection = ZendeskAPI::Collection.new(@client, klass, instance_opts.merge(:association => instance_association))
|
181
|
+
|
182
|
+
if association[:extensions].any?
|
183
|
+
collection.extend(*association[:extensions])
|
184
|
+
end
|
185
|
+
|
186
|
+
if resources.any?
|
187
|
+
collection.replace(resources)
|
188
|
+
end
|
189
|
+
|
190
|
+
send("#{association[:id_column]}=", resources.map(&:id)) if has_key?(association[:id_column])
|
191
|
+
instance_variable_set("@#{association[:name]}", collection)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
def define_has_many_setter(association)
|
196
|
+
define_method "#{association[:name]}=" do |resources|
|
197
|
+
if resources.is_a?(Array)
|
198
|
+
wrapped = resources.map { |attr| wrap_resource(attr, association) }
|
199
|
+
send(association[:name]).replace(wrapped)
|
200
|
+
else
|
201
|
+
resources.association = Association.new(association.merge(:parent => self))
|
202
|
+
instance_variable_set("@#{association[:name]}", resources)
|
203
|
+
end
|
204
|
+
|
205
|
+
send("#{association[:id_column]}=", resources.map(&:id)) if resources && has_key?(association[:id_column])
|
206
|
+
resource
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
@@ -0,0 +1,243 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
|
3
|
+
require 'zendesk_api/version'
|
4
|
+
require 'zendesk_api/sideloading'
|
5
|
+
require 'zendesk_api/configuration'
|
6
|
+
require 'zendesk_api/collection'
|
7
|
+
require 'zendesk_api/lru_cache'
|
8
|
+
require 'zendesk_api/silent_mash'
|
9
|
+
require 'zendesk_api/middleware/request/etag_cache'
|
10
|
+
require 'zendesk_api/middleware/request/retry'
|
11
|
+
require 'zendesk_api/middleware/request/raise_rate_limited'
|
12
|
+
require 'zendesk_api/middleware/request/upload'
|
13
|
+
require 'zendesk_api/middleware/request/encode_json'
|
14
|
+
require 'zendesk_api/middleware/request/url_based_access_token'
|
15
|
+
require 'zendesk_api/middleware/response/callback'
|
16
|
+
require 'zendesk_api/middleware/response/deflate'
|
17
|
+
require 'zendesk_api/middleware/response/gzip'
|
18
|
+
require 'zendesk_api/middleware/response/sanitize_response'
|
19
|
+
require 'zendesk_api/middleware/response/parse_iso_dates'
|
20
|
+
require 'zendesk_api/middleware/response/parse_json'
|
21
|
+
require 'zendesk_api/middleware/response/raise_error'
|
22
|
+
require 'zendesk_api/middleware/response/logger'
|
23
|
+
require 'zendesk_api/delegator'
|
24
|
+
|
25
|
+
module ZendeskAPI
|
26
|
+
# The top-level class that handles configuration and connection to the Zendesk API.
|
27
|
+
# Can also be used as an accessor to resource collections.
|
28
|
+
class Client
|
29
|
+
GZIP_EXCEPTIONS = [:em_http, :httpclient]
|
30
|
+
|
31
|
+
# @return [Configuration] Config instance
|
32
|
+
attr_reader :config
|
33
|
+
# @return [Array] Custom response callbacks
|
34
|
+
attr_reader :callbacks
|
35
|
+
|
36
|
+
# Handles resources such as 'tickets'. Any options are passed to the underlying collection, except reload which disregards
|
37
|
+
# memoization and creates a new Collection instance.
|
38
|
+
# @return [Collection] Collection instance for resource
|
39
|
+
def method_missing(method, *args, &block)
|
40
|
+
method = method.to_s
|
41
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
42
|
+
|
43
|
+
unless config.use_resource_cache
|
44
|
+
raise "Resource for #{method} does not exist" unless method_as_class(method)
|
45
|
+
return ZendeskAPI::Collection.new(self, method_as_class(method), options)
|
46
|
+
end
|
47
|
+
|
48
|
+
@resource_cache[method] ||= { :class => nil, :cache => ZendeskAPI::LRUCache.new }
|
49
|
+
if !options.delete(:reload) && (cached = @resource_cache[method][:cache].read(options.hash))
|
50
|
+
cached
|
51
|
+
else
|
52
|
+
@resource_cache[method][:class] ||= method_as_class(method)
|
53
|
+
raise "Resource for #{method} does not exist" unless @resource_cache[method][:class]
|
54
|
+
@resource_cache[method][:cache].write(options.hash, ZendeskAPI::Collection.new(self, @resource_cache[method][:class], options))
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def respond_to?(method, *args)
|
59
|
+
((cache = @resource_cache[method]) && cache[:class]) || !method_as_class(method).nil? || super
|
60
|
+
end
|
61
|
+
|
62
|
+
# Returns the current user (aka me)
|
63
|
+
# @return [ZendeskAPI::User] Current user or nil
|
64
|
+
def current_user(reload = false)
|
65
|
+
return @current_user if @current_user && !reload
|
66
|
+
@current_user = users.find(:id => 'me')
|
67
|
+
end
|
68
|
+
|
69
|
+
# Returns the current account
|
70
|
+
# @return [Hash] The attributes of the current account or nil
|
71
|
+
def current_account(reload = false)
|
72
|
+
return @current_account if @current_account && !reload
|
73
|
+
@current_account = SilentMash.new(connection.get('account/resolve').body)
|
74
|
+
end
|
75
|
+
|
76
|
+
# Returns the current locale
|
77
|
+
# @return [ZendeskAPI::Locale] Current locale or nil
|
78
|
+
def current_locale(reload = false)
|
79
|
+
return @locale if @locale && !reload
|
80
|
+
@locale = locales.find(:id => 'current')
|
81
|
+
end
|
82
|
+
|
83
|
+
# Creates a new {Client} instance and yields {#config}.
|
84
|
+
#
|
85
|
+
# Requires a block to be given.
|
86
|
+
#
|
87
|
+
# Does basic configuration constraints:
|
88
|
+
# * {Configuration#url} must be https unless {Configuration#allow_http} is set.
|
89
|
+
def initialize
|
90
|
+
raise ArgumentError, "block not given" unless block_given?
|
91
|
+
|
92
|
+
@config = ZendeskAPI::Configuration.new
|
93
|
+
yield config
|
94
|
+
|
95
|
+
@callbacks = []
|
96
|
+
@resource_cache = {}
|
97
|
+
|
98
|
+
check_url
|
99
|
+
|
100
|
+
config.retry = !!config.retry # nil -> false
|
101
|
+
|
102
|
+
set_raise_error_when_rated_limited
|
103
|
+
|
104
|
+
set_token_auth
|
105
|
+
|
106
|
+
set_default_logger
|
107
|
+
add_warning_callback
|
108
|
+
end
|
109
|
+
|
110
|
+
# Creates a connection if there is none, otherwise returns the existing connection.
|
111
|
+
#
|
112
|
+
# @return [Faraday::Connection] Faraday connection for the client
|
113
|
+
def connection
|
114
|
+
@connection ||= build_connection
|
115
|
+
return @connection
|
116
|
+
end
|
117
|
+
|
118
|
+
# Pushes a callback onto the stack. Callbacks are executed on responses, last in the Faraday middleware stack.
|
119
|
+
# @param [Proc] block The block to execute. Takes one parameter, env.
|
120
|
+
def insert_callback(&block)
|
121
|
+
@callbacks << block
|
122
|
+
end
|
123
|
+
|
124
|
+
# show a nice warning for people using the old style api
|
125
|
+
def self.check_deprecated_namespace_usage(attributes, name)
|
126
|
+
if attributes[name].is_a?(Hash)
|
127
|
+
raise "un-nest '#{name}' from the attributes"
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
ZendeskAPI::DataNamespace.descendants.each do |namespace|
|
132
|
+
delegator = ZendeskAPI::Helpers.snakecase_string(namespace.to_s.split("::").last)
|
133
|
+
define_method delegator do |*| # takes arguments, but doesn't do anything with them
|
134
|
+
Delegator.new(self)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
protected
|
139
|
+
|
140
|
+
# Called by {#connection} to build a connection. Can be overwritten in a
|
141
|
+
# subclass to add additional middleware and make other configuration
|
142
|
+
# changes.
|
143
|
+
#
|
144
|
+
# Uses middleware according to configuration options.
|
145
|
+
#
|
146
|
+
# Request logger if logger is not nil
|
147
|
+
#
|
148
|
+
# Retry middleware if retry is true
|
149
|
+
def build_connection
|
150
|
+
Faraday.new(config.options) do |builder|
|
151
|
+
# response
|
152
|
+
builder.use ZendeskAPI::Middleware::Response::RaiseError
|
153
|
+
builder.use ZendeskAPI::Middleware::Response::Callback, self
|
154
|
+
builder.use ZendeskAPI::Middleware::Response::Logger, config.logger if config.logger
|
155
|
+
builder.use ZendeskAPI::Middleware::Response::ParseIsoDates
|
156
|
+
builder.use ZendeskAPI::Middleware::Response::ParseJson
|
157
|
+
builder.use ZendeskAPI::Middleware::Response::SanitizeResponse
|
158
|
+
|
159
|
+
adapter = config.adapter || Faraday.default_adapter
|
160
|
+
|
161
|
+
unless GZIP_EXCEPTIONS.include?(adapter)
|
162
|
+
builder.use ZendeskAPI::Middleware::Response::Gzip
|
163
|
+
builder.use ZendeskAPI::Middleware::Response::Deflate
|
164
|
+
end
|
165
|
+
|
166
|
+
# request
|
167
|
+
if config.access_token && !config.url_based_access_token
|
168
|
+
builder.authorization("Bearer", config.access_token)
|
169
|
+
elsif config.access_token
|
170
|
+
builder.use ZendeskAPI::Middleware::Request::UrlBasedAccessToken, config.access_token
|
171
|
+
else
|
172
|
+
builder.use Faraday::Request::BasicAuthentication, config.username, config.password
|
173
|
+
end
|
174
|
+
|
175
|
+
if config.cache
|
176
|
+
builder.use ZendeskAPI::Middleware::Request::EtagCache, :cache => config.cache
|
177
|
+
end
|
178
|
+
|
179
|
+
builder.use ZendeskAPI::Middleware::Request::Upload
|
180
|
+
builder.request :multipart
|
181
|
+
builder.use ZendeskAPI::Middleware::Request::EncodeJson
|
182
|
+
|
183
|
+
# Should always be first in the stack
|
184
|
+
builder.use ZendeskAPI::Middleware::Request::Retry, :logger => config.logger, :retry_codes => config.retry_codes, :retry_on_exception => config.retry_on_exception if config.retry
|
185
|
+
if config.raise_error_when_rate_limited
|
186
|
+
builder.use ZendeskAPI::Middleware::Request::RaiseRateLimited, :logger => config.logger
|
187
|
+
end
|
188
|
+
|
189
|
+
builder.adapter(*adapter)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
private
|
194
|
+
|
195
|
+
def method_as_class(method)
|
196
|
+
klass_as_string = ZendeskAPI::Helpers.modulize_string(Inflection.singular(method.to_s.gsub(/\W/, '')))
|
197
|
+
ZendeskAPI::Association.class_from_namespace(klass_as_string)
|
198
|
+
end
|
199
|
+
|
200
|
+
def check_url
|
201
|
+
if !config.allow_http && config.url !~ /^https/
|
202
|
+
raise ArgumentError, "zendesk_api is ssl only; url must begin with https://"
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
def set_raise_error_when_rated_limited
|
207
|
+
config.raise_error_when_rate_limited = if config.retry
|
208
|
+
false
|
209
|
+
else
|
210
|
+
!!config.raise_error_when_rate_limited
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
def set_token_auth
|
215
|
+
return if config.password || config.token.nil?
|
216
|
+
|
217
|
+
if config.username.nil?
|
218
|
+
raise ArgumentError, "you need to provide a username when using API token auth"
|
219
|
+
else
|
220
|
+
config.password = config.token
|
221
|
+
config.username += "/token" unless config.username.end_with?("/token")
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
def set_default_logger
|
226
|
+
if config.logger.nil? || config.logger == true
|
227
|
+
require 'logger'
|
228
|
+
config.logger = Logger.new($stderr)
|
229
|
+
config.logger.level = Logger::WARN
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
def add_warning_callback
|
234
|
+
return unless logger = config.logger
|
235
|
+
|
236
|
+
insert_callback do |env|
|
237
|
+
if warning = env[:response_headers]["X-Zendesk-API-Warn"]
|
238
|
+
logger.warn "WARNING: #{warning}"
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|