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 a NamedArray instead of an Array

Public Class Methods

indexable_wrapper(method_name) click to toggle source
# 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

[](key) click to toggle source

first matching item

Calls superclass method
# File lib/cult/named_array.rb, line 188
def [](key)
  return super if normal_key?(key)
  all(key).first
end
all(key, method = :select) click to toggle source

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
exist?(key)
Alias for: key?
extract_index(key) click to toggle source
# 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
fetch(key) click to toggle source

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
fetch_by_index(ary, index) click to toggle source
# 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
first(key = nil) click to toggle source
Calls superclass method
# File lib/cult/named_array.rb, line 193
def first(key = nil)
  return super() if key.nil?
  all(key, :find).first
end
key?(key) click to toggle source
# File lib/cult/named_array.rb, line 204
def key?(key)
  !! first(key)
end
Also aliased as: exist?
keys() click to toggle source
# File lib/cult/named_array.rb, line 210
def keys
  map(&:named_array_identifier)
end
normal_key?(k) click to toggle source
# File lib/cult/named_array.rb, line 163
def normal_key?(k)
  [Integer, Range].any?{|cls| k.is_a?(cls) }
end
to_named_array() click to toggle source
# File lib/cult/named_array.rb, line 70
def to_named_array
  self
end
values() click to toggle source
# File lib/cult/named_array.rb, line 215
def values
  self
end
where(**kw)
Alias for: with
with(**kw) click to toggle source

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
Also aliased as: where

Private Instance Methods

build_regexp_from_string(s) click to toggle source

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
expand_predicate(predicate) click to toggle source

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
extract_regexp_options(s) click to toggle source
# 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