Class: Cult::NamedArray

Inherits:
Array
  • Object
show all
Defined in:
lib/cult/named_array.rb

Defined Under Namespace

Modules: ArrayExtensions, ObjectExtensions

Constant Summary collapse

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

%i(& * + - << | collect compact flatten reject reverse
rotate select shuffle slice sort uniq sort_by)

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.indexable_wrapper(method_name) ⇒ Object



57
58
59
60
61
62
63
64
65
66
67
# 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

Instance Method Details

#[](key) ⇒ Object

first matching item



188
189
190
191
# File 'lib/cult/named_array.rb', line 188

def [](key)
  return super if normal_key?(key)
  all(key).first
end

#all(key, method = :select) ⇒ Object

Returns all keys that match if method == :select, the first if method == :find



169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
# 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

#extract_index(key) ⇒ Object



132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# 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) ⇒ Object

first matching item, or raises KeyError



199
200
201
# 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) ⇒ Object



149
150
151
152
153
154
155
156
157
158
159
160
161
# 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) ⇒ Object



193
194
195
196
# File 'lib/cult/named_array.rb', line 193

def first(key = nil)
  return super() if key.nil?
  all(key, :find).first
end

#key?(key) ⇒ Boolean Also known as: exist?

Returns:

  • (Boolean)


204
205
206
# File 'lib/cult/named_array.rb', line 204

def key?(key)
  !! first(key)
end

#keysObject



210
211
212
# File 'lib/cult/named_array.rb', line 210

def keys
  map(&:named_array_identifier)
end

#normal_key?(k) ⇒ Boolean

Returns:

  • (Boolean)


163
164
165
# File 'lib/cult/named_array.rb', line 163

def normal_key?(k)
  [Integer, Range].any?{|cls| k.is_a?(cls) }
end

#to_named_arrayObject



70
71
72
# File 'lib/cult/named_array.rb', line 70

def to_named_array
  self
end

#valuesObject



215
216
217
# File 'lib/cult/named_array.rb', line 215

def values
  self
end

#with(**kw) ⇒ Object Also known as: where

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



238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
# 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