hoodoo 1.0.2

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.
Files changed (216) hide show
  1. checksums.yaml +7 -0
  2. data/bin/hoodoo +5 -0
  3. data/lib/hoodoo.rb +27 -0
  4. data/lib/hoodoo/active.rb +32 -0
  5. data/lib/hoodoo/active/active_model/uuid_validator.rb +45 -0
  6. data/lib/hoodoo/active/active_record/base.rb +81 -0
  7. data/lib/hoodoo/active/active_record/creator.rb +134 -0
  8. data/lib/hoodoo/active/active_record/dated.rb +343 -0
  9. data/lib/hoodoo/active/active_record/error_mapping.rb +351 -0
  10. data/lib/hoodoo/active/active_record/finder.rb +606 -0
  11. data/lib/hoodoo/active/active_record/search_helper.rb +189 -0
  12. data/lib/hoodoo/active/active_record/secure.rb +431 -0
  13. data/lib/hoodoo/active/active_record/support.rb +106 -0
  14. data/lib/hoodoo/active/active_record/translated.rb +87 -0
  15. data/lib/hoodoo/active/active_record/uuid.rb +80 -0
  16. data/lib/hoodoo/active/active_record/writer.rb +321 -0
  17. data/lib/hoodoo/client.rb +23 -0
  18. data/lib/hoodoo/client/augmented_array.rb +29 -0
  19. data/lib/hoodoo/client/augmented_base.rb +168 -0
  20. data/lib/hoodoo/client/augmented_hash.rb +23 -0
  21. data/lib/hoodoo/client/client.rb +354 -0
  22. data/lib/hoodoo/client/endpoint/endpoint.rb +427 -0
  23. data/lib/hoodoo/client/endpoint/endpoints/amqp.rb +180 -0
  24. data/lib/hoodoo/client/endpoint/endpoints/auto_session.rb +194 -0
  25. data/lib/hoodoo/client/endpoint/endpoints/http.rb +203 -0
  26. data/lib/hoodoo/client/endpoint/endpoints/http_based.rb +367 -0
  27. data/lib/hoodoo/client/endpoint/endpoints/not_found.rb +59 -0
  28. data/lib/hoodoo/client/headers.rb +269 -0
  29. data/lib/hoodoo/communicators.rb +23 -0
  30. data/lib/hoodoo/communicators/fast.rb +44 -0
  31. data/lib/hoodoo/communicators/pool.rb +601 -0
  32. data/lib/hoodoo/communicators/slow.rb +84 -0
  33. data/lib/hoodoo/data.rb +51 -0
  34. data/lib/hoodoo/data/resources/caller.rb +39 -0
  35. data/lib/hoodoo/data/resources/errors.rb +28 -0
  36. data/lib/hoodoo/data/resources/log.rb +31 -0
  37. data/lib/hoodoo/data/resources/session.rb +26 -0
  38. data/lib/hoodoo/data/types/error_primitive.rb +27 -0
  39. data/lib/hoodoo/data/types/permissions.rb +40 -0
  40. data/lib/hoodoo/data/types/permissions_defaults.rb +32 -0
  41. data/lib/hoodoo/data/types/permissions_full.rb +28 -0
  42. data/lib/hoodoo/data/types/permissions_resources.rb +31 -0
  43. data/lib/hoodoo/discovery.rb +20 -0
  44. data/lib/hoodoo/errors.rb +19 -0
  45. data/lib/hoodoo/errors/error_descriptions.rb +229 -0
  46. data/lib/hoodoo/errors/errors.rb +322 -0
  47. data/lib/hoodoo/generator.rb +139 -0
  48. data/lib/hoodoo/logger.rb +23 -0
  49. data/lib/hoodoo/logger/fast_writer.rb +27 -0
  50. data/lib/hoodoo/logger/flattener_mixin.rb +36 -0
  51. data/lib/hoodoo/logger/logger.rb +387 -0
  52. data/lib/hoodoo/logger/slow_writer.rb +49 -0
  53. data/lib/hoodoo/logger/writer_mixin.rb +52 -0
  54. data/lib/hoodoo/logger/writers/file_writer.rb +45 -0
  55. data/lib/hoodoo/logger/writers/log_entries_dot_com_writer.rb +64 -0
  56. data/lib/hoodoo/logger/writers/stream_writer.rb +43 -0
  57. data/lib/hoodoo/middleware.rb +33 -0
  58. data/lib/hoodoo/presenters.rb +45 -0
  59. data/lib/hoodoo/presenters/base.rb +281 -0
  60. data/lib/hoodoo/presenters/base_dsl.rb +519 -0
  61. data/lib/hoodoo/presenters/common_resource_fields.rb +31 -0
  62. data/lib/hoodoo/presenters/embedding.rb +232 -0
  63. data/lib/hoodoo/presenters/types/array.rb +118 -0
  64. data/lib/hoodoo/presenters/types/boolean.rb +26 -0
  65. data/lib/hoodoo/presenters/types/date.rb +26 -0
  66. data/lib/hoodoo/presenters/types/date_time.rb +26 -0
  67. data/lib/hoodoo/presenters/types/decimal.rb +47 -0
  68. data/lib/hoodoo/presenters/types/enum.rb +55 -0
  69. data/lib/hoodoo/presenters/types/field.rb +158 -0
  70. data/lib/hoodoo/presenters/types/float.rb +26 -0
  71. data/lib/hoodoo/presenters/types/hash.rb +361 -0
  72. data/lib/hoodoo/presenters/types/integer.rb +26 -0
  73. data/lib/hoodoo/presenters/types/object.rb +117 -0
  74. data/lib/hoodoo/presenters/types/string.rb +53 -0
  75. data/lib/hoodoo/presenters/types/tags.rb +24 -0
  76. data/lib/hoodoo/presenters/types/text.rb +26 -0
  77. data/lib/hoodoo/presenters/types/uuid.rb +54 -0
  78. data/lib/hoodoo/services.rb +34 -0
  79. data/lib/hoodoo/services/discovery/discoverers/by_consul.rb +66 -0
  80. data/lib/hoodoo/services/discovery/discoverers/by_convention.rb +173 -0
  81. data/lib/hoodoo/services/discovery/discoverers/by_drb/by_drb.rb +195 -0
  82. data/lib/hoodoo/services/discovery/discoverers/by_drb/drb_server.rb +166 -0
  83. data/lib/hoodoo/services/discovery/discoverers/by_drb/drb_server_start.rb +37 -0
  84. data/lib/hoodoo/services/discovery/discovery.rb +186 -0
  85. data/lib/hoodoo/services/discovery/results/for_amqp.rb +58 -0
  86. data/lib/hoodoo/services/discovery/results/for_http.rb +85 -0
  87. data/lib/hoodoo/services/discovery/results/for_local.rb +85 -0
  88. data/lib/hoodoo/services/discovery/results/for_remote.rb +57 -0
  89. data/lib/hoodoo/services/middleware/amqp_log_message.rb +186 -0
  90. data/lib/hoodoo/services/middleware/amqp_log_writer.rb +119 -0
  91. data/lib/hoodoo/services/middleware/endpoints/inter_resource_local.rb +130 -0
  92. data/lib/hoodoo/services/middleware/endpoints/inter_resource_remote.rb +202 -0
  93. data/lib/hoodoo/services/middleware/exception_reporting/base_reporter.rb +105 -0
  94. data/lib/hoodoo/services/middleware/exception_reporting/exception_reporting.rb +115 -0
  95. data/lib/hoodoo/services/middleware/exception_reporting/reporters/airbrake_reporter.rb +64 -0
  96. data/lib/hoodoo/services/middleware/exception_reporting/reporters/raygun_reporter.rb +63 -0
  97. data/lib/hoodoo/services/middleware/interaction.rb +127 -0
  98. data/lib/hoodoo/services/middleware/middleware.rb +2705 -0
  99. data/lib/hoodoo/services/middleware/rack_monkey_patch.rb +73 -0
  100. data/lib/hoodoo/services/services/context.rb +153 -0
  101. data/lib/hoodoo/services/services/implementation.rb +132 -0
  102. data/lib/hoodoo/services/services/interface.rb +934 -0
  103. data/lib/hoodoo/services/services/permissions.rb +250 -0
  104. data/lib/hoodoo/services/services/request.rb +189 -0
  105. data/lib/hoodoo/services/services/response.rb +316 -0
  106. data/lib/hoodoo/services/services/service.rb +141 -0
  107. data/lib/hoodoo/services/services/session.rb +729 -0
  108. data/lib/hoodoo/utilities.rb +12 -0
  109. data/lib/hoodoo/utilities/string_inquirer.rb +54 -0
  110. data/lib/hoodoo/utilities/utilities.rb +380 -0
  111. data/lib/hoodoo/utilities/uuid.rb +44 -0
  112. data/lib/hoodoo/version.rb +17 -0
  113. data/spec/active/active_record/base_spec.rb +57 -0
  114. data/spec/active/active_record/creator_spec.rb +88 -0
  115. data/spec/active/active_record/dated_spec.rb +248 -0
  116. data/spec/active/active_record/error_mapping_spec.rb +360 -0
  117. data/spec/active/active_record/finder_spec.rb +744 -0
  118. data/spec/active/active_record/search_helper_spec.rb +384 -0
  119. data/spec/active/active_record/secure_spec.rb +435 -0
  120. data/spec/active/active_record/support_spec.rb +225 -0
  121. data/spec/active/active_record/translated_spec.rb +19 -0
  122. data/spec/active/active_record/uuid_spec.rb +72 -0
  123. data/spec/active/active_record/writer_spec.rb +272 -0
  124. data/spec/alchemy/alchemy-amq.rb +33 -0
  125. data/spec/client/augmented_array_spec.rb +15 -0
  126. data/spec/client/augmented_base_spec.rb +50 -0
  127. data/spec/client/augmented_hash_spec.rb +15 -0
  128. data/spec/client/client_spec.rb +955 -0
  129. data/spec/client/endpoint/endpoint_spec.rb +70 -0
  130. data/spec/client/endpoint/endpoints/amqp_spec.rb +16 -0
  131. data/spec/client/endpoint/endpoints/auto_session_spec.rb +9 -0
  132. data/spec/client/endpoint/endpoints/http_based_spec.rb +9 -0
  133. data/spec/client/endpoint/endpoints/http_spec.rb +103 -0
  134. data/spec/client/endpoint/endpoints/not_found_spec.rb +35 -0
  135. data/spec/client/headers_spec.rb +172 -0
  136. data/spec/communicators/fast_spec.rb +9 -0
  137. data/spec/communicators/pool_spec.rb +339 -0
  138. data/spec/communicators/slow_spec.rb +15 -0
  139. data/spec/data/resources/caller_spec.rb +156 -0
  140. data/spec/data/resources/errors_spec.rb +22 -0
  141. data/spec/data/resources/log_spec.rb +20 -0
  142. data/spec/data/resources/session_spec.rb +15 -0
  143. data/spec/data/types/error_primitive_spec.rb +15 -0
  144. data/spec/data/types/permissions_defaults_spec.rb +25 -0
  145. data/spec/data/types/permissions_full_spec.rb +44 -0
  146. data/spec/data/types/permissions_resources_spec.rb +34 -0
  147. data/spec/data/types/permissions_spec.rb +37 -0
  148. data/spec/errors/error_descriptions_spec.rb +98 -0
  149. data/spec/errors/errors_spec.rb +346 -0
  150. data/spec/integration/service_actions_spec.rb +112 -0
  151. data/spec/logger/fast_writer_spec.rb +18 -0
  152. data/spec/logger/logger_spec.rb +259 -0
  153. data/spec/logger/slow_writer_spec.rb +144 -0
  154. data/spec/logger/writers/file_writer_spec.rb +37 -0
  155. data/spec/logger/writers/log_entries_dot_com_writer_spec.rb +29 -0
  156. data/spec/logger/writers/stream_writer_spec.rb +38 -0
  157. data/spec/presenters/base_dsl_spec.rb +111 -0
  158. data/spec/presenters/base_spec.rb +871 -0
  159. data/spec/presenters/common_resource_fields_spec.rb +30 -0
  160. data/spec/presenters/embedding_spec.rb +87 -0
  161. data/spec/presenters/types/array_spec.rb +249 -0
  162. data/spec/presenters/types/boolean_spec.rb +51 -0
  163. data/spec/presenters/types/date_spec.rb +57 -0
  164. data/spec/presenters/types/date_time_spec.rb +59 -0
  165. data/spec/presenters/types/decimal_spec.rb +58 -0
  166. data/spec/presenters/types/enum_spec.rb +71 -0
  167. data/spec/presenters/types/field_spec.rb +77 -0
  168. data/spec/presenters/types/float_spec.rb +50 -0
  169. data/spec/presenters/types/hash_spec.rb +1069 -0
  170. data/spec/presenters/types/integer_spec.rb +50 -0
  171. data/spec/presenters/types/object_spec.rb +177 -0
  172. data/spec/presenters/types/string_spec.rb +65 -0
  173. data/spec/presenters/types/tags_spec.rb +56 -0
  174. data/spec/presenters/types/text_spec.rb +50 -0
  175. data/spec/presenters/types/uuid_spec.rb +46 -0
  176. data/spec/presenters/walk_spec.rb +198 -0
  177. data/spec/services/discovery/discoverers/by_consul_spec.rb +29 -0
  178. data/spec/services/discovery/discoverers/by_convention_spec.rb +67 -0
  179. data/spec/services/discovery/discoverers/by_drb/by_drb_spec.rb +80 -0
  180. data/spec/services/discovery/discoverers/by_drb/drb_server_spec.rb +205 -0
  181. data/spec/services/discovery/discovery_spec.rb +73 -0
  182. data/spec/services/discovery/results/for_amqp_spec.rb +17 -0
  183. data/spec/services/discovery/results/for_http_spec.rb +37 -0
  184. data/spec/services/discovery/results/for_local_spec.rb +21 -0
  185. data/spec/services/discovery/results/for_remote_spec.rb +15 -0
  186. data/spec/services/middleware/amqp_log_message_spec.rb +60 -0
  187. data/spec/services/middleware/amqp_log_writer_spec.rb +95 -0
  188. data/spec/services/middleware/endpoints/inter_resource_local_spec.rb +9 -0
  189. data/spec/services/middleware/endpoints/inter_resource_remote_spec.rb +9 -0
  190. data/spec/services/middleware/exception_reporting/base_reporter_spec.rb +16 -0
  191. data/spec/services/middleware/exception_reporting/exception_reporting_spec.rb +92 -0
  192. data/spec/services/middleware/exception_reporting/reporters/airbrake_reporter_spec.rb +24 -0
  193. data/spec/services/middleware/exception_reporting/reporters/raygun_reporter_spec.rb +23 -0
  194. data/spec/services/middleware/middleware_cors_spec.rb +93 -0
  195. data/spec/services/middleware/middleware_create_update_spec.rb +489 -0
  196. data/spec/services/middleware/middleware_dated_at_spec.rb +186 -0
  197. data/spec/services/middleware/middleware_exotic_communication_spec.rb +560 -0
  198. data/spec/services/middleware/middleware_logging_spec.rb +356 -0
  199. data/spec/services/middleware/middleware_multi_local_spec.rb +1094 -0
  200. data/spec/services/middleware/middleware_multi_remote_spec.rb +1440 -0
  201. data/spec/services/middleware/middleware_permissions_spec.rb +1014 -0
  202. data/spec/services/middleware/middleware_public_spec.rb +238 -0
  203. data/spec/services/middleware/middleware_spec.rb +1569 -0
  204. data/spec/services/middleware/string_inquirer_spec.rb +30 -0
  205. data/spec/services/services/application_spec.rb +74 -0
  206. data/spec/services/services/context_spec.rb +48 -0
  207. data/spec/services/services/implementation_spec.rb +45 -0
  208. data/spec/services/services/interface_spec.rb +262 -0
  209. data/spec/services/services/permissions_spec.rb +249 -0
  210. data/spec/services/services/request_spec.rb +95 -0
  211. data/spec/services/services/response_spec.rb +250 -0
  212. data/spec/services/services/session_spec.rb +432 -0
  213. data/spec/spec_helper.rb +298 -0
  214. data/spec/utilities/utilities_spec.rb +537 -0
  215. data/spec/utilities/uuid_spec.rb +20 -0
  216. metadata +615 -0
