Skip to content

Redis Model

Here is a simple class I have been using more and more recently. It is a very simple Redis-backed Model implementation.
The class itself is minimal, only facilitating CRUD operations and automatic timestamp management.

The class assumes you have a Redis connection initialized in $redis.

require 'time'
require 'json'

# A basic Redis-backed Model implementation.
# Extend it by specifying your desired attr_accessors
# Enable timestamps by calling use_timestamps in your implementation
# Features auto-incremented ids and id namespacing
# Example:
#   class Zorro < RedisModel
#     attr_accessor :sword, :mask
#     use_timestamps
#   end
#   
#   z = Zorro.new(:sword => 'rapier', :mask => 'lame')
#   z.persist!
#   z
#   => #<Zorro @id="1", @sword="rapier", @mask="lame", @updated_at=2013-03-14 18:11:17 +0100, @created_at=2013-03-14 18:11:17 +0100>
#
class RedisModel
  # the only default attribute
  attr_accessor :id
  
  def initialize params = {}
    params.map do |k,v| 
      begin
        send(:"#{k}=",JSON.parse(v))
      rescue JSON::ParserError
        send(:"#{k}=",v)
      end
    end
    
    # typecast some standard attributes
    self.id = self.id.to_i if self.id
    if use_timestamps?
      self.created_at = Time.parse(self.created_at) if self.created_at.is_a?(String)
      self.updated_at = Time.parse(self.updated_at) if self.updated_at.is_a?(String)
    end
  end
  
  def persist!
    is_new = id.nil? or !self.class.exists?(id)
    
    if use_timestamps?
      self.updated_at = Time.now
      self.created_at = Time.now if is_new
    end
    
    self.id = generate_id if is_new
    
    $redis.hmset(namespaced_id, *self.to_array)
    return true
  end
  alias :save! :persist!
  
  def persist
    persist!
    self
  end
  alias :save :persist
  
  def destroy!
    $redis.del(namespaced_id)
  end
  
  def reload
    self.class.load(namespaced_id)
  end
  
  def to_hash
    instance_variables.inject({}) do |h,v|
      value = instance_variable_get(v)
      value = value.is_a?(String) ? value : value.to_json
      h.update v.to_s.gsub('@','') => value
    end
  end
  
  def use_timestamps?
    self.class.instance_variable_get(:@use_timestamps)
  end
  
  def to_array
    to_hash.to_a.flatten
  end
  
  def namespaced_id
    self.class.namespaced_id(self.id)
  end
  
  # useful for testing, but also a general convenience
  include Comparable
  def <=>(other)
    self.to_hash <=> other.to_hash
  end
  
  class << self
    @use_timestamps = false
    
    def namespaced_id(id)
      "#{self}::#{id}"
    end
    
    def all
      $redis.namespace(self)
    end
    
    def exists? id
      $redis.exists(namespaced_id(id))
    end
    
    def load id
      hash = $redis.hgetall(namespaced_id(id))
      hash == {} ? nil : new(hash)
    end
    alias :find :load
    
    def use_timestamps
      @use_timestamps = true
      self.send(:attr_accessor, :created_at)
      self.send(:attr_accessor, :updated_at)
    end
  end
  
protected
  def generate_id
    $redis.incr("_#{self.class}_current_id")
  end
end

Categories: Code.

Tags: ,

Comment Feed

No Responses (yet)



Some HTML is OK

or, reply to this post via trackback.