class DataMapper::Adapters::RedisAdapter

Public Class Methods

new(name, uri_or_options) click to toggle source

Make a new instance of the adapter. The @redis ivar is the 'data-store' for this adapter.

@param [String, Symbol] name

The name of the Repository using this adapter.

@param [String, Hash] uri_or_options

The connection uri string, or a hash of options to set up
the adapter

@api semipublic

Calls superclass method
# File lib/dm-redis-adapter/adapter.rb, line 345
def initialize(name, uri_or_options)
  uri_or_options.delete_if { |k,v| v.nil? || (v.is_a?(String) && v == '') }

  # If the :path ends in '.sock' assume that this really is a path, otherwise assume it's a DB name
  uri_or_options.delete(:path) if uri_or_options[:path] == '/'

  if uri_or_options[:path] && !uri_or_options[:path].match(/\.sock$/)
    uri_or_options[:db] = uri_or_options.delete(:path)
  end

  # Parse options from the URI's query string
  if uri_or_options[:query]
    opts = uri_or_options[:query].split(/&/)
    uri_or_options.merge!(Hash[opts.collect { |x| x.split(/=/) }])
    uri_or_options.delete(:query)
  end

  super
  @redis = Redis.new(@options)
end

Public Instance Methods

create(resources) click to toggle source

Used by DataMapper to put records into the redis data-store: “INSERT” in SQL-speak. It takes an array of the resources (model instances) to be saved. Resources each have a key that can be used to quickly look them up later without searching.

@param [Enumerable(Resource)] resources

The set of resources (model instances)

@api semipublic

# File lib/dm-redis-adapter/adapter.rb, line 18
def create(resources)
  storage_name = resources.first.model.storage_name
  resources.each do |resource|
    initialize_serial(resource, @redis.incr("#{storage_name}:#{redis_key_for(resource.model)}:serial"))
    @redis.sadd(key_set_for(resource.model), resource.key.join)
  end
  update_attributes(resources)
end
delete(collection) click to toggle source

Destroys all the records matching the given query. “DELETE” in SQL.

@param [DataMapper::Collection] collection

The query used to locate the resources to be deleted.

@return [Array]

An Array of Hashes containing the key-value pairs for
each record

@api semipublic

# File lib/dm-redis-adapter/adapter.rb, line 93
def delete(collection)
  collection.each do |record|
    @redis.del("#{collection.query.model.storage_name}:#{record[redis_key_for(collection.query.model)]}")
    @redis.srem(key_set_for(collection.query.model), record[redis_key_for(collection.query.model)])
    record.model.properties.select {|p| p.index}.each do |p|
      @redis.srem("#{collection.query.model.storage_name}:#{p.name}:#{encode(record[p.name])}", record[redis_key_for(collection.query.model)])
    end
  end
end
read(query) click to toggle source

Looks up one record or a collection of records from the data-store: “SELECT” in SQL.

@param [Query] query

The query to be used to seach for the resources

@return [Array]

An Array of Hashes containing the key-value pairs for
each record

@api semipublic

# File lib/dm-redis-adapter/adapter.rb, line 39
def read(query)
  storage_name = query.model.storage_name
  records = records_for(query)
  records.each do |record|
    record_data = @redis.hgetall("#{storage_name}:#{record[redis_key_for(query.model)]}")

    query.fields.each do |property|
      next if query.model.key.include?(property) and query.model.key.size == 1

      name = property.name.to_s
      value = record_data[name]

      # Integers are stored as Strings in Redis. If there's a
      # string coming out that should be an integer, convert it
      # now. All other typecasting is handled by datamapper
      # separately.
      record[name] = [Integer, Date].include?(property.primitive) ? property.typecast( value ) : value
      record
    end
  end
  query.filter_records(records)
end
update(attributes, collection) click to toggle source

Used by DataMapper to update the attributes on existing records in the redis data-store: “UPDATE” in SQL-speak. It takes a hash of the attributes to update with, as well as a collection object that specifies which resources should be updated.

