lib/ohm.rb in ohm-0.0.7 vs lib/ohm.rb in ohm-0.0.9
- old
+ new
@@ -1,71 +1,143 @@
+require "base64"
require File.join(File.dirname(__FILE__), "ohm", "redis")
require File.join(File.dirname(__FILE__), "ohm", "validations")
module Ohm
+
+ # Provides access to the redis database. This is shared accross all models and instances.
def redis
@redis
end
- def connect(*attrs)
- @redis = Ohm::Redis.new(*attrs)
+ # Connect to a redis database.
+ #
+ # @param options [Hash] options to create a message with.
+ # @option options [#to_s] :host ('127.0.0.1') Host of the redis database.
+ # @option options [#to_s] :port (6379) Port number.
+ # @option options [#to_s] :db (0) Database number.
+ # @option options [#to_s] :timeout (0) Database timeout in seconds.
+ # @example Connect to a database in port 6380.
+ # Ohm.connect(:port => 6380)
+ def connect(*options)
+ @redis = Ohm::Redis.new(*options)
end
+ # Clear the database.
def flush
@redis.flushdb
end
+ # Join the parameters with ":" to create a key.
def key(*args)
args.join(":")
end
module_function :key, :connect, :flush, :redis
module Attributes
- class Collection < Array
+ class Collection
+ include Enumerable
+
attr_accessor :key, :db
def initialize(db, key)
self.db = db
self.key = key
- super(retrieve)
end
+
+ def each(&block)
+ all.each(&block)
+ end
+
+ def all(model = nil)
+ model ? raw.collect { |id| model[id] } : raw
+ end
end
+ # Represents a Redis list.
+ #
+ # @example Use a list attribute.
+ #
+ # class Event < Ohm::Model
+ # attribute :name
+ # list :participants
+ # end
+ #
+ # event = Event.create :name => "Redis Meeting"
+ # event.participants << "Albert"
+ # event.participants << "Benoit"
+ # event.participants.all #=> ["Albert", "Benoit"]
class List < Collection
- def retrieve
- db.list(key)
- end
+ # @param value [#to_s] Pushes value to the list.
def << value
- super(value) if db.rpush(key, value)
+ db.rpush(key, value)
end
+
+ private
+
+ def raw
+ db.list(key)
+ end
end
+ # Represents a Redis set.
+ #
+ # @example Use a set attribute.
+ #
+ # class Company < Ohm::Model
+ # attribute :name
+ # set :employees
+ # end
+ #
+ # company = Company.create :name => "Redis Co."
+ # company.employees << "Albert"
+ # company.employees << "Benoit"
+ # company.employees.all #=> ["Albert", "Benoit"]
+ # company.include?("Albert") #=> true
class Set < Collection
- def retrieve
- db.smembers(key).sort
- end
+ # @param value [#to_s] Adds value to the list.
def << value
- super(value) if db.sadd(key, value)
+ db.sadd(key, value)
end
+ # @param value [Ohm::Model#id] Adds the id of the object if it's an Ohm::Model.
+ def add model
+ raise ArgumentError unless model.kind_of?(Ohm::Model)
+ raise ArgumentError unless model.id
+ self << model.id
+ end
+
def delete(value)
- super(value) if db.srem(key, value)
+ db.srem(key, value)
end
def include?(value)
db.sismember(key, value)
end
+
+ private
+
+ def raw
+ db.smembers(key).sort
+ end
end
end
class Model
module Validations
include Ohm::Validations
+ # Validates that the attribute or array of attributes are unique. For this,
+ # an index of the same kind must exist.
+ #
+ # @overload assert_unique :name
+ # Validates that the name attribute is unique.
+ # @overload assert_unique [:street, :city]
+ # Validates that the :street and :city pair is unique.
def assert_unique(attrs)
index_key = index_key_for(Array(attrs), read_locals(Array(attrs)))
assert(db.scard(index_key).zero? || db.sismember(index_key, id), [Array(attrs), :not_unique])
end
end
@@ -74,14 +146,19 @@
ModelIsNew = Class.new(StandardError)
@@attributes = Hash.new { |hash, key| hash[key] = [] }
@@collections = Hash.new { |hash, key| hash[key] = [] }
+ @@counters = Hash.new { |hash, key| hash[key] = [] }
@@indices = Hash.new { |hash, key| hash[key] = [] }
attr_accessor :id
+ # Defines a string attribute for the model. This attribute will be persisted by Redis
+ # as a string. Any value stored here will be retrieved in its string representation.
+ #
+ # @param name [Symbol] Name of the attribute.
def self.attribute(name)
define_method(name) do
read_local(name)
end
@@ -90,20 +167,62 @@
end
attributes << name
end
+ # Defines a counter attribute for the model. This attribute can't be assigned, only incremented
+ # or decremented. It will be zero by default.
+ #
+ # @param name [Symbol] Name of the counter.
+ def self.counter(name)
+ define_method(name) do
+ read_local(name).to_i
+ end
+
+ counters << name
+ end
+
+ # Defines a list attribute for the model. It can be accessed only after the model instance
+ # is created.
+ #
+ # @param name [Symbol] Name of the list.
def self.list(name)
attr_list_reader(name)
collections << name
end
+ # Defines a set attribute for the model. It can be accessed only after the model instance
+ # is created. Sets are recommended when insertion and retrival order is irrelevant, and
+ # operations like union, join, and membership checks are important.
+ #
+ # @param name [Symbol] Name of the set.
def self.set(name)
attr_set_reader(name)
collections << name
end
+ # Creates an index (a set) that will be used for finding instances.
+ #
+ # If you want to find a model instance by some attribute value, then an index for that
+ # attribute must exist.
+ #
+ # Each index declaration creates an index. It can be either an index on one particular attribute,
+ # or an index accross many attributes.
+ #
+ # @example
+ # class User < Ohm::Model
+ # attribute :email
+ # index :email
+ # end
+ #
+ # # Now this is possible:
+ # User.find :email, "ohm@example.com"
+ #
+ # @overload index :name
+ # Creates an index for the name attribute.
+ # @overload index [:street, :city]
+ # Creates a composite index for street and city.
def self.index(attrs)
indices << Array(attrs)
end
def self.attr_list_reader(name)
@@ -132,26 +251,42 @@
def self.attributes
@@attributes[self]
end
+ def self.counters
+ @@counters[self]
+ end
+
def self.collections
@@collections[self]
end
def self.indices
@@indices[self]
end
def self.create(*args)
- new(*args).create
+ model = new(*args)
+ model.create
+ model
end
def self.find(attribute, value)
- filter(Ohm.key(attribute, value))
+ filter(Ohm.key(attribute, encode(value)))
end
+ def self.encode(value)
+ Base64.encode64(value.to_s).chomp
+ end
+
+ def self.encode_each(values)
+ values.collect do |value|
+ encode(value)
+ end
+ end
+
def initialize(attrs = {})
@_attributes = Hash.new {|hash,key| hash[key] = read_remote(key) }
attrs.each do |key, value|
send(:"#{key}=", value)
@@ -177,20 +312,41 @@
save!
end
def delete
delete_from_indices
- delete_attributes(collections)
delete_attributes(attributes)
+ delete_attributes(counters)
+ delete_attributes(collections)
delete_model_membership
self
end
+ # Increment the attribute denoted by :att.
+ #
+ # @param att [Symbol] Attribute to increment.
+ def incr(att)
+ raise ArgumentError unless counters.include?(att)
+ write_local(att, db.incr(key(att)))
+ end
+
+ # Decrement the attribute denoted by :att.
+ #
+ # @param att [Symbol] Attribute to decrement.
+ def decr(att)
+ raise ArgumentError unless counters.include?(att)
+ write_local(att, db.decr(key(att)))
+ end
+
def attributes
self.class.attributes
end
+ def counters
+ self.class.counters
+ end
+
def collections
self.class.collections
end
def indices
@@ -301,9 +457,9 @@
read_remote(att)
end
end
def index_key_for(attrs, values)
- self.class.key *(attrs + values)
+ self.class.key *(attrs + self.class.encode_each(values))
end
end
end