lib/ohm.rb in ohm-0.0.34 vs lib/ohm.rb in ohm-0.0.35

- old
+ new

@@ -1,16 +1,17 @@ # encoding: UTF-8 require "base64" -require File.join(File.dirname(__FILE__), "ohm", "redis") +require "redis" + require File.join(File.dirname(__FILE__), "ohm", "validations") require File.join(File.dirname(__FILE__), "ohm", "compat-1.8.6") require File.join(File.dirname(__FILE__), "ohm", "key") require File.join(File.dirname(__FILE__), "ohm", "collection") module Ohm - VERSION = "0.0.34" + VERSION = "0.0.35" # Provides access to the Redis database. This is shared accross all models and instances. def redis threaded[:redis] ||= connection(*options) end @@ -37,13 +38,13 @@ @options = options end # Return a connection to Redis. # - # This is a wapper around Ohm::Redis.new(options) + # This is a wapper around Redis.new(options) def connection(*options) - Ohm::Redis.new(*options) + Redis.new(*options) end def options @options || [] end @@ -51,29 +52,60 @@ # Clear the database. def flush redis.flushdb end - # Join the parameters with ":" to create a key. def key(*args) Key[*args] end module_function :key, :connect, :connection, :flush, :redis, :redis=, :options, :threaded Error = Class.new(StandardError) class Model + + # Wraps a model name for lazy evaluation. + class Wrapper < BasicObject + def initialize(name, &block) + @name = name + @caller = ::Kernel.caller[2] + @block = block + + class << self + def method_missing(method_id, *args) + ::Kernel.raise ::NoMethodError, "You tried to call #{@name}##{method_id}, but #{@name} is not defined on #{@caller}" + end + end + end + + def self.wrap(object) + object.class == self ? object : new(object.inspect) { object } + end + + def unwrap + @block.call + end + + def class + Wrapper + end + + def inspect + "<Wrapper for #{@name} (in #{@caller})>" + end + end + class Collection include Enumerable attr :raw attr :model - def initialize(key, model, db = model.db) - @raw = self.class::Raw.new(key, db) - @model = model + def initialize(key, model, db = nil) + @model = model.unwrap + @raw = self.class::Raw.new(key, db || @model.db) end def <<(model) raw << model.id end @@ -195,11 +227,11 @@ # Apply a redis operation on a collection of sets. def apply(operation, hash, glue) target = key.volatile.group(glue).append(*keys(hash)) model.db.send(operation, target, *target.sub_keys) - Set.new(target, model) + Set.new(target, Wrapper.wrap(model)) end # Transform a hash of attribute/values into an array of keys. def keys(hash) [].tap do |keys| @@ -238,11 +270,11 @@ end class Index < Set def apply(operation, hash, glue) if hash.keys.size == 1 - return Set.new(keys(hash).first, model) + return Set.new(keys(hash).first, Wrapper.wrap(model)) else super end end end @@ -388,18 +420,20 @@ # @comment.post # # => nil # # @see Ohm::Model::collection def self.reference(name, model) + model = Wrapper.wrap(model) + reader = :"#{name}_id" writer = :"#{name}_id=" attribute reader index reader define_memoized_method(name) do - model[send(reader)] + model.unwrap[send(reader)] end define_method(:"#{name}=") do |value| instance_variable_set("@#{name}", nil) send(writer, value ? value.id : nil) @@ -449,19 +483,21 @@ # @see Ohm::Model::reference # @param name [Symbol] Name of the collection. # @param model [Constant] Model where the reference is defined. # @param reference [Symbol] Reference as defined in the associated model. def self.collection(name, model, reference = to_reference) - define_method(name) { model.find(:"#{reference}_id" => send(:id)) } + model = Wrapper.wrap(model) + define_method(name) { model.unwrap.find(:"#{reference}_id" => send(:id)) } end def self.to_reference name.to_s.gsub(/([a-z\d])([A-Z])/, '\1_\2').downcase.to_sym end def self.attr_collection_reader(name, type, model) if model + model = Wrapper.wrap(model) define_memoized_method(name) { Ohm::Model::const_get(type).new(key(name), model, db) } else define_memoized_method(name) { Ohm::const_get(type).new(key(name), db) } end end @@ -480,11 +516,11 @@ def self.to_proc Proc.new { |id| self[id] } end def self.all - @all ||= Ohm::Model::Index.new(key(:all), self) + @all ||= Ohm::Model::Index.new(key(:all), Wrapper.wrap(self)) end def self.attributes @@attributes[self] end @@ -657,39 +693,24 @@ def key(*args) self.class.key(id, *args) end - # Use MSET if possible, SET otherwise. - def write - db.support_mset? ? - write_with_mset : - write_with_set - end - - # Write attributes using SET - # This method will be removed once MSET becomes standard. - def write_with_set - attributes.each do |att| - value = send(att) - value.to_s.empty? ? - db.set(key(att), value) : - db.del(key(att)) - end - end - # Write attributes using MSET - # This is the preferred method, and will be the only option - # available once MSET becomes standard. - def write_with_mset + def write unless attributes.empty? rems, adds = attributes.map { |a| [key(a), send(a)] }.partition { |t| t.last.to_s.empty? } + db.del(*rems.flatten.compact) unless rems.empty? - db.mset(adds.flatten) unless adds.empty? + db.mapped_mset(adds.flatten) unless adds.empty? end end + def self.const_missing(name) + Wrapper.new(name) { const_get(name) } + end + private # Provides access to the Redis database. This is shared accross all models and instances. def self.db Ohm.threaded[self] || Ohm.redis @@ -757,10 +778,11 @@ end def delete_from_indices db.smembers(key(:_indices)).each do |index| db.srem(index, id) + db.srem(key(:_indices), index) end end def read_local(att) @_attributes[att] @@ -769,10 +791,15 @@ def write_local(att, value) @_attributes[att] = value end def read_remote(att) - db.get(key(att)) unless new? + unless new? + value = db.get(key(att)) + value.respond_to?(:force_encoding) ? + value.force_encoding("UTF-8") : + value + end end def read_locals(attrs) attrs.map do |att| send(att)