class Relix::IndexSet

Attributes

klass[R]
redis[RW]

Public Class Methods

new(klass, redis_source) click to toggle source
# File lib/relix/index_set.rb, line 5
def initialize(klass, redis_source)
  @klass = klass
  @redis_source = redis_source
  @indexes = Hash.new
  @obsolete_indexes = Hash.new
  @keyer = Keyer.default_for(@klass) unless parent
end

Public Instance Methods

[](name) click to toggle source
# File lib/relix/index_set.rb, line 229
def [](name)
  full_index_list[name.to_s]
end
add_index(index_type, name, options={}) click to toggle source
# File lib/relix/index_set.rb, line 49
def add_index(index_type, name, options={})
  raise Relix::InvalidIndexError.new("Index #{name} is already declared as obsolete.") if @obsolete_indexes[name.to_s]

  @indexes[name.to_s] = create_index(self, index_type, name, options)
end
add_obsolete_index(index_type, name, options={}) click to toggle source
# File lib/relix/index_set.rb, line 75
def add_obsolete_index(index_type, name, options={})
  raise Relix::InvalidIndexError.new("Primary key indexes cannot be obsoleted.") if(index_type == :primary_key)
  raise Relix::InvalidIndexError.new("Index #{name} is already declared as non-obsolete.") if @indexes[name.to_s]

  @obsolete_indexes[name.to_s] = create_index(self, index_type, name, options)
end
current_values_name(pk) click to toggle source
# File lib/relix/index_set.rb, line 225
def current_values_name(pk)
  keyer.values(pk, @klass)
end
deindex!(object) click to toggle source
# File lib/relix/index_set.rb, line 177
def deindex!(object)
  pk = primary_key_for(object)

  handle_concurrent_modifications(pk) do
    current_values_name = current_values_name(pk)
    redis.watch current_values_name
    current_values = redis.hgetall(current_values_name)

    full_index_list(:including_obsolete).map do |name, index|
      old_value = if index.attribute_immutable?
        index.read_normalized(object)
      else
        current_values[name]
      end

      ((watch = index.watch(old_value)) && !watch.empty? && redis.watch(*watch))
      proc { index.deindex(redis, pk, old_value) }
    end.tap { |ops| ops << proc { redis.del current_values_name } }
  end
end
deindex_by_primary_key!(pk) click to toggle source
# File lib/relix/index_set.rb, line 198
def deindex_by_primary_key!(pk)
  handle_concurrent_modifications(pk) do
    current_values_name = current_values_name(pk)
    redis.watch current_values_name
    current_values = redis.hgetall(current_values_name)

    full_index_list(:including_obsolete).map do |name, index|
      old_value = current_values[name]

      ((watch = index.watch(old_value)) && !watch.empty? && redis.watch(*watch))
      proc { index.deindex(redis, pk, old_value) }
    end.tap { |ops| ops << proc { redis.del current_values_name } }
  end
end
destroy_index(name) click to toggle source
# File lib/relix/index_set.rb, line 82
def destroy_index(name)
  name = name.to_s
  index = @obsolete_indexes[name]
  raise MissingIndexError.new("No obsolete index found for #{name}.") unless index
  raise InvalidIndexError.new("Indexes built on immutable attributes cannot be destroyed.") if index.attribute_immutable?

  lookup.each do |pk|
    handle_concurrent_modifications(pk) do
      current_values_name = current_values_name(pk)
      redis.watch current_values_name
      current_values = redis.hgetall(current_values_name)

      old_value = current_values[name]

      ((watch = index.watch(old_value)) && !watch.empty? && redis.watch(*watch))
      ops = []
      ops << proc{ index.destroy(redis, pk, old_value) } if index.respond_to?(:destroy)
      ops << proc{ redis.hdel current_values_name, name }
      ops
    end
  end

  if index.respond_to?(:destroy_all)
    index.destroy_all(redis)
  end
end
index!(object) click to toggle source
# File lib/relix/index_set.rb, line 169
def index!(object)
  pk = primary_key_for(object)

  handle_concurrent_modifications(pk) do
    index_ops(object, pk)
  end
