hoodoo 2.0.0 → 2.1.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: dc0db0a009b321df89a2eb2e8a879c76bc875bb9
4
- data.tar.gz: 29c85538f05080287181a67d3292b72f950f1872
2
+ SHA256:
3
+ metadata.gz: fcb6260d51d3dd934b17047fa2db6abe3b9dc25f7f95f81be5bd0af5035ff019
4
+ data.tar.gz: 97f48231ab533c2aec1557679007d7ea95f5e10c0aeb262cc8a1c84d2bf611ae
5
5
  SHA512:
6
- metadata.gz: 8519c31178c7439fc57888851eac50409e71c8225dfc0fd5421da540e370ab01ef92423f3a5753195efea9ca01f4954ee68d33d09fc083470a6ac5eade962f70
7
- data.tar.gz: 45d7b58441e42e224624123c74f15f812880a074ea07648628f49741a3a3ba918456632b456a6b617b73d65935884ec16ad30e92176e19cc939af93fcc3437c0
6
+ metadata.gz: 30633c2155b09f06ac6d75c53aef460b4c04b426019a11eb9bb08040ece7586295b875ec8f3034c87e3e4a80945c1ebd0514444f2437bccef3049cbf5b616080
7
+ data.tar.gz: f12b6ba55fd0ad9a8f7f66e1e6924a98cbbba2eed5999cf8b14a6810c15529bff0eaf5d2fe472b5d757e15bd5877baba9724e88caab2af846a2e6394b3ecd2ae
@@ -177,6 +177,17 @@ module Hoodoo
177
177
 
178
178
  self.nz_co_loyalty_hoodoo_dated_with = history_klass
179
179
 
180
+ # Enable the monkey patch to the Finder module's '#acquire_in' class
181
+ # method, if need be.
182
+
183
+ if self.include?( Hoodoo::ActiveRecord::Finder )
184
+ Hoodoo::Monkey.register(
185
+ target_unit: self,
186
+ extension_module: Hoodoo::Monkey::Patch::ActiveRecordDatedFinderAdditions
187
+ )
188
+
189
+ Hoodoo::Monkey.enable( extension_module: Hoodoo::Monkey::Patch::ActiveRecordDatedFinderAdditions )
190
+ end
180
191
  end
181
192
 
182
193
  # If a prior call has been made to #dating_enabled then this method
@@ -289,7 +300,7 @@ module Hoodoo
289
300
  #
290
301
  # +unquoted_column_names+:: (Optional) An Array of Strings giving one
291
302
  # or more column names to use for the query.
292
- # If omitted, all model attribtues are used
303
+ # If omitted, all model attributes are used
293
304
  # as columns. If the "id" column is not
294
305
  # included in the Array, it will be added
295
306
  # anyway as this column is mandatory. The
@@ -118,6 +118,29 @@ module Hoodoo
118
118
  Hoodoo::ActiveRecord::Support.full_scope_for( self, context )
119
119
  end
120
120
 
121
+ # As #scoped_in, but intentionally omits any historical dating modules
122
+ # from the returned scope. The scope might then address both historic
123
+ # and contemporary records, depending on whether you are using manual
124
+ # or automatic dating.
125
+ #
126
+ # +context+:: Hoodoo::Services::Context instance describing a call
127
+ # context. This is typically a value passed to one of
128
+ # the Hoodoo::Services::Implementation instance methods
129
+ # that a resource subclass implements.
130
+ #
131
+ # See also:
132
+ #
133
+ # * Hoodoo::ActiveRecord::Dated
134
+ # * Hoodoo::ActiveRecord::ManuallyDated
135
+ #
136
+ def scoped_undated_in( context )
137
+ Hoodoo::ActiveRecord::Support.add_undated_scope_to(
138
+ self.all(), # "all" -> returns anonymous scope
139
+ self,
140
+ context
141
+ )
142
+ end
143
+
121
144
  # "Polymorphic" find - support for finding a model by fields other
