KayakoClient 0.0.1b
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.
- data/lib/kayako_client.rb +36 -0
- data/lib/kayako_client/base.rb +98 -0
- data/lib/kayako_client/department.rb +31 -0
- data/lib/kayako_client/http/http.rb +217 -0
- data/lib/kayako_client/http/http_backend.rb +31 -0
- data/lib/kayako_client/http/http_client.rb +87 -0
- data/lib/kayako_client/http/http_response.rb +61 -0
- data/lib/kayako_client/http/net_http.rb +109 -0
- data/lib/kayako_client/mixins/api.rb +299 -0
- data/lib/kayako_client/mixins/attachment.rb +69 -0
- data/lib/kayako_client/mixins/authentication.rb +34 -0
- data/lib/kayako_client/mixins/client.rb +308 -0
- data/lib/kayako_client/mixins/logger.rb +29 -0
- data/lib/kayako_client/mixins/object.rb +456 -0
- data/lib/kayako_client/mixins/post_client.rb +42 -0
- data/lib/kayako_client/mixins/staff_visibility_api.rb +9 -0
- data/lib/kayako_client/mixins/ticket_api.rb +55 -0
- data/lib/kayako_client/mixins/ticket_client.rb +135 -0
- data/lib/kayako_client/mixins/user_visibility_api.rb +9 -0
- data/lib/kayako_client/staff.rb +25 -0
- data/lib/kayako_client/staff_group.rb +9 -0
- data/lib/kayako_client/ticket.rb +241 -0
- data/lib/kayako_client/ticket_attachment.rb +42 -0
- data/lib/kayako_client/ticket_count.rb +135 -0
- data/lib/kayako_client/ticket_custom_field.rb +110 -0
- data/lib/kayako_client/ticket_note.rb +105 -0
- data/lib/kayako_client/ticket_post.rb +61 -0
- data/lib/kayako_client/ticket_priority.rb +24 -0
- data/lib/kayako_client/ticket_status.rb +31 -0
- data/lib/kayako_client/ticket_time_track.rb +27 -0
- data/lib/kayako_client/ticket_type.rb +26 -0
- data/lib/kayako_client/user.rb +61 -0
- data/lib/kayako_client/user_group.rb +12 -0
- data/lib/kayako_client/user_organization.rb +23 -0
- data/lib/kayako_client/xml/lib_xml.rb +86 -0
- data/lib/kayako_client/xml/rexml_document.rb +77 -0
- data/lib/kayako_client/xml/xml.rb +63 -0
- data/lib/kayako_client/xml/xml_backend.rb +42 -0
- metadata +105 -0
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'tempfile'
|
2
|
+
|
3
|
+
module KayakoClient
|
4
|
+
module Attachment
|
5
|
+
|
6
|
+
def initialize(*args)
|
7
|
+
if args.last.is_a?(Hash) && args.last[:file]
|
8
|
+
self.file = args.last.delete(:file)
|
9
|
+
end
|
10
|
+
super(*args)
|
11
|
+
end
|
12
|
+
|
13
|
+
def file=(arg)
|
14
|
+
unless self.class.properties[:contents] && self.class.properties[:file_name]
|
15
|
+
raise RuntimeError, "missing :contents and/or :file_name properties"
|
16
|
+
end
|
17
|
+
raise ArgumentError, "object properties are read-only" if self.class.embedded?
|
18
|
+
if self.class.options[:contents] && self.class.options[:contents][:readonly]
|
19
|
+
raise ArgumentError, "property :contents is read-only"
|
20
|
+
end
|
21
|
+
if self.class.options[:contents] && self.class.options[:contents][:new] && !new?
|
22
|
+
raise ArgumentError, "property :contents cannot be changed"
|
23
|
+
end
|
24
|
+
case arg
|
25
|
+
when File, Tempfile
|
26
|
+
arg.rewind
|
27
|
+
arg.binmode
|
28
|
+
@contents = arg.read
|
29
|
+
when String
|
30
|
+
raise ArgumentError, "file path can't be empty" if arg.empty?
|
31
|
+
@contents = File.open(arg, 'rb') do |f|
|
32
|
+
f.read
|
33
|
+
end
|
34
|
+
else
|
35
|
+
raise ArgumentError, "invalid argument must be either File or path"
|
36
|
+
end
|
37
|
+
changes(:contents)
|
38
|
+
if !self.class.options[:file_name] ||
|
39
|
+
(!self.class.options[:contents][:readonly] && (!self.class.options[:contents][:new] || new?))
|
40
|
+
case arg
|
41
|
+
when File
|
42
|
+
@file_name = File.basename(arg.path)
|
43
|
+
when String
|
44
|
+
@file_name = File.basename(arg)
|
45
|
+
end
|
46
|
+
changes(:file_name)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def file
|
51
|
+
unless self.class.properties[:contents] && self.class.properties[:file_name]
|
52
|
+
raise RuntimeError, "missing :contents and/or :file_name properties"
|
53
|
+
end
|
54
|
+
if defined?(@file)
|
55
|
+
@file
|
56
|
+
elsif contents
|
57
|
+
raise RuntimeError, "not a remote file" unless id && !new?
|
58
|
+
@file = Tempfile.new(file_name.split('.').first || 'kayako_attachment')
|
59
|
+
@file.write(contents)
|
60
|
+
@file.flush
|
61
|
+
@file.rewind
|
62
|
+
@file
|
63
|
+
else
|
64
|
+
nil
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'base64'
|
2
|
+
|
3
|
+
module KayakoClient
|
4
|
+
module Authentication
|
5
|
+
|
6
|
+
def self.included(base)
|
7
|
+
base.extend(ClassMethods)
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
|
12
|
+
def salt(count = 32)
|
13
|
+
pass = ''
|
14
|
+
chars = ('a'..'z').to_a + ('A'..'Z').to_a + ('0'..'9').to_a
|
15
|
+
count.times do |i|
|
16
|
+
pass << chars[rand(chars.size - 1)]
|
17
|
+
end
|
18
|
+
pass
|
19
|
+
end
|
20
|
+
|
21
|
+
def signature(salt, secret = nil)
|
22
|
+
begin
|
23
|
+
require 'openssl'
|
24
|
+
hash = OpenSSL::HMAC::digest(OpenSSL::Digest::SHA256.new, secret || secret_key, salt)
|
25
|
+
rescue LoadError, NameError
|
26
|
+
hash = HMAC::sha256(secret || secret_key, salt)
|
27
|
+
end
|
28
|
+
Base64.encode64(hash).strip
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,308 @@
|
|
1
|
+
module KayakoClient
|
2
|
+
module Client
|
3
|
+
|
4
|
+
def departments(options = {})
|
5
|
+
KayakoClient::Department.all(options.merge(inherited_options))
|
6
|
+
end
|
7
|
+
|
8
|
+
def get_department(id = :all, options = {})
|
9
|
+
KayakoClient::Department.get(id, options.merge(inherited_options))
|
10
|
+
end
|
11
|
+
|
12
|
+
alias_method :find_department, :get_department
|
13
|
+
|
14
|
+
def post_department(options = {})
|
15
|
+
KayakoClient::Department.post(options.merge(inherited_options))
|
16
|
+
end
|
17
|
+
|
18
|
+
alias_method :create_department, :post_department
|
19
|
+
|
20
|
+
def delete_department(id, options = {})
|
21
|
+
KayakoClient::Department.delete(id, options.merge(inherited_options))
|
22
|
+
end
|
23
|
+
|
24
|
+
alias_method :destroy_department, :delete_department
|
25
|
+
|
26
|
+
|
27
|
+
def staff(options = {})
|
28
|
+
KayakoClient::Staff.all(options.merge(inherited_options))
|
29
|
+
end
|
30
|
+
|
31
|
+
alias_method :staff_users, :staff
|
32
|
+
|
33
|
+
def get_staff(id = :all, options = {})
|
34
|
+
KayakoClient::Staff.get(id, options.merge(inherited_options))
|
35
|
+
end
|
36
|
+
|
37
|
+
alias_method :find_staff, :get_staff
|
38
|
+
|
39
|
+
def post_staff(options = {})
|
40
|
+
KayakoClient::Staff.post(options.merge(inherited_options))
|
41
|
+
end
|
42
|
+
|
43
|
+
alias_method :create_staff, :post_staff
|
44
|
+
|
45
|
+
def delete_staff(id, options = {})
|
46
|
+
KayakoClient::Staff.delete(id, options.merge(inherited_options))
|
47
|
+
end
|
48
|
+
|
49
|
+
alias_method :destroy_staff, :delete_staff
|
50
|
+
|
51
|
+
|
52
|
+
def staff_groups(options = {})
|
53
|
+
KayakoClient::StaffGroup.all(options.merge(inherited_options))
|
54
|
+
end
|
55
|
+
|
56
|
+
def get_staff_group(id = :all, options = {})
|
57
|
+
KayakoClient::StaffGroup.get(id, options.merge(inherited_options))
|
58
|
+
end
|
59
|
+
|
60
|
+
alias_method :find_staff_group, :get_staff_group
|
61
|
+
|
62
|
+
def post_staff_group(options = {})
|
63
|
+
KayakoClient::StaffGroup.post(options.merge(inherited_options))
|
64
|
+
end
|
65
|
+
|
66
|
+
alias_method :create_staff_group, :post_staff_group
|
67
|
+
|
68
|
+
def delete_staff_group(id, options = {})
|
69
|
+
KayakoClient::StaffGroup.delete(id, options.merge(inherited_options))
|
70
|
+
end
|
71
|
+
|
72
|
+
alias_method :destroy_staff_group, :delete_staff_group
|
73
|
+
|
74
|
+
|
75
|
+
def tickets(*args)
|
76
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
77
|
+
args << options.merge(inherited_options)
|
78
|
+
KayakoClient::Ticket.all(*args)
|
79
|
+
end
|
80
|
+
|
81
|
+
def get_ticket(id = :all, options = {})
|
82
|
+
KayakoClient::Ticket.get(id, options.merge(inherited_options))
|
83
|
+
end
|
84
|
+
|
85
|
+
alias_method :find_ticket, :get_ticket
|
86
|
+
|
87
|
+
def ticket_search(query, flags = KayakoClient::Ticket::SEARCH_CONTENTS, options = {})
|
88
|
+
KayakoClient::Ticket.search(query, flags, options.merge(inherited_options))
|
89
|
+
end
|
90
|
+
|
91
|
+
def post_ticket(options = {})
|
92
|
+
KayakoClient::Ticket.post(options.merge(inherited_options))
|
93
|
+
end
|
94
|
+
|
95
|
+
alias_method :create_ticket, :post_ticket
|
96
|
+
|
97
|
+
def delete_ticket(id, options = {})
|
98
|
+
KayakoClient::Ticket.delete(id, options.merge(inherited_options))
|
99
|
+
end
|
100
|
+
|
101
|
+
alias_method :destroy_ticket, :delete_ticket
|
102
|
+
|
103
|
+
|
104
|
+
def ticket_attachments(ticket, options = {})
|
105
|
+
KayakoClient::TicketAttachment.all(ticket, options.merge(inherited_options))
|
106
|
+
end
|
107
|
+
|
108
|
+
def get_ticket_attachment(ticket, id, options = {})
|
109
|
+
KayakoClient::TicketAttachment.get(ticket, id, options.merge(inherited_options))
|
110
|
+
end
|
111
|
+
|
112
|
+
alias_method :find_ticket_attachment, :get_ticket_attachment
|
113
|
+
|
114
|
+
def post_ticket_attachment(options = {})
|
115
|
+
KayakoClient::TicketAttachment.post(options.merge(inherited_options))
|
116
|
+
end
|
117
|
+
|
118
|
+
alias_method :create_ticket_attachment, :post_ticket_attachment
|
119
|
+
|
120
|
+
def delete_ticket_attachment(ticket, id, options = {})
|
121
|
+
KayakoClient::TicketAttachment.delete(ticket, id, options.merge(inherited_options))
|
122
|
+
end
|
123
|
+
|
124
|
+
alias_method :destroy_ticket_attachment, :delete_ticket_attachment
|
125
|
+
|
126
|
+
|
127
|
+
def ticket_count(options = {})
|
128
|
+
KayakoClient::TicketCount.get(options.merge(inherited_options))
|
129
|
+
end
|
130
|
+
|
131
|
+
|
132
|
+
def ticket_custom_fields(ticket, options = {})
|
133
|
+
KayakoClient::TicketCustomField.get(ticket, options.merge(inherited_options))
|
134
|
+
end
|
135
|
+
|
136
|
+
|
137
|
+
def ticket_notes(ticket, options = {})
|
138
|
+
KayakoClient::TicketNote.all(ticket, options.merge(inherited_options))
|
139
|
+
end
|
140
|
+
|
141
|
+
def get_ticket_note(ticket, id, options = {})
|
142
|
+
KayakoClient::TicketNote.get(ticket, id, options.merge(inherited_options))
|
143
|
+
end
|
144
|
+
|
145
|
+
alias_method :find_ticket_note, :get_ticket_note
|
146
|
+
|
147
|
+
def post_ticket_note(options = {})
|
148
|
+
KayakoClient::TicketNote.post(options.merge(inherited_options))
|
149
|
+
end
|
150
|
+
|
151
|
+
alias_method :create_ticket_note, :post_ticket_note
|
152
|
+
|
153
|
+
def delete_ticket_note(ticket, id, options = {})
|
154
|
+
KayakoClient::TicketNote.delete(ticket, id, options.merge(inherited_options))
|
155
|
+
end
|
156
|
+
|
157
|
+
alias_method :destroy_ticket_note, :delete_ticket_note
|
158
|
+
|
159
|
+
|
160
|
+
def ticket_posts(ticket, options = {})
|
161
|
+
KayakoClient::TicketPost.all(ticket, options.merge(inherited_options))
|
162
|
+
end
|
163
|
+
|
164
|
+
def get_ticket_post(ticket, id, options = {})
|
165
|
+
KayakoClient::TicketPost.get(ticket, id, options.merge(inherited_options))
|
166
|
+
end
|
167
|
+
|
168
|
+
alias_method :find_ticket_post, :get_ticket_post
|
169
|
+
|
170
|
+
def post_ticket_post(options = {})
|
171
|
+
KayakoClient::TicketPost.post(options.merge(inherited_options))
|
172
|
+
end
|
173
|
+
|
174
|
+
alias_method :create_ticket_post, :post_ticket_post
|
175
|
+
|
176
|
+
def delete_ticket_post(ticket, id, options = {})
|
177
|
+
KayakoClient::TicketPost.delete(ticket, id, options.merge(inherited_options))
|
178
|
+
end
|
179
|
+
|
180
|
+
alias_method :destroy_ticket_post, :delete_ticket_post
|
181
|
+
|
182
|
+
|
183
|
+
def ticket_priorities(options = {})
|
184
|
+
KayakoClient::TicketPriority.all(options.merge(inherited_options))
|
185
|
+
end
|
186
|
+
|
187
|
+
def get_ticket_priority(id = :all, options = {})
|
188
|
+
KayakoClient::TicketPriority.get(id, options.merge(inherited_options))
|
189
|
+
end
|
190
|
+
|
191
|
+
alias_method :find_ticket_priority, :get_ticket_priority
|
192
|
+
|
193
|
+
|
194
|
+
def ticket_statuses(options = {})
|
195
|
+
KayakoClient::TicketStatus.all(options.merge(inherited_options))
|
196
|
+
end
|
197
|
+
|
198
|
+
def get_ticket_status(id = :all, options = {})
|
199
|
+
KayakoClient::TicketStatus.get(id, options.merge(inherited_options))
|
200
|
+
end
|
201
|
+
|
202
|
+
alias_method :find_ticket_status, :get_ticket_status
|
203
|
+
|
204
|
+
|
205
|
+
def ticket_time_tracks(ticket, options = {})
|
206
|
+
KayakoClient::TicketTimeTrack.all(ticket, options.merge(inherited_options))
|
207
|
+
end
|
208
|
+
|
209
|
+
def get_ticket_time_track(ticket, id, options = {})
|
210
|
+
KayakoClient::TicketTimeTrack.get(ticket, id, options.merge(inherited_options))
|
211
|
+
end
|
212
|
+
|
213
|
+
alias_method :find_ticket_time_track, :get_ticket_time_track
|
214
|
+
|
215
|
+
def post_ticket_time_track(options = {})
|
216
|
+
KayakoClient::TicketTimeTrack.post(options.merge(inherited_options))
|
217
|
+
end
|
218
|
+
|
219
|
+
alias_method :create_ticket_time_track, :post_ticket_time_track
|
220
|
+
|
221
|
+
def delete_ticket_time_track(ticket, id, options = {})
|
222
|
+
KayakoClient::TicketTimeTrack.delete(ticket, id, options.merge(inherited_options))
|
223
|
+
end
|
224
|
+
|
225
|
+
alias_method :destroy_ticket_time_track, :delete_ticket_time_track
|
226
|
+
|
227
|
+
|
228
|
+
def ticket_types(options = {})
|
229
|
+
KayakoClient::TicketType.all(options.merge(inherited_options))
|
230
|
+
end
|
231
|
+
|
232
|
+
def get_ticket_type(id = :all, options = {})
|
233
|
+
KayakoClient::TicketType.get(id, options.merge(inherited_options))
|
234
|
+
end
|
235
|
+
|
236
|
+
alias_method :find_ticket_type, :get_ticket_type
|
237
|
+
|
238
|
+
|
239
|
+
def users(marker = nil, limit = nil, options = {})
|
240
|
+
KayakoClient::User.all(marker, limit, options.merge(inherited_options))
|
241
|
+
end
|
242
|
+
|
243
|
+
def get_user(id = :all, options = {})
|
244
|
+
KayakoClient::User.get(id, options.merge(inherited_options))
|
245
|
+
end
|
246
|
+
|
247
|
+
alias_method :find_user, :get_user
|
248
|
+
|
249
|
+
def post_user(options = {})
|
250
|
+
KayakoClient::User.post(options.merge(inherited_options))
|
251
|
+
end
|
252
|
+
|
253
|
+
alias_method :create_user, :post_user
|
254
|
+
|
255
|
+
def delete_user(id, options = {})
|
256
|
+
KayakoClient::User.delete(id, options.merge(inherited_options))
|
257
|
+
end
|
258
|
+
|
259
|
+
alias_method :destroy_user, :delete_user
|
260
|
+
|
261
|
+
|
262
|
+
def user_groups(options = {})
|
263
|
+
KayakoClient::UserGroup.all(options.merge(inherited_options))
|
264
|
+
end
|
265
|
+
|
266
|
+
def get_user_group(id = :all, options = {})
|
267
|
+
KayakoClient::UserGroup.get(id, options.merge(inherited_options))
|
268
|
+
end
|
269
|
+
|
270
|
+
alias_method :find_user_group, :get_user_group
|
271
|
+
|
272
|
+
def post_user_group(options = {})
|
273
|
+
KayakoClient::UserGroup.post(options.merge(inherited_options))
|
274
|
+
end
|
275
|
+
|
276
|
+
alias_method :create_user_group, :post_user_group
|
277
|
+
|
278
|
+
def delete_user_group(id, options = {})
|
279
|
+
KayakoClient::UserGroup.delete(id, options.merge(inherited_options))
|
280
|
+
end
|
281
|
+
|
282
|
+
alias_method :destroy_user_group, :delete_user_group
|
283
|
+
|
284
|
+
|
285
|
+
def user_organizations(options = {})
|
286
|
+
KayakoClient::UserOrganization.all(options.merge(inherited_options))
|
287
|
+
end
|
288
|
+
|
289
|
+
def get_user_organization(id = :all, options = {})
|
290
|
+
KayakoClient::UserOrganization.get(id, options.merge(inherited_options))
|
291
|
+
end
|
292
|
+
|
293
|
+
alias_method :find_user_organization, :get_user_organization
|
294
|
+
|
295
|
+
def post_user_organization(options = {})
|
296
|
+
KayakoClient::UserOrganization.post(options.merge(inherited_options))
|
297
|
+
end
|
298
|
+
|
299
|
+
alias_method :create_user_organization, :post_user_organization
|
300
|
+
|
301
|
+
def delete_user_organization(id, options = {})
|
302
|
+
KayakoClient::UserOrganization.delete(id, options.merge(inherited_options))
|
303
|
+
end
|
304
|
+
|
305
|
+
alias_method :destroy_user_organization, :delete_user_organization
|
306
|
+
|
307
|
+
end
|
308
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module KayakoClient
|
2
|
+
module Logger
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
base.extend(ClassMethods)
|
6
|
+
end
|
7
|
+
|
8
|
+
def logger=(log)
|
9
|
+
@logger = log
|
10
|
+
end
|
11
|
+
|
12
|
+
def logger
|
13
|
+
@logger ||= self.class.logger
|
14
|
+
end
|
15
|
+
|
16
|
+
module ClassMethods
|
17
|
+
|
18
|
+
def logger=(log)
|
19
|
+
@@logger = log
|
20
|
+
end
|
21
|
+
|
22
|
+
def logger
|
23
|
+
@@logger ||= nil
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,456 @@
|
|
1
|
+
require 'base64'
|
2
|
+
|
3
|
+
module KayakoClient
|
4
|
+
module Object
|
5
|
+
|
6
|
+
PROPERTY_TYPES = [ :integer,
|
7
|
+
:string,
|
8
|
+
:symbol,
|
9
|
+
:boolean,
|
10
|
+
:date,
|
11
|
+
:object,
|
12
|
+
:binary
|
13
|
+
].freeze
|
14
|
+
|
15
|
+
COMMON_OPTIONS = [ :in,
|
16
|
+
:required,
|
17
|
+
:condition,
|
18
|
+
:readonly,
|
19
|
+
:new
|
20
|
+
].freeze
|
21
|
+
|
22
|
+
OPTIONS = { :integer => [ :range ],
|
23
|
+
:object => [ :class ]
|
24
|
+
}.freeze
|
25
|
+
|
26
|
+
def self.included(base)
|
27
|
+
base.extend(ClassMethods)
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_i
|
31
|
+
instance_variable_defined?(:@id) ? instance_variable_get(:@id).to_i : 0
|
32
|
+
end
|
33
|
+
|
34
|
+
def []=(name, value)
|
35
|
+
raise ArgumentError, "object properties are read-only" if self.class.embedded?
|
36
|
+
raise ArgumentError, "property :#{name} does not exist" unless self.class.properties.include?(name.to_sym)
|
37
|
+
if !self.class.options[name.to_sym] || !self.class.options[name.to_sym][:readonly]
|
38
|
+
if self.class.options[name.to_sym] && self.class.options[name.to_sym][:new] && !new?
|
39
|
+
raise ArgumentError, "property :#{name} cannot be changed"
|
40
|
+
end
|
41
|
+
changes(name.to_sym)
|
42
|
+
@associated.delete(name.to_sym)
|
43
|
+
value = assign(self.class.properties[name.to_sym], value, self.class.options[name.to_sym] ? self.class.options[name.to_sym] : {})
|
44
|
+
instance_variable_set("@#{name}", value)
|
45
|
+
else
|
46
|
+
raise ArgumentError, "property :#{name} is read-only"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def [](name)
|
51
|
+
if self.class.properties.include?(name.to_sym)
|
52
|
+
send("#{name}")
|
53
|
+
else
|
54
|
+
nil
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def errors
|
59
|
+
@errors ||= {}
|
60
|
+
end
|
61
|
+
|
62
|
+
def properties
|
63
|
+
self.class.properties.keys
|
64
|
+
end
|
65
|
+
|
66
|
+
def changed?
|
67
|
+
!changes.empty?
|
68
|
+
end
|
69
|
+
|
70
|
+
def new?
|
71
|
+
if defined?(@new)
|
72
|
+
@new
|
73
|
+
else
|
74
|
+
@new = true
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def has_errors?
|
79
|
+
errors.size > 0
|
80
|
+
end
|
81
|
+
|
82
|
+
def loaded!
|
83
|
+
@changes = []
|
84
|
+
@new = false
|
85
|
+
end
|
86
|
+
|
87
|
+
module ClassMethods
|
88
|
+
|
89
|
+
def embedded
|
90
|
+
@embedded ||= true
|
91
|
+
end
|
92
|
+
|
93
|
+
def embedded?
|
94
|
+
@embedded ||= false
|
95
|
+
end
|
96
|
+
|
97
|
+
def supports(*args)
|
98
|
+
args.each do |method|
|
99
|
+
if %w{all get put post delete}.include?(method.to_s)
|
100
|
+
@supported_methods ||= []
|
101
|
+
@supported_methods << method.to_sym
|
102
|
+
else
|
103
|
+
logger.warn "ignored unsupported method :#{method}" if logger
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def support?(method)
|
109
|
+
unless embedded?
|
110
|
+
defined?(@supported_methods) ? @supported_methods.include?(method) : true
|
111
|
+
else
|
112
|
+
false
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def path(path = nil)
|
117
|
+
if path
|
118
|
+
@path = path
|
119
|
+
else
|
120
|
+
@path ||= '/' + self.superclass.name.split('::').last + '/' + self.name.split('::').last
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def property(name, type, options = {})
|
125
|
+
if (type.is_a?(Array) && type.size == 1 && PROPERTY_TYPES.include?(type.first)) || PROPERTY_TYPES.include?(type)
|
126
|
+
init_variables
|
127
|
+
@properties[name] = type # save original type
|
128
|
+
type = type.first if type.is_a?(Array)
|
129
|
+
get_alias = options.delete(:get) if options[:get]
|
130
|
+
set_alias = options.delete(:set) if options[:set]
|
131
|
+
if (name.to_s.include?(?_))
|
132
|
+
altname = name.to_s.gsub(%r{_}, '')
|
133
|
+
get_alias = altname unless get_alias
|
134
|
+
set_alias = altname unless set_alias
|
135
|
+
end
|
136
|
+
# check options
|
137
|
+
options.each do |option, value|
|
138
|
+
if COMMON_OPTIONS.include?(option) || (OPTIONS.include?(type) && OPTIONS[type].include?(option))
|
139
|
+
@options[name] ||= {}
|
140
|
+
@options[name][option] = value
|
141
|
+
else
|
142
|
+
raise ArgumentError, "unsupported option: #{option}"
|
143
|
+
end
|
144
|
+
end
|
145
|
+
# check if :class was specified for :object
|
146
|
+
if type == :object
|
147
|
+
@options[name] ||= {}
|
148
|
+
@options[name][:readonly] = true
|
149
|
+
raise ArgumentError, ":object requires :class" unless @options[name][:class]
|
150
|
+
end
|
151
|
+
# define setter and getter methods
|
152
|
+
if !embedded? && (!@options[name] || !@options[name][:readonly])
|
153
|
+
define_method("#{name}=") do |value|
|
154
|
+
if self.class.options[name] && self.class.options[name][:new] && !new?
|
155
|
+
raise ArgumentError, "property :#{name} cannot be changed"
|
156
|
+
end
|
157
|
+
changes(name)
|
158
|
+
@associated.delete(name)
|
159
|
+
value = assign(self.class.properties[name], value, self.class.options[name] ? self.class.options[name] : {})
|
160
|
+
instance_variable_set("@#{name}", value)
|
161
|
+
end
|
162
|
+
if set_alias
|
163
|
+
alias_method("#{set_alias}=", "#{name}=")
|
164
|
+
@map[name] = set_alias.to_sym
|
165
|
+
end
|
166
|
+
end
|
167
|
+
define_method(name) do
|
168
|
+
instance_variable_defined?("@#{name}") ? instance_variable_get("@#{name}") : nil
|
169
|
+
end
|
170
|
+
alias_method("#{name}?", name) if type == :boolean
|
171
|
+
if get_alias
|
172
|
+
alias_method(get_alias, name)
|
173
|
+
@aliases[get_alias.to_sym] = name
|
174
|
+
end
|
175
|
+
else
|
176
|
+
raise ArgumentError, "unsupported type: #{type.inspect}"
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def associate(name, property, object)
|
181
|
+
unless @properties.include?(property)
|
182
|
+
raise ArgumentError, "undefined property: #{property}; use 'property :#{property} ...' first"
|
183
|
+
end
|
184
|
+
klass = object.is_a?(Class) ? object : KayakoClient.const_get(object)
|
185
|
+
# method for access to associated object
|
186
|
+
define_method(name) do
|
187
|
+
if @associated.has_key?(property)
|
188
|
+
@associated[property]
|
189
|
+
elsif instance_variable_defined?("@#{property}")
|
190
|
+
id = instance_variable_get("@#{property}")
|
191
|
+
if id.is_a?(Array)
|
192
|
+
@associated[property] = id.inject([]) do |array, i|
|
193
|
+
array << klass.get(i.to_i, inherited_options)
|
194
|
+
array
|
195
|
+
end
|
196
|
+
elsif id.respond_to?('to_i') && id.to_i > 0
|
197
|
+
@associated[property] = klass.get(id.to_i, inherited_options)
|
198
|
+
else
|
199
|
+
@associated[property] = nil
|
200
|
+
end
|
201
|
+
else
|
202
|
+
@associated[property] = nil
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
def check_conditions(params)
|
208
|
+
errors = params.delete(:errors)
|
209
|
+
return unless errors
|
210
|
+
options.each do |property, option|
|
211
|
+
if params[property]
|
212
|
+
if option[:condition] && option[:condition].is_a?(Hash)
|
213
|
+
option[:condition].each do |name, value|
|
214
|
+
errors[property] = "condition not met" unless params[name] == value
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
def require_properties(method, params)
|
222
|
+
options.each do |property, option|
|
223
|
+
next if params[property]
|
224
|
+
if option[:required]
|
225
|
+
if (option[:required].is_a?(Symbol) && option[:required] == method) ||
|
226
|
+
(option[:required].is_a?(Array) && option[:required].include?(method))
|
227
|
+
raise ArgumentError, "missing :#{property}"
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
def validate(params)
|
234
|
+
errors = params.delete(:errors)
|
235
|
+
params.inject({}) do |hash, (property, value)|
|
236
|
+
if properties.include?(property)
|
237
|
+
begin
|
238
|
+
name = map[property] ? map[property] : property
|
239
|
+
hash[name] = convert(properties[property], value, options[property] ? options[property] : {})
|
240
|
+
rescue => error
|
241
|
+
errors[property] = error.message if errors
|
242
|
+
end
|
243
|
+
else
|
244
|
+
logger.warn "skipping validation of unknown property: #{property}" if logger
|
245
|
+
end
|
246
|
+
hash
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
def convert(type, value, options = {})
|
251
|
+
raise "property is readonly" if options[:readonly]
|
252
|
+
if type.is_a?(Array)
|
253
|
+
type = type.first
|
254
|
+
if value.is_a?(Hash) && value.size == 1
|
255
|
+
value = value.values.first
|
256
|
+
end
|
257
|
+
value = [ value ] unless value.is_a?(Array)
|
258
|
+
value.map! do |item|
|
259
|
+
convert_value(type, item, options)
|
260
|
+
end
|
261
|
+
else
|
262
|
+
value = convert_value(type, value, options)
|
263
|
+
end
|
264
|
+
value
|
265
|
+
end
|
266
|
+
|
267
|
+
def convert_value(type, value, options = {})
|
268
|
+
if options[:in] && options[:in].is_a?(Array)
|
269
|
+
raise "not in list of allowed values" unless options[:in].include?(value)
|
270
|
+
end
|
271
|
+
case type
|
272
|
+
when :integer
|
273
|
+
if options[:range] && options[:range].is_a?(Range)
|
274
|
+
raise "out of range" unless options[:range].include?(value)
|
275
|
+
end
|
276
|
+
result = value
|
277
|
+
when :symbol
|
278
|
+
result = value.to_s
|
279
|
+
when :boolean
|
280
|
+
result = (value == true) ? 1 : 0
|
281
|
+
when :date
|
282
|
+
result = value ? value.to_i : 0
|
283
|
+
when :object
|
284
|
+
raise RuntimeError, ":object cannot be used as a parameter"
|
285
|
+
when :binary
|
286
|
+
result = Base64.encode64(value).strip
|
287
|
+
else
|
288
|
+
result = value
|
289
|
+
end
|
290
|
+
result
|
291
|
+
end
|
292
|
+
|
293
|
+
def properties
|
294
|
+
@properties
|
295
|
+
end
|
296
|
+
|
297
|
+
def aliases
|
298
|
+
@aliases
|
299
|
+
end
|
300
|
+
|
301
|
+
def options
|
302
|
+
@options
|
303
|
+
end
|
304
|
+
|
305
|
+
def map
|
306
|
+
@map
|
307
|
+
end
|
308
|
+
|
309
|
+
private
|
310
|
+
|
311
|
+
def init_variables
|
312
|
+
@properties ||= {}
|
313
|
+
@aliases ||= {}
|
314
|
+
@options ||= {}
|
315
|
+
@map ||= {}
|
316
|
+
end
|
317
|
+
|
318
|
+
end
|
319
|
+
|
320
|
+
private
|
321
|
+
|
322
|
+
def clean
|
323
|
+
@associated = {}
|
324
|
+
self.class.properties.each do |property, type|
|
325
|
+
if instance_variable_defined?("@#{property}")
|
326
|
+
remove_instance_variable("@#{property}")
|
327
|
+
end
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
def import(options = {})
|
332
|
+
if options.size == 1 && self.class.properties.size > 1
|
333
|
+
values = options.values.first
|
334
|
+
options = values if values.is_a?(Hash)
|
335
|
+
end
|
336
|
+
return if options.empty?
|
337
|
+
options.each do |property, value|
|
338
|
+
name = self.class.aliases.include?(property) ? self.class.aliases[property] : property
|
339
|
+
if self.class.properties.include?(name)
|
340
|
+
unless self.class.embedded? || (self.class.options[name] && self.class.options[name][:readonly])
|
341
|
+
changes(name)
|
342
|
+
end
|
343
|
+
value = assign(self.class.properties[name], value, self.class.options[name] ? self.class.options[name] : {})
|
344
|
+
instance_variable_set("@#{name}", value)
|
345
|
+
else
|
346
|
+
logger.debug "unsupported property :#{property}" if logger
|
347
|
+
end
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
def validate(method, params)
|
352
|
+
end
|
353
|
+
|
354
|
+
def check_conditions(params)
|
355
|
+
@errors ||= {}
|
356
|
+
self.class.options.each do |property, options|
|
357
|
+
# check :condition
|
358
|
+
if params[property]
|
359
|
+
if options[:condition] && options[:condition].is_a?(Hash)
|
360
|
+
options[:condition].each do |name, value|
|
361
|
+
param = params[name] || (instance_variable_defined?("@#{name}") ? instance_variable_get("@#{name}") : nil)
|
362
|
+
@errors[property] = "condition not met" unless param == value
|
363
|
+
end
|
364
|
+
end
|
365
|
+
end
|
366
|
+
# check :new
|
367
|
+
if params.has_key?(property)
|
368
|
+
@errors[property] = "value cannot be changed" if options[:new] && !new?
|
369
|
+
end
|
370
|
+
end
|
371
|
+
end
|
372
|
+
|
373
|
+
def require_properties(method, params)
|
374
|
+
self.class.options.each do |property, options|
|
375
|
+
next if params[property]
|
376
|
+
if options[:required]
|
377
|
+
if (options[:required].is_a?(Symbol) && options[:required] == method) ||
|
378
|
+
(options[:required].is_a?(Array) && options[:required].include?(method))
|
379
|
+
params[property] = instance_variable_get("@#{property}") if instance_variable_defined?("@#{property}")
|
380
|
+
raise ArgumentError, "missing :#{property}" if params[property].nil?
|
381
|
+
end
|
382
|
+
end
|
383
|
+
end
|
384
|
+
end
|
385
|
+
|
386
|
+
def changes(property = nil)
|
387
|
+
@changes ||= []
|
388
|
+
unless property.nil?
|
389
|
+
@changes << property unless @changes.include?(property)
|
390
|
+
end
|
391
|
+
@changes
|
392
|
+
end
|
393
|
+
|
394
|
+
def assign(type, value, options = {})
|
395
|
+
if type.is_a?(Array)
|
396
|
+
type = type.first
|
397
|
+
if value.is_a?(Hash) && value.size == 1
|
398
|
+
value = value.values.first
|
399
|
+
end
|
400
|
+
value = [ value ] unless value.is_a?(Array)
|
401
|
+
value.map! do |item|
|
402
|
+
assign_value(type, item, options)
|
403
|
+
end
|
404
|
+
value.freeze if options[:readonly]
|
405
|
+
else
|
406
|
+
value = assign_value(type, value, options)
|
407
|
+
end
|
408
|
+
value
|
409
|
+
end
|
410
|
+
|
411
|
+
def assign_value(type, value, options = {})
|
412
|
+
case type
|
413
|
+
when :integer
|
414
|
+
value.to_i
|
415
|
+
when :string
|
416
|
+
value.to_s
|
417
|
+
when :symbol
|
418
|
+
value.to_sym
|
419
|
+
when :boolean
|
420
|
+
if value.respond_to?('to_i')
|
421
|
+
value.to_i == 0 ? false : true
|
422
|
+
else
|
423
|
+
case value
|
424
|
+
when TrueClass, FalseClass
|
425
|
+
value
|
426
|
+
else
|
427
|
+
!!value
|
428
|
+
end
|
429
|
+
end
|
430
|
+
when :date
|
431
|
+
case value
|
432
|
+
when Time
|
433
|
+
value
|
434
|
+
else
|
435
|
+
value.to_i > 0 ? Time.at(value.to_i) : nil
|
436
|
+
end
|
437
|
+
when :object
|
438
|
+
raise RuntimeError, "missing :class" unless options[:class]
|
439
|
+
klass = options[:class].is_a?(Class) ? options[:class] : KayakoClient.const_get(options[:class])
|
440
|
+
object = klass.new(value.merge(inherited_options))
|
441
|
+
object.loaded!
|
442
|
+
object
|
443
|
+
when :binary
|
444
|
+
if value =~ %r{^[A-Za-z0-9+/]+={0,3}$} && (value.size % 4) == 0
|
445
|
+
logger.debug "decoding base64 string" if logger
|
446
|
+
Base64.decode64(value)
|
447
|
+
else
|
448
|
+
value
|
449
|
+
end
|
450
|
+
else
|
451
|
+
value
|
452
|
+
end
|
453
|
+
end
|
454
|
+
|
455
|
+
end
|
456
|
+
end
|