logical_model 0.4.10 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,41 @@
1
+ class LogicalModel
2
+ module ApiKey
3
+ def self.included(base)
4
+ base.send(:extend, ClassMethods)
5
+ end
6
+
7
+ module ClassMethods
8
+ attr_accessor :api_key, :api_key_name, :use_api_key
9
+
10
+ # Set api_key
11
+ # @param name [Symbol] name for api_key. Eg: app_key, token, etc.
12
+ # @param value [String] value of key. Eg: 1o2u3hqkfd, secret, etc.
13
+ #
14
+ # @example
15
+ # class Client < LogicalModel
16
+ # set_api_key(:token, 'asdfasdf')
17
+ # ...
18
+ # end
19
+ def set_api_key(name,value)
20
+ @use_api_key = true
21
+ @api_key_name = name
22
+ @api_key = value
23
+ end
24
+
25
+ def use_api_key
26
+ @use_api_key ||= false
27
+ end
28
+
29
+ # if needed will merge api_key into given hash
30
+ # returns merged hash
31
+ def merge_key(params = {})
32
+ if self.use_api_key
33
+ params.merge({self.api_key_name => self.api_key})
34
+ else
35
+ params
36
+ end
37
+ end
38
+
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,57 @@
1
+ class LogicalModel
2
+ module Attributes
3
+ # TODO replace this module with ActvieAttr ?
4
+ def self.included(base)
5
+ base.send(:include, InstanceMethods)
6
+ base.send(:extend, ClassMethods)
7
+ end
8
+
9
+ module InstanceMethods
10
+
11
+ def attributes=(attrs)
12
+ sanitize_for_mass_assignment(attrs).each{|k,v| send("#{k}=",v) if respond_to?("#{k}=")}
13
+ end
14
+
15
+ def attributes
16
+ attrs = self.class.attribute_keys.inject(ActiveSupport::HashWithIndifferentAccess.new) do |result,key|
17
+ result[key] = read_attribute_for_validation(key)
18
+ result
19
+ end
20
+
21
+ unless self.class.has_many_keys.blank?
22
+ self.class.has_many_keys.inject(attrs) do |result,key|
23
+ result["#{key}_attributes"] = send(key).map {|a| a.attributes}
24
+ result
25
+ end
26
+ end
27
+ attrs.reject {|key, value| key == "_id" && value.blank?}
28
+ end
29
+ end
30
+
31
+ module ClassMethods
32
+
33
+ # declares an attribute.
34
+ # @param name [Symbol]
35
+ # @example
36
+ # class Client < LogicalModel
37
+ # attribute :att_name
38
+ # end
39
+ def attribute(name)
40
+ @attribute_keys = [] if @attribute_keys.nil?
41
+ @attribute_keys << name
42
+ attr_accessor name
43
+ end
44
+
45
+ # overrides attributes with given keys.
46
+ # @param keys [Array<Symbol>]
47
+ def attribute_keys=(keys)
48
+ @attribute_keys = keys
49
+ attr_accessor *keys
50
+ end
51
+
52
+ def attribute_keys
53
+ @attribute_keys
54
+ end
55
+ end
56
+ end
57
+ end
@@ -1,69 +1,75 @@
1
1
  class LogicalModel
2
2
  module HasManyKeys
3
3
 
4
- def has_many_keys=(keys)
5
- @has_many_keys = keys
6
- attr_accessor *keys
4
+ def self.included(base)
5
+ base.send(:extend, ClassMethods)
6
+ end
7
7
 
8
- keys.each do |association|
8
+ module ClassMethods
9
9
 
10
- # return empty array or @association variable for each association
11
- define_method association do
12
- if instance_variable_get("@#{association}").blank?
13
- instance_variable_set("@#{association}", [])
14
- end
10
+ def has_many_keys=(keys)
11
+ @has_many_keys = keys
12
+ attr_accessor *keys
15
13
 
16
- instance_variable_get("@#{association}")
17
- end
14
+ keys.each do |association|
18
15
 
