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,12 @@
|
|
1
|
+
########################################################################
|
2
|
+
# File:: utilities.rb
|
3
|
+
# (C):: Loyalty New Zealand 2014
|
4
|
+
#
|
5
|
+
# Purpose:: Include various generalised bits of Hoodoo utility code.
|
6
|
+
# ----------------------------------------------------------------------
|
7
|
+
# 26-Jan-2015 (ADH): Split from top-level inclusion file.
|
8
|
+
########################################################################
|
9
|
+
|
10
|
+
require 'hoodoo/utilities/utilities'
|
11
|
+
require 'hoodoo/utilities/string_inquirer'
|
12
|
+
require 'hoodoo/utilities/uuid'
|
@@ -0,0 +1,54 @@
|
|
1
|
+
########################################################################
|
2
|
+
# File:: string_inquirer.rb
|
3
|
+
#
|
4
|
+
# Purpose:: StringInquirer class copied from ActiveSupport 4.1.6, to
|
5
|
+
# avoid dragging in that huge dependency for this one thing.
|
6
|
+
# ----------------------------------------------------------------------
|
7
|
+
# 02-Oct-2014 (ADH): Copied from ActiveSupport 4.1.6.
|
8
|
+
########################################################################
|
9
|
+
|
10
|
+
module Hoodoo
|
11
|
+
|
12
|
+
# Given a string, provides an object that takes the string's value and
|
13
|
+
# turns it into a method "#{value}?", returning +true+; other methods
|
14
|
+
# all respond +false+.
|
15
|
+
#
|
16
|
+
# Example:
|
17
|
+
#
|
18
|
+
# greeting = Hoodoo::StringInquirer.new( 'hello' )
|
19
|
+
# greeting.hello? # => true
|
20
|
+
# greeting.hi? # => false
|
21
|
+
#
|
22
|
+
class StringInquirer < String
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
# Asks if this object can respond to a given method. In practice returns
|
27
|
+
# +true+ for any method name ending in "?".
|
28
|
+
#
|
29
|
+
# +method_name+:: Method name to ask about
|
30
|
+
# +include_private+:: If +true+ include private methods in the test,
|
31
|
+
# else if omitted or +false+, ignore them.
|
32
|
+
#
|
33
|
+
def respond_to_missing?( method_name, include_private = false )
|
34
|
+
method_name[ -1 ] == '?'
|
35
|
+
end
|
36
|
+
|
37
|
+
# Called when a String receives a message it cannot handle. This is where
|
38
|
+
# StringInquirer adds in its string-value-dependent "fake" boolean method.
|
39
|
+
# For any method name ending in "?", returns +true+ if the string value
|
40
|
+
# matches the name except for that "?", else +false+. If the method name
|
41
|
+
# does not end in "?", the call is passed to +super+.
|
42
|
+
#
|
43
|
+
# +method_name+:: Method name that wasn't found in object instance.
|
44
|
+
# *arguments:: List of arguments passed to the method.
|
45
|
+
#
|
46
|
+
def method_missing( method_name, *arguments )
|
47
|
+
if method_name[ -1 ] == '?'
|
48
|
+
self == method_name[ 0..-2 ]
|
49
|
+
else
|
50
|
+
super
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,380 @@
|
|
1
|
+
########################################################################
|
2
|
+
# File:: utilities.rb
|
3
|
+
# (C):: Loyalty New Zealand 2014
|
4
|
+
#
|
5
|
+
# Purpose:: Miscellaneous useful functions.
|
6
|
+
# ----------------------------------------------------------------------
|
7
|
+
# 22-Sep-2014 (ADH): Created.
|
8
|
+
########################################################################
|
9
|
+
|
10
|
+
require 'socket'
|
11
|
+
|
12
|
+
module Hoodoo
|
13
|
+
|
14
|
+
# Useful tools, especially for those working without Rails components.
|
15
|
+
#
|
16
|
+
module Utilities
|
17
|
+
|
18
|
+
# Validation regular expression for DateTime subset selection.
|
19
|
+
#
|
20
|
+
DATETIME_ISO8601_SUBSET_REGEXP = /(\d{4})-(\d{2})-(\d{2})T(\d{2})\:(\d{2})\:(\d{2})(\.\d+)?(Z|[+-](\d{2})\:(\d{2}))/
|
21
|
+
|
22
|
+
# Validation regular expression for Date subset selection.
|
23
|
+
#
|
24
|
+
DATE_ISO8601_SUBSET_REGEXP = /(\d{4})-(\d{2})-(\d{2})/
|
25
|
+
|
26
|
+
# Given a hash, returns the same hash with keys converted to symbols.
|
27
|
+
# Works with nested hashes.
|
28
|
+
#
|
29
|
+
# +obj+:: Hash or Array of Hashes. Will recursively convert keys in Hashes
|
30
|
+
# to symbols. Hashes with values that are Arrays of Hashes will be
|
31
|
+
# dealt with properly. Does not modify other types (e.g. an Array
|
32
|
+
# of Strings would stay an Array of Strings).
|
33
|
+
#
|
34
|
+
# Returns a copy of your input object with keys converted to symbols.
|
35
|
+
#
|
36
|
+
def self.symbolize(obj)
|
37
|
+
|
38
|
+
# http://stackoverflow.com/questions/800122/best-way-to-convert-strings-to-symbols-in-hash
|
39
|
+
#
|
40
|
+
return obj.inject({}){|memo,(k,v)| memo[k.to_s.to_sym] = self.symbolize(v); memo} if obj.is_a?(::Hash)
|
41
|
+
return obj.inject([]){|memo,v | memo << self.symbolize(v); memo} if obj.is_a?(::Array)
|
42
|
+
return obj
|
43
|
+
end
|
44
|
+
|
45
|
+
# The keys-to-strings equivalent of ::symbolize.
|
46
|
+
#
|
47
|
+
# +obj+:: Hash or Array of Hashes. Will recursively convert keys in Hashes
|
48
|
+
# to strings. Hashes with values that are Arrays of Hashes will be
|
49
|
+
# dealt with properly. Does not modify other types (e.g. an Array
|
50
|
+
# of Symbols would stay an Array of Symbols).
|
51
|
+
#
|
52
|
+
# Returns a copy of your input object with keys converted to strings.
|
53
|
+
#
|
54
|
+
def self.stringify(obj)
|
55
|
+
return obj.inject({}){|memo,(k,v)| memo[k.to_s] = self.stringify(v); memo} if obj.is_a?(::Hash)
|
56
|
+
return obj.inject([]){|memo,v | memo << self.stringify(v); memo} if obj.is_a?(::Array)
|
57
|
+
return obj
|
58
|
+
end
|
59
|
+
|
60
|
+
# Thorough but slow deep duplication of any object (if it isn't
|
61
|
+
# duplicable, e.g. FixNum, you just get the same thing back). Usually
|
62
|
+
# used with Hashes or Arrays.
|
63
|
+
#
|
64
|
+
# +obj+:: Object to duplicate.
|
65
|
+
#
|
66
|
+
# Returns the duplicated object if duplicable, else returns the input
|
67
|
+
# parameter.
|
68
|
+
#
|
69
|
+
def self.deep_dup( obj )
|
70
|
+
duplicate = obj.dup rescue obj
|
71
|
+
|
72
|
+
result = if duplicate.is_a?( Hash )
|
73
|
+
duplicate.each { | k, v | duplicate[ k ] = deep_dup( v ) }
|
74
|
+
elsif duplicate.is_a?( Array )
|
75
|
+
duplicate.map { | entry | deep_dup( entry ) }
|
76
|
+
else
|
77
|
+
duplicate
|
78
|
+
end
|
79
|
+
|
80
|
+
return result
|
81
|
+
end
|
82
|
+
|
83
|
+
# Deep merge two hashes.
|
84
|
+
#
|
85
|
+
# Hash#merge/merge! only do a shallow merge. For example, without
|
86
|
+
# a block, when starting with this hash:
|
87
|
+
#
|
88
|
+
# { :one => { :two => { :three => 3 } } }
|
89
|
+
#
|
90
|
+
# ...and merging in this hash:
|
91
|
+
#
|
92
|
+
# { :one => { :two => { :and_four => 4 } } }
|
93
|
+
#
|
94
|
+
# ...the possibly unexpected result is this:
|
95
|
+
#
|
96
|
+
# { :one => { :two => { :and_four => 4 } } }
|
97
|
+
#
|
98
|
+
# Because the value for key ":one" in the original hash is simply
|
99
|
+
# overwritten with the value from the merged-in hash.
|
100
|
+
#
|
101
|
+
# Deep merging takes a target hash, into which an "inbound" source
|
102
|
+
# hash is merged and returns a new hash that is the deep merged
|
103
|
+
# result. Taking the above example:
|
104
|
+
#
|
105
|
+
# target_hash = { :one => { :two => { :three => 3 } } }
|
106
|
+
# inbound_hash = { :one => { :two => { :and_four => 4 } } }
|
107
|
+
# Hoodoo::Utilities.deep_merge_into( target_hash, inbound_hash )
|
108
|
+
#
|
109
|
+
# ...yields:
|
110
|
+
#
|
111
|
+
# { :one => { :two => { :three => 3, :and_four => 4 } } }
|
112
|
+
#
|
113
|
+
# For any same-named key with a non-hash value, the value in the
|
114
|
+
# inbound hash will overwrite the value in the target hash.
|
115
|
+
#
|
116
|
+
# Parameters:
|
117
|
+
#
|
118
|
+
# +target_hash+:: The hash into which something will be merged.
|
119
|
+
# +inbound_hash+:: The hash that will be merged into the target.
|
120
|
+
#
|
121
|
+
# Returns the merged result.
|
122
|
+
#
|
123
|
+
def self.deep_merge_into( target_hash, inbound_hash )
|
124
|
+
|
125
|
+
# http://stackoverflow.com/questions/9381553/ruby-merge-nested-hash
|
126
|
+
#
|
127
|
+
merger = proc { | key, v1, v2 |
|
128
|
+
Hash === v1 && Hash === v2 ? v1.merge( v2, &merger ) : v2.nil? ? v1 : v2
|
129
|
+
}
|
130
|
+
|
131
|
+
return target_hash.merge( inbound_hash, &merger )
|
132
|
+
end
|
133
|
+
|
134
|
+
# Deep diff two hashes.
|
135
|
+
#
|
136
|
+
# +hash1+:: "Left hand" hash for comparison.
|
137
|
+
# +hash2+:: "Right hand" hash for comparison.
|
138
|
+
#
|
139
|
+
# The returned result is a Hash itself, potentially nested, with any
|
140
|
+
# present key paths leading to an array describing the difference found
|
141
|
+
# at that key path. If the two input hashes had values at the path, the
|
142
|
+
# differing values are placed in the array ("left hand" value at index
|
143
|
+
# 0, "right hand" at index 1). If one input hash has a key leading to
|
144
|
+
# a value which the other omits, the array contains +nil+ for the
|
145
|
+
# omitted entry.
|
146
|
+
#
|
147
|
+
# Example:
|
148
|
+
#
|
149
|
+
# hash1 = { :foo => { :bar => 2 }, :baz => true, :boo => false }
|
150
|
+
# hash2 = { :foo => { :bar => 3 }, :boo => false }
|
151
|
+
#
|
152
|
+
# Hoodoo::Utilities.hash_diff( hash1, hash2 )
|
153
|
+
# # => { :foo => { :bar => [ 2, 3 ] }, :baz => [ true, nil ] }
|
154
|
+
#
|
155
|
+
# Hoodoo::Utilities.hash_diff( hash2, hash1 )
|
156
|
+
# # => { :foo => { :bar => [ 3, 2 ] }, :baz => [ nil, true ] }
|
157
|
+
#
|
158
|
+
# Bear in mind that the difference array contains values of everything
|
159
|
+
# different from the first part of the key path where things diverge. So
|
160
|
+
# in this case:
|
161
|
+
#
|
162
|
+
# hash1 = { :foo => { :bar => { :baz => [ 1, 2, 3 ] } } }
|
163
|
+
# hash2 = {}
|
164
|
+
#
|
165
|
+
# ...the difference starts all the way up at ":foo". The result is thus
|
166
|
+
# *not* a Hash where just the ":baz" array is picked out as a difference;
|
167
|
+
# the entire Hash sub-tree is picked out:
|
168
|
+
#
|
169
|
+
# diff = Hoodoo::Utilities.hash_diff( hash1, hash2 )
|
170
|
+
# # => { :foo => [ { :bar => { :baz => [ 1, 2, 3 ] } }, nil ] }
|
171
|
+
#
|
172
|
+
def self.hash_diff( hash1, hash2 )
|
173
|
+
|
174
|
+
# http://stackoverflow.com/questions/1766741/comparing-ruby-hashes
|
175
|
+
#
|
176
|
+
return ( hash1.keys | hash2.keys ).inject( {} ) do | memo, key |
|
177
|
+
unless hash1[ key ] == hash2[ key ]
|
178
|
+
if hash1[ key ].kind_of?( Hash ) && hash2[ key ].kind_of?( Hash )
|
179
|
+
memo[ key ] = hash_diff( hash1[ key ], hash2[ key ] )
|
180
|
+
else
|
181
|
+
memo[ key ] = [ hash1[ key ], hash2[ key ] ]
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
memo
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
# Convert a (potentially nested) Hash into an array of entries which
|
190
|
+
# represent its keys, with the notation "foo.bar.baz" for nested hashes.
|
191
|
+
#
|
192
|
+
# +hash+:: Input Hash.
|
193
|
+
#
|
194
|
+
# Example:
|
195
|
+
#
|
196
|
+
# hash = { :foo => 1, :bar => { :baz => 2, :boo => { :hello => :world } } }
|
197
|
+
#
|
198
|
+
# Hoodoo::Utilities.hash_key_paths( hash )
|
199
|
+
# # => [ 'foo', 'bar.baz', 'bar.boo.hello' ]
|
200
|
+
#
|
201
|
+
def self.hash_key_paths( hash )
|
202
|
+
return hash.map do | key, value |
|
203
|
+
if value.is_a?( Hash )
|
204
|
+
hash_key_paths( value ).map do | entry |
|
205
|
+
"#{ key }.#{ entry }"
|
206
|
+
end
|
207
|
+
else
|
208
|
+
key.to_s
|
209
|
+
end
|
210
|
+
end.flatten
|
211
|
+
end
|
212
|
+
|
213
|
+
# A very single-purpose method which converts an Array of specifc form
|
214
|
+
# into a Hash.
|
215
|
+
#
|
216
|
+
# The Hash class can already build itself from an Array of tuples, thus:
|
217
|
+
#
|
218
|
+
# array = [ [ :one, 1 ], [ :two, 2 ] ]
|
219
|
+
# hash = Hash[ array ]
|
220
|
+
# # => { :one => 1, :two => 2 }
|
221
|
+
#
|
222
|
+
# This is fine, but what if the array contains the same key twice?
|
223
|
+
#
|
224
|
+
# array = [ [ :one, 1 ], [ :two, 2 ], [ :one, 42 ] ]
|
225
|
+
# hash = Hash[ array ]
|
226
|
+
# # => { :one => 42, :two => 2 }
|
227
|
+
#
|
228
|
+
# The later duplicates simply override any former entries. This Array
|
229
|
+
# collation method is designed to instead take the tuples and set up a
|
230
|
+
# Hash where each key leads to an Array of unique values found in the
|
231
|
+
# original, thus:
|
232
|
+
#
|
233
|
+
# array = [ [ :one, 1 ], [ :two, 2 ], [ :one, 42 ] ]
|
234
|
+
# Hoodoo::Utilities.collated_hash_from( array )
|
235
|
+
# # => { :one => [ 1, 42 ], :two => [ 2 ] }
|
236
|
+
#
|
237
|
+
# Note that:
|
238
|
+
#
|
239
|
+
# * The Hash values are always Arrays, even if they only have one value.
|
240
|
+
# * The Array values are unique; duplicates are removed via +uniq!+.
|
241
|
+
#
|
242
|
+
# +array+:: Array of two-element Arrays. The first element becomes a key
|
243
|
+
# in the returned Hash. The last element is added to an Array
|
244
|
+
# of (unique) values associated with that key. An empty Array
|
245
|
+
# results in an empty Hash; +nil+ is not allowed.
|
246
|
+
#
|
247
|
+
# +dupes+:: Optional. If omitted, duplicates are removed as described;
|
248
|
+
# if present and +true+, duplicates are allowed.
|
249
|
+
#
|
250
|
+
# Returns a new Hash as described. The input Array is not modified.
|
251
|
+
#
|
252
|
+
def self.collated_hash_from( array, dupes = false )
|
253
|
+
hash_of_arrays = {}
|
254
|
+
|
255
|
+
array.reduce( hash_of_arrays ) do | memo, sub_array |
|
256
|
+
memo[ sub_array.first ] = ( memo[ sub_array.first ] || [] ) << sub_array.last
|
257
|
+
memo
|
258
|
+
end
|
259
|
+
|
260
|
+
hash_of_arrays.values.collect( &:uniq! ) unless dupes == true
|
261
|
+
return hash_of_arrays
|
262
|
+
end
|
263
|
+
|
264
|
+
# Is a parameter convertable to an integer cleanly? Returns the integer
|
265
|
+
# value if so, else +nil+.
|
266
|
+
#
|
267
|
+
# +value+:: Value to check, e.g. 2, "44", :'55' (yields 2, 44, 55) or
|
268
|
+
# "hello", Time.now (yields nil, nil).
|
269
|
+
#
|
270
|
+
def self.to_integer?( value )
|
271
|
+
value = value.to_s
|
272
|
+
value.to_i if value.to_i.to_s == value
|
273
|
+
end
|
274
|
+
|
275
|
+
# Return a spare TCP port on localhost. This is free at the instant of
|
276
|
+
# calling, though of course if you have anything in other local machine
|
277
|
+
# processes/threads running which might start using ports at any moment,
|
278
|
+
# there's a chance of the free port getting claimed in between you asking
|
279
|
+
# for it and it being returned. This utility method is usually therefore
|
280
|
+
# used for test environments only.
|
281
|
+
#
|
282
|
+
def self.spare_port
|
283
|
+
|
284
|
+
# http://stackoverflow.com/questions/5985822/how-do-you-find-a-random-open-port-in-ruby
|
285
|
+
#
|
286
|
+
socket = Socket.new( :INET, :STREAM, 0 )
|
287
|
+
socket.bind( Addrinfo.tcp( '127.0.0.1', 0 ) )
|
288
|
+
port = socket.local_address.ip_port
|
289
|
+
socket.close
|
290
|
+
|
291
|
+
return port
|
292
|
+
end
|
293
|
+
|
294
|
+
# Is the given String a valid ISO 8601 subset date and time as accepted by
|
295
|
+
# (for example) Hoodoo API calls?
|
296
|
+
#
|
297
|
+
# +str+:: Value to check
|
298
|
+
#
|
299
|
+
# Returns a DateTime instance holding the parsed result if a valid ISO
|
300
|
+
# 8601 subset date and time, else +false+.
|
301
|
+
#
|
302
|
+
def self.valid_iso8601_subset_datetime?( str )
|
303
|
+
|
304
|
+
# Relies on Ruby evaluation behaviour and operator precedence - "'foo'
|
305
|
+
# && true" => true, but "true && 'foo'" => 'foo'. Don't use "and" here!
|
306
|
+
|
307
|
+
value = begin
|
308
|
+
( DATETIME_ISO8601_SUBSET_REGEXP =~ str.to_s ) == 0 &&
|
309
|
+
str.size > 10 &&
|
310
|
+
::DateTime.parse( str )
|
311
|
+
|
312
|
+
rescue ArgumentError
|
313
|
+
end
|
314
|
+
|
315
|
+
return value.is_a?( ::DateTime ) && value
|
316
|
+
end
|
317
|
+
|
318
|
+
# Is the given String a valid ISO 8601 subset date (no time) as accepted
|
319
|
+
# by (for example) Hoodoo API calls?
|
320
|
+
#
|
321
|
+
# +str+:: Value to check
|
322
|
+
#
|
323
|
+
# Returns a Date instance holding the parsed result if a valid ISO 8601
|
324
|
+
# subset date, else +false+.
|
325
|
+
#
|
326
|
+
def self.valid_iso8601_subset_date?( str )
|
327
|
+
|
328
|
+
# Same reliance as 'valid_iso8601_subset_datetime'?.
|
329
|
+
|
330
|
+
value = begin
|
331
|
+
( DATE_ISO8601_SUBSET_REGEXP =~ str.to_s ) == 0 &&
|
332
|
+
str.size == 10 &&
|
333
|
+
::Date.parse( str )
|
334
|
+
|
335
|
+
rescue ArgumentError
|
336
|
+
end
|
337
|
+
|
338
|
+
return value.is_a?( ::Date ) && value
|
339
|
+
end
|
340
|
+
|
341
|
+
# Returns an ISO 8601 String equivalent of the given Time or DateTime
|
342
|
+
# instance, with nanosecond precision (subject to Ruby port / OS support).
|
343
|
+
# This is nothing more than a standardised central interface on calling
|
344
|
+
# Ruby's <tt>Time/DateTime#iso8601( 9 )</tt>, to avoid the risk of lots of
|
345
|
+
# variable length precision times floating around by authors picking their
|
346
|
+
# own arbitrary precision parameters.
|
347
|
+
#
|
348
|
+
# +date_time+:: Ruby Time or DateTime instance to convert to an ISO 8601
|
349
|
+
# String with nanosecond precision.
|
350
|
+
#
|
351
|
+
def self.nanosecond_iso8601( time_or_date_time )
|
352
|
+
time_or_date_time.iso8601( 9 )
|
353
|
+
end
|
354
|
+
|
355
|
+
# Turn a given value of various types into a DateTime instance or +nil+.
|
356
|
+
# If the input value is not +nil+, a DateTime instance, a Time instance
|
357
|
+
# or something that <tt>DateTime.parse</tt> can handle, the method will
|
358
|
+
# throw a RuntimeError exception.
|
359
|
+
#
|
360
|
+
# +input+:: A Time or DateTime instance, or a String that can be
|
361
|
+
# converted to a DateTime instance; in these cases, an
|
362
|
+
# equivalent DateTime is returned. If +nil+, returns +nil+.
|
363
|
+
#
|
364
|
+
def self.rationalise_datetime( input )
|
365
|
+
begin
|
366
|
+
if input.nil? || input.is_a?( DateTime )
|
367
|
+
input
|
368
|
+
elsif input.is_a?( Time )
|
369
|
+
input.to_datetime
|
370
|
+
else
|
371
|
+
DateTime.parse( input )
|
372
|
+
end
|
373
|
+
|
374
|
+
rescue
|
375
|
+
raise "Hoodoo::Utilities\#rationalise_datetime: Invalid parameter '#{ input }'"
|
376
|
+
|
377
|
+
end
|
378
|
+
end
|
379
|
+
end
|
380
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require "uuidtools"
|
2
|
+
|
3
|
+
module Hoodoo
|
4
|
+
|
5
|
+
# Class that handles generation and validation of UUIDs. Whenever you
|
6
|
+
# want to associate an identifier with something, you should use this
|
7
|
+
# class rather than (e.g.) relying on identifiers generated by a
|
8
|
+
# database. This allows you to cluster your database later on, should
|
9
|
+
# your application become big enough, without having to worry about
|
10
|
+
# ID synchronisation across instances.
|
11
|
+
#
|
12
|
+
class UUID
|
13
|
+
|
14
|
+
# A regexp which, as its name suggests, only matches a string that
|
15
|
+
# contains 16 pairs of hex digits (with upper or lower case A-F).
|
16
|
+
#
|
17
|
+
# http://stackoverflow.com/questions/287684/regular-expression-to-validate-hex-string
|
18
|
+
#
|
19
|
+
MATCH_16_PAIRS_OF_HEX_DIGITS = /^([[:xdigit:]]{2}){16}$/
|
20
|
+
|
21
|
+
# Generate a unique identifier. Returns a 32 character string.
|
22
|
+
#
|
23
|
+
def self.generate
|
24
|
+
UUIDTools::UUID.random_create().hexdigest()
|
25
|
+
end
|
26
|
+
|
27
|
+
# Checks if a UUID string is valid. Returns +true+ if so, else +false+.
|
28
|
+
#
|
29
|
+
# +uuid+:: UUID string to validate. Must be a String, 32 characters
|
30
|
+
# long (as 16 hex digit pairs) and parse to a valid result
|
31
|
+
# internally, according to internal UUID generation rules.
|
32
|
+
#
|
33
|
+
# Note that the validity of a UUID says nothing about where, if anywhere,
|
34
|
+
# it might have been used. So, just because a UUID is valid, doesn't mean
|
35
|
+
# you have (say) stored something with that UUID as the primary key in a
|
36
|
+
# row in a database.
|
37
|
+
#
|
38
|
+
def self.valid?( uuid )
|
39
|
+
uuid.is_a?( ::String ) &&
|
40
|
+
( uuid =~ MATCH_16_PAIRS_OF_HEX_DIGITS ) != nil &&
|
41
|
+
UUIDTools::UUID.parse_hexdigest( uuid ).valid?()
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|