122
145
  # than just +:id+, based on a single unique identifier. Use #acquire
123
146
  # just like you'd use +find_by_id+ and only bother with it if you
@@ -173,10 +196,10 @@ module Hoodoo
173
196
  #
174
197
  # SomeModel.where( :foo => :bar ).acquire( context.request.ident )
175
198
  #
176
- # Usually for convenience you should use #acquire_in instead, or only
177
- # call #acquire with (say) a secure scope via for example a call to
178
- # Hoodoo::ActiveRecord::Secure::ClassMethods#secure. Other scopes may
179
- # be needed depending on the mixins your model uses.
199
+ # Usually for convenience you should use #acquire_in! or acquire_in
200
+ # instead, or only call #acquire with (say) a secure scope via for
201
+ # example a call to Hoodoo::ActiveRecord::Secure::ClassMethods#secure.
202
+ # Other scopes may be needed depending on the mixins your model uses.
180
203
  #
181
204
  # +ident+:: The value to search for in the fields (attributes)
182
205
  # specified via #acquire_with, matched using calls to
@@ -246,6 +269,10 @@ module Hoodoo
246
269
  # The same applies to forgetting dated scopes, translated scopes, or
247
270
  # anything else that #scoped_in might include for you.
248
271
  #
272
+ # An even higher-level method, taking care of error handling as well,
273
+ # is #acquire_in!. You may prefer to call this higher level interface
274
+ # if you don't object to the way it modifies +context+.
275
+ #
249
276
  # Parameters:
250
277
  #
251
278
  # +context+:: Hoodoo::Services::Context instance describing a call
@@ -253,23 +280,93 @@ module Hoodoo
253
280
  # the Hoodoo::Services::Implementation instance methods
254
281
  # that a resource subclass implements.
255
282
  #
256
- # Returns a found model instance or +nil+ for no match.
283
+ # See also:
284
+ #
285
+ # * Hoodoo::ActiveRecord::Finder::ClassMethods#acquire_in!
286
+ # * Hoodoo::Services::Response#not_found
287
+ # * Hoodoo::Services::Response#contemporary_exists
288
+ #
289
+ # Returns a found model instance or +nil+ for no match / on error.
257
290
  #
258
291
  def acquire_in( context )
259
- return scoped_in( context ).acquire( context.request.ident )
292
+ scoped_in( context ).acquire( context.request.ident )
293
+ end
294
+
295
+ # A higher level equivalent of #acquire_in in which the given context
296
+ # will be updated with error information if the requested item cannot
297
+ # be found. Although modifying the passed-in context may be considered
298
+ # an unclean pattern, it does allow extensions to that mechanism. For
299
+ # example, in the presence of the Hoodoo::ActiveRecord::Dated or
300
+ # Hoodoo::ActiveRecord::ManuallyDated modules, an additional error
301
+ # entry of +generic.contemporary_exists+ will be added if conditions
302
+ # warrant it.
303
+ #
304
+ # At the time of writing only this and/or +generic.not_found+ can be
305
+ # added, but in future other mixin modules may cause other additions,
306
+ # making preferential use of this method over #acquire_in a good way
307
+ # to future-proof against such changes.
308
+ #
309
+ # To be sure that these additions work, always include this module
310
+ # before any others (unless documentation indicates a differing
311
+ # inclusion order requirement), so that the dating module is able to
312
+ # detect the presence of this Finder module and enable the extensions.
313
+ #
314
+ # Parameters:
315
+ #
316
+ # +context+:: Hoodoo::Services::Context instance describing a call
317
+ # context. This is typically a value passed to one of
318
+ # the Hoodoo::Services::Implementation instance methods
319
+ # that a resource subclass implements.
320
+ #
321
+ # See also:
322
+ #
323
+ # * Hoodoo::ActiveRecord::Finder::ClassMethods#acquire_in
324
+ # * Hoodoo::Services::Response#not_found
325
+ # * Hoodoo::Services::Response#contemporary_exists
326
+ #
327
+ # Returns a found model instance or +nil+ for no match / on error,
328
+ # wherein +context+ will have been updated with error details.
329
+ #
330
+ # Example, following on from those for #acquire_in:
331
+ #
332
+ # def show( context )
333
+ # resource = SomeModel.acquire_in!( context )
334
+ # return if context.response.halt_processing? # Or just use 'if resource.nil?'
335
+ #
336
+ # # ...else render...
337
+ # end
338
+ #
339
+ def acquire_in!( context )
340
+
341
+ # The method is patched internally by Hoodoo::ActiveRecord::Dated
342
+ # and Hoodoo::ActiveRecord::ManuallyDated. The patches add in a
343
+ # +generic.contemporary_exists+ error using appropriate checks for
344
+ # a contemporary record, where necessary. This way, any performance
345
+ # overhead that might be introduced by the added code is only
346
+ # present when a class uses one of the dating modules.
347
+ #
348
+ # It's an internal patch and not intended for additional external
349
+ # changes, so it does not use the public "monkey_" naming prefix.
350
+
351
+ result = acquire_in( context )
352
+ context.response.not_found( context.request.ident ) if result.nil?
353
+
354
+ return result
260
355
  end