19
- # this method loads the contact attributes recieved by logical model from the service
20
- define_method "#{association}=" do |params|
21
- collection = []
22
- params.each do |attr_params|
23
- if attr_params["_type"].present?
24
- attr_class = attr_params.delete("_type").to_s.constantize
25
- else
26
- attr_class = association.to_s.singularize.camelize.constantize
16
+ # return empty array or @association variable for each association
17
+ define_method association do
18
+ if instance_variable_get("@#{association}").blank?
19
+ instance_variable_set("@#{association}", [])
27
20
  end
28
- collection << attr_class.new(attr_params)
29
- end
30
- instance_variable_set("@#{association}", collection)
31
- end
32
21
 
33
- define_method "new_#{association.to_s.singularize}" do |attr_params|
34
- if attr_params["_type"].present?
35
- clazz = attr_params.delete(:_type).constantize
36
- else
37
- clazz = association.to_s.singularize.camelize.constantize
22
+ instance_variable_get("@#{association}")
38
23
  end
39
24
 
40
- return unless clazz
41
-
42
- temp_object = clazz.new(attr_params.merge({"#{self.json_root}_id" => self.id}))
43
- eval(association.to_s) << temp_object
44
- temp_object
45
- end
25
+ # this method loads the contact attributes recieved by logical model from the service
26
+ define_method "#{association}=" do |params|
27
+ collection = []
28
+ params.each do |attr_params|
29
+ if attr_params["_type"].present?
30
+ attr_class = attr_params.delete("_type").to_s.constantize
31
+ else
32
+ attr_class = association.to_s.singularize.camelize.constantize
33
+ end
34
+ collection << attr_class.new(attr_params)
35
+ end
36
+ instance_variable_set("@#{association}", collection)
37
+ end
46
38
 
47
- # this method loads the contact attributes from the html form (using nested resources conventions)
48
- define_method "#{association}_attributes=" do |key_attributes|
49
- array = []
50
- key_attributes.each do |attr_params|
51
- attr_params.to_hash.symbolize_keys!
39
+ define_method "new_#{association.to_s.singularize}" do |attr_params|
52
40
  if attr_params["_type"].present?
53
- attr_class = attr_params.delete("_type").to_s.constantize
41
+ clazz = attr_params.delete(:_type).constantize
54
42
  else
55
- attr_class = association.to_s.singularize.camelize.constantize
43
+ clazz = association.to_s.singularize.camelize.constantize
56
44
  end
57
- array << attr_class.new(attr_params)
45
+
46
+ return unless clazz
47
+
48
+ temp_object = clazz.new(attr_params.merge({"#{self.json_root}_id" => self.id}))
49
+ eval(association.to_s) << temp_object
50
+ temp_object
51
+ end
52
+
53
+ # this method loads the contact attributes from the html form (using nested resources conventions)
54
+ define_method "#{association}_attributes=" do |key_attributes|
55
+ array = []
56
+ key_attributes.each do |attr_params|
57
+ attr_params.to_hash.symbolize_keys!
58
+ if attr_params["_type"].present?
59
+ attr_class = attr_params.delete("_type").to_s.constantize
60
+ else
61
+ attr_class = association.to_s.singularize.camelize.constantize
62
+ end
63
+ array << attr_class.new(attr_params)
64
+ end
65
+ instance_variable_set("@#{association}", array)
58
66
  end
59
- instance_variable_set("@#{association}", array)
60
67
  end
61
68
  end
62
- end
63
69
 
64
- def has_many_keys
65
- @has_many_keys
70
+ def has_many_keys
71
+ @has_many_keys
72
+ end
66
73
  end
67
-
68
74
  end
69
75
  end
