Module: HashJoiner
- Defined in:
- lib/hash-joiner/version.rb,
lib/hash-joiner.rb
Defined Under Namespace
Classes: JoinError, MergeError
Constant Summary collapse
- MERGEABLE_CLASSES =
The set of mergeable classes
[::Hash, ::Array]
- VERSION =
"0.0.7"
Class Method Summary collapse
-
.assert_hash_properties_are_mergeable(key, lhs_value, rhs_value) ⇒ nil
Asserts that
rhs_valuecan be merged intolhs_valuefor the property identified bykey. -
.assert_is_hash_with_key(h, key, error_prefix) ⇒ nil
Asserts that
his a hash containingkey. -
.assert_objects_are_mergeable(lhs, rhs) ⇒ nil
Asserts that
lhsandrhsare of the same type and can be merged. -
.assign_empty_defaults(collection, array_properties, hash_properties, string_properties) ⇒ Object
Given a collection, initialize any missing properties to empty values.
-
.deep_merge(lhs, rhs) ⇒ Hash, Array
Performs a deep merge of
HashandArraystructures. -
.deep_merge_hashes(lhs, rhs) ⇒ Hash
Performs a deep merge of Hash structures.
-
.join_array_data(key_field, lhs, rhs) ⇒ Array<Hash>
Joins data in
lhswith data fromrhsbased onkey_field. -
.join_data(category, key_field, lhs, rhs) ⇒ Hash+
Joins objects in +lhs+[category] with data from +rhs+[category].
-
.promote_array_data(collection, key) ⇒ Array
Recursively promotes data within an Array.
-
.promote_data(collection, key) ⇒ Hash, ...
Recursively promotes data within the
collectionmatchingkeyto the same level askeyitself. -
.promote_hash_data(collection, key) ⇒ Hash
Recursively promotes data within a Hash.
-
.prune_empty_properties(collection) ⇒ Object
Recursively prunes all empty properties from every element of a collection.
-
.remove_data(collection, key) ⇒ Hash, ...
Recursively strips information from
collectionmatchingkey.
Class Method Details
.assert_hash_properties_are_mergeable(key, lhs_value, rhs_value) ⇒ nil
Asserts that rhs_value can be merged into lhs_value for the property
identified by key.
137 138 139 140 141 142 143 144 145 146 147 |
# File 'lib/hash-joiner.rb', line 137 def self.assert_hash_properties_are_mergeable(key, lhs_value, rhs_value) lhs_class = lhs_value == false ? ::TrueClass : lhs_value.class rhs_class = rhs_value == false ? ::TrueClass : rhs_value.class unless lhs_value.nil? or lhs_class == rhs_class raise MergeError.new( "LHS[#{key}] value (#{lhs_class}): #{lhs_value}\n" + "RHS[#{key}] value (#{rhs_class}): #{rhs_value}") end nil end |
.assert_is_hash_with_key(h, key, error_prefix) ⇒ nil
Asserts that h is a hash containing key. Used to ensure that a Hash
can be joined with another Hash object.
216 217 218 219 220 221 222 |
# File 'lib/hash-joiner.rb', line 216 def self.assert_is_hash_with_key(h, key, error_prefix) if !h.instance_of? ::Hash raise JoinError.new("#{error_prefix} is not a Hash: #{h}") elsif !h.member? key raise JoinError.new("#{error_prefix} missing \"#{key}\": #{h}") end end |
.assert_objects_are_mergeable(lhs, rhs) ⇒ nil
Asserts that lhs and rhs are of the same type and can be merged.
94 95 96 97 98 99 100 101 |
# File 'lib/hash-joiner.rb', line 94 def self.assert_objects_are_mergeable(lhs, rhs) if lhs.class != rhs.class raise MergeError.new("LHS (#{lhs.class}): #{lhs}\n" + "RHS (#{rhs.class}): #{rhs}") elsif !MERGEABLE_CLASSES.include? lhs.class raise MergeError.new "Class not mergeable: #{lhs.class}" end end |
.assign_empty_defaults(collection, array_properties, hash_properties, string_properties) ⇒ Object
Given a collection, initialize any missing properties to empty values.
273 274 275 276 277 278 279 280 281 282 283 284 285 286 |
# File 'lib/hash-joiner.rb', line 273 def self.assign_empty_defaults(collection, array_properties, hash_properties, string_properties) if collection.instance_of? ::Hash array_properties.each {|i| collection[i] ||= Array.new} hash_properties.each {|i| collection[i] ||= Hash.new} string_properties.each {|i| collection[i] ||= String.new} elsif collection.instance_of? ::Array collection.each do |i| assign_empty_defaults(i, array_properties, hash_properties, string_properties) end end collection end |
.deep_merge(lhs, rhs) ⇒ Hash, Array
Performs a deep merge of Hash and Array structures. If the collections
are +Hash+es, Hash or Array members of rhs will be deep-merged with
any existing members in lhs. If the collections are +Array+s, the values
from rhs will be appended to lhs.
113 114 115 116 117 118 119 120 121 122 |
# File 'lib/hash-joiner.rb', line 113 def self.deep_merge(lhs, rhs) assert_objects_are_mergeable lhs, rhs if rhs.instance_of? ::Hash deep_merge_hashes lhs, rhs elsif rhs.instance_of? ::Array lhs.concat rhs end lhs end |
.deep_merge_hashes(lhs, rhs) ⇒ Hash
Performs a deep merge of Hash structures. Used to implement deep_merge.
156 157 158 159 160 161 162 163 164 165 166 167 |
# File 'lib/hash-joiner.rb', line 156 def self.deep_merge_hashes(lhs, rhs) rhs.each do |key,rhs_value| lhs_value = lhs[key] assert_hash_properties_are_mergeable key, lhs_value, rhs_value if MERGEABLE_CLASSES.include? lhs_value.class deep_merge lhs_value, rhs_value else lhs[key] = rhs_value end end end |
.join_array_data(key_field, lhs, rhs) ⇒ Array<Hash>
Joins data in lhs with data from rhs based on key_field. Both lhs
and rhs should be of type Array<Hash>. Performs a deep_merge on
matching objects; assigns values from rhs to lhs if no corresponding
value yet exists in lhs.
238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 |
# File 'lib/hash-joiner.rb', line 238 def self.join_array_data(key_field, lhs, rhs) unless lhs.instance_of? ::Array and rhs.instance_of? ::Array raise JoinError.new("Both lhs (#{lhs.class}) and " + "rhs (#{rhs.class}) must be an Array of Hash") end lhs_index = {} lhs.each do |i| self.assert_is_hash_with_key(i, key_field, "LHS element") lhs_index[i[key_field]] = i end # TODO(mbland): Make exception-safe by splitting into two loops: one for # the assert; one to modify lhs after all the assertions have succeeded. rhs.each do |i| self.assert_is_hash_with_key(i, key_field, "RHS element") key = i[key_field] if lhs_index.member? key deep_merge lhs_index[key], i else lhs << i end end lhs end |
.join_data(category, key_field, lhs, rhs) ⇒ Hash+
Joins objects in +lhs+[category] with data from +rhs+[category]. If the
category objects are of type Array<Hash>, key_field will be used as
the primary key to join the objects in the two collections; otherwise
key_field is ignored.
190 191 192 193 194 195 196 197 198 199 200 201 202 203 |
# File 'lib/hash-joiner.rb', line 190 def self.join_data(category, key_field, lhs, rhs) rhs_data = rhs[category] return lhs unless rhs_data lhs_data = lhs[category] if !(lhs_data and [::Hash, ::Array].include? lhs_data.class) lhs[category] = rhs_data elsif lhs_data.instance_of? ::Hash self.deep_merge lhs_data, rhs_data else self.join_array_data key_field, lhs_data, rhs_data end lhs end |
.promote_array_data(collection, key) ⇒ Array
Recursively promotes data within an Array. Used to implement promote_data.
63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
# File 'lib/hash-joiner.rb', line 63 def self.promote_array_data(collection, key) collection.each do |i| # If the Array entry is a hash that contains only the target key, # then that key should map to an Array to be promoted. if i.instance_of? ::Hash and i.keys == [key] data_to_promote = i[key] i.delete key deep_merge collection, data_to_promote else promote_data i, key end end collection.delete_if {|i| i.empty?} end |
.promote_data(collection, key) ⇒ Hash, ...
Recursively promotes data within the collection matching key to the
same level as key itself. After promotion, each key reference will
be deleted.
34 35 36 37 38 39 40 |
# File 'lib/hash-joiner.rb', line 34 def self.promote_data(collection, key) if collection.instance_of? ::Hash promote_hash_data collection, key elsif collection.instance_of? ::Array promote_array_data collection, key end end |
.promote_hash_data(collection, key) ⇒ Hash
Recursively promotes data within a Hash. Used to implement promote_data.
48 49 50 51 52 53 54 55 |
# File 'lib/hash-joiner.rb', line 48 def self.promote_hash_data(collection, key) if collection.member? key data_to_promote = collection[key] collection.delete key deep_merge collection, data_to_promote end collection.each_value {|i| promote_data i, key} end |
.prune_empty_properties(collection) ⇒ Object
Recursively prunes all empty properties from every element of a collection.
291 292 293 |
# File 'lib/hash-joiner.rb', line 291 def self.prune_empty_properties(collection) prune_empty_properties_helper(collection, {}) end |
.remove_data(collection, key) ⇒ Hash, ...
Recursively strips information from collection matching key.
14 15 16 17 18 19 20 21 22 |
# File 'lib/hash-joiner.rb', line 14 def self.remove_data(collection, key) if collection.instance_of? ::Hash collection.delete key collection.each_value {|i| remove_data i, key} elsif collection.instance_of? ::Array collection.each {|i| remove_data i, key} collection.delete_if {|i| i.empty?} end end |