logical_model 0.4.10 → 0.5.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.
@@ -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