@@ -0,0 +1,106 @@
1
+ ########################################################################
2
+ # File:: support.rb
3
+ # (C):: Loyalty New Zealand 2015
4
+ #
5
+ # Purpose:: This file includes a support class that is basically a
6
+ # public, independent expression of a series of specialised
7
+ # methods that would otherwise have been private, were it not
8
+ # for them being called by mixin code. See
9
+ # Hoodoo::ActiveRecord::Support documentation for details.
10
+ # ----------------------------------------------------------------------
11
+ # 14-Jul-2015 (ADH): Created.
12
+ ########################################################################
13
+
14
+ module Hoodoo
15
+ module ActiveRecord
16
+
17
+ # Most of the ActiveRecord support code provides mixins with
18
+ # a public API. That public interface makes it obvious what
19
+ # the mixin's defined method names will be, helping to avoid
20
+ # collisions/shadowing. Sometimes, those methods want to share
21
+ # code but private methods don't work well in that context -
22
+ # their names could unwittingly collide with names in the
23
+ # including class, written by an author not aware of those
24
+ # essentially hidden but vital interfaces.
25
+ #
26
+ # This is a support class specifically designed to solve this
27
+ # issue. It's really a public, independent expression of a
28
+ # series of specialised methods that would otherwise have
29
+ # normally been private.
30
+ #
31
+ # Although this code forms part of the Hoodoo public API, its
32
+ # unusual status means that you should not really call any of
33
+ # these methods unless you're prepared to track unexpected
34
+ # API changes in them in future and update your calling code.
35
+ #
36
+ class Support
37
+
38
+ # Takes a Hash of possibly-non-String keys and with +nil+ values or
39
+ # Proc instances appropriate for Hoodoo::ActiveRecord::Finder#search_with
40
+ # / #filter_with. Returns a similar Hash with all-String keys and a Proc
41
+ # for every value.
42
+ #
43
+ # +hash+:: Hash Symbol or String keys and Proc instance or +nil+
44
+ # values.
45
+ #
46
+ def self.process_to_map( hash )
47
+ map = Hoodoo::Utilities.stringify( hash )
48
+
49
+ map.each do | attr, proc_or_nil |
50
+ if proc_or_nil.nil?
51
+ map[ attr ] = Hoodoo::ActiveRecord::Finder::SearchHelper.cs_match( attr )
52
+ end
53
+ end
54
+
55
+ return map
56
+ end
57
+
58
+ # Given an ActiveRecord class and Hoodoo request context, work out which
59
+ # Hoodoo support modules are included within this class and call base
60
+ # methods to provide a fully specified basic query chain obeying all the
61
+ # necessary aspects of the ActiveRecord model class and the request.
62
+ #
63
+ # Each of the following are called if the owning module is included:
64
+ #
65
+ # * Hoodoo::ActiveRecord::Secure#secure
66
+ # * Hoodoo::ActiveRecord::Translated#translated
67
+ # * Hoodoo::ActiveRecord::Dated#dated
68
+ #
69
+ # +klass+:: The ActiveRecord::Base subclass _class_ (not instance)
70
+ # which is making the call here. This is the entity which is
71
+ # checked for module inclusions to determine how the query
72
+ # chain should be assembled.
73
+ #
74
+ # +context+:: Hoodoo::Services::Context instance describing a call
75
+ # context. This is typically a value passed to one of
76
+ # the Hoodoo::Services::Implementation instance methods
77
+ # that a resource subclass implements.
78
+ #
79
+ # Returns an ActiveRecord::Relation instance which is anything from a
80
+ # generic anonymous scope, all the way through to a secured, translated,
81
+ # backdated scope for use with subsequent query refinements.
82
+ #
83
+ def self.full_scope_for( klass, context )
84
+ prevailing_scope = klass.all() # "Model.all" -> returns anonymous scope
85
+
86
+ # Due to the mechanism used, dating scope must be done first or the
87
+ # rest of the query may be invalid.
88
+ #
89
+ if klass.include?( Hoodoo::ActiveRecord::Dated )
90
+ prevailing_scope = prevailing_scope.dated( context )
91
+ end
92
+
93
+ if klass.include?( Hoodoo::ActiveRecord::Secure )
94
+ prevailing_scope = prevailing_scope.secure( context )
95
+ end
96
+
97
+ if klass.include?( Hoodoo::ActiveRecord::Translated )
98
+ prevailing_scope = prevailing_scope.translated( context )
99
+ end
100
+
101
+ return prevailing_scope
102
+ end
103
+
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,87 @@
1
+ ########################################################################
2
+ # File:: translated.rb
3
+ # (C):: Loyalty New Zealand 2015
4
+ #
5
+ # Purpose:: Support mixin for models subclassed from ActiveRecord::Base
6
+ # providing as-per-API-standard internationalisation support.
7
+ # ----------------------------------------------------------------------
8
+ # 14-Jul-2015 (ADH): Created.
9
+ ########################################################################
10
+
11
+ module Hoodoo
12
+ module ActiveRecord
13
+
14
+ # Support mixin for models subclassed from ActiveRecord::Base providing
15
+ # as-per-API-standard internationalisation support. See
16
+ # Hoodoo::ActiveRecord::Translated::ClassMethods#translated for details.
17
+ #
18
+ # See also:
19
+ #
20
+ # * http://guides.rubyonrails.org/active_record_basics.html
21
+ #
22
+ module Translated
23
+
24
+ # Instantiates this module when it is included:
25
+ #
26
+ # Example:
27
+ #
28
+ # class SomeModel < ActiveRecord::Base
29
+ # include Hoodoo::ActiveRecord::Translated
30
+ # # ...
31
+ # end
32
+ #
33
+ # +model+:: The ActiveRecord::Base descendant that is including
34
+ # this module.
35
+ #
36
+ def self.included( model )
37
+ model.class_attribute(
38
+ :nz_co_loyalty_hoodoo_translate_with,
39
+ {
40
+ :instance_predicate => false,
41
+ :instance_accessor => false
42
+ }
43
+ )
44
+
45
+ instantiate( model ) unless model == Hoodoo::ActiveRecord::Base
46
+ super( model )
47
+ end
48
+
49
+ # When instantiated in an ActiveRecord::Base subclass, all of the
50
+ # Hoodoo::ActiveRecord::Translated::ClassMethods methods are defined as
51
+ # class methods on the including class.
52
+ #
53
+ # +model+:: The ActiveRecord::Base descendant that is including
54
+ # this module.
55
+ #
56
+ def self.instantiate( model )
57
+ model.extend( ClassMethods )
58
+ end
59
+
60
+ # Collection of class methods that get defined on an including class via
61
+ # Hoodoo::ActiveRecord::Translated::included.
62
+ #
63
+ module ClassMethods
64
+
65
+ # TODO: Placeholder.
66
+ #
67
+ # +context+:: Hoodoo::Services::Context instance describing a call
68
+ # context. This is typically a value passed to one of
69
+ # the Hoodoo::Services::Implementation instance methods
70
+ # that a resource subclass implements.
71
+ #
72
+ def translated( context )
73
+ prevailing_scope = all() # "Model.all" -> returns anonymous scope
74
+ return prevailing_scope
75
+ end
76
+
77
+ # def translate_with( map )
78
+ # self.nz_co_loyalty_hoodoo_translate_with = map
79
+ # end
80
+ #
81
+ # def translated_with
82
+ # self.nz_co_loyalty_hoodoo_translate_with
83
+ # end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,80 @@
1
+ ########################################################################
2
+ # File:: uuid.rb
3
+ # (C):: Loyalty New Zealand 2014
4
+ #
5
+ # Purpose:: Support mixin for models subclassed from ActiveRecord::Base
6
+ # providing UUID management.
7
+ # ----------------------------------------------------------------------
8
+ # 17-Nov-2014 (ADH): Created.
9
+ ########################################################################
10
+
11
+ module Hoodoo
12
+ module ActiveRecord
13
+
14
+ # Support mixin for models subclassed from ActiveRecord::Base providing
15
+ # automatic UUID management. See:
16
+ #
17
+ # * http://guides.rubyonrails.org/active_record_basics.html
18
+ #
19
+ # By including this module, an on-create validation is added to the
20
+ # including model which assigns a UUID if none is currently set (+id+
21
+ # is +nil+). It also adds validations to ensure the +id+ is present,
22
+ # unique and a valid UUID. You should always make sure that there are
23
+ # accompanying database-level uniqueness and non-null constraints on
24
+ # the relevant table's `id` column, too.
25
+ #
26
+ # *IMPORTANT:* See Hoodoo::ActiveRecord::UUID::included for important
27
+ # information about database requirements / table creation when using
28
+ # this mixin.
29
+ #
30
+ module UUID
31
+
32
+ # Instantiates this module when it is included:
33
+ #
34
+ # Example:
35
+ #
36
+ # class SomeModel < ActiveRecord::Base
37
+ # include Hoodoo::ActiveRecord::UUID
38
+ # # ...
39
+ # end
40
+ #
41
+ # +model+:: The ActiveRecord::Base descendant class that is including
42
+ # this module.
43
+ #
44
+ def self.included( model )
45
+ instantiate( model ) unless model == Hoodoo::ActiveRecord::Base
46
+ super( model )
47
+ end
48
+
49
+ # When called, this method:
50
+ #
51
+ # - Declares 'id' as the primary key
52
+ # - Self-assigns a UUID to 'id' via an on-create validation
53
+ # - Adds validations to 'id' to ensure it is present, unique and a valid
54
+ # UUID.
55
+ #
56
+ # The model *MUST* define its database representation in migrations so
57
+ # that +id+ is a string based primary key, as follows:
58
+ #
59
+ # create_table :model_table_name, :id => :string do | t |
60
+ # # ...your normal column definitions go here...
61
+ # end
62
+ #
63
+ # change_column :model_table_name, :id, :string, :limit => 32
64
+ #
65
+ # +model+:: The ActiveRecord::Base descendant class that is including
66
+ # this module.
67
+ #
68
+ def self.instantiate( model )
69
+ model.primary_key = 'id'
70
+
71
+ model.validate( :on => :create ) do
72
+ self.id = Hoodoo::UUID.generate() if self.id.nil?
73
+ end
74
+
75
+ model.validates( :id, :uuid => true, :presence => true, :uniqueness => true )
76
+ end
77
+
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,321 @@
1
+ ########################################################################
2
+ # File:: writer.rb
3
+ # (C):: Loyalty New Zealand 2015
4
+ #
5
+ # Purpose:: Support mixin for models subclassed from ActiveRecord::Base
6
+ # providing context-aware data writing, allowing service
7
+ # authors to auto-inherit persistence-related features from
8
+ # Hoodoo without changing their code.
9
+ # ----------------------------------------------------------------------
10
+ # 31-Aug-2015 (ADH): Created.
11
+ ########################################################################
12
+
13
+ module Hoodoo
14
+
15
+ # Support mixins for models subclassed from ActiveRecord::Base. See:
16
+ #
17
+ # * http://guides.rubyonrails.org/active_record_basics.html
18
+ #
19
+ module ActiveRecord
20
+
21
+ # Support mixin for models subclassed from ActiveRecord::Base providing
22
+ # context-aware data writing, allowing service authors to auto-inherit
23
+ # persistence-related features from Hoodoo without changing their own
24
+ # code.
25
+ #
26
+ # See individual module methods for examples, along with:
27
+ #
28
+ # * http://guides.rubyonrails.org/active_record_basics.html
29
+ #
30
+ # Dependency Hoodoo::ActiveRecord::ErrorMapping is also included
31
+ # automatically.
32
+ #
33
+ module Writer
34
+
35
+ # Instantiates this module when it is included:
36
+ #
37
+ # Example:
38
+ #
39
+ # class SomeModel < ActiveRecord::Base
40
+ # include Hoodoo::ActiveRecord::Writer
41
+ # # ...
42
+ # end
43
+ #
44
+ # +model+:: The ActiveRecord::Base descendant class that is including
45
+ # this module.
46
+ #
47
+ def self.included( model )
48
+ unless model == Hoodoo::ActiveRecord::Base
49
+ model.send( :include, Hoodoo::ActiveRecord::ErrorMapping )
50
+ instantiate( model )
51
+ end
52
+
53
+ super( model )
54
+ end
55
+
56
+ # When instantiated in an ActiveRecord::Base subclass, all of the
57
+ # Hoodoo::ActiveRecord::Writer::ClassMethods methods are defined as
58
+ # class methods on the including class.
59
+ #
60
+ # This module depends upon Hoodoo::ActiveRecord::ErrorMapping, so
61
+ # that will be auto-included first if it isn't already.
62
+ #
63
+ # +model+:: The ActiveRecord::Base descendant that is including
64
+ # this module.
65
+ #
66
+ def self.instantiate( model )
67
+ model.extend( ClassMethods )
68
+
69
+ # See instance method "persist_in" for how this gets used.
70
+ #
71
+ model.validate do
72
+ if @nz_co_loyalty_hoodoo_writer_db_uniqueness_violation == true
73
+ errors.add( :base, 'has already been taken' )
74
+ end
75
+ end
76
+ end
77
+
78
+ # Instance equivalent of
79
+ # Hoodoo::ActiveRecord::Writer::ClassMethods.persist_in - see that for
80
+ # details. The class method just calls here, having constructed an
81
+ # instance based on the attributes it was given. If you have already
82
+ # built an instance yourself, just call this instance method equivalent
83
+ # instead.
84
+ #
85
+ # As an instance-based method, the return value and error handling
86
+ # semantics differ from the class-based counterpart. Instead of
87
+ # checking "persisted?", check the return value of +persist_in+. This
88
+ # means you can also use +persist_in+ to save a previousl persisted, but
89
+ # now updated record, should you so wish.
90
+ #
91
+ # def create( context )
92
+ # attributes = mapping_of( context.request.body )
93
+ # model_instance = Unique.new( attributes )
94
+ #
95
+ # # ...maybe make other changes to model_instance, then...
96
+ #
97
+ # unless model_instance.persist_in( context ) === :success
98
+ #
99
+ # # Error condition. If you're using the error handler mixin
100
+ # # in Hoodoo::ActiveRecord::ErrorMapping, do this:
101
+ # #
102
+ # context.response.add_errors( model_instance.platform_errors )
103
+ # return # Early exit
104
+ #
105
+ # end
106
+ #
107
+ # # ...any other processing...
108
+ #
109
+ # context.response.set_resource( rendering_of( context, model_instance ) )
110
+ # end
111
+ #
112
+ # Parameters:
113
+ #
114
+ # +context+:: Hoodoo::Services::Context instance describing a call
115
+ # context. This is typically a value passed to one of
116
+ # the Hoodoo::Services::Implementation instance methods
117
+ # that a resource subclass implements.
118
+ #
119
+ # Returns a Symbol of +:success+ or +:failure+ indicating the outcome
120
+ # of the same attempt. In the event of failure, the model will be
121
+ # invalid and not persisted; you can read errors immediately and should
122
+ # avoid unnecessarily re-running validations by calling +valid?+ or
123
+ # +validate+ on the instance.
124
+ #
125
+ def persist_in( context )
126
+
127
+ # If this model has an ActiveRecord uniqueness validation, it is
128
+ # still subject to race conditions and MUST be backed by a database
129
+ # constraint. If this constraint fails, try to re-run model
130
+ # validations just in case it was a race condition case; though of
131
+ # course, it could be that there is *only* a database constraint and
132
+ # no model validation. If there is *only* a model validation, the
133
+ # model is ill-defined and at risk.
134
+
135
+ # TODO: This flag is nasty but seems unavoidable. Whenever you query
136
+ # the validity of a record, AR will always clear all errors and
137
+ # then (re-)run validations. We cannot just add an error to
138
+ # "base" and expect it to survive. Instead, it's necessary to
139
+ # use this flag to signal to the custom validator added in the
140
+ # 'self.instantiate' implementation earlier that it should add
141
+ # an error. Trouble is, when do we clear the flag...?
142
+ #
143
+ # This solution works but is inelegant and fragile.
144
+ #
145
+ @nz_co_loyalty_hoodoo_writer_db_uniqueness_violation = false
146
+
147
+ # First just see if we have any problems saving anyway.
148
+ #
149
+ errors_occurred = begin
150
+ self.transaction( :requires_new => true ) do
151
+ :any unless self.save
152
+ end
153
+ rescue ::ActiveRecord::RecordNotUnique => error
154
+ :duplication
155
+ end
156
+
157
+ # If an exception caught a duplication violation then either there is
158
+ # a race condition on an AR-level uniqueness validation, or no such
159
+ # validation at all. Thus, re-run validations with "valid?" and if it
160
+ # still seems OK we must be dealing with a database-only constraint.
161
+ # Set the magic flag (ugh, see earlier) to signal that when
162
+ # validations run, they should add a relevant error to "base".
163
+ #
164
+ if errors_occurred == :duplication
165
+ if self.valid?
166
+ @nz_co_loyalty_hoodoo_writer_db_uniqueness_violation = true
167
+ self.validate
168
+ end
169
+ end
170
+
171
+ return errors_occurred.nil? ? :success : :failure
172
+ end
173
+
174
+ # Collection of class methods that get defined on an including class via
175
+ # Hoodoo::ActiveRecord::Writer::included.
176
+ #
177
+ module ClassMethods
178
+
179
+ # == Overview
180
+ #
181
+ # Service authors _SHOULD_ use this method when persisting data with
182
+ # ActiveRecord if there is a risk of duplication constraint violation
183
+ # of any kind. This will include a violation on the UUID of a resource
184
+ # if you support external setting of this value via the body of a
185
+ # +create+ call containing the +id+ field, injected by Hoodoo as the
186
+ # result of an authorised use of the <tt>X-Resource-UUID</tt> HTTP
187
+ # header.
188
+ #
189
+ # Services often run in highly concurrent environments and uniqueness
190
+ # constraint validations with ActiveRecord cannot protect against
191
+ # race conditions in such cases. IT works at the application level;
192
+ # the check to see if a record exists with a duplicate value in some
193
+ # given column is a separate operation from that which stores the
194
+ # record subsequently. As per the Rails Guides entry on the uniqueness
195
+ # validation at the time of writing:
196
+ #
197
+ # http://guides.rubyonrails.org/active_record_validations.html#uniqueness
198
+ #
199
+ # <i>"It does not create a uniqueness constraint in the database, so
200
+ # it may happen that two different database connections create two
201
+ # records with the same value for a column that you intend to be
202
+ # unique. To avoid that, you must create a unique index on both
203
+ # columns in your database."</i>
204
+ #
205
+ # You *MUST* always use a uniqueness constraint at the database level
206
+ # and *MAY* additionally use ActiveRecord validations for a higher
207
+ # level warning in all but race condition edge cases. If you then use
208
+ # this +persist_in+ method to store records, all duplication cases
209
+ # will be handled elegantly and reported as a
210
+ # <tt>generic.invalid_duplication</tt> error. In the event that a
211
+ # caller has used the <tt>X-Deja-Vu</tt> HTTP header, Hoodoo will take
212
+ # such an error and transform it into a non-error 204 HTTP response;
213
+ # so by using +persist_in+, you also ensure that your service
214
+ # participates successfully in this process without any additional
215
+ # coding effort. You get safe concurrency and protection against the
216
+ # inherent lack of idempotency in HTTP +POST+ operations via any
217
+ # must-be-unique fields (within your defined scope) automatically.
218
+ #
219
+ # Using this method for data storage instead of plain ActiveRecord
220
+ # +send+ or <tt>send!</tt> will also help your code auto-inherit any
221
+ # additional future write-related enhancements in Hoodoo should they
222
+ # arise, without necessarily needing service code changes.
223
+ #
224
+ #
225
+ # == Example
226
+ #
227
+ # class Unique < ActiveRecord::Base
228
+ # include Hoodoo::ActiveRecord::Writer
229
+ # validates :unique_code, :presence => true, :uniqueness => true
230
+ # end
231
+ #
232
+ # The migration to create the table for the Unique model _MUST_ have a
233
+ # uniqueness constraint on the +unique_code+ field, e.g.:
234
+ #
235
+ # def change
236
+ # add_column :uniques, :unique_code, :null => false
237
+ # add_index :uniques, [ :unique_code ], :unique => true
238
+ # end
239
+ #
240
+ # Then, inside the implementation class which uses the above model,
241
+ # where you have (say) written private methods +mapping_of+ which
242
+ # maps +context.request.body+ to an attributes Hash for persistence
243
+ # and +rendering_of+ which uses Hoodoo::Presenters::Base.render_in to
244
+ # properly render a representation of your resource, you would write:
245
+ #
246
+ # def create( context )
247
+ # attributes = mapping_of( context.request.body )
248
+ # model_instance = Unique.persist_in( context, attributes )
249
+ #
250
+ # unless model_instance.persisted?
251
+ #
252
+ # # Error condition. If you're using the error handler mixin
253
+ # # in Hoodoo::ActiveRecord::ErrorMapping, do this:
254
+ # #
255
+ # context.response.add_errors( model_instance.platform_errors )
256
+ # return # Early exit
257
+ #
258
+ # end
259
+ #
260
+ # # ...any other processing...
261
+ #
262
+ # context.response.set_resource( rendering_of( context, model_instance ) )
263
+ # end
264
+ #
265
+ #
266
+ # == Parameters
267
+ #
268
+ # +context+:: Hoodoo::Services::Context instance describing a call
269
+ # context. This is typically a value passed to one of
270
+ # the Hoodoo::Services::Implementation instance methods
271
+ # that a resource subclass implements.
272
+ #
273
+ # +attributes+:: Attributes hash to be passed to this model class's
274
+ # constructor, via <tt>self.new( attributes )</tt>.
275
+ #
276
+ # See also the Hoodoo::ActiveRecord::Writer#persist_in instance method
277
+ # equivalent of this class method.
278
+ #
279
+ #
280
+ # == Nested transaction note
281
+ #
282
+ # Ordinarily an exception in a nested transaction does not roll back.
283
+ # ActiveRecord wraps all saves in a transaction "out of the box", so
284
+ # the following construct could have unexpected results...
285
+ #
286
+ # Model.transaction do
287
+ # instance.persist_in( context )
288
+ # end
289
+ #
290
+ # ...if <tt>instance.valid?</tt> runs any SQL queries - which is very
291
+ # likely. PostgreSQL, for example, would then raise an exception; the
292
+ # inner transaction failed, leaving the outer one in an aborted state:
293
+ #
294
+ # PG::InFailedSqlTransaction: ERROR: current transaction is
295
+ # aborted, commands ignored until end of transaction block
296
+ #
297
+ # ActiveRecord provides us with a way to define a transaction that
298
+ # does roll back via the <tt>requires_new: true</tt> option. Hoodoo
299
+ # thus protects callers from the above artefacts by ensuring that all
300
+ # saves are wrapped in an outer transaction that causes rollback in
301
+ # any parents. This sidesteps the unexpected behaviour, but service
302
+ # authors might sometimes need to be aware of this if using complex
303
+ # transaction behaviour along with <tt>persist_in</tt>.
304
+ #
305
+ # In pseudocode, the internal implementation is:
306
+ #
307
+ # self.transaction( :requires_new => true ) do
308
+ # self.save
309
+ # end
310
+ #
311
+ def persist_in( context, attributes )
312
+ instance = self.new( attributes )
313
+ instance.persist_in( context )
314
+
315
+ return instance
316
+ end
317
+
318
+ end
319
+ end
320
+ end
321
+ end