@param [Hash] attributes

A set of key-value pairs of the attributes to update the resources with.

@param [DataMapper::Collection] collection

The collection object that should be used to find the resource(s) to update.

@api semipublic

# File lib/dm-redis-adapter/adapter.rb, line 74
def update(attributes, collection)
  attributes = attributes_as_fields(attributes)

  records_to_update = records_for(collection.query)
  records_to_update.each {|r| r.update(attributes)}
  update_attributes(collection)
end

Private Instance Methods

encode(value) click to toggle source

Base64 encode a value as a string key for an index

@return String

Base64 representation of a value

@api private

# File lib/dm-redis-adapter/adapter.rb, line 330
def encode(value)
  Base64.encode64(value.to_s).gsub("\n", "")
end
find_indexed_matches(subject, value) click to toggle source

Find a matching entry for a query

@return [Array]

Array of id's of all members for an indexed field

@api private

# File lib/dm-redis-adapter/adapter.rb, line 320
def find_indexed_matches(subject, value)
  @redis.smembers("#{subject.model.storage_name}:#{subject.name}:#{encode(value)}").map {|id| id =~ /^\d+$/ ? id.to_i : id}
end
find_subject_and_value(query, operand) click to toggle source
# File lib/dm-redis-adapter/adapter.rb, line 197
def find_subject_and_value(query, operand)
  if operand.is_a?(DataMapper::Query::Conditions::NotOperation)
    subject = operand.first.subject
    value = operand.first.value
  elsif operand.subject.is_a?(DataMapper::Associations::ManyToOne::Relationship)
    subject = operand.subject.child_key.first
    value = if operand.is_a?(DataMapper::Query::Conditions::InclusionComparison)
      operand.value.map{|v| v[operand.subject.parent_key.first.name]}
    else
      operand.value[operand.subject.parent_key.first.name]
    end
  else
    subject = operand.subject
    value = operand.value
  end

  if subject.is_a?(DataMapper::Associations::ManyToOne::Relationship)
    subject = subject.child_key.first
  end

  return subject, value
end
is_composed_key_based_query?(query) click to toggle source
# File lib/dm-redis-adapter/adapter.rb, line 142
def is_composed_key_based_query?(query)
  fields_length = query.model.key.size
  is_key = query.condition_properties.select { |field| field.key? }
  (fields_length >1 and fields_length == is_key.length)
end
key_query(query) click to toggle source
# File lib/dm-redis-adapter/adapter.rb, line 148
def key_query(query)
  matched_records = []
  value = ""
  storage_name = query.model.storage_name
  query.conditions.operands.each do |operand|
    value += operand.value.to_s if operand.subject.key?
  end

  if @redis.sismember(key_set_for(query.model), value)
    #matched_records <<  @redis.hgetall("#{storage_name}:#{value}")
    matched_records << {redis_key_for(query.model) => value}
  end
  matched_records
end
key_set_for(model) click to toggle source

Return the key string for the set that contains all keys for a particular resource

@return String

The string key for the :all set

@api private

# File lib/dm-redis-adapter/adapter.rb, line 310
def key_set_for(model)
  "#{model.storage_name}:#{redis_key_for(model)}:all"
end
perform_query(query, operand) click to toggle source

Find records that match have a matching value

@param [DataMapper::Query] query

The query used to locate the resources to be matched.

@param [DataMapper::Operation] the operation for the query

@api private

