lib/ohm.rb in ohm-0.0.18 vs lib/ohm.rb in ohm-0.0.19

- old
+ new

@@ -79,11 +79,12 @@ # @blog.posts.sort(Post, :by => :votes, :start => 5, :limit => 10") def sort(options = {}) return [] if empty? options[:start] ||= 0 options[:limit] = [options[:start], options[:limit]] if options[:limit] - instantiate(db.sort(key, options)) + result = db.sort(key, options) + options[:get] ? result : instantiate(result) end # Sort the model instances by the given attribute. # # @example Sorting elements by name: @@ -220,10 +221,61 @@ # @return [Integer] Returns the number of elements in the set. def size db.scard(key) end + + # Returns an intersection with the sets generated from the passed hash. + # + # @see Ohm::Model.filter + # @yield [results] Results of the filtering. Beware that the set of results is deleted from Redis when the block ends. + # @example + # Event.search(day: "2009-09-11") do |search_results| + # events = search_results.all + # end + def filter(hash, &block) + apply(:sinterstore, keys(hash).push(key), &block) + end + + # Returns a union with the sets generated from the passed hash. + # + # @see Ohm::Model.search + # @yield [results] Results of the search. Beware that the set of results is deleted from Redis when the block ends. + # @example + # Event.search(day: "2009-09-11") do |search_results| + # search_results.filter(public: true) do |filter_results| + # events = filter_results.all + # end + # end + def search(hash, &block) + apply(:sunionstore, keys(hash), &block) + end + + def delete! + db.del(key) + end + + # Apply a redis operation on a collection of sets. Note that + # the resulting set is removed inmediatly after use. + def apply(operation, source, &block) + target = source.join(operation) + db.send(operation, target, *source) + set = self.class.new(db, target, model) + block.call(set) + set.delete! + end + + private + + # Transform a hash of attribute/values into an array of keys. + def keys(hash) + hash.inject([]) do |acc, t| + acc + Array(t[1]).map do |v| + model.key(t[0], model.encode(v)) + end + end + end end end class Model module Validations @@ -235,12 +287,12 @@ # @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]) + result = db.sinter(*Array(attrs).map { |att| index_key_for(att, send(att)) }) + assert(result.empty? || result.include?(id.to_s), [attrs, :not_unique]) end end include Validations @@ -303,28 +355,22 @@ # 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) + # @param name [Symbol] Name of the attribute to be indexed. + def self.index(att) + indices << att end def self.attr_list_reader(name, model = nil) define_method(name) do instance_variable_get("@#{name}") || @@ -367,24 +413,48 @@ model = new(*args) model.create model end - def self.find(attribute, value) - Attributes::Set.new(db, key(attribute, encode(value)), self) + # Find all the records matching the specified attribute-value pair. + # + # @example + # Event.find(:starts_on, Date.today) + def self.find(attrs, value) + Attributes::Set.new(db, key(attrs, encode(value)), self) end - def self.encode(value) - Base64.encode64(value.to_s).chomp + # Search across multiple indices and return the intersection of the sets. + # + # @example Finds all the user events for the supplied days + # event1 = Event.create day: "2009-09-09", author: "Albert" + # event2 = Event.create day: "2009-09-09", author: "Benoit" + # event3 = Event.create day: "2009-09-10", author: "Albert" + # Event.filter(author: "Albert", day: "2009-09-09") do |events| + # assert_equal [event1], events + # end + def self.filter(hash, &block) + self.all.filter(hash, &block) end - def self.encode_each(values) - values.collect do |value| - encode(value) - end + # Search across multiple indices and return the union of the sets. + # + # @example Finds all the events for the supplied days + # event1 = Event.create day: "2009-09-09" + # event2 = Event.create day: "2009-09-10" + # event3 = Event.create day: "2009-09-11" + # Event.search(day: ["2009-09-09", "2009-09-10", "2009-09-011"]) do |events| + # assert_equal [event1, event2, event3], events + # end + def self.search(hash, &block) + self.all.search(hash, &block) end + def self.encode(value) + Base64.encode64(value.to_s).gsub("\n", "") + end + def initialize(attrs = {}) @_attributes = Hash.new {|hash,key| hash[key] = read_remote(key) } update_attributes(attrs) end @@ -531,18 +601,34 @@ delete_from_indices add_to_indices end def add_to_indices - indices.each do |attrs| - db.sadd(index_key_for(attrs, read_locals(attrs)), id) + indices.each do |att| + next add_to_index(att) unless collection?(send(att)) + send(att).each { |value| add_to_index(att, value) } end end + def collection?(value) + self.class.collection?(value) + end + + def self.collection?(value) + value.kind_of?(Enumerable) && + value.kind_of?(String) == false + end + + def add_to_index(att, value = send(att)) + index = index_key_for(att, value) + db.sadd(index, id) + db.sadd(key(:_indices), index) + end + def delete_from_indices - indices.each do |attrs| - db.srem(index_key_for(attrs, read_remotes(attrs)), id) + db.smembers(key(:_indices)).each do |index| + db.srem(index, id) end end def read_local(att) @_attributes[att] @@ -570,11 +656,11 @@ attrs.map do |att| read_remote(att) end end - def index_key_for(attrs, values) - self.class.key *(attrs + self.class.encode_each(values)) + def index_key_for(att, value) + self.class.key(att, self.class.encode(value)) end # Lock the object so no other instances can modify it. # @see Model#mutex def lock!