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.
- checksums.yaml +7 -0
- data/bin/hoodoo +5 -0
- data/lib/hoodoo.rb +27 -0
- data/lib/hoodoo/active.rb +32 -0
- data/lib/hoodoo/active/active_model/uuid_validator.rb +45 -0
- data/lib/hoodoo/active/active_record/base.rb +81 -0
- data/lib/hoodoo/active/active_record/creator.rb +134 -0
- data/lib/hoodoo/active/active_record/dated.rb +343 -0
- data/lib/hoodoo/active/active_record/error_mapping.rb +351 -0
- data/lib/hoodoo/active/active_record/finder.rb +606 -0
- data/lib/hoodoo/active/active_record/search_helper.rb +189 -0
- data/lib/hoodoo/active/active_record/secure.rb +431 -0
- data/lib/hoodoo/active/active_record/support.rb +106 -0
- data/lib/hoodoo/active/active_record/translated.rb +87 -0
- data/lib/hoodoo/active/active_record/uuid.rb +80 -0
- data/lib/hoodoo/active/active_record/writer.rb +321 -0
- data/lib/hoodoo/client.rb +23 -0
- data/lib/hoodoo/client/augmented_array.rb +29 -0
- data/lib/hoodoo/client/augmented_base.rb +168 -0
- data/lib/hoodoo/client/augmented_hash.rb +23 -0
- data/lib/hoodoo/client/client.rb +354 -0
- data/lib/hoodoo/client/endpoint/endpoint.rb +427 -0
- data/lib/hoodoo/client/endpoint/endpoints/amqp.rb +180 -0
- data/lib/hoodoo/client/endpoint/endpoints/auto_session.rb +194 -0
- data/lib/hoodoo/client/endpoint/endpoints/http.rb +203 -0
- data/lib/hoodoo/client/endpoint/endpoints/http_based.rb +367 -0
- data/lib/hoodoo/client/endpoint/endpoints/not_found.rb +59 -0
- data/lib/hoodoo/client/headers.rb +269 -0
- data/lib/hoodoo/communicators.rb +23 -0
- data/lib/hoodoo/communicators/fast.rb +44 -0
- data/lib/hoodoo/communicators/pool.rb +601 -0
- data/lib/hoodoo/communicators/slow.rb +84 -0
- data/lib/hoodoo/data.rb +51 -0
- data/lib/hoodoo/data/resources/caller.rb +39 -0
- data/lib/hoodoo/data/resources/errors.rb +28 -0
- data/lib/hoodoo/data/resources/log.rb +31 -0
- data/lib/hoodoo/data/resources/session.rb +26 -0
- data/lib/hoodoo/data/types/error_primitive.rb +27 -0
- data/lib/hoodoo/data/types/permissions.rb +40 -0
- data/lib/hoodoo/data/types/permissions_defaults.rb +32 -0
- data/lib/hoodoo/data/types/permissions_full.rb +28 -0
- data/lib/hoodoo/data/types/permissions_resources.rb +31 -0
- data/lib/hoodoo/discovery.rb +20 -0
- data/lib/hoodoo/errors.rb +19 -0
- data/lib/hoodoo/errors/error_descriptions.rb +229 -0
- data/lib/hoodoo/errors/errors.rb +322 -0
- data/lib/hoodoo/generator.rb +139 -0
- data/lib/hoodoo/logger.rb +23 -0
- data/lib/hoodoo/logger/fast_writer.rb +27 -0
- data/lib/hoodoo/logger/flattener_mixin.rb +36 -0
- data/lib/hoodoo/logger/logger.rb +387 -0
- data/lib/hoodoo/logger/slow_writer.rb +49 -0
- data/lib/hoodoo/logger/writer_mixin.rb +52 -0
- data/lib/hoodoo/logger/writers/file_writer.rb +45 -0
- data/lib/hoodoo/logger/writers/log_entries_dot_com_writer.rb +64 -0
- data/lib/hoodoo/logger/writers/stream_writer.rb +43 -0
- data/lib/hoodoo/middleware.rb +33 -0
- data/lib/hoodoo/presenters.rb +45 -0
- data/lib/hoodoo/presenters/base.rb +281 -0
- data/lib/hoodoo/presenters/base_dsl.rb +519 -0
- data/lib/hoodoo/presenters/common_resource_fields.rb +31 -0
- data/lib/hoodoo/presenters/embedding.rb +232 -0
- data/lib/hoodoo/presenters/types/array.rb +118 -0
- data/lib/hoodoo/presenters/types/boolean.rb +26 -0
- data/lib/hoodoo/presenters/types/date.rb +26 -0
- data/lib/hoodoo/presenters/types/date_time.rb +26 -0
- data/lib/hoodoo/presenters/types/decimal.rb +47 -0
- data/lib/hoodoo/presenters/types/enum.rb +55 -0
- data/lib/hoodoo/presenters/types/field.rb +158 -0
- data/lib/hoodoo/presenters/types/float.rb +26 -0
- data/lib/hoodoo/presenters/types/hash.rb +361 -0
- data/lib/hoodoo/presenters/types/integer.rb +26 -0
- data/lib/hoodoo/presenters/types/object.rb +117 -0
- data/lib/hoodoo/presenters/types/string.rb +53 -0
- data/lib/hoodoo/presenters/types/tags.rb +24 -0
- data/lib/hoodoo/presenters/types/text.rb +26 -0
- data/lib/hoodoo/presenters/types/uuid.rb +54 -0
- data/lib/hoodoo/services.rb +34 -0
- data/lib/hoodoo/services/discovery/discoverers/by_consul.rb +66 -0
- data/lib/hoodoo/services/discovery/discoverers/by_convention.rb +173 -0
- data/lib/hoodoo/services/discovery/discoverers/by_drb/by_drb.rb +195 -0
- data/lib/hoodoo/services/discovery/discoverers/by_drb/drb_server.rb +166 -0
- data/lib/hoodoo/services/discovery/discoverers/by_drb/drb_server_start.rb +37 -0
- data/lib/hoodoo/services/discovery/discovery.rb +186 -0
- data/lib/hoodoo/services/discovery/results/for_amqp.rb +58 -0
- data/lib/hoodoo/services/discovery/results/for_http.rb +85 -0
- data/lib/hoodoo/services/discovery/results/for_local.rb +85 -0
- data/lib/hoodoo/services/discovery/results/for_remote.rb +57 -0
- data/lib/hoodoo/services/middleware/amqp_log_message.rb +186 -0
- data/lib/hoodoo/services/middleware/amqp_log_writer.rb +119 -0
- data/lib/hoodoo/services/middleware/endpoints/inter_resource_local.rb +130 -0
- data/lib/hoodoo/services/middleware/endpoints/inter_resource_remote.rb +202 -0
- data/lib/hoodoo/services/middleware/exception_reporting/base_reporter.rb +105 -0
- data/lib/hoodoo/services/middleware/exception_reporting/exception_reporting.rb +115 -0
- data/lib/hoodoo/services/middleware/exception_reporting/reporters/airbrake_reporter.rb +64 -0
- data/lib/hoodoo/services/middleware/exception_reporting/reporters/raygun_reporter.rb +63 -0
- data/lib/hoodoo/services/middleware/interaction.rb +127 -0
- data/lib/hoodoo/services/middleware/middleware.rb +2705 -0
- data/lib/hoodoo/services/middleware/rack_monkey_patch.rb +73 -0
- data/lib/hoodoo/services/services/context.rb +153 -0
- data/lib/hoodoo/services/services/implementation.rb +132 -0
- data/lib/hoodoo/services/services/interface.rb +934 -0
- data/lib/hoodoo/services/services/permissions.rb +250 -0
- data/lib/hoodoo/services/services/request.rb +189 -0
- data/lib/hoodoo/services/services/response.rb +316 -0
- data/lib/hoodoo/services/services/service.rb +141 -0
- data/lib/hoodoo/services/services/session.rb +729 -0
- data/lib/hoodoo/utilities.rb +12 -0
- data/lib/hoodoo/utilities/string_inquirer.rb +54 -0
- data/lib/hoodoo/utilities/utilities.rb +380 -0
- data/lib/hoodoo/utilities/uuid.rb +44 -0
- data/lib/hoodoo/version.rb +17 -0
- data/spec/active/active_record/base_spec.rb +57 -0
- data/spec/active/active_record/creator_spec.rb +88 -0
- data/spec/active/active_record/dated_spec.rb +248 -0
- data/spec/active/active_record/error_mapping_spec.rb +360 -0
- data/spec/active/active_record/finder_spec.rb +744 -0
- data/spec/active/active_record/search_helper_spec.rb +384 -0
- data/spec/active/active_record/secure_spec.rb +435 -0
- data/spec/active/active_record/support_spec.rb +225 -0
- data/spec/active/active_record/translated_spec.rb +19 -0
- data/spec/active/active_record/uuid_spec.rb +72 -0
- data/spec/active/active_record/writer_spec.rb +272 -0
- data/spec/alchemy/alchemy-amq.rb +33 -0
- data/spec/client/augmented_array_spec.rb +15 -0
- data/spec/client/augmented_base_spec.rb +50 -0
- data/spec/client/augmented_hash_spec.rb +15 -0
- data/spec/client/client_spec.rb +955 -0
- data/spec/client/endpoint/endpoint_spec.rb +70 -0
- data/spec/client/endpoint/endpoints/amqp_spec.rb +16 -0
- data/spec/client/endpoint/endpoints/auto_session_spec.rb +9 -0
- data/spec/client/endpoint/endpoints/http_based_spec.rb +9 -0
- data/spec/client/endpoint/endpoints/http_spec.rb +103 -0
- data/spec/client/endpoint/endpoints/not_found_spec.rb +35 -0
- data/spec/client/headers_spec.rb +172 -0
- data/spec/communicators/fast_spec.rb +9 -0
- data/spec/communicators/pool_spec.rb +339 -0
- data/spec/communicators/slow_spec.rb +15 -0
- data/spec/data/resources/caller_spec.rb +156 -0
- data/spec/data/resources/errors_spec.rb +22 -0
- data/spec/data/resources/log_spec.rb +20 -0
- data/spec/data/resources/session_spec.rb +15 -0
- data/spec/data/types/error_primitive_spec.rb +15 -0
- data/spec/data/types/permissions_defaults_spec.rb +25 -0
- data/spec/data/types/permissions_full_spec.rb +44 -0
- data/spec/data/types/permissions_resources_spec.rb +34 -0
- data/spec/data/types/permissions_spec.rb +37 -0
- data/spec/errors/error_descriptions_spec.rb +98 -0
- data/spec/errors/errors_spec.rb +346 -0
- data/spec/integration/service_actions_spec.rb +112 -0
- data/spec/logger/fast_writer_spec.rb +18 -0
- data/spec/logger/logger_spec.rb +259 -0
- data/spec/logger/slow_writer_spec.rb +144 -0
- data/spec/logger/writers/file_writer_spec.rb +37 -0
- data/spec/logger/writers/log_entries_dot_com_writer_spec.rb +29 -0
- data/spec/logger/writers/stream_writer_spec.rb +38 -0
- data/spec/presenters/base_dsl_spec.rb +111 -0
- data/spec/presenters/base_spec.rb +871 -0
- data/spec/presenters/common_resource_fields_spec.rb +30 -0
- data/spec/presenters/embedding_spec.rb +87 -0
- data/spec/presenters/types/array_spec.rb +249 -0
- data/spec/presenters/types/boolean_spec.rb +51 -0
- data/spec/presenters/types/date_spec.rb +57 -0
- data/spec/presenters/types/date_time_spec.rb +59 -0
- data/spec/presenters/types/decimal_spec.rb +58 -0
- data/spec/presenters/types/enum_spec.rb +71 -0
- data/spec/presenters/types/field_spec.rb +77 -0
- data/spec/presenters/types/float_spec.rb +50 -0
- data/spec/presenters/types/hash_spec.rb +1069 -0
- data/spec/presenters/types/integer_spec.rb +50 -0
- data/spec/presenters/types/object_spec.rb +177 -0
- data/spec/presenters/types/string_spec.rb +65 -0
- data/spec/presenters/types/tags_spec.rb +56 -0
- data/spec/presenters/types/text_spec.rb +50 -0
- data/spec/presenters/types/uuid_spec.rb +46 -0
- data/spec/presenters/walk_spec.rb +198 -0
- data/spec/services/discovery/discoverers/by_consul_spec.rb +29 -0
- data/spec/services/discovery/discoverers/by_convention_spec.rb +67 -0
- data/spec/services/discovery/discoverers/by_drb/by_drb_spec.rb +80 -0
- data/spec/services/discovery/discoverers/by_drb/drb_server_spec.rb +205 -0
- data/spec/services/discovery/discovery_spec.rb +73 -0
- data/spec/services/discovery/results/for_amqp_spec.rb +17 -0
- data/spec/services/discovery/results/for_http_spec.rb +37 -0
- data/spec/services/discovery/results/for_local_spec.rb +21 -0
- data/spec/services/discovery/results/for_remote_spec.rb +15 -0
- data/spec/services/middleware/amqp_log_message_spec.rb +60 -0
- data/spec/services/middleware/amqp_log_writer_spec.rb +95 -0
- data/spec/services/middleware/endpoints/inter_resource_local_spec.rb +9 -0
- data/spec/services/middleware/endpoints/inter_resource_remote_spec.rb +9 -0
- data/spec/services/middleware/exception_reporting/base_reporter_spec.rb +16 -0
- data/spec/services/middleware/exception_reporting/exception_reporting_spec.rb +92 -0
- data/spec/services/middleware/exception_reporting/reporters/airbrake_reporter_spec.rb +24 -0
- data/spec/services/middleware/exception_reporting/reporters/raygun_reporter_spec.rb +23 -0
- data/spec/services/middleware/middleware_cors_spec.rb +93 -0
- data/spec/services/middleware/middleware_create_update_spec.rb +489 -0
- data/spec/services/middleware/middleware_dated_at_spec.rb +186 -0
- data/spec/services/middleware/middleware_exotic_communication_spec.rb +560 -0
- data/spec/services/middleware/middleware_logging_spec.rb +356 -0
- data/spec/services/middleware/middleware_multi_local_spec.rb +1094 -0
- data/spec/services/middleware/middleware_multi_remote_spec.rb +1440 -0
- data/spec/services/middleware/middleware_permissions_spec.rb +1014 -0
- data/spec/services/middleware/middleware_public_spec.rb +238 -0
- data/spec/services/middleware/middleware_spec.rb +1569 -0
- data/spec/services/middleware/string_inquirer_spec.rb +30 -0
- data/spec/services/services/application_spec.rb +74 -0
- data/spec/services/services/context_spec.rb +48 -0
- data/spec/services/services/implementation_spec.rb +45 -0
- data/spec/services/services/interface_spec.rb +262 -0
- data/spec/services/services/permissions_spec.rb +249 -0
- data/spec/services/services/request_spec.rb +95 -0
- data/spec/services/services/response_spec.rb +250 -0
- data/spec/services/services/session_spec.rb +432 -0
- data/spec/spec_helper.rb +298 -0
- data/spec/utilities/utilities_spec.rb +537 -0
- data/spec/utilities/uuid_spec.rb +20 -0
- 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
|