261
356
 
262
357
  # Describe the list of model fields _in_ _addition_ _to_ +id+ which
263
- # are to be used to "find-by-identifier" through calls #acquire and
264
- # #acquire_in. See those methods for more details.
358
+ # are to be used to "find-by-identifier" through calls #acquire,
359
+ # #acquire_in and #acquire_in!. See those methods for more details.
265
360
  #
266
361
  # Fields will be searched in the order listed. If duplicate items are
267
362
  # present, the first occurrence is kept and the rest are removed.
268
363
  #
269
364
  # *args:: One or more field names as Strings or Symbols.
270
365
  #
271
- # See also: #acquired_with
272
- # #acquire_with_id_substitute
366
+ # See also:
367
+ #
368
+ # * #acquired_with
369
+ # * #acquire_with_id_substitute
273
370
  #
274
371
  def acquire_with( *args )
275
372
  self.nz_co_loyalty_hoodoo_show_id_fields = args.map( & :to_s )
@@ -277,30 +374,32 @@ module Hoodoo
277
374
  end
278
375
 
279
376
  # Return the list of model fields _in_ _addition_ _to_ +id+ which
280
- # are being used to "find-by-identifier" through calls to #acquire
281
- # and #acquire_in. The returned Array contains de-duplicated String
282
- # values only.
377
+ # are being used to "find-by-identifier" through calls to #acquire,
378
+ # #acquire_in and #acquire_in!. The returned Array contains
379
+ # de-duplicated String values only.
283
380
  #
284
- # See also: #acquire_with
285
- # #acquire_with_id_substitute
381
+ # See also:
382
+ #
383
+ # * #acquire_with
384
+ # * #acquire_with_id_substitute
286
385
  #
287
386
  def acquired_with
288
387
  self.nz_co_loyalty_hoodoo_show_id_fields || []
289
388
  end
290
389
 
291
- # The #acquire_with method allows methods like #acquire and
292
- # #acquire_in to transparently find a record based on _one_ _or_
293
- # _more_ columns in the database. The columns (and corresponding
294
- # model attributes) specified through a call to #acquire_with will
295
- # normally be used _in_ _addition_ _to_ a lookup on the +id+
296
- # column, but in rare circumstances you might need to bypass that
297
- # and use an entirely different field. This is distinct from the
298
- # ActiveRecord-level concept of the model's primary key column.
390
+ # The #acquire_with method allows methods like #acquire, #acquire_in
391
+ # and #acquire_in! to transparently find a record based on _one_ _or_
392
+ # _more_ columns in the database. The columns (and corresponding model
393
+ # attributes) specified through a call to #acquire_with will normally
394
+ # be used _in_ _addition_ _to_ a lookup on the +id+ column, but in rare
395
+ # circumstances you might need to bypass that and use an entirely
396
+ # different field. This is distinct from the ActiveRecord-level concept
397
+ # of the model's primary key column.
299
398
  #
