class Registry

Constants

DEFAULT_INDEX
MoreThanOneRecordFound
VERSION

Public Class Methods

new(*args, indexes: []) click to toggle source
Calls superclass method
# File lib/registry.rb, line 9
def initialize(*args, indexes: [])
  @indexed = {}
  super(*args)
  reindex!(indexes)
end

Public Instance Methods

<<(item)
Alias for: add
add(item) click to toggle source
Calls superclass method
# File lib/registry.rb, line 41
def add(item)
  @indexed.each do |idx, store|
    watch_setter(item, idx) unless include?(item)
    begin
      idx_value = item.send(idx)
      (store[idx_value] ||= Set.new) << (item)
    rescue NoMethodError => e
      raise "#{item.name} cannot be added because indexable attribute (#{idx}) is missing."
    end
  end
  super(item)
end
Also aliased as: <<
delete(item) click to toggle source
Calls superclass method
# File lib/registry.rb, line 27
def delete(item)
  @indexed.each do |idx, store|
    ignore_setter(item, idx) if include?(item)
    begin
      idx_value = item.send(idx)
      (store[idx_value] ||= Set.new).delete(item)
      store.delete(idx_value) if store[idx_value].empty?
    rescue NoMethodError => e
      raise "#{item.name} cannot be added because indexable attribute (#{idx}) is missing."
    end
  end
  super(item)
end
find(search_criteria) click to toggle source
# File lib/registry.rb, line 59
def find(search_criteria)
  _find(search_criteria) { warn "There were more than 1 records found" }
end
find!(search_criteria) click to toggle source
# File lib/registry.rb, line 55
def find!(search_criteria)
  _find(search_criteria) { raise MoreThanOneRecordFound, "There were more than 1 records found" }
end
index(*indexes) click to toggle source
# File lib/registry.rb, line 74
def index(*indexes)
  indexes.each do |idx|
    warn "Index #{idx} already exists!" and next if @indexed.key?(idx)
    each { |item| watch_setter(item, idx) }
    indexed_records = group_by { |a| a.send(idx) }
    indexed_sets = Hash[indexed_records.keys.zip(indexed_records.values.map { |e| Set.new(e) })]
    @indexed[idx] = indexed_sets
  end
end
indexes() click to toggle source
# File lib/registry.rb, line 23
def indexes
  @indexed.keys - [:object_id]
end
inspect() click to toggle source
# File lib/registry.rb, line 15
def inspect
  to_a.inspect
end
reindex!(indexes = []) click to toggle source
# File lib/registry.rb, line 84
def reindex!(indexes = [])
  @indexed = {}
  index(*([DEFAULT_INDEX] | indexes))
end
to_h() click to toggle source
# File lib/registry.rb, line 19
def to_h
  @indexed
end
where(search_criteria) click to toggle source
# File lib/registry.rb, line 63
def where(search_criteria)
  sets = search_criteria.inject([]) do |sets, (idx, value)|
    raise "No '#{idx}' index! Add it with '.index(:#{idx})'" unless @indexed.include?(idx)
    sets << (@indexed.dig(idx, value) || Set.new)
  end

  subset_records = sets.reduce(sets.first, &:&)
  subset_registry = Registry.new(subset_records, indexes: indexes)
  subset_registry
end

Protected Instance Methods

reindex(idx, item, old_value, new_value) click to toggle source
# File lib/registry.rb, line 91
def reindex(idx, item, old_value, new_value)
  if (new_value != old_value)
    @indexed[idx][old_value].delete item
    (@indexed[idx][new_value] ||= Set.new).add item
  end
end

Private Instance Methods

_find(search_criteria) { || ... } click to toggle source
# File lib/registry.rb, line 100
def _find(search_criteria)
  results = where(search_criteria)
  yield if block_given? && results.count > 1
  results.first
end
ignore_setter(item, idx) click to toggle source
# File lib/registry.rb, line 127
def ignore_setter(item, idx)
  return if item.frozen?
  item.public_methods.select { |m| m.match(/^#{idx}=$/) }.each do |original_method|
    watched_method = "__watched_#{original_method}".to_sym
    renamed_method = "__unwatched_#{original_method}".to_sym
    next unless item.methods.include?(watched_method)
    item.singleton_class.class_eval do
      alias_method original_method, renamed_method
      remove_method(watched_method)
      remove_method(renamed_method)
    end
  end
end
watch_setter(item, idx) click to toggle source
# File lib/registry.rb, line 106
def watch_setter(item, idx)
  return if item.frozen?
  __registry__ = self
  item.public_methods.select { |m| m.match(/^#{idx}=$/) }.each do |original_method|
    watched_method = "__watched_#{original_method}".to_sym
    renamed_method = "__unwatched_#{original_method}".to_sym
    next if item.methods.include?(watched_method)

    item.singleton_class.class_eval do
      define_method(watched_method) do |*args|
        old_value = item.send(idx)
        send(renamed_method, *args).tap do |new_value|
          __registry__.send(:reindex, idx, item, old_value, new_value)
        end
      end
      alias_method renamed_method, original_method
      alias_method original_method, watched_method
    end
  end
end