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."