module Collapsium::ViralCapabilities
Tries to make extended Hash capabilities viral, i.e. provides the same features to nested Hash structures as the Hash that includes this module.
Virality is ensured by changing the return value of various methods; if it is derived from Hash, it is attempted to convert it to the including class.
The module uses HashMethods and ArrayMethods to decide which methods to make viral in this manner.
There are two ways for using this module: a) in a `Class`, either include, prepend or extend it. b) in a `Module`, extend this module. The resulting module can be included,
prepended or extended in a `Class` again.
Constants
- DEFAULT_ANCESTORS
The default ancestor values for the accessors in
ViralAncestorTypes
are defined here. They're also used for generating functions that should provide the best ancestor class.- ENHANCED_MARKER
- READ_METHODS
We want to wrap methods for Arrays and Hashes alike
- WRITE_METHODS
Public Class Methods
# File lib/collapsium/viral_capabilities.rb, line 229 def copy_mods(parent, value) # We want to extend all the modules in self. That might be a # no-op due to the above block, but not necessarily so. value_mods = (class << value; self end).included_modules parent_mods = (class << parent; self end).included_modules parent_mods << ViralAncestorTypes mods_to_copy = (parent_mods - value_mods).uniq # Small fixup for JSON; this doesn't technically belong here, but let's # play nice. if value.is_a? Array mods_to_copy.delete(::JSON::Ext::Generator::GeneratorMethods::Hash) elsif value.is_a? Hash mods_to_copy.delete(::JSON::Ext::Generator::GeneratorMethods::Array) end # Copy mods. mods_to_copy.each do |mod| value.extend(mod) end end
Enhance the base by wrapping all READ_METHODS
and WRITE_METHODS
in a wrapper that uses enhance_value
to, well, enhance Hash and Array results.
# File lib/collapsium/viral_capabilities.rb, line 102 def enhance(base) # rubocop:disable Style/ClassVars @@write_block ||= proc do |wrapped_method, *args, &block| arg_copy = args.map do |arg| enhance_value(wrapped_method.receiver, arg) end result = wrapped_method.call(*arg_copy, &block) next enhance_value(wrapped_method.receiver, result) end @@read_block ||= proc do |wrapped_method, *args, &block| result = wrapped_method.call(*args, &block) next enhance_value(wrapped_method.receiver, result) end # rubocop:enable Style/ClassVars # Minimally: add the ancestor functions to classes if base.is_a? Class base.extend(ViralAncestorTypes) end READ_METHODS.each do |method_name| wrap_method(base, method_name, raise_on_missing: false, &@@read_block) end WRITE_METHODS.each do |method_name| wrap_method(base, method_name, raise_on_missing: false, &@@write_block) end end
# File lib/collapsium/viral_capabilities.rb, line 156 def enhance_array_value(parent, value, *args) # If the value is not of the best ancestor type, make sure it becomes # that type. # XXX: DO NOT replace the loop with a simpler function - it could lead # to infinite recursion! enc_class = array_ancestor(parent, value) if value.class != enc_class new_value = enc_class.new value.each do |item| if not item.is_a? Hash and not item.is_a? Array new_value << item next end new_item = enhance_value(value, item) new_value << new_item end value = new_value end # Copy all modules from the parent to the value copy_mods(parent, value) # Set appropriate ancestors on the value set_ancestors(parent, value) return call_virality(parent, value, *args) end
Given an outer Hash and a value, enhance Hash values so that they have the same capabilities as the outer Hash. Non-Hash values are returned unchanged.
# File lib/collapsium/viral_capabilities.rb, line 189 def enhance_hash_value(parent, value, *args) # If the value is not of the best ancestor type, make sure it becomes # that type. # XXX: DO NOT replace the loop with :merge! or :merge - those are # potentially wrapped write functions, leading to an infinite # recursion. enc_class = hash_ancestor(parent, value) if value.class != enc_class new_value = enc_class.new value.each do |key, item| if not item.is_a? Hash and not item.is_a? Array new_value[key] = item next end new_item = enhance_value(value, item) new_value[key] = new_item end value = new_value end # Copy all modules from the parent to the value copy_mods(parent, value) # If we have a default_proc and the value doesn't, we want to use our # own. This *can* override a perfectly fine default_proc with our own, # which might suck. if parent.respond_to?(:default_proc) # FIXME: need to inherit this for arrays, too? value.default_proc ||= parent.default_proc end # Set appropriate ancestors on the value set_ancestors(parent, value) return call_virality(parent, value, *args) end
Enhance Hash or Array value
# File lib/collapsium/viral_capabilities.rb, line 133 def enhance_value(parent, value, *args) if value.is_a? Hash value = enhance_hash_value(parent, value, *args) elsif value.is_a? Array value = enhance_array_value(parent, value, *args) end # It's possible that the value is a Hash or an Array, but there's no # ancestor from which capabilities can be copied. We can find out by # checking whether any wrappers are defined for it. # Turns out that that's quite expensive at run-time, though, so instead # we resort to flagging enhanced values. if value.is_a? Array or value.is_a? Hash needs_wrapping = !value.instance_variable_get(ENHANCED_MARKER) if needs_wrapping enhance(value) value.instance_variable_set(ENHANCED_MARKER, true) end end return value end
# File lib/collapsium/viral_capabilities.rb, line 84 def extended(base) enhance(base) end
# File lib/collapsium/viral_capabilities.rb, line 80 def included(base) enhance(base) end
When prepended, included or extended, enhance the base.
# File lib/collapsium/viral_capabilities.rb, line 76 def prepended(base) enhance(base) end
# File lib/collapsium/viral_capabilities.rb, line 295 def set_ancestors(parent, value) DEFAULT_ANCESTORS.each do |getter, default| setter = "#{getter}=".to_sym ancestor = nil if parent.is_a? default ancestor = parent.class elsif parent.respond_to?(getter) ancestor = parent.send(getter) else ancestor = default end value.send(setter, ancestor) end end
Public Instance Methods
# File lib/collapsium/viral_capabilities.rb, line 67 def extended(base) ViralCapabilities.enhance(base) end
# File lib/collapsium/viral_capabilities.rb, line 63 def included(base) ViralCapabilities.enhance(base) end
When prepended, included or extended, enhance the base.
# File lib/collapsium/viral_capabilities.rb, line 59 def prepended(base) ViralCapabilities.enhance(base) end