300
399
  # To permanently change the use of the +id+ attribute as the first
301
- # search parameter in #acquire and #acquire_in, by modifying the
302
- # behaviour of #acquisition_scope, call here and pass in the new
303
- # attribute name.
400
+ # search parameter in #acquire, #acquire_in and #acquire_in! by
401
+ # modifying the behaviour of #acquisition_scope, call here and pass in
402
+ # the new attribute name.
304
403
  #
305
404
  # +attr+:: Attribute name as a Symbol or String to use _instead_
306
405
  # of +id+, as a default mandatory column in
@@ -310,10 +409,10 @@ module Hoodoo
310
409
  self.nz_co_loyalty_hoodoo_show_id_substitute = attr.to_sym
311
410
  end
312
411
 
313
- # Back-end to #acquire and therefore, in turn, #acquire_in. Returns
314
- # an ActiveRecord::Relation instance which scopes the search for a
315
- # record by +id+ and across any other columns specified by
316
- # #acquire_with, via SQL +OR+.
412
+ # Back-end to #acquire and therefore, in turn, #acquire_in and
413
+ # #acquire_in!. Returns an ActiveRecord::Relation instance which
414
+ # scopes the search for a record by +id+ and across any other columns
415
+ # specified by #acquire_with, via SQL +OR+.
317
416
  #
318
417
  # If you need to change the use of attribute +id+, specify a
319
418
  # different attribute with #acquire_with_id_substitute. In that case,
@@ -595,9 +694,9 @@ module Hoodoo
595
694
  #
596
695
  # counter = Proc.new do | sql |
597
696
  # begin
598
- # sql = sql.gsub( "'", "''" ) # Escape SQL for insertion below
697
+ # escaped_sql = sql.gsub( "'", "''" )
599
698
  # ActiveRecord::Base.connection.execute(
600
- # "SELECT estimated_count('#{ sql }')"
699
+ # "SELECT estimated_count('#{ escaped_sql }')"
601
700
  # ).first[ 'estimated_count' ].to_i
602
701
  # rescue
603
702
  # nil
@@ -733,7 +832,9 @@ module Hoodoo
733
832
  # for +filter_with+ tends to work reasonably when the query is
734
833
  # negated for filter use via <tt>...NOT(...)...</tt>. Examining the
735
834
  # implementation of Hoodoo::ActiveRecord::Finder::SearchHelper may
736
- # help if confused. See also:
835
+ # help if confused.
836
+ #
837
+ # See also:
737
838
  #
738
839
  # * https://en.wikipedia.org/wiki/Null_(SQL)
739
840
  #
@@ -111,9 +111,9 @@ module Hoodoo
111
111
  #
112
112
  # === Show and List
113
113
  #
114
- # You might use Hoodoo::ActiveRecord::Finder#list_in or
115
- # Hoodoo::ActiveRecord::Finder#acquire_in for +list+ or +show+ actions;
116
- # such code changes from e.g.:
114
+ # You might use Hoodoo::ActiveRecord::Finder::ClassMethods#list_in or
115
+ # Hoodoo::ActiveRecord::Finder::ClassMethods#acquire_in for +list+ or
116
+ # +show+ actions; such code changes from e.g.:
117
117
  #
118
118
  # SomeModel.list_in( context )
119
119
  #
@@ -487,11 +487,23 @@ module Hoodoo
487
487
  }
488
488
  )
489
489
 
490
- # Lastly, we must specify an acquisition scope that's based on
491
- # the "uuid" column only and *not* the "id" column.
490
+ # We must specify an acquisition scope that's based on the "uuid"
491
+ # column only and *not* the "id" column.
492
492
 
