hoodoo 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
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