Finalizing structure and tests for new basic design view support
This commit is contained in:
parent
800c2b322c
commit
4d1aebec43
|
@ -23,8 +23,10 @@ module CouchRest
|
|||
|
||||
def design(*args, &block)
|
||||
mapper = DesignMapper.new(self)
|
||||
mapper.instance_eval(&block)
|
||||
mapper.create_view_method(:all)
|
||||
|
||||
mapper.instance_eval(&block)
|
||||
|
||||
req_design_doc_refresh
|
||||
end
|
||||
|
||||
|
@ -43,6 +45,10 @@ module CouchRest
|
|||
# View instance when requested.
|
||||
def view(name, opts = {})
|
||||
View.create(model, name, opts)
|
||||
create_view_method(name)
|
||||
end
|
||||
|
||||
def create_view_method(name)
|
||||
model.class_eval <<-EOS, __FILE__, __LINE__ + 1
|
||||
def self.#{name}(opts = {})
|
||||
CouchRest::Model::Designs::View.new(self, opts, '#{name}')
|
||||
|
|
|
@ -12,6 +12,7 @@ module CouchRest
|
|||
# a normal relational database are not possible. At least not yet!
|
||||
#
|
||||
class View
|
||||
include Enumerable
|
||||
|
||||
attr_accessor :model, :name, :query, :result
|
||||
|
||||
|
@ -20,7 +21,7 @@ module CouchRest
|
|||
if parent.is_a?(Class) && parent < CouchRest::Model::Base
|
||||
raise "Name must be provided for view to be initialized" if name.nil?
|
||||
self.model = parent
|
||||
self.name = name
|
||||
self.name = name.to_s
|
||||
# Default options:
|
||||
self.query = { :reduce => false }
|
||||
elsif parent.is_a?(self.class)
|
||||
|
@ -37,33 +38,11 @@ module CouchRest
|
|||
|
||||
# == View Execution Methods
|
||||
#
|
||||
# Send a request to the CouchDB database using the current query values.
|
||||
|
||||
# Inmediatly send a request to the database for all documents provided by the query.
|
||||
#
|
||||
def all(&block)
|
||||
include_docs.rows.map{|r| r.doc}
|
||||
end
|
||||
|
||||
# Inmediatly send a request for the first result of the dataset.
|
||||
# This will override any limit set in the view previously.
|
||||
def first
|
||||
limit(1).all.first
|
||||
end
|
||||
|
||||
|
||||
def info
|
||||
|
||||
end
|
||||
|
||||
def offset
|
||||
execute['offset']
|
||||
end
|
||||
|
||||
def total_rows
|
||||
execute['total_rows']
|
||||
end
|
||||
|
||||
# Request to the CouchDB database using the current query values.
|
||||
|
||||
# Return each row wrapped in a ViewRow object. Unlike the raw
|
||||
# CouchDB request, this will provide an empty array if there
|
||||
# are no results.
|
||||
def rows
|
||||
return @rows if @rows
|
||||
if execute && result['rows']
|
||||
|
@ -73,10 +52,101 @@ module CouchRest
|
|||
end
|
||||
end
|
||||
|
||||
# Fetch all the documents the view can access. If the view has
|
||||
# not already been prepared for including documents in the query,
|
||||
# it will be added automatically and reset any previously cached
|
||||
# results.
|
||||
def all
|
||||
include_docs!
|
||||
docs
|
||||
end
|
||||
|
||||
# Provide all the documents from the view. If the view has not been
|
||||
# prepared with the +include_docs+ option, each document will be
|
||||
# loaded individually.
|
||||
def docs
|
||||
@docs ||= rows.map{|r| r.doc}
|
||||
end
|
||||
|
||||
# If another request has been made on the view, this will return
|
||||
# the first document in the set. If not, a new query object will be
|
||||
# generated with a limit of 1 so that only the first document is
|
||||
# loaded.
|
||||
def first
|
||||
result ? all.first : limit(1).all.first
|
||||
end
|
||||
|
||||
# Same as first but will order the view in descending order. This
|
||||
# does not however reverse the search keys or the offset, so if you
|
||||
# are using a +startkey+ and +endkey+ you might end up with
|
||||
# unexpected results.
|
||||
#
|
||||
# If in doubt, don't use this method!
|
||||
#
|
||||
def last
|
||||
result ? all.last : limit(1).descending.all.last
|
||||
end
|
||||
|
||||
# Perform a count operation based on the current view. If the view
|
||||
# can be reduced, the reduce will be performed and return the first
|
||||
# value. This is okay for most simple queries, but may provide
|
||||
# unexpected results if your reduce method does not calculate
|
||||
# the total number of documents in a result set.
|
||||
#
|
||||
# Trying to use this method with the group option will raise an error.
|
||||
#
|
||||
# If no reduce function is defined, a query will be performed
|
||||
# to return the total number of rows, this is the equivalant of:
|
||||
#
|
||||
# view.limit(0).total_rows
|
||||
#
|
||||
def count
|
||||
raise "View#count cannot be used with group options" if query[:group]
|
||||
if can_reduce?
|
||||
row = reduce.rows.first
|
||||
row.nil? ? 0 : row.value
|
||||
else
|
||||
limit(0).total_rows
|
||||
end
|
||||
end
|
||||
|
||||
# Run through each document provided by the +#all+ method.
|
||||
# This is also used by the Enumerator mixin to provide all the standard
|
||||
# ruby collection directly on the view.
|
||||
def each(&block)
|
||||
all.each(&block)
|
||||
end
|
||||
|
||||
# Wrapper for the results offset. As per the CouchDB API,
|
||||
# this may be nil if groups are used.
|
||||
def offset
|
||||
execute['offset']
|
||||
end
|
||||
|
||||
# Wrapper for the total_rows value provided by the query. As per the
|
||||
# CouchDB API, this may be nil if groups are used.
|
||||
def total_rows
|
||||
execute['total_rows']
|
||||
end
|
||||
|
||||
# Convenience wrapper around the rows result set. This will provide
|
||||
# and array of keys.
|
||||
def keys
|
||||
rows.map{|r| r.key}
|
||||
end
|
||||
|
||||
# Convenience wrapper to provide all the values from the route
|
||||
# set without having to go through +rows+.
|
||||
def values
|
||||
rows.map{|r| r.value}
|
||||
end
|
||||
|
||||
# No yet implemented. Eventually this will provide a raw hash
|
||||
# of the information CouchDB holds about the view.
|
||||
def info
|
||||
raise "Not yet implemented"
|
||||
end
|
||||
|
||||
|
||||
# == View Filter Methods
|
||||
#
|
||||
|
@ -85,6 +155,8 @@ module CouchRest
|
|||
# are combined in an incorrect fashion.
|
||||
#
|
||||
|
||||
# Specify the database the view should use. If not defined,
|
||||
# an attempt will be made to load its value from the model.
|
||||
def database(value)
|
||||
update_query(:database => value)
|
||||
end
|
||||
|
@ -97,11 +169,11 @@ module CouchRest
|
|||
update_query(:key => value)
|
||||
end
|
||||
|
||||
# Find all index keys that start with the value provided. May or may not be used in
|
||||
# conjunction with the +endkey+ option.
|
||||
# Find all index keys that start with the value provided. May or may
|
||||
# not be used in conjunction with the +endkey+ option.
|
||||
#
|
||||
# When the +#descending+ option is used (not the default), the start and end keys should
|
||||
# be reversed.
|
||||
# When the +#descending+ option is used (not the default), the start
|
||||
# and end keys should be reversed, as per the CouchDB API.
|
||||
#
|
||||
# Cannot be used if the key has been set.
|
||||
def startkey(value)
|
||||
|
@ -116,17 +188,19 @@ module CouchRest
|
|||
update_query(:startkey_docid => value.is_a?(String) ? value : value.id)
|
||||
end
|
||||
|
||||
# The opposite of +#startkey+, finds all index entries whose key is before the value specified.
|
||||
# The opposite of +#startkey+, finds all index entries whose key is before
|
||||
# the value specified.
|
||||
#
|
||||
# See the +#startkey+ method for more details and the +#inclusive_end+ option.
|
||||
# See the +#startkey+ method for more details and the +#inclusive_end+
|
||||
# option.
|
||||
def endkey(value)
|
||||
raise "View#endkey cannot be used when key has been set" unless query[:key].nil?
|
||||
update_query(:endkey => value)
|
||||
end
|
||||
|
||||
# The result set should end at the position of the provided document.
|
||||
# The value may be provided as an object that responds to the +#id+ call
|
||||
# or a string.
|
||||
# The value may be provided as an object that responds to the +#id+
|
||||
# call or a string.
|
||||
def endkey_doc(value)
|
||||
update_query(:endkey_docid => value.is_a?(String) ? value : value.id)
|
||||
end
|
||||
|
@ -134,7 +208,8 @@ module CouchRest
|
|||
|
||||
# The results should be provided in descending order.
|
||||
#
|
||||
# Descending is false by default, this method will enable it and cannot be undone.
|
||||
# Descending is false by default, this method will enable it and cannot
|
||||
# be undone.
|
||||
def descending
|
||||
update_query(:descending => true)
|
||||
end
|
||||
|
@ -156,54 +231,77 @@ module CouchRest
|
|||
|
||||
# Use the reduce function on the view. If none is available this method will fail.
|
||||
def reduce
|
||||
raise "Cannot reduce a view without a reduce method" unless can_reduce?
|
||||
update_query(:reduce => true)
|
||||
end
|
||||
|
||||
# Control whether the reduce function reduces to a set of distinct keys or to a single
|
||||
# result row.
|
||||
# Control whether the reduce function reduces to a set of distinct keys
|
||||
# or to a single result row.
|
||||
#
|
||||
# By default the value is false, and can only be set when the view's +#reduce+ option
|
||||
# has been set.
|
||||
# By default the value is false, and can only be set when the view's
|
||||
# +#reduce+ option has been set.
|
||||
def group
|
||||
raise "View#reduce must have been set before grouping is permitted" unless query[:reduce]
|
||||
update_query(:group => true)
|
||||
end
|
||||
|
||||
# Will set the level the grouping should be performed to. As per the
|
||||
# CouchDB API, it only makes sense when the index key is an array.
|
||||
#
|
||||
# This will automatically set the group option.
|
||||
def group_level(value)
|
||||
raise "View#reduce and View#group must have been set before group_level is called" unless query[:reduce] && query[:group]
|
||||
update_query(:group_level => value.to_i)
|
||||
group.update_query(:group_level => value.to_i)
|
||||
end
|
||||
|
||||
def include_docs
|
||||
update_query.include_docs!
|
||||
end
|
||||
|
||||
# Return any cached values to their nil state so that any queries
|
||||
# requested later will have a fresh set of data.
|
||||
def reset!
|
||||
self.result = nil
|
||||
@rows = nil
|
||||
@docs = nil
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def include_docs!
|
||||
reset! if result && !include_docs?
|
||||
query[:include_docs] = true
|
||||
self
|
||||
end
|
||||
|
||||
def include_docs?
|
||||
!!query[:include_docs]
|
||||
end
|
||||
|
||||
def update_query(new_query = {})
|
||||
self.class.new(self, new_query)
|
||||
end
|
||||
|
||||
def database
|
||||
query[:database] || model.database
|
||||
def design_doc
|
||||
model.design_doc
|
||||
end
|
||||
|
||||
# Used internally to ensure that docs are provided. Should not be used outside of
|
||||
# the view class under normal circumstances.
|
||||
def include_docs
|
||||
raise "Documents cannot be returned from a view that is prepared for a reduce" if query[:reduce]
|
||||
query.delete(:reduce)
|
||||
update_query(:include_docs => true)
|
||||
def can_reduce?
|
||||
!design_doc['views'][name]['reduce'].blank?
|
||||
end
|
||||
|
||||
def execute(&block)
|
||||
|
||||
def execute
|
||||
return self.result if result
|
||||
raise "Database must be defined in model or view!" if database.nil?
|
||||
db = query[:database] || model.database
|
||||
raise "Database must be defined in model or view!" if db.nil?
|
||||
retryable = true
|
||||
# Remove the reduce value if its not needed
|
||||
query.delete(:reduce) if !query[:reduce] && model.design_doc['views'][name.to_s]['reduce'].blank?
|
||||
query.delete(:reduce) unless can_reduce?
|
||||
begin
|
||||
self.result = model.design_doc.view_on(database, name, query, &block)
|
||||
self.result = model.design_doc.view_on(db, name, query)
|
||||
rescue RestClient::ResourceNotFound => e
|
||||
if retryable
|
||||
model.save_design_doc(database)
|
||||
model.save_design_doc(db)
|
||||
retryable = false
|
||||
retry
|
||||
else
|
||||
|
@ -248,19 +346,24 @@ module CouchRest
|
|||
emit = keys.length == 1 ? keys.first : "[#{keys.join(', ')}]"
|
||||
opts[:guards] += keys.map{|k| "(#{k} != null)"}
|
||||
opts[:map] = <<-EOF
|
||||
function(doc) {
|
||||
if (#{opts[:guards].join(' && ')}) {
|
||||
emit(#{emit}, null);
|
||||
}
|
||||
}
|
||||
EOF
|
||||
function(doc) {
|
||||
if (#{opts[:guards].join(' && ')}) {
|
||||
emit(#{emit}, 1);
|
||||
}
|
||||
}
|
||||
EOF
|
||||
opts[:reduce] = <<-EOF
|
||||
function(key, values, rereduce) {
|
||||
return sum(values);
|
||||
}
|
||||
EOF
|
||||
end
|
||||
|
||||
model.design_doc['views'] ||= {}
|
||||
model.design_doc['views'][name.to_s] = {
|
||||
'map' => opts[:map],
|
||||
'reduce' => opts[:reduce]
|
||||
}
|
||||
view = model.design_doc['views'][name.to_s] = { }
|
||||
view['map'] = opts[:map]
|
||||
view['reduce'] = opts[:reduce] if opts[:reduce]
|
||||
view
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -12,116 +12,553 @@ end
|
|||
|
||||
describe "Design View" do
|
||||
|
||||
before :each do
|
||||
@klass = CouchRest::Model::Designs::View
|
||||
end
|
||||
describe "(unit tests)" do
|
||||
|
||||
describe ".new" do
|
||||
|
||||
describe "with invalid parent model" do
|
||||
it "should burn" do
|
||||
lambda { @klass.new(String) }.should raise_exception
|
||||
end
|
||||
before :each do
|
||||
@klass = CouchRest::Model::Designs::View
|
||||
end
|
||||
|
||||
describe "with CouchRest Model" do
|
||||
describe ".new" do
|
||||
|
||||
it "should setup attributes" do
|
||||
@obj = @klass.new(DesignViewModel, {}, 'test_view')
|
||||
@obj.model.should eql(DesignViewModel)
|
||||
@obj.name.should eql('test_view')
|
||||
@obj.query.should eql({:reduce => false})
|
||||
describe "with invalid parent model" do
|
||||
it "should burn" do
|
||||
lambda { @klass.new(String) }.should raise_exception
|
||||
end
|
||||
end
|
||||
|
||||
it "should complain if there is no name" do
|
||||
lambda { @klass.new(DesignViewModel, {}, nil) }.should raise_error
|
||||
describe "with CouchRest Model" do
|
||||
|
||||
it "should setup attributes" do
|
||||
@obj = @klass.new(DesignViewModel, {}, 'test_view')
|
||||
@obj.model.should eql(DesignViewModel)
|
||||
@obj.name.should eql('test_view')
|
||||
@obj.query.should eql({:reduce => false})
|
||||
end
|
||||
|
||||
it "should complain if there is no name" do
|
||||
lambda { @klass.new(DesignViewModel, {}, nil) }.should raise_error
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe "with previous view instance" do
|
||||
|
||||
before :each do
|
||||
first = @klass.new(DesignViewModel, {}, 'test_view')
|
||||
@obj = @klass.new(first, {:foo => :bar})
|
||||
end
|
||||
|
||||
it "should copy attributes" do
|
||||
@obj.model.should eql(DesignViewModel)
|
||||
@obj.name.should eql('test_view')
|
||||
@obj.query.should eql({:reduce => false, :foo => :bar})
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe "with previous view instance" do
|
||||
describe ".create" do
|
||||
|
||||
before :each do
|
||||
first = @klass.new(DesignViewModel, {}, 'test_view')
|
||||
@obj = @klass.new(first, {:foo => :bar})
|
||||
@design_doc = {}
|
||||
DesignViewModel.stub!(:design_doc).and_return(@design_doc)
|
||||
end
|
||||
|
||||
it "should copy attributes" do
|
||||
@obj.model.should eql(DesignViewModel)
|
||||
@obj.name.should eql('test_view')
|
||||
@obj.query.should eql({:reduce => false, :foo => :bar})
|
||||
it "should add a basic view" do
|
||||
@klass.create(DesignViewModel, 'test_view', :map => 'foo')
|
||||
@design_doc['views']['test_view'].should_not be_nil
|
||||
end
|
||||
|
||||
it "should auto generate mapping from name" do
|
||||
lambda { @klass.create(DesignViewModel, 'by_title') }.should_not raise_error
|
||||
str = @design_doc['views']['by_title']['map']
|
||||
str.should include("((doc['couchrest-type'] == 'DesignViewModel') && (doc['title'] != null))")
|
||||
str.should include("emit(doc['title'], 1);")
|
||||
str = @design_doc['views']['by_title']['reduce']
|
||||
str.should include("return sum(values);")
|
||||
end
|
||||
|
||||
it "should auto generate mapping from name with and" do
|
||||
@klass.create(DesignViewModel, 'by_title_and_name')
|
||||
str = @design_doc['views']['by_title_and_name']['map']
|
||||
str.should include("(doc['title'] != null) && (doc['name'] != null)")
|
||||
str.should include("emit([doc['title'], doc['name']], 1);")
|
||||
str = @design_doc['views']['by_title_and_name']['reduce']
|
||||
str.should include("return sum(values);")
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
describe "instance methods" do
|
||||
|
||||
describe ".create" do
|
||||
|
||||
before :each do
|
||||
@design_doc = {}
|
||||
DesignViewModel.stub!(:design_doc).and_return(@design_doc)
|
||||
end
|
||||
|
||||
it "should add a basic view" do
|
||||
@klass.create(DesignViewModel, 'test_view', :map => 'foo')
|
||||
@design_doc['views']['test_view'].should_not be_nil
|
||||
end
|
||||
|
||||
it "should auto generate mapping from name" do
|
||||
lambda { @klass.create(DesignViewModel, 'by_title') }.should_not raise_error
|
||||
str = @design_doc['views']['by_title']['map']
|
||||
str.should include("((doc['couchrest-type'] == 'DesignViewModel') && (doc['title'] != null))")
|
||||
str.should include("emit(doc['title'], null);")
|
||||
end
|
||||
|
||||
it "should auto generate mapping from name with and" do
|
||||
@klass.create(DesignViewModel, 'by_title_and_name')
|
||||
str = @design_doc['views']['by_title_and_name']['map']
|
||||
str.should include("(doc['title'] != null) && (doc['name'] != null)")
|
||||
str.should include("emit([doc['title'], doc['name']], null);")
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe "instance methods" do
|
||||
|
||||
before :each do
|
||||
@obj = @klass.new(DesignViewModel, {}, 'test_view')
|
||||
end
|
||||
|
||||
describe "#update_query" do
|
||||
it "returns a new instance of view" do
|
||||
@obj.send(:update_query).object_id.should_not eql(@obj.object_id)
|
||||
before :each do
|
||||
@obj = @klass.new(DesignViewModel, {}, 'test_view')
|
||||
end
|
||||
|
||||
it "returns a new instance of view with extra parameters" do
|
||||
new_obj = @obj.send(:update_query, {:foo => :bar})
|
||||
new_obj.query[:foo].should eql(:bar)
|
||||
describe "#rows" do
|
||||
it "should execute query" do
|
||||
@obj.should_receive(:execute).and_return(true)
|
||||
@obj.should_receive(:result).twice.and_return({'rows' => []})
|
||||
@obj.rows.should be_empty
|
||||
end
|
||||
|
||||
it "should wrap rows in ViewRow class" do
|
||||
@obj.should_receive(:execute).and_return(true)
|
||||
@obj.should_receive(:result).twice.and_return({'rows' => [{:foo => :bar}]})
|
||||
CouchRest::Model::Designs::ViewRow.should_receive(:new).with({:foo => :bar}, @obj.model)
|
||||
@obj.rows
|
||||
end
|
||||
end
|
||||
|
||||
describe "#all" do
|
||||
it "should ensure docs included and call docs" do
|
||||
@obj.should_receive(:include_docs!)
|
||||
@obj.should_receive(:docs)
|
||||
@obj.all
|
||||
end
|
||||
end
|
||||
|
||||
describe "#docs" do
|
||||
it "should provide docs from rows" do
|
||||
@obj.should_receive(:rows).and_return([])
|
||||
@obj.docs
|
||||
end
|
||||
it "should cache the results" do
|
||||
@obj.should_receive(:rows).once.and_return([])
|
||||
@obj.docs
|
||||
@obj.docs
|
||||
end
|
||||
end
|
||||
|
||||
describe "#first" do
|
||||
it "should provide the first result of loaded query" do
|
||||
@obj.should_receive(:result).and_return(true)
|
||||
@obj.should_receive(:all).and_return([:foo])
|
||||
@obj.first.should eql(:foo)
|
||||
end
|
||||
it "should perform a query if no results cached" do
|
||||
view = mock('SubView')
|
||||
@obj.should_receive(:result).and_return(nil)
|
||||
@obj.should_receive(:limit).with(1).and_return(view)
|
||||
view.should_receive(:all).and_return([:foo])
|
||||
@obj.first.should eql(:foo)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#last" do
|
||||
it "should provide the last result of loaded query" do
|
||||
@obj.should_receive(:result).and_return(true)
|
||||
@obj.should_receive(:all).and_return([:foo, :bar])
|
||||
@obj.first.should eql(:foo)
|
||||
end
|
||||
it "should perform a query if no results cached" do
|
||||
view = mock('SubView')
|
||||
@obj.should_receive(:result).and_return(nil)
|
||||
@obj.should_receive(:limit).with(1).and_return(view)
|
||||
view.should_receive(:descending).and_return(view)
|
||||
view.should_receive(:all).and_return([:foo, :bar])
|
||||
@obj.last.should eql(:bar)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#count" do
|
||||
it "should raise an error if view prepared for group" do
|
||||
@obj.should_receive(:query).and_return({:group => true})
|
||||
lambda { @obj.count }.should raise_error
|
||||
end
|
||||
|
||||
it "should return first row value if reduce possible" do
|
||||
view = mock("SubView")
|
||||
row = mock("Row")
|
||||
@obj.should_receive(:can_reduce?).and_return(true)
|
||||
@obj.should_receive(:reduce).and_return(view)
|
||||
view.should_receive(:rows).and_return([row])
|
||||
row.should_receive(:value).and_return(2)
|
||||
@obj.count.should eql(2)
|
||||
end
|
||||
it "should return 0 if no rows and reduce possible" do
|
||||
view = mock("SubView")
|
||||
@obj.should_receive(:can_reduce?).and_return(true)
|
||||
@obj.should_receive(:reduce).and_return(view)
|
||||
view.should_receive(:rows).and_return([])
|
||||
@obj.count.should eql(0)
|
||||
end
|
||||
|
||||
it "should perform limit request for total_rows" do
|
||||
view = mock("SubView")
|
||||
@obj.should_receive(:limit).with(0).and_return(view)
|
||||
view.should_receive(:total_rows).and_return(4)
|
||||
@obj.should_receive(:can_reduce?).and_return(false)
|
||||
@obj.count.should eql(4)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#each" do
|
||||
it "should call each method on all" do
|
||||
@obj.should_receive(:all).and_return([])
|
||||
@obj.each
|
||||
end
|
||||
it "should call each and pass block" do
|
||||
set = [:foo, :bar]
|
||||
@obj.should_receive(:all).and_return(set)
|
||||
result = []
|
||||
@obj.each do |s|
|
||||
result << s
|
||||
end
|
||||
result.should eql(set)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#offset" do
|
||||
it "should excute" do
|
||||
@obj.should_receive(:execute).and_return({'offset' => 3})
|
||||
@obj.offset.should eql(3)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#total_rows" do
|
||||
it "should excute" do
|
||||
@obj.should_receive(:execute).and_return({'total_rows' => 3})
|
||||
@obj.total_rows.should eql(3)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#keys" do
|
||||
it "should request each row and provide key value" do
|
||||
row = mock("Row")
|
||||
row.should_receive(:key).twice.and_return('foo')
|
||||
@obj.should_receive(:rows).and_return([row, row])
|
||||
@obj.keys.should eql(['foo', 'foo'])
|
||||
end
|
||||
end
|
||||
|
||||
describe "#values" do
|
||||
it "should request each row and provide value" do
|
||||
row = mock("Row")
|
||||
row.should_receive(:value).twice.and_return('foo')
|
||||
@obj.should_receive(:rows).and_return([row, row])
|
||||
@obj.values.should eql(['foo', 'foo'])
|
||||
end
|
||||
end
|
||||
|
||||
describe "#info" do
|
||||
it "should raise error" do
|
||||
lambda { @obj.info }.should raise_error
|
||||
end
|
||||
end
|
||||
|
||||
describe "#database" do
|
||||
it "should update query with value" do
|
||||
@obj.should_receive(:update_query).with({:database => 'foo'})
|
||||
@obj.database('foo')
|
||||
end
|
||||
end
|
||||
|
||||
describe "#key" do
|
||||
it "should update query with value" do
|
||||
@obj.should_receive(:update_query).with({:key => 'foo'})
|
||||
@obj.key('foo')
|
||||
end
|
||||
it "should raise and error if startkey set" do
|
||||
@obj.query[:startkey] = 'bar'
|
||||
lambda { @obj.key('foo') }.should raise_error
|
||||
end
|
||||
it "should raise and error if endkey set" do
|
||||
@obj.query[:endkey] = 'bar'
|
||||
lambda { @obj.key('foo') }.should raise_error
|
||||
end
|
||||
it "should raise and error if both startkey and endkey set" do
|
||||
@obj.query[:startkey] = 'bar'
|
||||
@obj.query[:endkey] = 'bar'
|
||||
lambda { @obj.key('foo') }.should raise_error
|
||||
end
|
||||
end
|
||||
|
||||
describe "#startkey" do
|
||||
it "should update query with value" do
|
||||
@obj.should_receive(:update_query).with({:startkey => 'foo'})
|
||||
@obj.startkey('foo')
|
||||
end
|
||||
it "should raise and error if key set" do
|
||||
@obj.query[:key] = 'bar'
|
||||
lambda { @obj.startkey('foo') }.should raise_error
|
||||
end
|
||||
end
|
||||
|
||||
describe "#startkey_doc" do
|
||||
it "should update query with value" do
|
||||
@obj.should_receive(:update_query).with({:startkey_docid => 'foo'})
|
||||
@obj.startkey_doc('foo')
|
||||
end
|
||||
it "should update query with object id if available" do
|
||||
doc = mock("Document")
|
||||
doc.should_receive(:id).and_return(44)
|
||||
@obj.should_receive(:update_query).with({:startkey_docid => 44})
|
||||
@obj.startkey_doc(doc)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#endkey" do
|
||||
it "should update query with value" do
|
||||
@obj.should_receive(:update_query).with({:endkey => 'foo'})
|
||||
@obj.endkey('foo')
|
||||
end
|
||||
it "should raise and error if key set" do
|
||||
@obj.query[:key] = 'bar'
|
||||
lambda { @obj.endkey('foo') }.should raise_error
|
||||
end
|
||||
end
|
||||
|
||||
describe "#endkey_doc" do
|
||||
it "should update query with value" do
|
||||
@obj.should_receive(:update_query).with({:endkey_docid => 'foo'})
|
||||
@obj.endkey_doc('foo')
|
||||
end
|
||||
it "should update query with object id if available" do
|
||||
doc = mock("Document")
|
||||
doc.should_receive(:id).and_return(44)
|
||||
@obj.should_receive(:update_query).with({:endkey_docid => 44})
|
||||
@obj.endkey_doc(doc)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#descending" do
|
||||
it "should update query" do
|
||||
@obj.should_receive(:update_query).with({:descending => true})
|
||||
@obj.descending
|
||||
end
|
||||
end
|
||||
|
||||
describe "#limit" do
|
||||
it "should update query with value" do
|
||||
@obj.should_receive(:update_query).with({:limit => 3})
|
||||
@obj.limit(3)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#skip" do
|
||||
it "should update query with value" do
|
||||
@obj.should_receive(:update_query).with({:skip => 3})
|
||||
@obj.skip(3)
|
||||
end
|
||||
it "should update query with default value" do
|
||||
@obj.should_receive(:update_query).with({:skip => 0})
|
||||
@obj.skip
|
||||
end
|
||||
end
|
||||
|
||||
describe "#reduce" do
|
||||
it "should update query" do
|
||||
@obj.should_receive(:can_reduce?).and_return(true)
|
||||
@obj.should_receive(:update_query).with({:reduce => true})
|
||||
@obj.reduce
|
||||
end
|
||||
it "should raise error if query cannot be reduced" do
|
||||
@obj.should_receive(:can_reduce?).and_return(false)
|
||||
lambda { @obj.reduce }.should raise_error
|
||||
end
|
||||
end
|
||||
|
||||
describe "#group" do
|
||||
it "should update query" do
|
||||
@obj.should_receive(:query).and_return({:reduce => true})
|
||||
@obj.should_receive(:update_query).with({:group => true})
|
||||
@obj.group
|
||||
end
|
||||
it "should raise error if query not prepared for reduce" do
|
||||
@obj.should_receive(:query).and_return({:reduce => false})
|
||||
lambda { @obj.group }.should raise_error
|
||||
end
|
||||
end
|
||||
|
||||
describe "#group" do
|
||||
it "should update query" do
|
||||
@obj.should_receive(:query).and_return({:reduce => true})
|
||||
@obj.should_receive(:update_query).with({:group => true})
|
||||
@obj.group
|
||||
end
|
||||
it "should raise error if query not prepared for reduce" do
|
||||
@obj.should_receive(:query).and_return({:reduce => false})
|
||||
lambda { @obj.group }.should raise_error
|
||||
end
|
||||
end
|
||||
|
||||
describe "#group_level" do
|
||||
it "should update query" do
|
||||
@obj.should_receive(:group).and_return(@obj)
|
||||
@obj.should_receive(:update_query).with({:group_level => 3})
|
||||
@obj.group_level(3)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#include_docs" do
|
||||
it "should call include_docs! on new view" do
|
||||
@obj.should_receive(:update_query).and_return(@obj)
|
||||
@obj.should_receive(:include_docs!)
|
||||
@obj.include_docs
|
||||
end
|
||||
end
|
||||
|
||||
describe "#reset!" do
|
||||
it "should empty all cached data" do
|
||||
@obj.should_receive(:result=).with(nil)
|
||||
@obj.instance_exec { @rows = 'foo'; @docs = 'foo' }
|
||||
@obj.reset!
|
||||
@obj.instance_exec { @rows }.should be_nil
|
||||
@obj.instance_exec { @docs }.should be_nil
|
||||
end
|
||||
end
|
||||
|
||||
#### PROTECTED METHODS
|
||||
|
||||
describe "#include_docs!" do
|
||||
it "should set query value" do
|
||||
@obj.should_receive(:result).and_return(false)
|
||||
@obj.should_not_receive(:reset!)
|
||||
@obj.send(:include_docs!)
|
||||
@obj.query[:include_docs].should be_true
|
||||
end
|
||||
it "should reset if result and no docs" do
|
||||
@obj.should_receive(:result).and_return(true)
|
||||
@obj.should_receive(:include_docs?).and_return(false)
|
||||
@obj.should_receive(:reset!)
|
||||
@obj.send(:include_docs!)
|
||||
@obj.query[:include_docs].should be_true
|
||||
end
|
||||
end
|
||||
|
||||
describe "#include_docs?" do
|
||||
it "should return true if set" do
|
||||
@obj.should_receive(:query).and_return({:include_docs => true})
|
||||
@obj.send(:include_docs?).should be_true
|
||||
end
|
||||
it "should return false if not set" do
|
||||
@obj.should_receive(:query).and_return({})
|
||||
@obj.send(:include_docs?).should be_false
|
||||
@obj.should_receive(:query).and_return({:include_docs => false})
|
||||
@obj.send(:include_docs?).should be_false
|
||||
end
|
||||
end
|
||||
|
||||
describe "#update_query" do
|
||||
it "returns a new instance of view" do
|
||||
@obj.send(:update_query).object_id.should_not eql(@obj.object_id)
|
||||
end
|
||||
|
||||
it "returns a new instance of view with extra parameters" do
|
||||
new_obj = @obj.send(:update_query, {:foo => :bar})
|
||||
new_obj.query[:foo].should eql(:bar)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#design_doc" do
|
||||
it "should call design_doc on model" do
|
||||
@obj.model.should_receive(:design_doc)
|
||||
@obj.send(:design_doc)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#can_reduce?" do
|
||||
it "should check and prove true" do
|
||||
@obj.should_receive(:name).and_return('test_view')
|
||||
@obj.should_receive(:design_doc).and_return({'views' => {'test_view' => {'reduce' => 'foo'}}})
|
||||
@obj.send(:can_reduce?).should be_true
|
||||
end
|
||||
it "should check and prove false" do
|
||||
@obj.should_receive(:name).and_return('test_view')
|
||||
@obj.should_receive(:design_doc).and_return({'views' => {'test_view' => {'reduce' => nil}}})
|
||||
@obj.send(:can_reduce?).should be_false
|
||||
end
|
||||
end
|
||||
|
||||
describe "#execute" do
|
||||
before :each do
|
||||
# disable real execution!
|
||||
@design_doc = mock("DesignDoc")
|
||||
@design_doc.stub!(:view_on)
|
||||
@obj.model.stub!(:design_doc).and_return(@design_doc)
|
||||
end
|
||||
|
||||
it "should return previous result if set" do
|
||||
@obj.result = "foo"
|
||||
@obj.send(:execute).should eql('foo')
|
||||
end
|
||||
|
||||
it "should raise issue if no database" do
|
||||
@obj.should_receive(:query).and_return({:database => nil})
|
||||
model = mock("SomeModel")
|
||||
model.should_receive(:database).and_return(nil)
|
||||
@obj.should_receive(:model).and_return(model)
|
||||
lambda { @obj.send(:execute) }.should raise_error
|
||||
end
|
||||
|
||||
it "should delete the reduce option if not going to be used" do
|
||||
@obj.should_receive(:can_reduce?).and_return(false)
|
||||
@obj.query.should_receive(:delete).with(:reduce)
|
||||
@obj.send(:execute)
|
||||
end
|
||||
|
||||
it "should populate the results" do
|
||||
@obj.should_receive(:can_reduce?).and_return(true)
|
||||
@design_doc.should_receive(:view_on).and_return('foos')
|
||||
@obj.send(:execute)
|
||||
@obj.result.should eql('foos')
|
||||
end
|
||||
|
||||
it "should retry once on a resource not found error" do
|
||||
@obj.should_receive(:can_reduce?).and_return(true)
|
||||
@obj.model.should_receive(:save_design_doc)
|
||||
@design_doc.should_receive(:view_on).ordered
|
||||
.and_raise(RestClient::ResourceNotFound)
|
||||
@design_doc.should_receive(:view_on).ordered
|
||||
.and_return('foos')
|
||||
@obj.send(:execute)
|
||||
@obj.result.should eql('foos')
|
||||
end
|
||||
|
||||
it "should retry twice and fail on a resource not found error" do
|
||||
@obj.should_receive(:can_reduce?).and_return(true)
|
||||
@obj.model.should_receive(:save_design_doc)
|
||||
@design_doc.should_receive(:view_on).twice
|
||||
.and_raise(RestClient::ResourceNotFound)
|
||||
lambda { @obj.send(:execute) }.should raise_error(RestClient::ResourceNotFound)
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
##############
|
||||
|
||||
describe "with real data" do
|
||||
describe "scenarios" do
|
||||
|
||||
before :all do
|
||||
@objs = [
|
||||
{:name => "Sam"},
|
||||
{:name => "Judith"},
|
||||
{:name => "Lorena"},
|
||||
{:name => "Peter"},
|
||||
{:name => "Judith"},
|
||||
{:name => "Sam"},
|
||||
{:name => "Vilma"}
|
||||
].map{|h| DesignViewModel.create(h)}
|
||||
end
|
||||
|
||||
describe "just documents" do
|
||||
describe "loading documents" do
|
||||
|
||||
it "should return all" do
|
||||
DesignViewModel.by_name.all.last.name.should eql("Vilma")
|
||||
it "should return first" do
|
||||
DesignViewModel.by_name.first.name.should eql("Judith")
|
||||
end
|
||||
|
||||
it "should return last" do
|
||||
DesignViewModel.by_name.last.name.should eql("Vilma")
|
||||
end
|
||||
|
||||
it "should allow multiple results" do
|
||||
view = DesignViewModel.by_name.limit(3)
|
||||
view.total_rows.should eql(5)
|
||||
view.last.name.should eql("Peter")
|
||||
view.all.length.should eql(3)
|
||||
end
|
||||
|
||||
end
|
||||
|
@ -139,7 +576,6 @@ describe "Design View" do
|
|||
it "should provide a set of keys" do
|
||||
DesignViewModel.by_name.limit(2).keys.should eql(["Judith", "Lorena"])
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -11,21 +11,22 @@ describe "Design" do
|
|||
end
|
||||
|
||||
describe ".design" do
|
||||
|
||||
it "should instantiate a new DesignMapper" do
|
||||
CouchRest::Model::Designs::DesignMapper.should_receive(:new).and_return(DesignModel)
|
||||
DesignModel.design() { }
|
||||
|
||||
before :each do
|
||||
@mapper = mock('DesignMapper')
|
||||
@mapper.stub!(:create_view_method)
|
||||
end
|
||||
|
||||
it "should instantiate a new DesignMapper with model" do
|
||||
CouchRest::Model::Designs::DesignMapper.should_receive(:new).with(DesignModel).and_return(DesignModel)
|
||||
it "should instantiate a new DesignMapper" do
|
||||
CouchRest::Model::Designs::DesignMapper.should_receive(:new).with(DesignModel).and_return(@mapper)
|
||||
@mapper.should_receive(:create_view_method).with(:all)
|
||||
@mapper.should_receive(:instance_eval)
|
||||
DesignModel.design() { }
|
||||
end
|
||||
|
||||
it "should allow methods to be called in mapper" do
|
||||
model = mock('Foo')
|
||||
model.should_receive(:foo)
|
||||
CouchRest::Model::Designs::DesignMapper.stub!(:new).and_return(model)
|
||||
@mapper.should_receive(:foo)
|
||||
CouchRest::Model::Designs::DesignMapper.stub!(:new).and_return(@mapper)
|
||||
DesignModel.design { foo }
|
||||
end
|
||||
|
||||
|
@ -64,10 +65,22 @@ describe "Design" do
|
|||
DesignModel.should respond_to(:test_view)
|
||||
end
|
||||
|
||||
it "should create a method that returns view instance" do
|
||||
it "should create a method for view instance" do
|
||||
CouchRest::Model::Designs::View.stub!(:create)
|
||||
@object.view('test_view')
|
||||
@object.should_receive(:create_view_method).with('test')
|
||||
@object.view('test')
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe "#create_view_method" do
|
||||
before :each do
|
||||
@object = @klass.new(DesignModel)
|
||||
end
|
||||
|
||||
it "should create a method that returns view instance" do
|
||||
CouchRest::Model::Designs::View.should_receive(:new).with(DesignModel, {}, 'test_view').and_return(nil)
|
||||
@object.create_view_method('test_view')
|
||||
DesignModel.test_view
|
||||
end
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ RSpec.configure do |config|
|
|||
cr = TEST_SERVER
|
||||
test_dbs = cr.databases.select { |db| db =~ /^#{TESTDB}/ }
|
||||
test_dbs.each do |db|
|
||||
#cr.database(db).delete! rescue nil
|
||||
cr.database(db).delete! rescue nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue