lib/ohm.rb in ohm-1.0.0.alpha2 vs lib/ohm.rb in ohm-1.0.0.rc1

- old
+ new

@@ -265,50 +265,150 @@ execute { |key| key.sismember(id) } end def fetch(ids) arr = model.db.pipelined do - ids.each { |id| namespace[id].hgetall } + ids.each { |id| model.db.hgetall(namespace[id]) } end return [] if arr.nil? arr.map.with_index do |atts, idx| - model.new(atts.update(id: ids[idx])) + model.new(Hash[*atts].update(id: ids[idx])) end end end - class Set < Struct.new(:key, :namespace, :model) - include Collection + class List < Struct.new(:key, :namespace, :model) + include Enumerable - # Add a model directly to the set. + # Returns the total size of the list using LLEN. + def size + key.llen + end + + # Returns the first element of the list using LINDEX. + def first + model[key.lindex(0)] + end + + # Returns the last element of the list using LINDEX. + def last + model[key.lindex(-1)] + end + + # Checks if the model is part of this List. # + # An important thing to note is that this method loads all of the + # elements of the List since there is no command in Redis that + # allows you to actually check the list contents efficiently. + # + # You may want to avoid doing this if your list has say, 10K entries. + def include?(model) + ids.include?(model.id.to_s) + end + + # Replace all the existing elements of a list with a different + # collection of models. This happens atomically in a MULTI-EXEC + # block. + # # Example: # # user = User.create - # post = Post.create + # p1 = Post.create + # user.posts.push(p1) # - # user.posts.add(post) + # p2, p3 = Post.create, Post.create + # user.posts.replace([p2, p3]) # - def add(model) - key.sadd(model.id) + # user.posts.include?(p1) + # # => false + # + def replace(models) + ids = models.map { |model| model.id } + + model.db.multi do + key.del + ids.each { |id| key.rpush(id) } + end end - # Remove a model directly from the set. + # Fetch the data from Redis in one go. + def to_a + fetch(ids) + end + + def each + to_a.each { |element| yield element } + end + + def empty? + size == 0 + end + + # Pushes the model to the _end_ of the list using RPUSH. + def push(model) + key.rpush(model.id) + end + + # Pushes the model to the _beginning_ of the list using LPUSH. + def unshift(model) + key.lpush(model.id) + end + + # Delete a model from the list. # + # Note: If your list contains the model multiple times, this method + # will delete all instances of that model in one go. + # # Example: # - # user = User.create - # post = Post.create + # class Comment < Ohm::Model + # end # - # user.posts.delete(post) + # class Post < Ohm::Model + # list :comments, Comment + # end # + # p = Post.create + # c = Comment.create + # + # p.comments.push(c) + # p.comments.push(c) + # + # p.comments.delete(c) + # + # p.comments.size == 0 + # # => true + # def delete(model) - key.srem(model.id) + # LREM key 0 <id> means remove all elements matching <id> + # @see http://redis.io/commands/lrem + key.lrem(0, model.id) end + private + def ids + key.lrange(0, -1) + end + + def fetch(ids) + arr = model.db.pipelined do + ids.each { |id| model.db.hgetall(namespace[id]) } + end + + return [] if arr.nil? + + arr.map.with_index do |atts, idx| + model.new(Hash[*atts].update(id: ids[idx])) + end + end + end + + class Set < Struct.new(:key, :namespace, :model) + include Collection + # Chain new fiters on an existing set. # # Example: # # set = User.find(name: "John") @@ -347,10 +447,43 @@ # def union(dict) MultiSet.new([key], namespace, model).union(dict) end + private + def execute + yield key + end + end + + class MutableSet < Set + # Add a model directly to the set. + # + # Example: + # + # user = User.create + # post = Post.create + # + # user.posts.add(post) + # + def add(model) + key.sadd(model.id) + end + + # Remove a model directly from the set. + # + # Example: + # + # user = User.create + # post = Post.create + # + # user.posts.delete(post) + # + def delete(model) + key.srem(model.id) + end + # Replace all the existing elements of a set with a different # collection of models. This happens atomically in a MULTI-EXEC # block. # # Example: @@ -371,17 +504,13 @@ key.redis.multi do key.del ids.each { |id| key.sadd(id) } end end - - private - def execute - yield key - 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. # @@ -691,14 +820,44 @@ collections << name unless collections.include?(name) define_method name do model = Utils.const(self.class, model) - Ohm::Set.new(key[name], model.key, model) + Ohm::MutableSet.new(key[name], model.key, model) end end + # Declare an Ohm::List with the given name. + # + # Example: + # + # class Comment < Ohm::Model + # end + # + # class Post < Ohm::Model + # list :comments, :Comment + # end + # + # p = Post.create + # p.comments.push(Comment.create) + # p.comments.unshift(Comment.create) + # p.comments.size == 2 + # # => true + # + # Note: You can't use the list until you save the model. If you try + # to do it, you'll receive an Ohm::MissingID error. + # + def self.list(name, model) + collections << name unless collections.include?(name) + + define_method name do + model = Utils.const(self.class, model) + + Ohm::List.new(key[name], model.key, model) + end + end + # A macro for defining a method which basically does a find. # # Example: # class Post < Ohm::Model # reference :user, :User @@ -1025,15 +1184,10 @@ attrs[:errors] = errors if errors.any? return attrs end - # Export a JSON representation of the model by encoding `to_hash`. - def to_json(*args) - to_hash.to_json(*args) - end - # Persist the model attributes and update indices and unique # indices. The `counter`s and `set`s are not touched during save. # # If the model is not valid, nil is returned. Otherwise, the # persisted model is returned. @@ -1208,19 +1362,23 @@ def _skip_empty(atts) {}.tap do |ret| atts.each do |att, val| ret[att] = send(att).to_s unless val.to_s.empty? end + + throw :empty if ret.empty? end end def _unique_keys model.uniques.map { |att| model.key[:uniques][att] } end def _save - key.del - key.hmset(*_skip_empty(attributes).flatten) + catch :empty do + key.del + key.hmset(*_skip_empty(attributes).flatten) + end end def _verify_uniques if att = _detect_duplicate raise UniqueIndexViolation, "#{att} is not unique."