hoodoo 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (216) hide show
  1. checksums.yaml +7 -0
  2. data/bin/hoodoo +5 -0
  3. data/lib/hoodoo.rb +27 -0
  4. data/lib/hoodoo/active.rb +32 -0
  5. data/lib/hoodoo/active/active_model/uuid_validator.rb +45 -0
  6. data/lib/hoodoo/active/active_record/base.rb +81 -0
  7. data/lib/hoodoo/active/active_record/creator.rb +134 -0
  8. data/lib/hoodoo/active/active_record/dated.rb +343 -0
  9. data/lib/hoodoo/active/active_record/error_mapping.rb +351 -0
  10. data/lib/hoodoo/active/active_record/finder.rb +606 -0
  11. data/lib/hoodoo/active/active_record/search_helper.rb +189 -0
  12. data/lib/hoodoo/active/active_record/secure.rb +431 -0
  13. data/lib/hoodoo/active/active_record/support.rb +106 -0
  14. data/lib/hoodoo/active/active_record/translated.rb +87 -0
  15. data/lib/hoodoo/active/active_record/uuid.rb +80 -0
  16. data/lib/hoodoo/active/active_record/writer.rb +321 -0
  17. data/lib/hoodoo/client.rb +23 -0
  18. data/lib/hoodoo/client/augmented_array.rb +29 -0
  19. data/lib/hoodoo/client/augmented_base.rb +168 -0
  20. data/lib/hoodoo/client/augmented_hash.rb +23 -0
  21. data/lib/hoodoo/client/client.rb +354 -0
  22. data/lib/hoodoo/client/endpoint/endpoint.rb +427 -0
  23. data/lib/hoodoo/client/endpoint/endpoints/amqp.rb +180 -0
  24. data/lib/hoodoo/client/endpoint/endpoints/auto_session.rb +194 -0
  25. data/lib/hoodoo/client/endpoint/endpoints/http.rb +203 -0
  26. data/lib/hoodoo/client/endpoint/endpoints/http_based.rb +367 -0
  27. data/lib/hoodoo/client/endpoint/endpoints/not_found.rb +59 -0
  28. data/lib/hoodoo/client/headers.rb +269 -0
  29. data/lib/hoodoo/communicators.rb +23 -0
  30. data/lib/hoodoo/communicators/fast.rb +44 -0
  31. data/lib/hoodoo/communicators/pool.rb +601 -0
  32. data/lib/hoodoo/communicators/slow.rb +84 -0
  33. data/lib/hoodoo/data.rb +51 -0
  34. data/lib/hoodoo/data/resources/caller.rb +39 -0
  35. data/lib/hoodoo/data/resources/errors.rb +28 -0
  36. data/lib/hoodoo/data/resources/log.rb +31 -0
  37. data/lib/hoodoo/data/resources/session.rb +26 -0
  38. data/lib/hoodoo/data/types/error_primitive.rb +27 -0
  39. data/lib/hoodoo/data/types/permissions.rb +40 -0
  40. data/lib/hoodoo/data/types/permissions_defaults.rb +32 -0
  41. data/lib/hoodoo/data/types/permissions_full.rb +28 -0
  42. data/lib/hoodoo/data/types/permissions_resources.rb +31 -0
  43. data/lib/hoodoo/discovery.rb +20 -0
  44. data/lib/hoodoo/errors.rb +19 -0
  45. data/lib/hoodoo/errors/error_descriptions.rb +229 -0
  46. data/lib/hoodoo/errors/errors.rb +322 -0
  47. data/lib/hoodoo/generator.rb +139 -0
  48. data/lib/hoodoo/logger.rb +23 -0
  49. data/lib/hoodoo/logger/fast_writer.rb +27 -0
  50. data/lib/hoodoo/logger/flattener_mixin.rb +36 -0
  51. data/lib/hoodoo/logger/logger.rb +387 -0
  52. data/lib/hoodoo/logger/slow_writer.rb +49 -0
  53. data/lib/hoodoo/logger/writer_mixin.rb +52 -0
  54. data/lib/hoodoo/logger/writers/file_writer.rb +45 -0
  55. data/lib/hoodoo/logger/writers/log_entries_dot_com_writer.rb +64 -0
  56. data/lib/hoodoo/logger/writers/stream_writer.rb +43 -0
  57. data/lib/hoodoo/middleware.rb +33 -0
  58. data/lib/hoodoo/presenters.rb +45 -0
  59. data/lib/hoodoo/presenters/base.rb +281 -0
  60. data/lib/hoodoo/presenters/base_dsl.rb +519 -0
  61. data/lib/hoodoo/presenters/common_resource_fields.rb +31 -0
  62. data/lib/hoodoo/presenters/embedding.rb +232 -0
  63. data/lib/hoodoo/presenters/types/array.rb +118 -0
  64. data/lib/hoodoo/presenters/types/boolean.rb +26 -0
  65. data/lib/hoodoo/presenters/types/date.rb +26 -0
  66. data/lib/hoodoo/presenters/types/date_time.rb +26 -0
  67. data/lib/hoodoo/presenters/types/decimal.rb +47 -0
  68. data/lib/hoodoo/presenters/types/enum.rb +55 -0
  69. data/lib/hoodoo/presenters/types/field.rb +158 -0
  70. data/lib/hoodoo/presenters/types/float.rb +26 -0
  71. data/lib/hoodoo/presenters/types/hash.rb +361 -0
  72. data/lib/hoodoo/presenters/types/integer.rb +26 -0
  73. data/lib/hoodoo/presenters/types/object.rb +117 -0
  74. data/lib/hoodoo/presenters/types/string.rb +53 -0
  75. data/lib/hoodoo/presenters/types/tags.rb +24 -0
  76. data/lib/hoodoo/presenters/types/text.rb +26 -0
  77. data/lib/hoodoo/presenters/types/uuid.rb +54 -0
  78. data/lib/hoodoo/services.rb +34 -0
  79. data/lib/hoodoo/services/discovery/discoverers/by_consul.rb +66 -0
  80. data/lib/hoodoo/services/discovery/discoverers/by_convention.rb +173 -0
  81. data/lib/hoodoo/services/discovery/discoverers/by_drb/by_drb.rb +195 -0
  82. data/lib/hoodoo/services/discovery/discoverers/by_drb/drb_server.rb +166 -0
  83. data/lib/hoodoo/services/discovery/discoverers/by_drb/drb_server_start.rb +37 -0
  84. data/lib/hoodoo/services/discovery/discovery.rb +186 -0
  85. data/lib/hoodoo/services/discovery/results/for_amqp.rb +58 -0
  86. data/lib/hoodoo/services/discovery/results/for_http.rb +85 -0
  87. data/lib/hoodoo/services/discovery/results/for_local.rb +85 -0
  88. data/lib/hoodoo/services/discovery/results/for_remote.rb +57 -0
  89. data/lib/hoodoo/services/middleware/amqp_log_message.rb +186 -0
  90. data/lib/hoodoo/services/middleware/amqp_log_writer.rb +119 -0
  91. data/lib/hoodoo/services/middleware/endpoints/inter_resource_local.rb +130 -0
  92. data/lib/hoodoo/services/middleware/endpoints/inter_resource_remote.rb +202 -0
  93. data/lib/hoodoo/services/middleware/exception_reporting/base_reporter.rb +105 -0
  94. data/lib/hoodoo/services/middleware/exception_reporting/exception_reporting.rb +115 -0
  95. data/lib/hoodoo/services/middleware/exception_reporting/reporters/airbrake_reporter.rb +64 -0
  96. data/lib/hoodoo/services/middleware/exception_reporting/reporters/raygun_reporter.rb +63 -0
  97. data/lib/hoodoo/services/middleware/interaction.rb +127 -0
  98. data/lib/hoodoo/services/middleware/middleware.rb +2705 -0
  99. data/lib/hoodoo/services/middleware/rack_monkey_patch.rb +73 -0
  100. data/lib/hoodoo/services/services/context.rb +153 -0
  101. data/lib/hoodoo/services/services/implementation.rb +132 -0
  102. data/lib/hoodoo/services/services/interface.rb +934 -0
  103. data/lib/hoodoo/services/services/permissions.rb +250 -0
  104. data/lib/hoodoo/services/services/request.rb +189 -0
  105. data/lib/hoodoo/services/services/response.rb +316 -0
  106. data/lib/hoodoo/services/services/service.rb +141 -0
  107. data/lib/hoodoo/services/services/session.rb +729 -0
  108. data/lib/hoodoo/utilities.rb +12 -0
  109. data/lib/hoodoo/utilities/string_inquirer.rb +54 -0
  110. data/lib/hoodoo/utilities/utilities.rb +380 -0
  111. data/lib/hoodoo/utilities/uuid.rb +44 -0
  112. data/lib/hoodoo/version.rb +17 -0
  113. data/spec/active/active_record/base_spec.rb +57 -0
  114. data/spec/active/active_record/creator_spec.rb +88 -0
  115. data/spec/active/active_record/dated_spec.rb +248 -0
  116. data/spec/active/active_record/error_mapping_spec.rb +360 -0
  117. data/spec/active/active_record/finder_spec.rb +744 -0
  118. data/spec/active/active_record/search_helper_spec.rb +384 -0
  119. data/spec/active/active_record/secure_spec.rb +435 -0
  120. data/spec/active/active_record/support_spec.rb +225 -0
  121. data/spec/active/active_record/translated_spec.rb +19 -0
  122. data/spec/active/active_record/uuid_spec.rb +72 -0
  123. data/spec/active/active_record/writer_spec.rb +272 -0
  124. data/spec/alchemy/alchemy-amq.rb +33 -0
  125. data/spec/client/augmented_array_spec.rb +15 -0
  126. data/spec/client/augmented_base_spec.rb +50 -0
  127. data/spec/client/augmented_hash_spec.rb +15 -0
  128. data/spec/client/client_spec.rb +955 -0
  129. data/spec/client/endpoint/endpoint_spec.rb +70 -0
  130. data/spec/client/endpoint/endpoints/amqp_spec.rb +16 -0
  131. data/spec/client/endpoint/endpoints/auto_session_spec.rb +9 -0
  132. data/spec/client/endpoint/endpoints/http_based_spec.rb +9 -0
  133. data/spec/client/endpoint/endpoints/http_spec.rb +103 -0
  134. data/spec/client/endpoint/endpoints/not_found_spec.rb +35 -0
  135. data/spec/client/headers_spec.rb +172 -0
  136. data/spec/communicators/fast_spec.rb +9 -0
  137. data/spec/communicators/pool_spec.rb +339 -0
  138. data/spec/communicators/slow_spec.rb +15 -0
  139. data/spec/data/resources/caller_spec.rb +156 -0
  140. data/spec/data/resources/errors_spec.rb +22 -0
  141. data/spec/data/resources/log_spec.rb +20 -0
  142. data/spec/data/resources/session_spec.rb +15 -0
  143. data/spec/data/types/error_primitive_spec.rb +15 -0
  144. data/spec/data/types/permissions_defaults_spec.rb +25 -0
  145. data/spec/data/types/permissions_full_spec.rb +44 -0
  146. data/spec/data/types/permissions_resources_spec.rb +34 -0
  147. data/spec/data/types/permissions_spec.rb +37 -0
  148. data/spec/errors/error_descriptions_spec.rb +98 -0
  149. data/spec/errors/errors_spec.rb +346 -0
  150. data/spec/integration/service_actions_spec.rb +112 -0
  151. data/spec/logger/fast_writer_spec.rb +18 -0
  152. data/spec/logger/logger_spec.rb +259 -0
  153. data/spec/logger/slow_writer_spec.rb +144 -0
  154. data/spec/logger/writers/file_writer_spec.rb +37 -0
  155. data/spec/logger/writers/log_entries_dot_com_writer_spec.rb +29 -0
  156. data/spec/logger/writers/stream_writer_spec.rb +38 -0
  157. data/spec/presenters/base_dsl_spec.rb +111 -0
  158. data/spec/presenters/base_spec.rb +871 -0
  159. data/spec/presenters/common_resource_fields_spec.rb +30 -0
  160. data/spec/presenters/embedding_spec.rb +87 -0
  161. data/spec/presenters/types/array_spec.rb +249 -0
  162. data/spec/presenters/types/boolean_spec.rb +51 -0
  163. data/spec/presenters/types/date_spec.rb +57 -0
  164. data/spec/presenters/types/date_time_spec.rb +59 -0
  165. data/spec/presenters/types/decimal_spec.rb +58 -0
  166. data/spec/presenters/types/enum_spec.rb +71 -0
  167. data/spec/presenters/types/field_spec.rb +77 -0
  168. data/spec/presenters/types/float_spec.rb +50 -0
  169. data/spec/presenters/types/hash_spec.rb +1069 -0
  170. data/spec/presenters/types/integer_spec.rb +50 -0
  171. data/spec/presenters/types/object_spec.rb +177 -0
  172. data/spec/presenters/types/string_spec.rb +65 -0
  173. data/spec/presenters/types/tags_spec.rb +56 -0
  174. data/spec/presenters/types/text_spec.rb +50 -0
  175. data/spec/presenters/types/uuid_spec.rb +46 -0
  176. data/spec/presenters/walk_spec.rb +198 -0
  177. data/spec/services/discovery/discoverers/by_consul_spec.rb +29 -0
  178. data/spec/services/discovery/discoverers/by_convention_spec.rb +67 -0
  179. data/spec/services/discovery/discoverers/by_drb/by_drb_spec.rb +80 -0
  180. data/spec/services/discovery/discoverers/by_drb/drb_server_spec.rb +205 -0
  181. data/spec/services/discovery/discovery_spec.rb +73 -0
  182. data/spec/services/discovery/results/for_amqp_spec.rb +17 -0
  183. data/spec/services/discovery/results/for_http_spec.rb +37 -0
  184. data/spec/services/discovery/results/for_local_spec.rb +21 -0
  185. data/spec/services/discovery/results/for_remote_spec.rb +15 -0
  186. data/spec/services/middleware/amqp_log_message_spec.rb +60 -0
  187. data/spec/services/middleware/amqp_log_writer_spec.rb +95 -0
  188. data/spec/services/middleware/endpoints/inter_resource_local_spec.rb +9 -0
  189. data/spec/services/middleware/endpoints/inter_resource_remote_spec.rb +9 -0
  190. data/spec/services/middleware/exception_reporting/base_reporter_spec.rb +16 -0
  191. data/spec/services/middleware/exception_reporting/exception_reporting_spec.rb +92 -0
  192. data/spec/services/middleware/exception_reporting/reporters/airbrake_reporter_spec.rb +24 -0
  193. data/spec/services/middleware/exception_reporting/reporters/raygun_reporter_spec.rb +23 -0
  194. data/spec/services/middleware/middleware_cors_spec.rb +93 -0
  195. data/spec/services/middleware/middleware_create_update_spec.rb +489 -0
  196. data/spec/services/middleware/middleware_dated_at_spec.rb +186 -0
  197. data/spec/services/middleware/middleware_exotic_communication_spec.rb +560 -0
  198. data/spec/services/middleware/middleware_logging_spec.rb +356 -0
  199. data/spec/services/middleware/middleware_multi_local_spec.rb +1094 -0
  200. data/spec/services/middleware/middleware_multi_remote_spec.rb +1440 -0
  201. data/spec/services/middleware/middleware_permissions_spec.rb +1014 -0
  202. data/spec/services/middleware/middleware_public_spec.rb +238 -0
  203. data/spec/services/middleware/middleware_spec.rb +1569 -0
  204. data/spec/services/middleware/string_inquirer_spec.rb +30 -0
  205. data/spec/services/services/application_spec.rb +74 -0
  206. data/spec/services/services/context_spec.rb +48 -0
  207. data/spec/services/services/implementation_spec.rb +45 -0
  208. data/spec/services/services/interface_spec.rb +262 -0
  209. data/spec/services/services/permissions_spec.rb +249 -0
  210. data/spec/services/services/request_spec.rb +95 -0
  211. data/spec/services/services/response_spec.rb +250 -0
  212. data/spec/services/services/session_spec.rb +432 -0
  213. data/spec/spec_helper.rb +298 -0
  214. data/spec/utilities/utilities_spec.rb +537 -0
  215. data/spec/utilities/uuid_spec.rb +20 -0
  216. metadata +615 -0
@@ -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