class Cult::NamedArray
Constants
- PROXY_METHODS
Wrap any non-mutating methods that can return an Array, and wrap the result with a
NamedArray
. This is why NamedArray.select results in aNamedArray
instead of an Array
Public Class Methods
# File lib/cult/named_array.rb, line 57 def self.indexable_wrapper(method_name) old_method_name = "#{method_name}_without_wrapper" alias_method old_method_name, method_name define_method(method_name) do |*a| if a.empty? return IndexWrapper.new(self, method_name) else return send(old_method_name, *a) end end end
Public Instance Methods
first matching item
# File lib/cult/named_array.rb, line 188 def [](key) return super if normal_key?(key) all(key).first end
Returns all keys that match if method == :select, the first if method == :find
# File lib/cult/named_array.rb, line 169 def all(key, method = :select) return [self[key]] if normal_key?(key) return [] if key.nil? key, index = extract_index(key) predicate = expand_predicate(key) effective_method = index.nil? ? method : :select result = send(effective_method) do |v| predicate === v.named_array_identifier end result = fetch_by_index(result, index) if index Array(result).to_named_array end
# File lib/cult/named_array.rb, line 132 def extract_index(key) re = /\[\s*([^\]]*)\s*\]$/ if key.is_a?(String) && (m = key.match(re)) subs, expr = m[0], m[1] index = case expr when /^(\-?\d+)$/; $1.to_i #.. $1.to_i when /^(\-?\d+)\s*\.\.\s*(\-?\d+)$/; $1.to_i .. $2.to_i when /^(\-?\d+)\s*\.\.\.\s*(\-?\d+)$/; $1.to_i ... $2.to_i when /^((?:\-?\d+\s*,?\s*)+)$/; $1.split(',').map(&:to_i) end # We return [predicate string with index removed, expanded index] [ key[0 ... key.size - subs.size], index ] else [ key, nil ] end end
first matching item, or raises KeyError
# File lib/cult/named_array.rb, line 199 def fetch(key) first(key) or raise KeyError, "Not found: #{key.inspect}" end
# File lib/cult/named_array.rb, line 149 def fetch_by_index(ary, index) case index when Array ary.values_at(*index).compact when Integer v = ary.at(index) v.nil? ? [] : [v] when Range ary[index] else fail ArgumentError, "weird index: #{index.inspect}" end end
# File lib/cult/named_array.rb, line 193 def first(key = nil) return super() if key.nil? all(key, :find).first end
# File lib/cult/named_array.rb, line 204 def key?(key) !! first(key) end
# File lib/cult/named_array.rb, line 210 def keys map(&:named_array_identifier) end
# File lib/cult/named_array.rb, line 163 def normal_key?(k) [Integer, Range].any?{|cls| k.is_a?(cls) } end
# File lib/cult/named_array.rb, line 70 def to_named_array self end
# File lib/cult/named_array.rb, line 215 def values self end
Takes a predicate in the form of:
key: value
And returns all items that both respond_to?(key), and predicate === the result of sending key.
Instances can override what predicates mean by defining “names_for_*” to override what is tested.
For example, if you have an Object that contains a list of “Foos”, but you want to select them by name, you'd do something like:
class Object
attr_reader :foos # Instances of Foo class def names_for_foos # Now we can select by name foos.map(&:name) end
end
# File lib/cult/named_array.rb, line 238 def with(**kw) fail ArgumentError, "with requires exactly one predicate" if kw.size != 1 key, predicate = kw.first predicate = expand_predicate(predicate) select do |candidate| methods = [key, "query_for_#{key}", "names_for_#{key}"].select do |m| candidate.respond_to?(m) end methods.any? do |method| Array(candidate.send(method)).any? do |r| begin predicate === r rescue # We're going to assume this is a result of a string # comparison to a custom #== false end end end end end
Private Instance Methods
It's unforunate that there's not a Regexp constructor that'll accept this string format with options.
# File lib/cult/named_array.rb, line 90 def build_regexp_from_string(s) fail RegexpError, "Isn't a Regexp: #{s}" if s[0] != '/' options = extract_regexp_options(s) Regexp.new(s[1 ... s.rindex('/')], options) end
Most of the named-array predicates are meant to be useful for user input or an interactive session. We give special behavior to certain strings the user might enter to convert them to a regexp, etc.
# File lib/cult/named_array.rb, line 116 def expand_predicate(predicate) case predicate when String predicate[0] == '/' ? build_regexp_from_string(predicate) : predicate when Regexp, Proc, Range predicate when Symbol, Integer ->(v) { predicate.to_s == v.to_s } when NilClass nil else predicate end end
# File lib/cult/named_array.rb, line 98 def extract_regexp_options(s) offset = s.rindex('/') fail RegexpError, "Unterminated Regexp: #{s}" if offset == 0 trailing = s[offset + 1 ... s.size] re_string = "%r!!#{trailing}" begin (eval re_string).options rescue SyntaxError => e fail RegexpError, "invalid Regexp options: #{trailing}" end end