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
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a8eb578694851746b49e396703f6c903f54ec246
4
+ data.tar.gz: c7af73f5ff6427953502ff131b5342d1e7993886
5
+ SHA512:
6
+ metadata.gz: 7b67ac9d46b515be1e8d66a1ca41b4e16c2598ee3cf24a9e10199fc66d5daf1dfba74a7a437f75626463a98be1de8f673d180b22a561d460193c11b151cd15ce
7
+ data.tar.gz: c7b45c63ac2e9fd1588e127c449cece947f5aecb0c5cba0182c097b7381ff53aee94c2f1cc37025c7c56c0a0f1b1bdbab625da22a0fea77fbbdc463e77c31b82
data/bin/hoodoo ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
3
+ require 'hoodoo/generator'
4
+
5
+ exit Hoodoo::Generator.instance.run!(ARGV)
data/lib/hoodoo.rb ADDED
@@ -0,0 +1,27 @@
1
+ ########################################################################
2
+ # File:: hoodoo.rb
3
+ # (C):: Loyalty New Zealand 2014
4
+ #
5
+ # Purpose:: Include all parts of Hoodoo.
6
+ # ----------------------------------------------------------------------
7
+ # 29-Jan-2015 (ADH): Added file documentation.
8
+ ########################################################################
9
+
10
+ # Module used as a namespace for all of Hoodoo's facilities.
11
+ #
12
+ module Hoodoo
13
+ end
14
+
15
+ require 'hoodoo/utilities'
16
+ require 'hoodoo/communicators'
17
+ require 'hoodoo/logger'
18
+ require 'hoodoo/presenters'
19
+ require 'hoodoo/data'
20
+ require 'hoodoo/errors'
21
+ require 'hoodoo/active'
22
+ require 'hoodoo/client'
23
+ require 'hoodoo/discovery'
24
+ require 'hoodoo/services'
25
+ require 'hoodoo/middleware'
26
+
27
+ require 'hoodoo/version'
@@ -0,0 +1,32 @@
1
+ ########################################################################
2
+ # File:: active.rb
3
+ # (C):: Loyalty New Zealand 2014
4
+ #
5
+ # Purpose:: Include ActiveRecord-/ActiveModel-dependent optional
6
+ # extras.
7
+ # ----------------------------------------------------------------------
8
+ # 26-Jan-2015 (ADH): Split from top-level inclusion file.
9
+ ########################################################################
10
+
11
+ # Dependencies
12
+
13
+ require 'hoodoo/utilities'
14
+ require 'hoodoo/errors'
15
+
16
+ # ActiveRecord / ActiveModel extras
17
+
18
+ require 'hoodoo/active/active_model/uuid_validator'
19
+
20
+ require 'hoodoo/active/active_record/support'
21
+ require 'hoodoo/active/active_record/error_mapping'
22
+
23
+ require 'hoodoo/active/active_record/secure'
24
+ require 'hoodoo/active/active_record/dated'
25
+ require 'hoodoo/active/active_record/translated'
26
+ require 'hoodoo/active/active_record/finder'
27
+
28
+ require 'hoodoo/active/active_record/uuid'
29
+ require 'hoodoo/active/active_record/creator'
30
+ require 'hoodoo/active/active_record/writer'
31
+
32
+ require 'hoodoo/active/active_record/base'
@@ -0,0 +1,45 @@
1
+ ########################################################################
2
+ # File:: uuid_validator.rb
3
+ # (C):: Loyalty New Zealand 2014
4
+ #
5
+ # Purpose:: UUID validator for models.
6
+ #
7
+ # ----------------------------------------------------------------------
8
+ # 26-Nov-2014 (RJS): Created.
9
+ ########################################################################
10
+
11
+ module Hoodoo
12
+ module ActiveRecord
13
+ begin
14
+ require 'active_model'
15
+
16
+ # Provides simple UUID validation via an ActiveModel::EachValidator.
17
+ # Uuid is not capitalised as ActiveModel's "magic" cannot find the
18
+ # validator if it is.
19
+ #
20
+ class ::UuidValidator < ::ActiveModel::EachValidator
21
+
22
+ # Any field this validator is applied to is considered valid if it is
23
+ # +nil+ or a valid UUID. In the case of UUIDs which should not be nil,
24
+ # a separate validation must be added.
25
+ #
26
+ # Example:
27
+ #
28
+ # class SomeModel < ActiveRecord::Base
29
+ #
30
+ # validates :somefield, uuid: true
31
+ # end
32
+ #
33
+ def validate_each( record, attribute, value )
34
+
35
+ unless value.nil? || Hoodoo::UUID.valid?( value )
36
+ record.errors[ attribute ] << ( options[ :message ] || 'is invalid' )
37
+ end
38
+
39
+ end
40
+
41
+ end
42
+ rescue LoadError
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,81 @@
1
+ ########################################################################
2
+ # File:: base.rb
3
+ # (C):: Loyalty New Zealand 2014
4
+ #
5
+ # Purpose:: Include all mixins.
6
+ # ----------------------------------------------------------------------
7
+ # 25-Nov-2014 (ADH): Created.
8
+ ########################################################################
9
+
10
+ module Hoodoo
11
+ module ActiveRecord
12
+
13
+ begin
14
+ require 'active_record'
15
+
16
+ # While individual ActiveRecord mixins can be included as and
17
+ # when needed, if you want the set of mixins, just define a model
18
+ # which subclasses from this Hoodoo::ActiveRecord::Base class
19
+ # instead of ActiveRecord::Base.
20
+ #
21
+ # This will include:
22
+ #
23
+ # * Hoodoo::ActiveRecord::Secure
24
+ # * Hoodoo::ActiveRecord::Dated
25
+ # * Hoodoo::ActiveRecord::Translated
26
+ # * Hoodoo::ActiveRecord::Finder
27
+ # * Hoodoo::ActiveRecord::UUID
28
+ # * Hoodoo::ActiveRecord::Creator
29
+ # * Hoodoo::ActiveRecord::Writer
30
+ # * Hoodoo::ActiveRecord::ErrorMapping
31
+ #
32
+ class Base < ::ActiveRecord::Base
33
+
34
+ # Reading data.
35
+ #
36
+ include Hoodoo::ActiveRecord::Secure
37
+ include Hoodoo::ActiveRecord::Dated
38
+ include Hoodoo::ActiveRecord::Translated
39
+ include Hoodoo::ActiveRecord::Finder
40
+
41
+ # Writing data.
42
+ #
43
+ include Hoodoo::ActiveRecord::UUID
44
+ include Hoodoo::ActiveRecord::Creator
45
+ include Hoodoo::ActiveRecord::Writer
46
+
47
+ # Other features.
48
+ #
49
+ include Hoodoo::ActiveRecord::ErrorMapping
50
+
51
+ # Tells ActiveRecord this is not a model that is persisted.
52
+ #
53
+ self.abstract_class = true
54
+
55
+ # Instantiates all the ActiveRecord mixins when this class is
56
+ # inherited.
57
+ #
58
+ # +model+:: The ActiveRecord::Base descendant that is including
59
+ # this module.
60
+ #
61
+ def self.inherited( model )
62
+
63
+ Hoodoo::ActiveRecord::Secure.instantiate( model )
64
+ Hoodoo::ActiveRecord::Dated.instantiate( model )
65
+ Hoodoo::ActiveRecord::Translated.instantiate( model )
66
+ Hoodoo::ActiveRecord::Finder.instantiate( model )
67
+
68
+ Hoodoo::ActiveRecord::UUID.instantiate( model )
69
+ Hoodoo::ActiveRecord::Creator.instantiate( model )
70
+ Hoodoo::ActiveRecord::Writer.instantiate( model )
71
+
72
+ super( model )
73
+
74
+ end
75
+ end
76
+
77
+ rescue LoadError
78
+ end
79
+
80
+ end
81
+ end
@@ -0,0 +1,134 @@
1
+ ########################################################################
2
+ # File:: creator.rb
3
+ # (C):: Loyalty New Zealand 2015
4
+ #
5
+ # Purpose:: Support mixin for models subclassed from ActiveRecord::Base
6
+ # providing context-aware model instance creation, allowing
7
+ # service authors to auto-inherit related features from Hoodoo
8
+ # without changing their code.
9
+ # ----------------------------------------------------------------------
10
+ # 07-Dec-2015 (ADH): Created as a proper place for "new_in",
11
+ # which had historically and confusingly
12
+ # resided inside the Finder mixin.
13
+ ########################################################################
14
+
15
+ module Hoodoo
16
+
17
+ # Support mixins for models subclassed from ActiveRecord::Base. See:
18
+ #
19
+ # * http://guides.rubyonrails.org/active_record_basics.html
20
+ #
21
+ module ActiveRecord
22
+
23
+ # Support mixin for models subclassed from ActiveRecord::Base providing
24
+ # context-aware model instance creation, allowing service authors to
25
+ # auto-inherit related features from Hoodoo without changing their code.
26
+ #
27
+ # It is _STRONGLY_ _RECOMMENDED_ that you use the likes of:
28
+ #
29
+ # * Hoodoo::ActiveRecord::Creator::ClassMethods::new_in
30
+ #
31
+ # ...to create model instances and participate "for free" in whatever
32
+ # plug-in ActiveRecord modules are mixed into the model classes, such as
33
+ # Hoodoo::ActiveRecord::Dated.
34
+ #
35
+ # See also:
36
+ #
37
+ # * http://guides.rubyonrails.org/active_record_basics.html
38
+ #
39
+ module Creator
40
+
41
+ # Instantiates this module when it is included:
42
+ #
43
+ # Example:
44
+ #
45
+ # class SomeModel < ActiveRecord::Base
46
+ # include Hoodoo::ActiveRecord::Creator
47
+ # # ...
48
+ # end
49
+ #
50
+ # +model+:: The ActiveRecord::Base descendant that is including
51
+ # this module.
52
+ #
53
+ def self.included( model )
54
+ instantiate( model ) unless model == Hoodoo::ActiveRecord::Base
55
+ super( model )
56
+ end
57
+
58
+ # When instantiated in an ActiveRecord::Base subclass, all of the
59
+ # Hoodoo::ActiveRecord::Dated::ClassMethods methods are defined as
60
+ # class methods on the including class.
61
+ #
62
+ # +model+:: The ActiveRecord::Base descendant that is including
63
+ # this module.
64
+ #
65
+ def self.instantiate( model )
66
+ model.extend( ClassMethods )
67
+ end
68
+
69
+ # Collection of class methods that get defined on an including class via
70
+ # Hoodoo::ActiveRecord::Creator::included.
71
+ #
72
+ module ClassMethods
73
+
74
+ # Create an instance of this model with knowledge of the wider request
75
+ # context. This may lead to important things like support of inbound
76
+ # "dated_from" values, depending upon the Hoodoo mixins included (or
77
+ # not) by this class - see Hoodoo::ActiveRecord::Dated.
78
+ #
79
+ # You use this exactly as you would for ActiveRecord::Core#new, but an
80
+ # additional, mandatory first parameter providing the request context
81
+ # must be supplied. For example, instead of this:
82
+ #
83
+ # instance = SomeActiveRecordSubclass.new( attrs )
84
+ #
85
+ # ...do this inside a resource implementation:
86
+ #
87
+ # instance = SomeActiveRecordSubclass.new_in( context, attrs )
88
+ #
89
+ # See also:
90
+ #
91
+ # * http://api.rubyonrails.org/classes/ActiveRecord/Base.html
92
+ #
93
+ # Parameters:
94
+ #
95
+ # +context+:: Hoodoo::Services::Context instance describing a call
96
+ # context. This is typically a value passed to one of
97
+ # the Hoodoo::Services::Implementation instance methods
98
+ # that a resource subclass implements.
99
+ #
100
+ # +attributes+:: Optional model attributes Hash, passed through to
101
+ # ActiveRecord::Core#new.
102
+ #
103
+ # &block:: Optional block for initialisation, passed through to
104
+ # ActiveRecord::Core#new.
105
+ #
106
+ # Returns a new model instance which may have context-derived values
107
+ # set for some attributes, in addition to anything set through the
108
+ # +attributes+ or <tt>&block</tt> parameters, if present.
109
+ #
110
+ # Note that context-dependent data is set _AFTER_ attribute or block
111
+ # based values, so takes precedence over anything you might set up
112
+ # using those parameters.
113
+ #
114
+ def new_in( context, attributes = nil, &block )
115
+
116
+ instance = self.new( attributes, &block )
117
+
118
+ # TODO: Refactor this to use the scope chain plugin approach in due
119
+ # course, but for now, pragmatic implementation does the only
120
+ # thing we currently need to do - set "created_at".
121
+ #
122
+ if self.include?( Hoodoo::ActiveRecord::Dated )
123
+ unless context.request.dated_from.nil?
124
+ instance.created_at = instance.updated_at = context.request.dated_from
125
+ end
126
+ end
127
+
128
+ return instance
129
+ end
130
+
131
+ end
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,343 @@
1
+ ########################################################################
2
+ # File:: dated.rb
3
+ # (C):: Loyalty New Zealand 2015
4
+ #
5
+ # Purpose:: Support mixin for models subclassed from ActiveRecord::Base
6
+ # providing as-per-API-standard dating support.
7
+ # ----------------------------------------------------------------------
8
+ # 14-Jul-2015 (ADH): Created.
9
+ # 21-Jul-2015 (RJS): Functionality implemented.
10
+ ########################################################################
11
+
12
+ module Hoodoo
13
+ module ActiveRecord
14
+
15
+ # Support mixin for models subclassed from ActiveRecord::Base providing
16
+ # as-per-API-standard dating support.
17
+ #
18
+ # The facilities provided here are powerful but relatively complex, so
19
+ # please read through this documentation section in full to understand
20
+ # everything you need to do.
21
+ #
22
+ # == Overview
23
+ #
24
+ # This mixin adds finder methods to the model it is applied to (see
25
+ # Hoodoo::ActiveRecord::Dated::ClassMethods#dated and
26
+ # Hoodoo::ActiveRecord::Dated::ClassMethods#dated_at). These finders require
27
+ # two database tables in order to function correctly - the primary table
28
+ # (the model table) and a history table. When a record is updated it should
29
+ # be moved to the history table and a new record inserted with the new
30
+ # values. When a record is deleted it should be moved to the history
31
+ # table. This can be done manually with application code, or by things like
32
+ # SQL triggers (see later).
33
+ #
34
+ # Dating is only enabled if the including class explicitly calls the
35
+ # Hoodoo::ActiveRecord::Dated::ClassMethods#dating_enabled method.
36
+ #
37
+ # == Database table requirements
38
+ #
39
+ # In all related tables, all date-time values must be stored as UTC.
40
+ #
41
+ # The primary table _must_ have a unique column named +id+ and two
42
+ # timestamp columns named +updated_at+ and +created_at+ which both need
43
+ # to be set by the application code (the ActiveRecord +timestamps+ macro
44
+ # in a migration file defines appropriate columns).
45
+ #
46
+ # The history table requires the same columns as the primary table with two
47
+ # differences:
48
+ #
49
+ # 1. The history table's +id+ column must be populated with any unique
50
+ # value whilst the history table's +uuid+ column must be populated with
51
+ # the primary table's +id+ value.
52
+ #
53
+ # 2. The history table must have two additional columns, +effective_start+
54
+ # and +effective_end+. The +effective_start+ column determines when the
55
+ # history entry becomes effective (inclusive) whilst the +effective_end+
56
+ # determines when the history entry was effective to (exclusive). A record
57
+ # is considered to be effective at a particular time if that time is the
58
+ # same or after the +effective_start+ and before the +effective_end+.
59
+ #
60
+ # The +effective_start+ must be set to the +effective_end+ of the last
61
+ # record with same +uuid+, or to the +created_at+ of the record if there
62
+ # is no previous records with the same +uuid+.
63
+ #
64
+ # The +effective_end+ must be set to the current time (UTC) when
65
+ # deleting a record or to the updated record's +updated_at+ when updating
66
+ # a record.
67
+ #
68
+ # Additionally there are two constraints on the history table that must not
69
+ # be broken for the finder methods to function correctly:
70
+ #
71
+ # 1. When adding a record to the history table its +effective_end+ must be
72
+ # after all other records in the history table with the same +uuid+.
73
+ #
74
+ # 2. When inserting a new record to the primary table its +id+ must not
75
+ # exist in the history table.
76
+ #
77
+ # The history table name defaults to the name of the primary table
78
+ # concatenated with +_history_entries+. This can be overriden when calling
79
+ # Hoodoo::ActiveRecord::Dated::ClassMethods#dating_enabled.
80
+ #
81
+ # Example:
82
+ #
83
+ # class Post < ActiveRecord::Base
84
+ # include Hoodoo::ActiveRecord::Dated
85
+ # dating_enabled( history_table_name: 'historical_posts' )
86
+ # end
87
+ #
88
+ # == Migration assistance
89
+ #
90
+ # Compatible database migration generators are included in +service_shell+.
91
+ # These migrations create the history table and add database triggers
92
+ # (PostgreSQL specific) which will handle the creation of the appropriate
93
+ # history entry when a record is deleted or updated without breaking the
94
+ # history table constraints. See
95
+ # https://github.com/LoyaltyNZ/service_shell/blob/master/bin/generators/effective_date.rb
96
+ # for more information.
97
+ #
98
+ # == Model instance creation
99
+ #
100
+ # It is _VERY_ _IMPORTANT_ that you use
101
+ # Hoodoo::ActiveRecord::Creator::ClassMethods::new_in to create new
102
+ # resource instances when using Dating. You _could_ just manually read the
103
+ # `context.request.dated_from` value to ensure that an appropriate creation
104
+ # time is set; presently, `created_at` and `updated_at` are set from the
105
+ # `dated_from` value. However, using `new_in` for this isolates your code
106
+ # from any possible under-the-hood implementation changes therein and
107
+ # future-proof your code.
108
+ #
109
+ module Dated
110
+
111
+ # Instantiates this module when it is included:
112
+ #
113
+ # Example:
114
+ #
115
+ # class SomeModel < ActiveRecord::Base
116
+ # include Hoodoo::ActiveRecord::Dated
117
+ # # ...
118
+ # end
119
+ #
120
+ # +model+:: The ActiveRecord::Base descendant that is including
121
+ # this module.
122
+ #
123
+ def self.included( model )
124
+ model.class_attribute(
125
+ :nz_co_loyalty_hoodoo_dated_with,
126
+ {
127
+ :instance_predicate => false,
128
+ :instance_accessor => false
129
+ }
130
+ )
131
+
132
+ instantiate( model ) unless model == Hoodoo::ActiveRecord::Base
133
+ super( model )
134
+ end
135
+
136
+ # When instantiated in an ActiveRecord::Base subclass, all of the
137
+ # Hoodoo::ActiveRecord::Dated::ClassMethods methods are defined as
138
+ # class methods on the including class.
139
+ #
140
+ # +model+:: The ActiveRecord::Base descendant that is including
141
+ # this module.
142
+ #
143
+ def self.instantiate( model )
144
+ model.extend( ClassMethods )
145
+ end
146
+
147
+ # Forms a String containing the specified +model_klass+'s attribute names
148
+ # escaped and joined with commas.
149
+ #
150
+ # +model_klass+ Class which responds to .attribute_names
151
+ #
152
+ def self.sanitised_column_string( model_klass )
153
+ model_klass.attribute_names.map{ | c | ActiveRecord::Base.connection.quote_column_name( c ) }.join( ',' )
154
+ end
155
+
156
+ # Collection of class methods that get defined on an including class via
157
+ # Hoodoo::ActiveRecord::Dated::included.
158
+ #
159
+ module ClassMethods
160
+
161
+ # Activate historic dating for this model.
162
+ #
163
+ # See the module documentation for Hoodoo::ActiveRecord::Dated for
164
+ # full information on dating, table requirements, default table names
165
+ # and so forth.
166
+ #
167
+ # *Named* parameters are:
168
+ #
169
+ # +history_table_name+:: Optional String or Symbol name of the table
170
+ # which stores the history entries for this
171
+ # model. If omitted, defaults to the value
172
+ # described by the documentation for
173
+ # Hoodoo::ActiveRecord::Dated.
174
+ #
175
+ def dating_enabled( history_table_name: self.table_name + '_history_entries' )
176
+
177
+ # Define an anonymous model for the history entries.
178
+
179
+ history_klass = Class.new( ::ActiveRecord::Base ) do
180
+ self.primary_key = :id
181
+ self.table_name = history_table_name
182
+ end
183
+
184
+ # Record the anonymous model class in a namespaced class attribute
185
+ # for reference in the rest of the dating code via #dated_with().
186
+
187
+ self.nz_co_loyalty_hoodoo_dated_with = history_klass
188
+
189
+ end
190
+
191
+ # Return an ActiveRecord::Relation containing the model instances which
192
+ # are effective at +context.request.dated_at+. If this value is nil the
193
+ # current time in UTC is used.
194
+ #
195
+ # If historic dating hasn't been enabled via a call to #dating_enabled,
196
+ # then the default 'all' scope is returned instead.
197
+ #
198
+ # +context+:: Hoodoo::Services::Context instance describing a call
199
+ # context. This is typically a value passed to one of
200
+ # the Hoodoo::Services::Implementation instance methods
201
+ # that a resource subclass implements.
202
+ #
203
+ def dated( context )
204
+ date_time = context.request.dated_at || Time.now
205
+ return self.dated_at( date_time )
206
+ end
207
+
208
+ # Return an ActiveRecord::Relation scoping a query to include only model
209
+ # instances that are relevant/effective at the specified date_time.
210
+ #
211
+ # If historic dating hasn't been enabled via a call to #dating_enabled,
212
+ # then the default 'all' scope is returned instead.
213
+ #
214
+ # +date_time+:: (Optional) A Time or DateTime instance, or a String that
215
+ # can be converted to a DateTime instance, for which the
216
+ # "effective dated" scope is to be constructed.
217
+ #
218
+ def dated_at( date_time = Time.now )
219
+
220
+ dating_table_name = dated_with_table_name()
221
+ return all() if dating_table_name.nil? # "Model.all" -> returns anonymous scope
222
+
223
+ # Rationalise and convert the date time to UTC.
224
+
225
+ date_time = Hoodoo::Utilities.rationalise_datetime( date_time ).utc
226
+
227
+ # Create a string that specifies this model's attributes escaped and
228
+ # joined by commas for use in a SQL query.
229
+
230
+ formatted_model_attributes = Hoodoo::ActiveRecord::Dated.sanitised_column_string( self )
231
+
232
+ # Convert date_time to a String suitable for an SQL query.
233
+
234
+ string_date_time = sanitize( date_time )
235
+
236
+ # A query that combines historical and current records which are
237
+ # effective at the specified date time.
238
+
239
+ nested_query = %{
240
+ (
241
+ SELECT #{ formatted_model_attributes } FROM (
242
+ SELECT #{ formatted_model_attributes }, updated_at as effective_start, null AS effective_end
243
+ FROM #{ self.table_name }
244
+
245
+ UNION ALL
246
+
247
+ SELECT #{ self.dated_with_history_column_mapping }, effective_start, effective_end
248
+ FROM #{ dating_table_name }
249
+ ) AS u
250
+ WHERE effective_start <= #{ string_date_time } AND (effective_end > #{ string_date_time } OR effective_end IS NULL)
251
+ ) AS #{ self.table_name }
252
+ }
253
+
254
+ # Form a query which uses ActiveRecord to list a dated or current
255
+ # record.
256
+
257
+ return from( nested_query )
258
+ end
259
+
260
+ # Return an ActiveRecord::Relation scoping a query that would include
261
+ # all historical and current model instances.
262
+ #
263
+ # If historic dating hasn't been enabled via a call to #dating_enabled,
264
+ # then the default 'all' scope is returned instead.
265
+ #
266
+ def dated_historical_and_current
267
+
268
+ dating_table_name = dated_with_table_name()
269
+ return all() if dating_table_name.nil? # "Model.all" -> returns anonymous scope
270
+
271
+ # Create a string that specifies this model's attributes escaped and
272
+ # joined by commas for use in a SQL query.
273
+
274
+ formatted_model_attributes = Hoodoo::ActiveRecord::Dated.sanitised_column_string( self )
275
+
276
+ # A query that combines historical and current records.
277
+
278
+ nested_query = %{
279
+ (
280
+ SELECT #{ formatted_model_attributes }
281
+ FROM #{ self.table_name }
282
+
283
+ UNION ALL
284
+
285
+ SELECT #{ self.dated_with_history_column_mapping }
286
+ FROM #{ dating_table_name }
287
+ ) AS #{ self.table_name }
288
+ }
289
+
290
+ # Form a query which uses ActiveRecord to list current and dated
291
+ # records.
292
+
293
+ return from( nested_query )
294
+ end
295
+
296
+ # Returns the anonymous ActiveRecord::Base instance used for this
297
+ # model's history entries, or +nil+ if historic dating has not been
298
+ # enabled via a prior call to #dating_enabled.
299
+ #
300
+ def dated_with
301
+ return self.nz_co_loyalty_hoodoo_dated_with
302
+ end
303
+
304
+ # Get the symbolised name of the history table for model. This defaults
305
+ # to the name of the model's table concatenated with +_history_entries+.
306
+ # If the table name is +:posts+, the history table would be
307
+ # +:posts_history_entries+.
308
+ #
309
+ # If historic dating hasn't been enabled via a call to #dating_enabled,
310
+ # returns +nil+.
311
+ #
312
+ def dated_with_table_name
313
+ instance = self.dated_with()
314
+ instance.nil? ? nil : instance.table_name
315
+ end
316
+
317
+ protected
318
+
319
+ # Forms and returns string which maps history table column names to the
320
+ # primary table column names for use in SQL queries.
321
+ #
322
+ def dated_with_history_column_mapping
323
+ desired_attributes = self.attribute_names.dup
324
+
325
+ # Locate the primary key field, which must be called "id".
326
+
327
+ primary_key_index = desired_attributes.index( 'id' )
328
+
329
+ # Sanitise the attribute names.
330
+
331
+ desired_attributes.map!{ | c | ActiveRecord::Base.connection.quote_column_name( c ) }
332
+
333
+ # Map the primary key.
334
+
335
+ desired_attributes[ primary_key_index ] = 'uuid as ' << desired_attributes[ primary_key_index ]
336
+
337
+ return desired_attributes.join( ',' )
338
+ end
339
+
340
+ end
341
+ end
342
+ end
343
+ end