discourse_zendesk_api 1.0.0
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 +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
|