Module: Collapsium::PathedAccess
- Extended by:
- Support::Methods
- Includes:
- Support::PathComponents
- Included in:
- UberHash
- Defined in:
- lib/collapsium/pathed_access.rb
Overview
The PathedAccess module can be used to extend Hash with pathed access on top of regular access, i.e. instead of ‘h[“second”]` you can write `h`.
The main benefit is much simpler code for accessing nested structured. For any given path, PathedAccess will return nil from ‘[]` if any of the path components do not exist.
Similarly, intermediate nodes will be created when you write a value for a path.
Constant Summary collapse
- READ_METHODS =
We want to wrap methods for Arrays and Hashes alike
( ::Collapsium::Support::HashMethods::KEYED_READ_METHODS \ + ::Collapsium::Support::ArrayMethods::INDEXED_READ_METHODS ).uniq.freeze
- WRITE_METHODS =
( ::Collapsium::Support::HashMethods::KEYED_WRITE_METHODS \ + ::Collapsium::Support::ArrayMethods::INDEXED_WRITE_METHODS ).uniq.freeze
- PATHED_ACCESS_READER =
Create a reader and write proc, because we only know
PathedAccess.create_proc(false).freeze
- PATHED_ACCESS_WRITER =
PathedAccess.create_proc(true).freeze
Constants included from Support::Methods
Support::Methods::BUILTINS, Support::Methods::WRAPPER_HASH
Constants included from Support::PathComponents
Support::PathComponents::DEFAULT_SEPARATOR
Instance Attribute Summary
Attributes included from Support::PathComponents
Class Method Summary collapse
-
.create_proc(write_access) ⇒ Object
Returns a proc for either read or write access.
- .enhance(base) ⇒ Object
- .extended(base) ⇒ Object
- .included(base) ⇒ Object
- .prepended(base) ⇒ Object
-
.recursive_fetch(path, data, current_path = [], options = {}) ⇒ Object
Given the path components, recursively fetch any but the last key.
Instance Method Summary collapse
-
#virality(value, *args) ⇒ Object
Ensure that all values have their path_prefix set.
Methods included from Support::Methods
builtins, loop_detected?, repeated, resolve_helpers, wrap_method, wrappers
Methods included from Support::PathComponents
#filter_components, #join_path, #normalize_path, #parent_path, #path_components, #path_prefix, #path_prefix=, #split_pattern
Class Method Details
.create_proc(write_access) ⇒ Object
Returns a proc for either read or write access. Procs for write access will create intermediary hashes when e.g. setting a value for ‘foo.bar.baz`, and the `bar` Hash doesn’t exist yet.
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 |
# File 'lib/collapsium/pathed_access.rb', line 53 def create_proc(write_access) return proc do |wrapped_method, *args, &block| # If there are no arguments, there's nothing to do with paths. Just # delegate to the hash. if args.empty? next wrapped_method.call(*args, &block) end # The method's receiver is encapsulated in the wrapped_method; we'll # use it a few times so let's reduce typing. This is essentially the # equivalent of `self`. receiver = wrapped_method.receiver # With any of the dispatch methods, we know that the first argument has # to be a key. We'll try to split it by the path separator. components = receiver.path_components(args[0].to_s) # If there are no components, return the receiver itself/the root if components.empty? next receiver end # Try to find the leaf, based on the given components. leaf = recursive_fetch(components, receiver, [], create: write_access) # Since Methods already contains loop prevention and we may want to # call wrapped methods, let's just find the method to call from the # leaf by name. meth = leaf.method(wrapped_method.name) # If the first argument was a symbol key, we want to use it verbatim. # Otherwise we had pathed access, and only want to pass the last # component to whatever method we're calling. the_args = args if not args[0].is_a?(Symbol) and args[0] != components.last the_args = args.dup the_args[0] = components.last end # Array methods we're modifying here are indexed, so the first argument # must be an integer. Let's make it so :) if leaf.is_a? Array and the_args[0][0] =~ /[0-9]/ the_args = the_args.dup the_args[0] = the_args[0].to_s.to_i end # Then we can continue with that method. result = meth.call(*the_args, &block) # Sadly, we can't just return the result and be done with it. # We need to tell the virality function (below) what we know about the # result's path prefix, so we enhance the result value explicitly here. result_path = receiver.path_components(receiver.path_prefix) result_path += components next ViralCapabilities.enhance_value(leaf, result, result_path) end # proc end |
.enhance(base) ⇒ Object
127 128 129 130 131 132 133 134 135 136 137 138 |
# File 'lib/collapsium/pathed_access.rb', line 127 def enhance(base) # Make the capabilities of classes using PathedAccess viral. base.extend(ViralCapabilities) # Wrap all accessor functions to deal with paths READ_METHODS.each do |method| wrap_method(base, method, raise_on_missing: false, &PATHED_ACCESS_READER) end WRITE_METHODS.each do |method| wrap_method(base, method, raise_on_missing: false, &PATHED_ACCESS_WRITER) end end |
.extended(base) ⇒ Object
119 120 121 |
# File 'lib/collapsium/pathed_access.rb', line 119 def extended(base) enhance(base) end |
.included(base) ⇒ Object
115 116 117 |
# File 'lib/collapsium/pathed_access.rb', line 115 def included(base) enhance(base) end |
.prepended(base) ⇒ Object
123 124 125 |
# File 'lib/collapsium/pathed_access.rb', line 123 def prepended(base) enhance(base) end |
.recursive_fetch(path, data, current_path = [], options = {}) ⇒ Object
Given the path components, recursively fetch any but the last key.
142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 |
# File 'lib/collapsium/pathed_access.rb', line 142 def recursive_fetch(path, data, current_path = [], = {}) # Split path into head and tail; for the next iteration, we'll look use # only head, and pass tail on recursively. head = path[0] current_path << head tail = path.slice(1, path.length) # For the leaf element, we do nothing because that's where we want to # dispatch to. if path.length == 1 return data end # If we're a write function, then we need to create intermediary objects, # i.e. what's at head if nothing is there. if data[head].nil? # If the head is nil, we can't recurse. In create mode that means we # want to create hash children, but in read mode we're done recursing. # By returning a hash here, we allow the caller to send methods on to # this temporary, making a PathedAccess Hash act like any other Hash. if not [:create] return {} end data[head] = {} end # Ok, recurse. return recursive_fetch(tail, data[head], current_path, ) end |
Instance Method Details
#virality(value, *args) ⇒ Object
Ensure that all values have their path_prefix set.
176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 |
# File 'lib/collapsium/pathed_access.rb', line 176 def (value, *args) # Figure out what path prefix to set on the value, if any. # Candidates for the prefix are: explicit = args[0] || [] from_self = path_components(path_prefix) from_value = path_components(value.path_prefix) prefix = [] if not explicit.empty? # If we got explicit information, we most likely want to use that. prefix = explicit elsif not from_self.empty? # If we got information from self, that's the next best candidate. prefix = from_self end # However, if the value already has a better path prefix than either # of the above, we want to keep that. if prefix.length > from_value.length value.path_prefix = normalize_path(prefix) end return value end |