lib/sohm.rb in sohm-0.9.0 vs lib/sohm.rb in sohm-0.10.0

- old
+ new

@@ -38,10 +38,11 @@ # class Error < StandardError; end class MissingID < Error; end class IndexNotFound < Error; end class CasViolation < Error; end + class NotSupported < Error; end # Instead of monkey patching Kernel or trying to be clever, it's # best to confine all the helper methods in a Utils module. module Utils @@ -414,65 +415,10 @@ @key = key @namespace = namespace @model = model end - # Chain new fiters on an existing set. - # - # Example: - # - # set = User.find(:name => "John") - # set.find(:age => 30) - # - def find(dict) - MultiSet.new( - namespace, model, Command[:sinterstore, key, *model.filters(dict)] - ) - end - - # Reduce the set using any number of filters. - # - # Example: - # - # set = User.find(:name => "John") - # set.except(:country => "US") - # - # # You can also do it in one line. - # User.find(:name => "John").except(:country => "US") - # - def except(dict) - MultiSet.new(namespace, model, key).except(dict) - end - - # Perform an intersection between the existent set and - # the new set created by the union of the passed filters. - # - # Example: - # - # set = User.find(:status => "active") - # set.combine(:name => ["John", "Jane"]) - # - # # The result will include all users with active status - # # and with names "John" or "Jane". - def combine(dict) - MultiSet.new(namespace, model, key).combine(dict) - end - - # Do a union to the existing set using any number of filters. - # - # Example: - # - # set = User.find(:name => "John") - # set.union(:name => "Jane") - # - # # You can also do it in one line. - # User.find(:name => "John").union(:name => "Jane") - # - def union(dict) - MultiSet.new(namespace, model, key).union(dict) - end - private def execute yield key end @@ -509,132 +455,10 @@ def delete(model) redis.call("SREM", key, model.id) end end - # Anytime you filter a set with more than one requirement, you - # internally use a `MultiSet`. `MultiSet` is a bit slower than just - # a `Set` because it has to `SINTERSTORE` all the keys prior to - # retrieving the members, size, etc. - # - # Example: - # - # User.all.kind_of?(Sohm::Set) - # # => true - # - # User.find(:name => "John").kind_of?(Sohm::Set) - # # => true - # - # User.find(:name => "John", :age => 30).kind_of?(Sohm::MultiSet) - # # => true - # - class MultiSet < BasicSet - attr :namespace - attr :model - attr :command - - def initialize(namespace, model, command) - @namespace = namespace - @model = model - @command = command - end - - # Chain new fiters on an existing set. - # - # Example: - # - # set = User.find(:name => "John", :age => 30) - # set.find(:status => 'pending') - # - def find(dict) - MultiSet.new( - namespace, model, Command[:sinterstore, command, intersected(dict)] - ) - end - - # Reduce the set using any number of filters. - # - # Example: - # - # set = User.find(:name => "John") - # set.except(:country => "US") - # - # # You can also do it in one line. - # User.find(:name => "John").except(:country => "US") - # - def except(dict) - MultiSet.new( - namespace, model, Command[:sdiffstore, command, unioned(dict)] - ) - end - - # Perform an intersection between the existent set and - # the new set created by the union of the passed filters. - # - # Example: - # - # set = User.find(:status => "active") - # set.combine(:name => ["John", "Jane"]) - # - # # The result will include all users with active status - # # and with names "John" or "Jane". - def combine(dict) - MultiSet.new( - namespace, model, Command[:sinterstore, command, unioned(dict)] - ) - end - - # Do a union to the existing set using any number of filters. - # - # Example: - # - # set = User.find(:name => "John") - # set.union(:name => "Jane") - # - # # You can also do it in one line. - # User.find(:name => "John").union(:name => "Jane") - # - def union(dict) - MultiSet.new( - namespace, model, Command[:sunionstore, command, intersected(dict)] - ) - end - - private - def redis - model.redis - end - - def intersected(dict) - Command[:sinterstore, *model.filters(dict)] - end - - def unioned(dict) - Command[:sunionstore, *model.filters(dict)] - end - - def execute - # namespace[:tmp] is where all the temp keys should be stored in. - # redis will be where all the commands are executed against. - response = command.call(namespace[:tmp], redis) - - begin - - # 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 response - - ensure - - # We have to make sure we clean up the temporary keys to avoid - # memory leaks and the unintended explosion of memory usage. - command.clean - end - end - end - # The base class for all your models. In order to better understand # it, here is a semi-realtime explanation of the details involved # when creating a User instance. # # Example: @@ -653,37 +477,10 @@ # # u = User.create(:name => "John", :email => "foo@bar.com") # u.incr :points # u.posts.add(Post.create) # - # When you execute `User.create(...)`, you run the following Redis - # commands: - # - # # Generate an ID - # INCR User:id - # - # # Add the newly generated ID, (let's assume the ID is 1). - # SADD User:all 1 - # - # # Store the unique index - # HSET User:uniques:email foo@bar.com 1 - # - # # Store the name index - # SADD User:indices:name:John 1 - # - # # Store the HASH - # HMSET User:1 name John email foo@bar.com - # - # Next we increment points: - # - # HINCR User:1:_counters points 1 - # - # And then we add a Post to the `posts` set. - # (For brevity, let's assume the Post created has an ID of 1). - # - # SADD User:1:posts 1 - # class Model def self.redis=(redis) @redis = redis end @@ -786,20 +583,20 @@ # # => true # # User.find(:tag => "python").include?(u) # # => true # - # User.find(:tag => ["ruby", "python"]).include?(u) - # # => true - # + # Due to restrictions in Codis, we only support single-index query. + # If you want to query based on multiple fields, you can make an + # index based on all the fields. def self.find(dict) keys = filters(dict) if keys.size == 1 Sohm::Set.new(keys.first, key, self) else - Sohm::MultiSet.new(key, self, Command.new(:sinterstore, *keys)) + raise NotSupported end end # Retrieve a set of models given an array of IDs. # @@ -1099,21 +896,13 @@ end # Access the ID used to store this model. The ID is used together # with the name of the class in order to form the Redis key. # - # Example: - # - # class User < Sohm::Model; end - # - # u = User.create - # u.id - # # => 1 - # - # u.key - # # => User:1 - # + # Different from ohm, id must be provided by the user in sohm, + # if you want to use auto-generated id, you can include Sohm::AutoId + # module. def id raise MissingID if not defined?(@id) @id end @@ -1203,9 +992,21 @@ @attributes end def serial_attributes @serial_attributes + end + + def counters + hash = {} + self.class.counters.each do |name| + hash[name] = 0 + end + return hash if new? + redis.call("HGETALL", key[:_counters]).each_slice(2).each do |pair| + hash[pair[0].to_sym] = pair[1].to_i + end + hash end # Export the ID of the model. The approach of Ohm is to # whitelist public attributes, as opposed to exporting each # (possibly sensitive) attribute.