end
index_ops(object, pk) click to toggle source
# File lib/relix/index_set.rb, line 128
def index_ops(object, pk)
  current_values_name = current_values_name(pk)
  redis.watch current_values_name
  current_values = redis.hgetall(current_values_name)
  new_current_values = {}

  ops = full_index_list.collect do |name,index|
    value = index.read_normalized(object)
    old_value = current_values[name]

    ((watch = index.watch(value, old_value)) && redis.watch(*watch))

    if index.index?(redis, object, value)
      new_current_values[name] = value unless index.attribute_immutable?
      next if value == old_value
      next unless index.filter(redis, pk, object, value)

      query_value = index.query(redis, value)
      proc do
        index.index(redis, pk, object, value, old_value, *query_value)
      end
    else
      proc do
        index.deindex(redis, pk, old_value)
      end
    end
  end.compact

  if new_current_values.any?
    ops << proc do
      redis.hmset(current_values_name, *new_current_values.flatten)
    end
  elsif current_values.any?
    ops << proc do
      redis.del(current_values_name)
    end
  end

  ops
end
indexes() click to toggle source
# File lib/relix/index_set.rb, line 109
def indexes
  Relix.deprecate("Calling #indexes is deprecated; use #[] instead.", "2")
  self
end
key_prefix(name) click to toggle source
# File lib/relix/index_set.rb, line 213
def key_prefix(name)
  "#{@klass.name}:#{name}"
end
keyer(value=nil, options={}) click to toggle source
# File lib/relix/index_set.rb, line 33
def keyer(value=nil, options={})
  if value
    @keyer = value.new(@klass, options)
  else
    (@keyer || parent.keyer)
  end
end
lookup() { |query| ... } click to toggle source
# File lib/relix/index_set.rb, line 114
def lookup(&block)
  if block
    query = Query.new(self)
    yield(query)
    query.run
  else
    primary_key_index.all(redis)
  end
end
lookup_values(index) click to toggle source
# File lib/relix/index_set.rb, line 124
def lookup_values(index)
  self[index].values(redis)
end
method_missing(m, *args) click to toggle source
Calls superclass method
# File lib/relix/index_set.rb, line 41
def method_missing(m, *args)
  if Relix.index_types.keys.include?(m.to_sym)
    add_index(m, *args)
  else
    super
  end
end
obsolete(&block) click to toggle source
# File lib/relix/index_set.rb, line 69
def obsolete(&block)
  raise ArgumentError.new("No block passed.") unless block_given?

  Obsolater.new(self).instance_eval(&block)
end
parent() click to toggle source
# File lib/relix/index_set.rb, line 217
def parent
  unless @parent || @parent == false
    parent = @klass.superclass
    @parent = (parent.respond_to?(:relix) ? parent.relix : false)
  end
  @parent
end
pk(accessor)
Alias for: primary_key
primary_key(accessor) click to toggle source
# File lib/relix/index_set.rb, line 17
def primary_key(accessor)
  @primary_key_index = add_index(:primary_key, accessor)
end
Also aliased as: pk
primary_key_index() click to toggle source
# File lib/relix/index_set.rb, line 22
def primary_key_index
  unless @primary_key_index
    if parent
      @primary_key_index = parent.primary_key_index
    else
      raise MissingPrimaryKeyError.new("You must declare a primary key for #{@klass.name}")
    end
  end
  @primary_key_index
end

Protected Instance Methods

full_index_list(including_obsolete=false) click to toggle source
# File lib/relix/index_set.rb, line 235
def full_index_list(including_obsolete=false)
  list = (parent ? parent.full_index_list.merge(@indexes) : @indexes)
  if including_obsolete
    list = @obsolete_indexes.merge(list)
  end
  list
end

Private Instance Methods

create_index(index_set, index_type, name, options) click to toggle source
# File lib/relix/index_set.rb, line 245
def create_index(index_set, index_type, name, options)
  accessor = (options.delete(:on) || name)
  Relix.index_types[index_type].new(index_set, name, accessor, options)
end
handle_concurrent_modifications(primary_key) { || ... } click to toggle source
# File lib/relix/index_set.rb, line 250
def handle_concurrent_modifications(primary_key)
  retries = 5
  loop do
    ops = yield

    results = redis.multi do
      ops.each do |op|
        op.call(primary_key)
      end
    end

    if results
      Array(results).each do |result|
        raise RedisIndexingError.new(result.message) if Exception === result
      end
      break
    else
      retries -= 1
      raise ExceededRetriesForConcurrentWritesError.new if retries <= 0
    end
  end
rescue Redis::CommandError => e
  raise RedisIndexingError, e.message, e.backtrace
end
primary_key_for(object) click to toggle source
# File lib/relix/index_set.rb, line 275
def primary_key_for(object)
  primary_key_index.read_normalized(object)
end