493
493
  acquire_with_id_substitute( :uuid )
494
494
 
495
+ # Finally, enable the monkey patch to the Finder module's
496
+ # '#acquire_in' class method, if need be.
497
+
498
+ if self.include?( Hoodoo::ActiveRecord::Finder )
499
+ Hoodoo::Monkey.register(
500
+ target_unit: self,
501
+ extension_module: Hoodoo::Monkey::Patch::ActiveRecordManuallyDatedFinderAdditions
502
+ )
503
+
504
+ Hoodoo::Monkey.enable( extension_module: Hoodoo::Monkey::Patch::ActiveRecordManuallyDatedFinderAdditions )
505
+ end
506
+
495
507
  end
496
508
 
497
509
  # If a prior call has been made to #manual_dating_enabled then this
@@ -4,7 +4,8 @@
4
4
  #
5
5
  # Purpose:: Supplementary helper class included by "finder.rb". See
6
6
  # Hoodoo::ActiveRecord::Finder, especially
7
- # Hoodoo::ActiveRecord::Finder#search_with, for details.
7
+ # Hoodoo::ActiveRecord::Finder::ClassMethods#search_with, for
8
+ # details.
8
9
  # ----------------------------------------------------------------------
9
10
  # 09-Jul-2015 (ADH): Created.
10
11
  ########################################################################
@@ -82,9 +82,10 @@ module Hoodoo
82
82
  end
83
83
 
84
84
  # Takes a Hash of possibly-non-String keys and with +nil+ values or
85
- # Proc instances appropriate for Hoodoo::ActiveRecord::Finder#search_with
86
- # / #filter_with. Returns a similar Hash with all-String keys and a Proc
87
- # for every value.
85
+ # Proc instances appropriate for
86
+ # Hoodoo::ActiveRecord::Finder::ClassMethods#search_with /
87
+ # Hoodoo::ActiveRecord::Finder::ClassMethods#filter_with. Returns a
88
+ # similar Hash with all-String keys and a Proc for every value.
88
89
  #
89
90
  # +hash+:: Hash Symbol or String keys and Proc instance or +nil+
90
91
  # values.
@@ -142,15 +143,44 @@ module Hoodoo
142
143
  prevailing_scope = prevailing_scope.manually_dated( context )
143
144
  end
144
145
 
146
+ return self.add_undated_scope_to( prevailing_scope, klass, context )
147
+ end
148
+
149
+ # Back-end of sorts for ::full_scope_for. Given a base scope (e.g.
150
+ # '<tt>Model.all</tt>'), applies all available appropriate scoping
151
+ # additions included by that model, such as Hoodoo::ActiveRecord::Secure
152
+ # and Hoodoo::ActiveRecord::Translated, _except_ for the dating modules
153
+ # Hoodoo::ActiveRecord::Dated and Hoodoo::ActiveRecord::ManuallyDated.
154
+ #
155
+ # If you wish to use dating as well, call ::full_scope_for instead.
156
+ #
157
+ # +base_scope+:: The ActiveRecord::Relation instance providing the base
158
+ # scope to which additions will be made.
159
+ #
160
+ # +klass+:: The ActiveRecord::Base subclass _class_ (not instance)
161
+ # which is making the call here. This is the entity which
162
+ # is checked for module inclusions to determine how the
163
+ # query chain should be assembled.
164
+ #
165
+ # +context+:: Hoodoo::Services::Context instance describing a call
166
+ # context. This is typically a value passed to one of
167
+ # the Hoodoo::Services::Implementation instance methods
168
+ # that a resource subclass implements.
169
+ #
170
+ # Returns the given input scope, with additional conditions added for
171
+ # any Hoodoo ActiveRecord extension modules included by the ActiveRecord
172
+ # model class that the scope targets.
173
+ #
174
+ def self.add_undated_scope_to( base_scope, klass, context )
145
175
  if klass.include?( Hoodoo::ActiveRecord::Secure )
