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,189 @@
|
|
1
|
+
########################################################################
|
2
|
+
# File:: search_helper.rb
|
3
|
+
# (C):: Loyalty New Zealand 2015
|
4
|
+
#
|
5
|
+
# Purpose:: Supplementary helper class included by "finder.rb". See
|
6
|
+
# Hoodoo::ActiveRecord::Finder, especially
|
7
|
+
# Hoodoo::ActiveRecord::Finder#search_with, for details.
|
8
|
+
# ----------------------------------------------------------------------
|
9
|
+
# 09-Jul-2015 (ADH): Created.
|
10
|
+
########################################################################
|
11
|
+
|
12
|
+
module Hoodoo
|
13
|
+
module ActiveRecord
|
14
|
+
module Finder
|
15
|
+
|
16
|
+
# Help build up Hash maps to pass into Hoodoo::ActiveRecord::Finder
|
17
|
+
# methods Hoodoo::ActiveRecord::Finder#search_with and
|
18
|
+
# Hoodoo::ActiveRecord::Finder#filter_with.
|
19
|
+
#
|
20
|
+
# The usage pattern is as follows, using "sh" as a local variable
|
21
|
+
# just for brevity - it isn't required:
|
22
|
+
#
|
23
|
+
# sh = Hoodoo::ActiveRecord::Finder::SearchHelper
|
24
|
+
#
|
25
|
+
# class SomeModel < ActiveRecord::Base
|
26
|
+
# search_with(
|
27
|
+
# :colour => sh.cs_match_generic,
|
28
|
+
# :name => sh.ci_match_generic,
|
29
|
+
# :resource_ids => sh.cs_match_csv( :associated_id )
|
30
|
+
# end
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# The helper methods just provide values to pass into the Hash used
|
34
|
+
# with the search/fitler Hoodoo::ActiveRecord::Finder methods, so
|
35
|
+
# they're optional and compatible with calls that write it out "by
|
36
|
+
# hand".
|
37
|
+
#
|
38
|
+
# In all cases, in normal use the generated SQL _will not_ match +null+
|
39
|
+
# values; if negated for a filter ("<tt>where.not</tt>"), the generated
|
40
|
+
# SQL _will_ match +null+ values. As a result, passing in explicit
|
41
|
+
# searches for +nil+ won't work - but that's never expected as a use
|
42
|
+
# case here since search values are coming in via e.g. query
|
43
|
+
# string information from a URI.
|
44
|
+
#
|
45
|
+
class SearchHelper
|
46
|
+
|
47
|
+
# Case-sensitive match (default-style matching). *WARNING:* This
|
48
|
+
# will be case sensitive only if your database is configured for
|
49
|
+
# case sensitive matching by default.
|
50
|
+
#
|
51
|
+
# Results in a <tt>foo = bar</tt> query.
|
52
|
+
#
|
53
|
+
# +model_field_name+:: If the model attribute name differs from the
|
54
|
+
# search key you want to use in the URI, give
|
55
|
+
# the model attribute name here, else omit.
|
56
|
+
#
|
57
|
+
# Returns a value that can be asssigned to a URI query string key in
|
58
|
+
# the Hash given to Hoodoo::ActiveRecord::Finder#search_with or
|
59
|
+
# Hoodoo::ActiveRecord::Finder#filter_with.
|
60
|
+
#
|
61
|
+
def self.cs_match( model_field_name = nil )
|
62
|
+
Proc.new { | attr, value |
|
63
|
+
column = model_field_name || attr
|
64
|
+
|
65
|
+
[ "#{ column } = ? AND #{ column } IS NOT NULL", value ]
|
66
|
+
}
|
67
|
+
end
|
68
|
+
|
69
|
+
# Case-sensitive match of a series of values separated by commas,
|
70
|
+
# which are split into an array then processed by AREL back to
|
71
|
+
# something SQL-safe.
|
72
|
+
#
|
73
|
+
# Results in a <tt>foo IN bar,baz,boo</tt> query.
|
74
|
+
#
|
75
|
+
# +model_field_name+:: If the model attribute name differs from the
|
76
|
+
# search key you want to use in the URI, give
|
77
|
+
# the model attribute name here, else omit.
|
78
|
+
#
|
79
|
+
# Returns a value that can be asssigned to a URI query string key in
|
80
|
+
# the Hash given to Hoodoo::ActiveRecord::Finder#search_with or
|
81
|
+
# Hoodoo::ActiveRecord::Finder#filter_with.
|
82
|
+
#
|
83
|
+
def self.cs_match_csv( model_field_name = nil )
|
84
|
+
Proc.new { | attr, value |
|
85
|
+
column = model_field_name || attr
|
86
|
+
value = value.split( ',' )
|
87
|
+
|
88
|
+
[ "#{ column } IN (?) AND #{ column } IS NOT NULL", value ]
|
89
|
+
}
|
90
|
+
end
|
91
|
+
|
92
|
+
# Case-sensitive match of a series of values given as an Array.
|
93
|
+
# Normally, query string information comes in as a String so the
|
94
|
+
# use cases for this are quite unusual; you probably want to use
|
95
|
+
# #cs_match_csv most of the time.
|
96
|
+
#
|
97
|
+
# Results in a <tt>foo IN bar,baz,boo</tt> query.
|
98
|
+
#
|
99
|
+
# +model_field_name+:: If the model attribute name differs from the
|
100
|
+
# search key you want to use in the URI, give
|
101
|
+
# the model attribute name here, else omit.
|
102
|
+
#
|
103
|
+
# Returns a value that can be asssigned to a URI query string key in
|
104
|
+
# the Hash given to Hoodoo::ActiveRecord::Finder#search_with or
|
105
|
+
# Hoodoo::ActiveRecord::Finder#filter_with.
|
106
|
+
#
|
107
|
+
def self.cs_match_array( model_field_name = nil )
|
108
|
+
Proc.new { | attr, value |
|
109
|
+
column = model_field_name || attr
|
110
|
+
value = [ value ].flatten
|
111
|
+
|
112
|
+
[ "#{ column } IN (?) AND #{ column } IS NOT NULL", value ]
|
113
|
+
}
|
114
|
+
end
|
115
|
+
|
116
|
+
# Case-insensitive match which should be fairly database independent
|
117
|
+
# but will run relatively slowly as a result. If you are using
|
118
|
+
# PostgreSQL, consider using the faster #ci_match_postgres method
|
119
|
+
# instead.
|
120
|
+
#
|
121
|
+
# Results in a <tt>lower(foo) = bar</tt> query with +bar+ coerced to
|
122
|
+
# a String and converted to lower case by Ruby first.
|
123
|
+
#
|
124
|
+
# +model_field_name+:: If the model attribute name differs from the
|
125
|
+
# search key you want to use in the URI, give
|
126
|
+
# the model attribute name here, else omit.
|
127
|
+
#
|
128
|
+
# Returns a value that can be asssigned to a URI query string key in
|
129
|
+
# the Hash given to Hoodoo::ActiveRecord::Finder#search_with or
|
130
|
+
# Hoodoo::ActiveRecord::Finder#filter_with.
|
131
|
+
#
|
132
|
+
def self.ci_match_generic( model_field_name = nil )
|
133
|
+
Proc.new { | attr, value |
|
134
|
+
column = model_field_name || attr
|
135
|
+
value = ( value || '' ).to_s.downcase
|
136
|
+
|
137
|
+
[ "lower(#{ column }) = ? AND #{ column } IS NOT NULL", value ]
|
138
|
+
}
|
139
|
+
end
|
140
|
+
|
141
|
+
# As #ci_match_generic, but adds wildcards at the front and end of
|
142
|
+
# the string for a case-insensitive-all-wildcard match.
|
143
|
+
#
|
144
|
+
def self.ciaw_match_generic( model_field_name = nil )
|
145
|
+
Proc.new { | attr, value |
|
146
|
+
column = model_field_name || attr
|
147
|
+
value = ( value || '' ).to_s.downcase
|
148
|
+
|
149
|
+
[ "lower(#{ column }) LIKE ? AND #{ column } IS NOT NULL", "%#{ value }%" ]
|
150
|
+
}
|
151
|
+
end
|
152
|
+
|
153
|
+
# Case-insensitive match which requires PostgreSQL but should run
|
154
|
+
# quickly. If you need a database agnostic solution, consider using
|
155
|
+
# the slower #ci_match_generic method instead.
|
156
|
+
#
|
157
|
+
# Results in a <tt>foo ILIKE bar</tt> query.
|
158
|
+
#
|
159
|
+
# +model_field_name+:: If the model attribute name differs from the
|
160
|
+
# search key you want to use in the URI, give
|
161
|
+
# the model attribute name here, else omit.
|
162
|
+
#
|
163
|
+
# Returns a value that can be asssigned to a URI query string key in
|
164
|
+
# the Hash given to Hoodoo::ActiveRecord::Finder#search_with or
|
165
|
+
# Hoodoo::ActiveRecord::Finder#filter_with.
|
166
|
+
#
|
167
|
+
def self.ci_match_postgres( model_field_name = nil )
|
168
|
+
Proc.new { | attr, value |
|
169
|
+
column = model_field_name || attr
|
170
|
+
|
171
|
+
[ "#{ column } ILIKE ? AND #{ column } IS NOT NULL", value ]
|
172
|
+
}
|
173
|
+
end
|
174
|
+
|
175
|
+
# As #ci_match_postgres, but adds wildcards at the front and end of
|
176
|
+
# the string for a case-insensitive-all-wildcard match.
|
177
|
+
#
|
178
|
+
def self.ciaw_match_postgres( model_field_name = nil )
|
179
|
+
Proc.new { | attr, value |
|
180
|
+
column = model_field_name || attr
|
181
|
+
|
182
|
+
[ "#{ column } ILIKE ? AND #{ column } IS NOT NULL", "%#{ value }%" ]
|
183
|
+
}
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
@@ -0,0 +1,431 @@
|
|
1
|
+
########################################################################
|
2
|
+
# File:: secure.rb
|
3
|
+
# (C):: Loyalty New Zealand 2014
|
4
|
+
#
|
5
|
+
# Purpose:: Support mixin for models subclassed from ActiveRecord::Base
|
6
|
+
# providing enhanced find mechanisms for +show+ and +list+
|
7
|
+
# action handling.
|
8
|
+
# ----------------------------------------------------------------------
|
9
|
+
# 25-Nov-2014 (ADH): Created.
|
10
|
+
########################################################################
|
11
|
+
|
12
|
+
module Hoodoo
|
13
|
+
module ActiveRecord
|
14
|
+
|
15
|
+
# Support mixin for models subclassed from ActiveRecord::Base providing
|
16
|
+
# a core out-of-box Hoodoo data access security model. See
|
17
|
+
# Hoodoo::ActiveRecord::Secure::ClassMethods#secure for details.
|
18
|
+
#
|
19
|
+
# See also:
|
20
|
+
#
|
21
|
+
# * http://guides.rubyonrails.org/active_record_basics.html
|
22
|
+
#
|
23
|
+
module Secure
|
24
|
+
|
25
|
+
# Instantiates this module when it is included:
|
26
|
+
#
|
27
|
+
# Example:
|
28
|
+
#
|
29
|
+
# class SomeModel < ActiveRecord::Base
|
30
|
+
# include Hoodoo::ActiveRecord::Secure
|
31
|
+
# # ...
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# +model+:: The ActiveRecord::Base descendant that is including
|
35
|
+
# this module.
|
36
|
+
#
|
37
|
+
def self.included( model )
|
38
|
+
model.class_attribute(
|
39
|
+
:nz_co_loyalty_hoodoo_secure_with,
|
40
|
+
{
|
41
|
+
:instance_predicate => false,
|
42
|
+
:instance_accessor => false
|
43
|
+
}
|
44
|
+
)
|
45
|
+
|
46
|
+
instantiate( model ) unless model == Hoodoo::ActiveRecord::Base
|
47
|
+
super( model )
|
48
|
+
end
|
49
|
+
|
50
|
+
# When instantiated in an ActiveRecord::Base subclass, all of the
|
51
|
+
# Hoodoo::ActiveRecord::Secure::ClassMethods methods are defined as
|
52
|
+
# class methods on the including class.
|
53
|
+
#
|
54
|
+
# +model+:: The ActiveRecord::Base descendant that is including
|
55
|
+
# this module.
|
56
|
+
#
|
57
|
+
def self.instantiate( model )
|
58
|
+
model.extend( ClassMethods )
|
59
|
+
end
|
60
|
+
|
61
|
+
# Collection of class methods that get defined on an including class via
|
62
|
+
# Hoodoo::ActiveRecord::Secure::included.
|
63
|
+
#
|
64
|
+
module ClassMethods
|
65
|
+
|
66
|
+
# Internal.
|
67
|
+
#
|
68
|
+
# See #secure for details - this is the Proc used by default if no
|
69
|
+
# alternative argument generator is given in the longhand form's
|
70
|
+
# value Hash's +:using+ key.
|
71
|
+
#
|
72
|
+
DEFAULT_SECURE_PROC = Proc.new { | model_class, database_column_name, session_field_value | [ { database_column_name => session_field_value } ] }
|
73
|
+
|
74
|
+
# The core of out-of-the-box Hoodoo data access security layer.
|
75
|
+
#
|
76
|
+
# Parameters:
|
77
|
+
#
|
78
|
+
# +context+:: Hoodoo::Services::Context instance describing a call
|
79
|
+
# context. This is typically a value passed to one of
|
80
|
+
# the Hoodoo::Services::Implementation instance methods
|
81
|
+
# that a resource subclass implements.
|
82
|
+
#
|
83
|
+
# == Overview
|
84
|
+
#
|
85
|
+
# In most non-trivial systems, people calling into the system under
|
86
|
+
# a Session will have limited access to resource records. Often the
|
87
|
+
# broad pattern is: Someone can only see what they create. Maybe
|
88
|
+
# there's a superuser-like monitoring concept of someone who can
|
89
|
+
# see what everyone creates... In any event, there needs to be some
|
90
|
+
# kind of support for this.
|
91
|
+
#
|
92
|
+
# In the Hoodoo generic case, it's tackled at several levels.
|
93
|
+
#
|
94
|
+
# * A Caller object can describe fields that are identify who that
|
95
|
+
# Caller is (which may be as simple as the Caller instance's
|
96
|
+
# resource UUID, or may include additional concepts specific to
|
97
|
+
# the API being designed/implemented).
|
98
|
+
#
|
99
|
+
# * A Session instance is bound to a particular Caller. Someone
|
100
|
+
# calling the API creates a Session using a caller ID and secret,
|
101
|
+
# and gains whatever access permissions and data privileges it
|
102
|
+
# describes.
|
103
|
+
#
|
104
|
+
# * Custom implementations of a Session resource and Caller resource
|
105
|
+
# endpoint might add in other identifying fields to the session
|
106
|
+
# payload too. That's what the Session's +identity+ section is
|
107
|
+
# for. See Hoodoo::Services::Session#identity.
|
108
|
+
#
|
109
|
+
# * When resource endpoint implementations create data, they have
|
110
|
+
# an opportunity to use a database field to record (say) the
|
111
|
+
# caller UUID and/or some other session value(s) in indexed table
|
112
|
+
# columns along the lines of "creating_caller_uuid", or similar.
|
113
|
+
# This way, the "who made me" information is preserved.
|
114
|
+
#
|
115
|
+
# * When resource endpoints read back any data from the database
|
116
|
+
# (for show, list, update or delete actions) the "who made me"
|
117
|
+
# information needs to be compared against 'what the session is
|
118
|
+
# allowed to see'. That's in the Session's +scoping+ section.
|
119
|
+
# See Hoodoo::Services::Session#scoping. For example, a custom
|
120
|
+
# Session resource endpoint might record one or more caller
|
121
|
+
# UUIDs in "scoping.authorised_caller_uuids".
|
122
|
+
#
|
123
|
+
# Given things along this line, resource endpoints would have to
|
124
|
+
# individually scope ActiveRecord +find+ calls to make sure that it
|
125
|
+
# only dealt with database records where the 'who made me' data
|
126
|
+
# matched up with the 'what can this Session see'. That can be done
|
127
|
+
# but it might be error prone, especially if a lot of resource
|
128
|
+
# endpoints all have the same data access scoping rules.
|
129
|
+
#
|
130
|
+
# == Automatic session-based finder scoping
|
131
|
+
#
|
132
|
+
# That's where the ActiveRecord secure context extension comes in.
|
133
|
+
# Models declare _mappings_ between database fields and fields in
|
134
|
+
# the Session's +scoping+ container. An ActiveRecord::Relation is
|
135
|
+
# returned which produces a simple query along the lines of:
|
136
|
+
#
|
137
|
+
# Model.where( :database_field => session.scoping.scoped_field )
|
138
|
+
#
|
139
|
+
# At the time of writing, only simple matches of as shown above can
|
140
|
+
# be defined; bespoke resource endpoint implementation code would be
|
141
|
+
# needed for something more complex. All you can do is make sure
|
142
|
+
# that one or more fields in the database match with one more fields
|
143
|
+
# in the Session scoping data.
|
144
|
+
#
|
145
|
+
# Taking the examples of a database column +creating_caller_uuid+
|
146
|
+
# and a Session scoping entry called +authorised_caller_uuids+, a
|
147
|
+
# model would do the following to declare the mapped connection
|
148
|
+
# between database and session:
|
149
|
+
#
|
150
|
+
# class Audit < ActiveRecord::Base
|
151
|
+
# include Hoodoo::ActiveRecord::Secure
|
152
|
+
#
|
153
|
+
# secure_with( {
|
154
|
+
# :creating_caller_uuid => :authorised_caller_uuids
|
155
|
+
# } )
|
156
|
+
# end
|
157
|
+
#
|
158
|
+
# Then, inside subclass implementation of (for example)
|
159
|
+
# Hoodoo::Services::Implementation#list:
|
160
|
+
#
|
161
|
+
# def list( context )
|
162
|
+
# secure_scope = Audit.secure( context )
|
163
|
+
# end
|
164
|
+
#
|
165
|
+
# The 'secure_scope' is just an ActiveRecord::Relation instance;
|
166
|
+
# you could call +to_sql+ on the result for debugging and print the
|
167
|
+
# result to console if you wanted to see the query built up so far.
|
168
|
+
# Otherwise, any of the ActiveRecord::QueryMethods can be called;
|
169
|
+
# see:
|
170
|
+
#
|
171
|
+
# http://api.rubyonrails.org/classes/ActiveRecord/QueryMethods.html
|
172
|
+
#
|
173
|
+
# The most common use cases, though, involve finding a specific
|
174
|
+
# record or listing records. Hoodoo::ActiveRecord::Finder provides
|
175
|
+
# much higher level constructs that build on top of #secure and
|
176
|
+
# you are strongly encouraged to use these wherever possible, rather
|
177
|
+
# than calling #secure directly.
|
178
|
+
#
|
179
|
+
# For more advanced query conditions that a single database column
|
180
|
+
# checked against a session value with an implicit +AND+, see later.
|
181
|
+
#
|
182
|
+
# == Important!
|
183
|
+
#
|
184
|
+
# If you state a model must be secured by one or more fields, then:
|
185
|
+
#
|
186
|
+
# * If there is no session at all in the given context, _or_
|
187
|
+
# * The session has no scoping data, _or_
|
188
|
+
# * The session scoping data does not have one or more of the
|
189
|
+
# fields that the #secure_with map's values describe, _then_
|
190
|
+
#
|
191
|
+
# ...the returned scope *will* *find* *no* *results*, by design.
|
192
|
+
# The default failure mode is to reveal no data at all.
|
193
|
+
#
|
194
|
+
# == Rendering resources
|
195
|
+
#
|
196
|
+
# Models aren't directly connected to Resource representations, but
|
197
|
+
# since the security later interfaces with session data herein, there
|
198
|
+
# is clearly an intersection of concepts. Even though fields in a
|
199
|
+
# Model may not map directly to fields in a related Resource (or
|
200
|
+
# many Models might contribute to a Resource), the security scoping
|
201
|
+
# rules that led to the limitations on data retrieval may be useful
|
202
|
+
# to an API caller. The API basic definitions support this through
|
203
|
+
# a +secured_with+ standard (but optional) resource field.
|
204
|
+
#
|
205
|
+
# The +secured_with+ field's value is an object of key/value pairs.
|
206
|
+
# Its contents depend on how the #secure_with method is used in a
|
207
|
+
# model. The #secure_with call actually supports _two_ modes of
|
208
|
+
# operation. One is as already shown above; suppose we have:
|
209
|
+
#
|
210
|
+
# secure_with( {
|
211
|
+
# :creating_caller_uuid => :authorised_caller_uuids,
|
212
|
+
# :programme_code => :authorised_programme_codes
|
213
|
+
# } )
|
214
|
+
#
|
215
|
+
# If Hoodoo::Presenters::Base::render_in is called and an instance of
|
216
|
+
# a model with the above declaration is passed in the +secured_with+
|
217
|
+
# option, then the keys from the declaration appear in the resource
|
218
|
+
# representation's +secured_with+ field's object and the values are
|
219
|
+
# the _actual_ scoping values which were used, i.e. the rendered
|
220
|
+
# data would contain:
|
221
|
+
#
|
222
|
+
# {
|
223
|
+
# "id": "<UUID>",
|
224
|
+
# "kind": "Example",
|
225
|
+
# "created_at": "2015-04-30T16:25:17+12:00",
|
226
|
+
# "secured_with": {
|
227
|
+
# "creating_caller_uuid": "<UUID>",
|
228
|
+
# "programme_code": "<code>"
|
229
|
+
# },
|
230
|
+
# ...
|
231
|
+
# }
|
232
|
+
#
|
233
|
+
# This binds the field values in the model to the values in the
|
234
|
+
# rendered resource representation, though; and what if we only wanted
|
235
|
+
# (say) the "creating_caller_uuid" to be revealed, but did not want to
|
236
|
+
# show the "programme_code" value? To do this, instead of passing a
|
237
|
+
# Symbol in the values of the #secure_with options, you provide a Hash
|
238
|
+
# of options for that particular security entry. Option keys are
|
239
|
+
# Symbols:
|
240
|
+
#
|
241
|
+
# +session_field_name+:: This is the field that's looked up in the
|
242
|
+
# session's scoping section.
|
243
|
+
#
|
244
|
+
# +resource_field_name+:: This is the name that'll appear in the
|
245
|
+
# rendered resource.
|
246
|
+
#
|
247
|
+
# +hide_from_resource+:: If present and set to +true+, the entry will
|
248
|
+
# not be shown; else it is shown by default
|
249
|
+
# (if you're passing in a model instance to a
|
250
|
+
# render call via the +secured_with+ option it
|
251
|
+
# is assumed that you explicitly _do_ want to
|
252
|
+
# include this kind of information rather than
|
253
|
+
# hide it).
|
254
|
+
#
|
255
|
+
# +using+:: See the _Advanced_ _query_ _conditions_
|
256
|
+
# section later for details.
|
257
|
+
#
|
258
|
+
# To help clarify the above, the following two calls to #secure_with
|
259
|
+
# have exactly the same effect.
|
260
|
+
#
|
261
|
+
# secure_with( {
|
262
|
+
# :creating_caller_uuid => :authorised_caller_uuids
|
263
|
+
# } )
|
264
|
+
#
|
265
|
+
# # ...is equivalent to...
|
266
|
+
#
|
267
|
+
# secure_with( {
|
268
|
+
# :creating_caller_uuid => {
|
269
|
+
# :session_field_name => :authorised_caller_uuids,
|
270
|
+
# :resource_field_name => :creating_caller_uuid, # (Or just omit this option)
|
271
|
+
# :hide_from_resource => false # (Or just omit this option)
|
272
|
+
# }
|
273
|
+
# } )
|
274
|
+
#
|
275
|
+
# Taking the previous example, let's change the name of the field shown
|
276
|
+
# in the resource and hide the "programme_code" entry:
|
277
|
+
#
|
278
|
+
# secure_with( {
|
279
|
+
# :creating_caller_uuid => {
|
280
|
+
# :session_field_name => :authorised_caller_uuids,
|
281
|
+
# :resource_field_name => :caller_id # Note renaming of field
|
282
|
+
# },
|
283
|
+
# :programme_code => {
|
284
|
+
# :session_field_name => :authorised_programme_codes,
|
285
|
+
# :hide_from_resource => true
|
286
|
+
# }
|
287
|
+
# } )
|
288
|
+
#
|
289
|
+
# ...would lead to a rendered resource looking something like this:
|
290
|
+
#
|
291
|
+
# {
|
292
|
+
# "id": "<UUID>",
|
293
|
+
# "kind": "Example",
|
294
|
+
# "created_at": "2015-04-30T16:25:17+12:00",
|
295
|
+
# "secured_with": {
|
296
|
+
# "caller_id": "<UUID>",
|
297
|
+
# },
|
298
|
+
# ...
|
299
|
+
# }
|
300
|
+
#
|
301
|
+
# == Advanced query conditions
|
302
|
+
#
|
303
|
+
# A simple implicit +AND+ clause on a single database column might
|
304
|
+
# not be sufficient for your scoping. In this case, the "longhand"
|
305
|
+
# Hash form described for rendering is used, this time including the
|
306
|
+
# key +:using+ to specify a Proc that is executed to return an array
|
307
|
+
# of parameters for <tt>where</tt>. For example:
|
308
|
+
#
|
309
|
+
# secure_with( {
|
310
|
+
# :creating_caller_uuid => :authorised_caller_uuids
|
311
|
+
# } )
|
312
|
+
#
|
313
|
+
# # ...has this minimal longhand equivalent...
|
314
|
+
#
|
315
|
+
# secure_with( {
|
316
|
+
# :creating_caller_uuid => {
|
317
|
+
# :session_field_name => :authorised_caller_uuids
|
318
|
+
# }
|
319
|
+
# } )
|
320
|
+
#
|
321
|
+
# This leads to SQL along the following lines:
|
322
|
+
#
|
323
|
+
# AND ("model_table"."creating_caller_uuid" = '[val]')
|
324
|
+
#
|
325
|
+
# ...where <tt>val</tt> is from the Session +authorised_caller_uuids+
|
326
|
+
# data in the +scoping+ section (so this might be an SQL +IN+ rather
|
327
|
+
# than <tt>=</tt> if that data is a multi-element array). Suppose you
|
328
|
+
# need to change this to check that value _or_ something else? Use the
|
329
|
+
# +:using+ key and a Proc. Since ActiveRecord at the time of writing
|
330
|
+
# lacks a high level way to do 'OR' via methods, it's easiest and most
|
331
|
+
# flexible just to give up and fall to an SQL string:
|
332
|
+
#
|
333
|
+
# or_matcher = Proc.new do | model_class, database_column_name, session_field_value |
|
334
|
+
#
|
335
|
+
# # This example works for non-array and array field values.
|
336
|
+
# #
|
337
|
+
# session_field_value = [ session_field_value ].flatten
|
338
|
+
#
|
339
|
+
# [
|
340
|
+
# "\"#{ database_column_name }\" IN (?) OR \"other_column_name\" IN (?)",
|
341
|
+
# session_field_value,
|
342
|
+
# session_field_value
|
343
|
+
# ]
|
344
|
+
# end
|
345
|
+
#
|
346
|
+
# secure_with( {
|
347
|
+
# :creating_caller_uuid => {
|
348
|
+
# :session_field_name => :authorised_caller_uuids,
|
349
|
+
# :using => or_matcher
|
350
|
+
# }
|
351
|
+
# } )
|
352
|
+
#
|
353
|
+
# ...yields something like:
|
354
|
+
#
|
355
|
+
# AND ( "model_table"."creating_caller_uuid" = '[val]' OR "model_table"."other_column_name" = '[val]' )
|
356
|
+
#
|
357
|
+
# A Proc specified with +:using+ is called with:
|
358
|
+
#
|
359
|
+
# * The model class which is involved in the query.
|
360
|
+
#
|
361
|
+
# * The name of the database column specified in the +secure_with+
|
362
|
+
# Hash as the top-level key (e.g. +creating_called_uuid+ above).
|
363
|
+
#
|
364
|
+
# * The session field _value_ that was recovered under the given key -
|
365
|
+
# the value of +session.scoping.authorised_caller_uuids+ in the
|
366
|
+
# example above.
|
367
|
+
#
|
368
|
+
# You must return _AN ARRAY_ of arguments that will be passed to
|
369
|
+
# +where+ via <tt>where( *returned_values )</tt> as part of the wider
|
370
|
+
# query chain.
|
371
|
+
#
|
372
|
+
def secure( context )
|
373
|
+
prevailing_scope = all() # "Model.all" -> returns anonymous scope
|
374
|
+
extra_scope_map = secured_with()
|
375
|
+
|
376
|
+
unless extra_scope_map.nil?
|
377
|
+
return none() if context.session.nil? || context.session.scoping.nil?
|
378
|
+
|
379
|
+
extra_scope_map.each do | model_field_name, key_or_options |
|
380
|
+
params_proc = DEFAULT_SECURE_PROC
|
381
|
+
|
382
|
+
if key_or_options.is_a?( Hash )
|
383
|
+
session_scoping_key = key_or_options[ :session_field_name ]
|
384
|
+
params_proc = key_or_options[ :using ] if key_or_options.has_key?( :using )
|
385
|
+
else
|
386
|
+
session_scoping_key = key_or_options
|
387
|
+
end
|
388
|
+
|
389
|
+
if context.session.scoping.respond_to?( session_scoping_key )
|
390
|
+
args = params_proc.call(
|
391
|
+
self,
|
392
|
+
model_field_name,
|
393
|
+
context.session.scoping.send( session_scoping_key )
|
394
|
+
)
|
395
|
+
prevailing_scope = prevailing_scope.where( *args )
|
396
|
+
else
|
397
|
+
prevailing_scope = none()
|
398
|
+
break
|
399
|
+
end
|
400
|
+
end
|
401
|
+
end
|
402
|
+
|
403
|
+
return prevailing_scope
|
404
|
+
end
|
405
|
+
|
406
|
+
# Declare the mapping between database columns and Session scoping
|
407
|
+
# entries. See #secure for details and examples.
|
408
|
+
#
|
409
|
+
# Parameters:
|
410
|
+
#
|
411
|
+
# +map+:: A Hash of String or Symbol keys and values that gives the
|
412
|
+
# secure mapping details. The keys are names of fields in
|
413
|
+
# the model. The values are names of fields in the
|
414
|
+
# Hoodoo::Services::Session#scoping object, or can be a Hash
|
415
|
+
# of options; see #secure for full details and examples.
|
416
|
+
#
|
417
|
+
def secure_with( map )
|
418
|
+
self.nz_co_loyalty_hoodoo_secure_with = map
|
419
|
+
end
|
420
|
+
|
421
|
+
# Retrieve the mapping declared between database columns and
|
422
|
+
# Session scoping entries via #secure_with. Returns a map as passed
|
423
|
+
# to #secure_with, or +nil+ if no such declaration has been made.
|
424
|
+
#
|
425
|
+
def secured_with
|
426
|
+
self.nz_co_loyalty_hoodoo_secure_with
|
427
|
+
end
|
428
|
+
end
|
429
|
+
end
|
430
|
+
end
|
431
|
+
end
|