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