146
- prevailing_scope = prevailing_scope.secure( context )
176
+ base_scope = base_scope.secure( context )
147
177
  end
148
178
 
149
179
  if klass.include?( Hoodoo::ActiveRecord::Translated )
150
- prevailing_scope = prevailing_scope.translated( context )
180
+ base_scope = base_scope.translated( context )
151
181
  end
152
182
 
153
- return prevailing_scope
183
+ return base_scope
154
184
  end
155
185
 
156
186
  end
@@ -97,6 +97,7 @@ module Hoodoo
97
97
 
98
98
  errors_for 'generic' do
99
99
  error 'not_found', status: 404, message: 'Resource not found', reference: [ :ident ]
100
+ error 'contemporary_exists', status: 404, message: 'Contemporary record exists', reference: [ :ident ]
100
101
  error 'malformed', status: 422, message: 'Malformed payload'
101
102
  error 'required_field_missing', status: 422, message: 'Required field missing', reference: [ :field_name ]
102
103
  error 'invalid_string', status: 422, message: 'Invalid string format', reference: [ :field_name ]
data/lib/hoodoo/monkey.rb CHANGED
@@ -10,6 +10,9 @@
10
10
  ########################################################################
11
11
 
12
12
  require 'hoodoo/monkey/monkey'
13
+
14
+ require 'hoodoo/monkey/patch/active_record_dated_finder_additions'
15
+ require 'hoodoo/monkey/patch/active_record_manually_dated_finder_additions'
16
+ require 'hoodoo/monkey/patch/datadog_traced_amqp'
13
17
  require 'hoodoo/monkey/patch/newrelic_middleware_analytics'
14
18
  require 'hoodoo/monkey/patch/newrelic_traced_amqp'
15
- require 'hoodoo/monkey/patch/datadog_traced_amqp'
@@ -0,0 +1,52 @@
1
+ ########################################################################
2
+ # File:: active_record_dated_finder_additions.rb
3
+ # (C):: Loyalty New Zealand 2017
4
+ #
5
+ # Purpose:: Extend
6
+ # Hoodoo::ActiveRecord::Finder::ClassMethods#acquire_in! so
7
+ # that it adds error +generic.contemporary_exists+ to the
8
+ # provided +context+ if a dated instance is absent.
9
+ # ----------------------------------------------------------------------
10
+ # 01-Nov-2017 (ADH): Created.
11
+ ########################################################################
12
+
13
+ module Hoodoo
14
+ module Monkey
15
+ module Patch
16
+
17
+ # Extend Hoodoo::ActiveRecord::Finder::ClassMethods#acquire_in! so
18
+ # that it adds error +generic.contemporary_exists+ to the provided
19
+ # +context+ if a dated instance is absent.
20
+ #
21
+ module ActiveRecordDatedFinderAdditions
22
+
23
+ # Class methods to patch over an ActiveRecord::Base subclass
24
+ # which includes Hoodoo::ActiveRecord::Finder and
25
+ # Hoodoo::ActiveRecord::Dated.
26
+ #
27
+ module ClassExtensions
28
+
29
+ # See Hoodoo::ActiveRecord::Finder::ClassMethods#acquire_in! for
30
+ # details. Calls that method then, upon error, checks to see if a
31
+ # contemporary version of the resource exists and adds error
32
+ # +generic.contemporary_exists+ to the given +context+ if so.
33
+ #
34
+ def acquire_in!( context )
35
+ result = super( context )
36
+
37
+ if result.nil? && context.request.dated_at
38
+ ident = context.request.ident
39
+ contemporary_result = scoped_undated_in( context ).acquire( ident )
40
+
41
+ context.response.contemporary_exists( ident ) if contemporary_result.present?
42
+ end
43
+
44
+ return result
45
+ end
46
+
47
+ end
48
+ end
49
+
50
+ end # module Patch
51
+ end # module Monkey
52
+ end # module Hoodoo