# File lib/dm-redis-adapter/adapter.rb, line 229
def perform_query(query, operand)
  storage_name = query.model.storage_name
  matched_records = []
  subject, value = find_subject_and_value(query, operand)

  case operand
    when DataMapper::Query::Conditions::NotOperation
      if query.model.key.include?(subject)
        @redis.smembers(key_set_for(query.model)).each do |key|
          if operand.matches?(subject.typecast(key))
            matched_records << {redis_key_for(query.model) => key}
          end
        end
      else
        search_all_resources(query, operand, subject, matched_records)
      end
    when DataMapper::Query::Conditions::InclusionComparison
      if query.model.key.include?(subject)
        value.each do |val|
          if @redis.sismember(key_set_for(query.model), val)
            matched_records << {redis_key_for(query.model) => val}
          end
        end
      elsif subject.respond_to?(:index) && subject.index
        value.each do |val|
          find_indexed_matches(subject, val).each do |k|
            matched_records << {redis_key_for(query.model) => k, "#{subject.name}" => val}
          end
        end
      else
        search_all_resources(query, operand, subject, matched_records)
      end
    when DataMapper::Query::Conditions::EqualToComparison
      if query.model.key.include?(subject) and @redis.sismember(key_set_for(query.model), value)
          matched_records << {redis_key_for(query.model) => value}

      elsif subject.respond_to?(:index) && subject.index
        find_indexed_matches(subject, value).each do |k|
          matched_records << {redis_key_for(query.model) => k, "#{subject.name}" => value}
        end
      else
        search_all_resources(query, operand, subject, matched_records)
      end
    else # worst case here, loop through all members, typecast and match
      search_all_resources(query, operand, subject, matched_records)
  end
  matched_records
end
records_for(query) click to toggle source

Retrieves records for a particular model.

@param [DataMapper::Query] query

The query used to locate the resources

@return [Array]

An array of hashes of all of the records for a particular model

@api private

# File lib/dm-redis-adapter/adapter.rb, line 173
def records_for(query)
  keys = []

  if query.conditions.nil?
    @redis.smembers(key_set_for(query.model)).each do |key|
      key = key.to_i if key =~ /^\d+$/
      keys << {redis_key_for(query.model) => key}
    end
  elsif is_composed_key_based_query?(query)
    keys = key_query(query)
  else
    query.conditions.operands.each do |operand|
      if operand.is_a?(DataMapper::Query::Conditions::OrOperation)
        operand.each do |op|
          keys = keys + perform_query(query, op)
        end
      else
        keys = perform_query(query, operand)
      end
    end
  end
  keys
end
redis_key_for(model) click to toggle source

Creates a string representation for the keys in a given model

@param [DataMapper::Model] model

The query used to locate the resources to be deleted.

@return [String]

A string representation of the string key for this model

@api private

# File lib/dm-redis-adapter/adapter.rb, line 300
def redis_key_for(model)
  model.key.collect {|k| k.name}.join(":")
end
search_all_resources(query, operand, subject, matched_records) click to toggle source

Searches through each key :(

@api private

# File lib/dm-redis-adapter/adapter.rb, line 282
def search_all_resources(query, operand, subject, matched_records)
  @redis.smembers(key_set_for(query.model)).each do |key|
    if operand.matches?(subject.typecast(@redis.hget("#{subject.model.storage_name}:#{key}", subject.name)))
      matched_records << {redis_key_for(query.model) => key}
    end
  end
end
update_attributes(resources) click to toggle source

Saves each resource to the redis data store

@param [Array] Resources

An array of resource to save

@api private

# File lib/dm-redis-adapter/adapter.rb, line 112
def update_attributes(resources)
  storage_name = resources.first.model.storage_name
  resources.each do |resource|

    model = resource.model
    attributes = resource.dirty_attributes

    resource.model.properties.select {|p| p.index}.each do |property|
      @redis.sadd("#{storage_name}:#{property.name}:#{encode(resource[property.name.to_s])}", resource.key.join.to_s)
    end

    properties_to_set = []
    properties_to_del = []

    fields = model.properties(self.name).select {|property| attributes.key?(property)}
    fields.each do |property|
      value = attributes[property]
      if value.nil?
        properties_to_del << property.name
      else
        properties_to_set << property.name << attributes[property]
      end
    end

    hash_key = "#{storage_name}:#{resource.key.join}"
    properties_to_del.each {|prop| @redis.hdel(hash_key, prop) }
    @redis.hmset(hash_key, *properties_to_set) unless properties_to_set.empty?
  end
end