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!