@@ -0,0 +1,410 @@
1
+ class LogicalModel
2
+ module RESTActions
3
+
4
+ def self.included(base)
5
+ base.send(:include, InstanceMethods)
6
+ base.send(:extend, ClassMethods)
7
+ end
8
+
9
+ module InstanceMethods
10
+
11
+ def create(params={})
12
+ run_callbacks :save do
13
+ run_callbacks :create do
14
+ _create(params)
15
+ end
16
+ end
17
+ end
18
+
19
+ def save
20
+ run_callbacks :save do
21
+ run_callbacks new_record?? :create : :update do
22
+ _save
23
+ end
24
+ end
25
+ end
26
+
27
+ def destroy(params={})
28
+ run_callbacks :destroy do
29
+ _destroy(params)
30
+ end
31
+ end
32
+
33
+ # @param params [Hash] parameters to be sent to service
34
+ def update(params)
35
+ run_callbacks :save do
36
+ run_callbacks :update do
37
+ _update(params)
38
+ end
39
+ end
40
+
41
+ end
42
+
43
+ #
44
+ # creates model.
45
+ #
46
+ # returns:
47
+ # @return false if model invalid
48
+ # @return nil if there was a connection problem
49
+ # @return created model ID if successfull
50
+ #
51
+ # @example Usage:
52
+ # @person = Person.new(params[:person])
53
+ # @person.create( non_attribute_param: "value" )
54
+ def _create(params = {})
55
+ return false unless valid?
56
+
57
+ params = { self.json_root => self.attributes }.merge(params)
58
+ params = self.class.merge_key(params)
59
+
60
+ response = nil
61
+ Timeout::timeout(self.class.timeout/1000) do
62
+ response = Typhoeus::Request.post( self.class.resource_uri, :body => params, :timeout => self.class.timeout )
63
+ end
64
+ self.last_response_code = response.code
65
+ if response.code == 201 || response.code == 202
66
+ log_ok(response)
67
+ if self.respond_to?('id=')
68
+ self.id = ActiveSupport::JSON.decode(response.body)["id"]
69
+ else
70
+ true
71
+ end
72
+ elsif response.code == 400
73
+ log_failed(response)
74
+ ws_errors = ActiveSupport::JSON.decode(response.body)["errors"]
75
+ ws_errors.each_key do |k|
76
+ self.errors.add k, ws_errors[k]
77
+ end
78
+ return false
79
+ else
80
+ log_failed(response)
81
+ return nil
82
+ end
83
+ rescue Timeout::Error
84
+ self.class.logger.warn "timeout"
85
+ return nil
86
+ end
87
+
88
+ # Updates Objects attributes, this will only send attributes passed as arguments
89
+ #
90
+ #
91
+ # Returns false if Object#valid? is false.
92
+ # Returns updated object if successfull.
93
+ # Returns nil if update failed
94
+ #
95
+ # Usage:
96
+ # @person.update(params[:person])
97
+ def _update(params)
98
+
99
+ self.attributes = params[self.json_root]
100
+
101
+ return false unless valid?
102
+
103
+ params = self.class.merge_key(params)
104
+
105
+ response = nil
106
+ Timeout::timeout(self.class.timeout/1000) do
107
+ response = Typhoeus::Request.put( self.class.resource_uri(id),
108
+ :params => params,
109
+ :timeout => self.class.timeout )
110
+ end
111
+
112
+ if response.code == 200
113
+ log_ok(response)
114
+ return self
115
+ else
116
+ log_failed(response)
117
+ return nil
118
+ end
119
+
120
+ rescue Timeout::Error
121
+ self.class.logger.warn("request timed out")
122
+ return nil
123
+ end
124
+
125
+ # Saves Objects attributes
126
+ #
127
+ #
128
+ # Returns false if Object#valid? is false.
129
+ # Returns updated object if successfull.
130
+ # Returns nil if update failed
131
+ #
132
+ # Usage:
133
+ # @person.save
134
+ def _save
135
+ self.attributes = attributes
136
+
137
+ return false unless valid?
138
+
139
+ sending_params = self.attributes
140
+ sending_params.delete(:id)
141
+
142
+ params = { self.json_root => sending_params }
143
+ params = self.class.merge_key(params)
144
+ response = nil
145
+ Timeout::timeout(self.class.timeout/1000) do
146
+ response = Typhoeus::Request.put( self.class.resource_uri(id), :params => params, :timeout => self.class.timeout )
147
+ end
148
+ if response.code == 200
149
+ log_ok(response)
150
+ return self
151
+ else
152
+ log_failed(response)
153
+ return nil
154
+ end
155
+ rescue Timeout::Error
156
+ self.class.logger.warn "timeout"
157
+ return nil
158
+ end
159
+
160
+ # Destroy object
161
+ #
162
+ # Usage:
163
+ # @person.destroy
164
+ def _destroy(params={})
165
+ self.class.delete(self.id,params)
166
+ end
167
+
168
+ end
169
+
170
+ module ClassMethods
171
+
172
+ attr_accessor :enable_delete_multiple
173
+
174
+ # ============================================================================================================
175
+ # Following methods are API specific.
176
+ # They assume we are using a RESTfull API.
177
+ # for get, put, delete :id is expected
178
+ # for post, put attributes are excepted under class_name directly. eg. put( {:id => 1, :class_name => {:attr => "new value for attr"}} )
179
+ #
180
+ # On error (400) a "errors" is expected.
181
+ # ============================================================================================================
182
+
183
+ # Asynchronic Pagination
184
+ # This pagination won't block excecution waiting for result, pagination will be enqueued in Objectr#hydra.
185
+ #
186
+ # Parameters:
187
+ # @param options [Hash].
188
+ # Valid options are:
189
+ # * :page - indicated what page to return. Defaults to 1.
190
+ # * :per_page - indicates how many records to be returned per page. Defauls to 20
191
+ # * all other options will be sent in :params to WebService
192
+ #
193
+ # Usage:
194
+ # Person.async_paginate(:page => params[:page]){|i| result = i}
195
+ def async_paginate(options={})
196
+ options[:page] ||= 1
197
+ options[:per_page] ||= 20
198
+
199
+ options = self.merge_key(options)
200
+
201
+ request = Typhoeus::Request.new(resource_uri, :params => options)
202
+ request.on_complete do |response|
203
+ if response.code >= 200 && response.code < 400
204
+ log_ok(response)
205
+
206
+ result_set = self.from_json(response.body)
207
+
208
+ # this paginate is will_paginate's Array pagination
209
+ collection = Kaminari.paginate_array(
210
+ result_set[:collection],
211
+ {
212
+ :total_count=>result_set[:total],
213
+ :limit => options[:per_page],
214
+ :offset => options[:per_page] * ([options[:page], 1].max - 1)
215
+ }
216
+ )
217
+
218
+ yield collection
219
+ else
220
+ log_failed(response)
221
+ end
222
+ end
223
+ self.hydra.queue(request)
224
+ end
225
+
226
+ #synchronic pagination
227
+ def paginate(options={})
228
+ result = nil
229
+ self.retries.times do
230
+ begin
231
+ async_paginate(options){|i| result = i}
232
+ Timeout::timeout(self.timeout/1000) do
233
+ self.hydra.run
234
+ end
235
+ break unless result.nil?
236
+ rescue Timeout::Error
237
+ self.logger.warn("timeout")
238
+ result = nil
239
+ end
240
+ end
241
+ result
242
+ end
243
+
244
+ # Asynchronic Count
245
+ # This count won't block excecution waiting for result, count will be enqueued in Objectr#hydra.
246
+ #
247
+ # Parameters:
248
+ # @param options [Hash].
249
+ # Valid options are:
250
+ # @option options [Integer] :page - indicated what page to return. Defaults to 1.
251
+ # @option options [Integer] :per_page - indicates how many records to be returned per page. Defauls to 20
252
+ # @option options [Hash] all other options will be forwarded in :params to WebService
253
+ #
254
+ # @example 'Count bobs'
255
+ # Person.async_count(:when => {:name => 'bob'}}){|i| result = i}
256
+ def async_count(options={})
257
+ options[:page] = 1
258
+ options[:per_page] = 1
259
+
260
+ options = self.merge_key(options)
261
+
262
+ request = Typhoeus::Request.new(resource_uri, :params => options)
263
+ request.on_complete do |response|
264
+ if response.code >= 200 && response.code < 400
265
+ log_ok(response)
266
+
267
+ result_set = self.from_json(response.body)
268
+
269
+ yield result_set[:total]
270
+ else
271
+ log_failed(response)
272
+ end
273
+ end
274
+ self.hydra.queue(request)
275
+ end
276
+
277
+ # synchronic count
278
+ def count(options={})
279
+ result = nil
280
+ async_count(options){|i| result = i}
281
+ Timeout::timeout(self.timeout/1000) do
282
+ self.hydra.run
283
+ end
284
+ result
285
+ rescue Timeout::Error
286
+ self.logger.warn("timeout")
287
+ return nil
288
+ end
289
+
290
+ # Asynchronic Find
291
+ # This find won't block excecution waiting for result, excecution will be enqueued in Objectr#hydra.
292
+ #
293
+ # Parameters:
294
+ # - id, id of object to find
295
+ # @param [String/Integer] id
296
+ # @param [Hash] params
297
+ #
298
+ # Usage:
299
+ # Person.async_find(params[:id])
300
+ def async_find(id, params = {})
301
+ params = self.merge_key(params)
302
+ request = Typhoeus::Request.new( resource_uri(id), :params => params )
303
+
304
+ request.on_complete do |response|
305
+ if response.code >= 200 && response.code < 400
306
+ log_ok(response)
307
+ yield self.new.from_json(response.body) # this from_json is defined in ActiveModel::Serializers::JSON
308
+ else
309
+ log_failed(response)
310
+ end
311
+ end
312
+
313
+ self.hydra.queue(request)
314
+ end
315
+
316
+ # synchronic find
317
+ def find(id, params = {})
318
+ result = nil
319
+ async_find(id, params){|i| result = i}
320
+ Timeout::timeout(self.timeout/1000) do
321
+ self.hydra.run
322
+ end
323
+ result
324
+ rescue Timeout::Error
325
+ self.logger.warn("timeout")
326
+ return nil
327
+ end
328
+
329
+ # Deletes Object#id
330
+ #
331
+ # Returns nil if delete failed
332
+ #
333
+ # @param [String] id - id of contact to be deleted
334
+ # @param [Hash] params - other params to be sent to WS on request
335
+ #
336
+ # Usage:
337
+ # Person.delete(params[:id])
338
+ def delete(id, params={})
339
+
340
+ params = self.merge_key(params)
341
+
342
+ response = nil
343
+ Timeout::timeout(self.timeout/1000) do
344
+ response = Typhoeus::Request.delete( self.resource_uri(id),
345
+ :params => params,
346
+ :timeout => self.timeout
347
+ )
348
+ end
349
+ if response.code == 200
350
+ log_ok(response)
351
+ return self
352
+ else
353
+ log_failed(response)
354
+ return nil
355
+ end
356
+ rescue Timeout::Error
357
+ self.logger.warn "timeout"
358
+ return nil
359
+ end
360
+
361
+ # Deletes all Objects matching given ids
362
+ #
363
+ # This method will make a DELETE request to resource_uri/destroy_multiple
364
+ #
365
+ # Returns nil if delete failed
366
+ #
367
+ # @param [Array] ids - ids of contacts to be deleted
368
+ # @param [Hash] params - other params to be sent to WS on request
369
+ #
370
+ # Usage:
371
+ # Person.delete_multiple([1,2,4,5,6])
372
+ def delete_multiple(ids, params={})
373
+ raise "not-enabled" unless self.delete_multiple_enabled?
374
+
375
+ params = self.merge_key(params)
376
+ params = params.merge({:ids => ids})
377
+
378
+ response = nil
379
+ Timeout::timeout(self.timeout/1000) do
380
+ response = Typhoeus::Request.delete( self.resource_uri+"/destroy_multiple",
381
+ :params => params,
382
+ :timeout => self.timeout
383
+ )
384
+ end
385
+ if response.code == 200
386
+ log_ok(response)
387
+ return self
388
+ else
389
+ log_failed(response)
390
+ return nil
391
+ end
392
+ rescue Timeout::Error
393
+ self.logger.warn "timeout"
394
+ return nil
395
+ end
396
+ end
397
+ end
398
+
399
+ ##
400
+ # Will parse JSON string and initialize classes for all hashes in json_string[collection].
401
+ #
402
+ # @param json_string [JSON String] This JSON should have format: {collection: [...], total: X}
403
+ #
404
+ def self.from_json(json_string)
405
+ parsed = ActiveSupport::JSON.decode(json_string)
406
+ collection = parsed["collection"].map{|i|self.new(i)}
407
+ return { :collection => collection, :total => parsed["total"].to_i }
408
+ end
409
+
410
+ end