lib/ohm.rb in ohm-1.0.0.rc3 vs lib/ohm.rb in ohm-1.0.0.rc4
- old
+ new
@@ -421,14 +421,13 @@
#
# set = User.find(:name => "John")
# set.find(:age => 30)
#
def find(dict)
- keys = model.filters(dict)
- keys.push(key)
+ filters = model.filters(dict).push(key)
- MultiSet.new(keys, namespace, model)
+ MultiSet.new(namespace, model).append(:sinterstore, filters)
end
# Reduce the set using any number of filters.
#
# Example:
@@ -438,11 +437,11 @@
#
# # You can also do it in one line.
# User.find(:name => "John").except(:country => "US")
#
def except(dict)
- MultiSet.new([key], namespace, model).except(dict)
+ MultiSet.new(namespace, model).append(:sinterstore, key).except(dict)
end
# Do a union to the existing set using any number of filters.
#
# Example:
@@ -452,11 +451,11 @@
#
# # You can also do it in one line.
# User.find(:name => "John").union(:name => "Jane")
#
def union(dict)
- MultiSet.new([key], namespace, model).union(dict)
+ MultiSet.new(namespace, model).append(:sinterstore, key).union(dict)
end
private
def execute
yield key
@@ -514,11 +513,10 @@
ids.each { |id| key.sadd(id) }
end
end
end
-
# Anytime you filter a set with more than one requirement, you
# internally use a `MultiSet`. `MutiSet` is a bit slower than just
# a `Set` because it has to `SINTERSTORE` all the keys prior to
# retrieving the members, size, etc.
#
@@ -531,25 +529,30 @@
# # => true
#
# User.find(:name => "John", :age => 30).kind_of?(Ohm::MultiSet)
# # => true
#
- class MultiSet < Struct.new(:keys, :namespace, :model)
+ class MultiSet < Struct.new(:namespace, :model)
include Collection
+ def append(operation, list)
+ filters.push([operation, list])
+
+ return self
+ end
+
# Chain new fiters on an existing set.
#
# Example:
#
# set = User.find(:name => "John", :age => 30)
# set.find(:status => 'pending')
#
def find(dict)
- keys = model.filters(dict)
- keys.push(*self.keys)
+ filters.push([:sinterstore, model.filters(dict)])
- MultiSet.new(keys, namespace, model)
+ return self
end
# Reduce the set using any number of filters.
#
# Example:
@@ -559,11 +562,11 @@
#
# # You can also do it in one line.
# User.find(:name => "John").except(:country => "US")
#
def except(dict)
- sdiff.push(*model.filters(dict)).uniq!
+ filters.push([:sdiffstore, model.filters(dict)])
return self
end
# Do a union to the existing set using any number of filters.
@@ -575,34 +578,75 @@
#
# # You can also do it in one line.
# User.find(:name => "John").union(:name => "Jane")
#
def union(dict)
- sunion.push(*model.filters(dict)).uniq!
+ filters.push([:sunionstore, model.filters(dict)])
return self
end
private
- def sunion
- @sunion ||= []
+ def filters
+ @filters ||= []
end
- def sdiff
- @sdiff ||= []
+ def temp_keys
+ @temp_keys ||= []
end
- def execute
+ def clean_temp_keys
+ model.db.del(*temp_keys)
+ temp_keys.clear
+ end
+
+ def generate_temp_key
key = namespace[:temp][SecureRandom.hex(32)]
- key.sinterstore(*keys)
- key.sdiffstore(key, *sdiff) if sdiff.any?
- key.sunionstore(key, *sunion) if sunion.any?
+ temp_keys << key
+ key
+ end
+ def execute
+
+ # Hold the final result key for this MultiSet.
+ main = nil
+
+ filters.each do |operation, list|
+
+ # Operation can be sinterstore, sdiffstore, or sunionstore.
+ # each operation we do, i.e. `.union(...)`, will be considered
+ # one intersected set, hence we need to `sinterstore` all
+ # the filters in a temporary set.
+ temp = generate_temp_key
+ temp.sinterstore(*list)
+
+ # If this is the first set, we simply assign the generated
+ # set to main, which could possibly be the return value
+ # for simple filters like one `.find(...)`.
+ if main.nil?
+ main = temp
+ else
+
+ # Append the generated temporary set using the operation.
+ # i.e. if we have (mood=happy & book=1) and we have an
+ # `sunionstore`, we do (mood=happy & book=1) | (mood=sad & book=1)
+ main.send(operation, main, temp)
+ end
+ end
+
begin
- yield key
+
+ # At this point, we have the final aggregated set, which we yield
+ # to the caller. the caller can do all the normal set operations,
+ # i.e. SCARD, SMEMBERS, etc.
+ yield main
+
ensure
- key.del
+
+ # We have to make sure we clean up the temporary keys to avoid
+ # memory leaks and the unintended explosion of memory usage.
+ clean_temp_keys
end
end
end
# The base class for all your models. In order to better understand
@@ -787,10 +831,10 @@
keys = filters(dict)
if keys.size == 1
Ohm::Set.new(keys.first, key, self)
else
- Ohm::MultiSet.new(keys, key, self)
+ Ohm::MultiSet.new(key, self).append(:sinterstore, keys)
end
end
# Index any method on your model. Once you index a method, you can
# use it in `find` statements.