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.