diff --git a/lib/redis_model/base.rb b/lib/redis_model/base.rb
index d3aaa9e..289e070 100644
--- a/lib/redis_model/base.rb
+++ b/lib/redis_model/base.rb
@@ -1,7 +1,10 @@
# IMPROVE: UUID identifiers?
# IMPROVE: counters
module RedisModel
- class RecordNotFound < StandardError
+ class RedisModelError < StandardError
+ end
+
+ class RecordNotFound < RedisModelError
end
class Base
@@ -31,5 +34,12 @@ def new_record?
def ==(other)
!new_record? && self.class == other.class && self.id == other.id
end
+
+ def self.instanciate(attributes)
+ record = new(attributes)
+ record.id = attributes[:id] || attributes['id']
+ record.persisted!
+ record
+ end
end
end
diff --git a/lib/redis_model/finders.rb b/lib/redis_model/finders.rb
index b503973..c23a814 100644
--- a/lib/redis_model/finders.rb
+++ b/lib/redis_model/finders.rb
@@ -11,64 +11,127 @@ def hkey(attr_name)
key("*->#{attr_name}")
end
- def all
- _find_all(:id)
+ def exists?(id)
+ connection.exists(key(id))
end
- def find(id)
+ # Finds records.
+ #
+ # Examples:
+ #
+ # posts = Post.find :all
+ # posts = Post.find :all, :offset => 20, :limit => 10
+ #
+ # The following calls are equivalent, and will return the same record:
+ #
+ # TODO: it actually doesn't work!
+ #
+ # post = Post.find :first, :by => :position, :order => :desc
+ # post = Post.find :last, :by => :position, :order => :asc
+ #
+ # Finds all comments for a given post:
+ #
+ # comments = Comment.find :all, :index => [ :post_id, 123 ], :by => :approved_at
+ #
+ # Options:
+ #
+ # - :index - either an indexed attribute name, or an array of [ attr_name, value ] (defaults to :id).
+ # - :by - an attribute name to sort by or :nosort to not sort result (defaults to :nosort).
+ # - :order - either :asc, :desc or :alpha or an array of :asc or :desc with :alpha.
+ # - :limit - an array of [ offset, limit ]
+ # - :select - an array of attribute names to get (defaults to all attributes)
+ #
+ def find(*args)
+ if args.first.is_a?(Symbol)
+ options = args.extract_options!
+
+ index = (options[:index] || :id)
+ index = [ index ] unless index.kind_of?(Array)
+ limit = options[:limit]
+ by = options[:by] unless options[:by].blank?
+
+ if options[:order].blank?
+ order = [ :asc ] unless by.blank?
+ else
+ order = options[:order]
+ order = [ order ] unless order.kind_of?(Array)
+ end
+ unless order.nil?
+ order << :alpha unless order.include?(:alpha) && by.nil? && [ :integer, :float ].include?(schema[by][:type])
+ order = order.join(" ").upcase
+ end
+
+ case args.first
+ when :all
+ when :first
+ return find_with_range(index, 0, 0) if by.nil? && order.blank?
+ limit = [ 0, 0 ]
+ when :last
+ return find_with_range(index, -1, -1) if by.nil? && order.blank?
+ limit = [ -1, -1 ]
+ else
+ raise RedisModelError.new("unknown find method #{args.first.inspect}")
+ end
+
+ fields = (options[:select] || attribute_names).sort
+ results = connection.sort(index_key(*index),
+ :get => fields.collect { |k| hkey(k) },
+ :by => by ? hkey(by) : :nosort,
+ :order => order,
+ :limit => limit
+ )
+ collection = []
+ results.each_slice(fields.size) do |values|
+ collection << instanciate(Hash[ *fields.zip(values).flatten ])
+ end
+
+ case args.first
+ when :all
+ collection
+ when :first, :last
+ collection.first
+ end
+ else
+ find_with_id(*args)
+ end
+ end
+
+ def find_with_range(index, offset, limit)
+ ids = connection.lrange(index_key(*index), offset, limit)
+ instanciate(connection.hgetall(key(ids.first))) if ids.any?
+ end
+
+ def find_with_id(id)
attributes = connection.hgetall(key(id))
raise RedisModel::RecordNotFound.new("No such #{model_name} with id: #{id}") if attributes.empty?
instanciate(attributes)
end
- def exists?(id)
- connection.exists(key(id))
+ def all
+ find(:all)
end
def first
- ids = connection.lrange(index_key(:id), 0, 0)
- instanciate(connection.hgetall(key(ids.first))) if ids.any?
+ find(:first)
end
def last
- ids = connection.lrange(index_key(:id), -1, -1)
- instanciate(connection.hgetall(key(ids.first))) if ids.any?
+ find(:last)
end
def method_missing(method_name, *args)
if method_name.to_s =~ /^find_(all_by|by)_(.*)$/
case $1
when 'all_by'
- _find_all($2, args.first)
+ find :all, :index => [ $2, args.first ]
when 'by'
+# find :first, :index = [ $2, args.first ]
super
end
else
super
end
end
-
- protected
- def instanciate(attributes)
- record = new(attributes)
- record.id = attributes[:id] || attributes['id']
- record.persisted!
- record
- end
-
- def _find_all(attr_name, value = nil)
- keys = attribute_names.sort
- results = connection.sort(
- index_key(attr_name, value),
- :by => :nosort,
- :get => keys.collect { |k| hkey(k) }
- )
- collection = []
- results.each_slice(keys.size) do |values|
- collection << instanciate(Hash[ *keys.zip(values).flatten ])
- end
- collection
- end
end
def reload
diff --git a/test/finders_test.rb b/test/finders_test.rb
index 2bbe2b3..02531e1 100644
--- a/test/finders_test.rb
+++ b/test/finders_test.rb
@@ -13,6 +13,39 @@ def test_find
assert_raises(RedisModel::RecordNotFound) { Post.find(12346890) }
end
+ def test_find_all
+ assert_equal [ posts(:welcome), posts(:post1) ], Post.find(:all)
+ assert_equal [], Row.find(:all)
+ end
+
+ def test_find_all_with_index
+ assert_equal [ posts(:welcome) ], Post.find(:all, :index => [ :approved, true ])
+ assert_equal [ posts(:post1) ], Post.find(:all, :index => [ :approved, false ])
+ end
+
+ def test_find_all_with_select
+ posts = Post.find(:all, :select => [ :id, :title ])
+ assert_equal [ posts(:welcome).id, posts(:post1).id ], posts.collect(&:id)
+ assert_equal [ posts(:welcome).title, posts(:post1).title ], posts.collect(&:title)
+ assert_equal [ nil, nil ], posts.collect(&:body)
+ end
+
+ def test_find_all_with_order
+ assert_equal [ posts(:post1), posts(:welcome) ], Post.find(:all, :by => :title)
+ end
+
+ def test_find_all_with_limit
+ end
+
+ def test_find_first
+ end
+
+ def test_find_first_with_order
+ end
+
+ def test_find_last_with_order
+ end
+
def test_all
assert_equal [ posts(:welcome).id, posts(:post1).id ], Post.all.collect(&:id)
assert_equal [], Row.all