Finalizing structure and tests for new basic design view support
This commit is contained in:
parent
800c2b322c
commit
4d1aebec43
|
@ -23,6 +23,8 @@ module CouchRest
|
||||||
|
|
||||||
def design(*args, &block)
|
def design(*args, &block)
|
||||||
mapper = DesignMapper.new(self)
|
mapper = DesignMapper.new(self)
|
||||||
|
mapper.create_view_method(:all)
|
||||||
|
|
||||||
mapper.instance_eval(&block)
|
mapper.instance_eval(&block)
|
||||||
|
|
||||||
req_design_doc_refresh
|
req_design_doc_refresh
|
||||||
|
@ -43,6 +45,10 @@ module CouchRest
|
||||||
# View instance when requested.
|
# View instance when requested.
|
||||||
def view(name, opts = {})
|
def view(name, opts = {})
|
||||||
View.create(model, name, opts)
|
View.create(model, name, opts)
|
||||||
|
create_view_method(name)
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_view_method(name)
|
||||||
model.class_eval <<-EOS, __FILE__, __LINE__ + 1
|
model.class_eval <<-EOS, __FILE__, __LINE__ + 1
|
||||||
def self.#{name}(opts = {})
|
def self.#{name}(opts = {})
|
||||||
CouchRest::Model::Designs::View.new(self, opts, '#{name}')
|
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!
|
# a normal relational database are not possible. At least not yet!
|
||||||
#
|
#
|
||||||
class View
|
class View
|
||||||
|
include Enumerable
|
||||||
|
|
||||||
attr_accessor :model, :name, :query, :result
|
attr_accessor :model, :name, :query, :result
|
||||||
|
|
||||||
|
@ -20,7 +21,7 @@ module CouchRest
|
||||||
if parent.is_a?(Class) && parent < CouchRest::Model::Base
|
if parent.is_a?(Class) && parent < CouchRest::Model::Base
|
||||||
raise "Name must be provided for view to be initialized" if name.nil?
|
raise "Name must be provided for view to be initialized" if name.nil?
|
||||||
self.model = parent
|
self.model = parent
|
||||||
self.name = name
|
self.name = name.to_s
|
||||||
# Default options:
|
# Default options:
|
||||||
self.query = { :reduce => false }
|
self.query = { :reduce => false }
|
||||||
elsif parent.is_a?(self.class)
|
elsif parent.is_a?(self.class)
|
||||||
|
@ -37,33 +38,11 @@ module CouchRest
|
||||||
|
|
||||||
# == View Execution Methods
|
# == View Execution Methods
|
||||||
#
|
#
|
||||||
# Send a request to the CouchDB database using the current query values.
|
# 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
|
|
||||||
|
|
||||||
|
# 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
|
def rows
|
||||||
return @rows if @rows
|
return @rows if @rows
|
||||||
if execute && result['rows']
|
if execute && result['rows']
|
||||||
|
@ -73,10 +52,101 @@ module CouchRest
|
||||||
end
|
end
|
||||||
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
|
def keys
|
||||||
rows.map{|r| r.key}
|
rows.map{|r| r.key}
|
||||||
end
|
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
|
# == View Filter Methods
|
||||||
#
|
#
|
||||||
|
@ -85,6 +155,8 @@ module CouchRest
|
||||||
# are combined in an incorrect fashion.
|
# 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)
|
def database(value)
|
||||||
update_query(:database => value)
|
update_query(:database => value)
|
||||||
end
|
end
|
||||||
|
@ -97,11 +169,11 @@ module CouchRest
|
||||||
update_query(:key => value)
|
update_query(:key => value)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Find all index keys that start with the value provided. May or may not be used in
|
# Find all index keys that start with the value provided. May or may
|
||||||
# conjunction with the +endkey+ option.
|
# not be used in conjunction with the +endkey+ option.
|
||||||
#
|
#
|
||||||
# When the +#descending+ option is used (not the default), the start and end keys should
|
# When the +#descending+ option is used (not the default), the start
|
||||||
# be reversed.
|
# and end keys should be reversed, as per the CouchDB API.
|
||||||
#
|
#
|
||||||
# Cannot be used if the key has been set.
|
# Cannot be used if the key has been set.
|
||||||
def startkey(value)
|
def startkey(value)
|
||||||
|
@ -116,17 +188,19 @@ module CouchRest
|
||||||
update_query(:startkey_docid => value.is_a?(String) ? value : value.id)
|
update_query(:startkey_docid => value.is_a?(String) ? value : value.id)
|
||||||
end
|
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)
|
def endkey(value)
|
||||||
raise "View#endkey cannot be used when key has been set" unless query[:key].nil?
|
raise "View#endkey cannot be used when key has been set" unless query[:key].nil?
|
||||||
update_query(:endkey => value)
|
update_query(:endkey => value)
|
||||||
end
|
end
|
||||||
|
|
||||||
# The result set should end at the position of the provided document.
|
# 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
|
# The value may be provided as an object that responds to the +#id+
|
||||||
# or a string.
|
# call or a string.
|
||||||
def endkey_doc(value)
|
def endkey_doc(value)
|
||||||
update_query(:endkey_docid => value.is_a?(String) ? value : value.id)
|
update_query(:endkey_docid => value.is_a?(String) ? value : value.id)
|
||||||
end
|
end
|
||||||
|
@ -134,7 +208,8 @@ module CouchRest
|
||||||
|
|
||||||
# The results should be provided in descending order.
|
# 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
|
def descending
|
||||||
update_query(:descending => true)
|
update_query(:descending => true)
|
||||||
end
|
end
|
||||||
|
@ -156,54 +231,77 @@ module CouchRest
|
||||||
|
|
||||||
# Use the reduce function on the view. If none is available this method will fail.
|
# Use the reduce function on the view. If none is available this method will fail.
|
||||||
def reduce
|
def reduce
|
||||||
|
raise "Cannot reduce a view without a reduce method" unless can_reduce?
|
||||||
update_query(:reduce => true)
|
update_query(:reduce => true)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Control whether the reduce function reduces to a set of distinct keys or to a single
|
# Control whether the reduce function reduces to a set of distinct keys
|
||||||
# result row.
|
# or to a single result row.
|
||||||
#
|
#
|
||||||
# By default the value is false, and can only be set when the view's +#reduce+ option
|
# By default the value is false, and can only be set when the view's
|
||||||
# has been set.
|
# +#reduce+ option has been set.
|
||||||
def group
|
def group
|
||||||
raise "View#reduce must have been set before grouping is permitted" unless query[:reduce]
|
raise "View#reduce must have been set before grouping is permitted" unless query[:reduce]
|
||||||
update_query(:group => true)
|
update_query(:group => true)
|
||||||
end
|
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)
|
def group_level(value)
|
||||||
raise "View#reduce and View#group must have been set before group_level is called" unless query[:reduce] && query[:group]
|
group.update_query(:group_level => value.to_i)
|
||||||
update_query(:group_level => value.to_i)
|
|
||||||
end
|
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
|
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 = {})
|
def update_query(new_query = {})
|
||||||
self.class.new(self, new_query)
|
self.class.new(self, new_query)
|
||||||
end
|
end
|
||||||
|
|
||||||
def database
|
def design_doc
|
||||||
query[:database] || model.database
|
model.design_doc
|
||||||
end
|
end
|
||||||
|
|
||||||
# Used internally to ensure that docs are provided. Should not be used outside of
|
def can_reduce?
|
||||||
# the view class under normal circumstances.
|
!design_doc['views'][name]['reduce'].blank?
|
||||||
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)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def execute(&block)
|
|
||||||
|
def execute
|
||||||
return self.result if result
|
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
|
retryable = true
|
||||||
# Remove the reduce value if its not needed
|
# 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
|
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
|
rescue RestClient::ResourceNotFound => e
|
||||||
if retryable
|
if retryable
|
||||||
model.save_design_doc(database)
|
model.save_design_doc(db)
|
||||||
retryable = false
|
retryable = false
|
||||||
retry
|
retry
|
||||||
else
|
else
|
||||||
|
@ -248,19 +346,24 @@ module CouchRest
|
||||||
emit = keys.length == 1 ? keys.first : "[#{keys.join(', ')}]"
|
emit = keys.length == 1 ? keys.first : "[#{keys.join(', ')}]"
|
||||||
opts[:guards] += keys.map{|k| "(#{k} != null)"}
|
opts[:guards] += keys.map{|k| "(#{k} != null)"}
|
||||||
opts[:map] = <<-EOF
|
opts[:map] = <<-EOF
|
||||||
function(doc) {
|
function(doc) {
|
||||||
if (#{opts[:guards].join(' && ')}) {
|
if (#{opts[:guards].join(' && ')}) {
|
||||||
emit(#{emit}, null);
|
emit(#{emit}, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
EOF
|
EOF
|
||||||
|
opts[:reduce] = <<-EOF
|
||||||
|
function(key, values, rereduce) {
|
||||||
|
return sum(values);
|
||||||
|
}
|
||||||
|
EOF
|
||||||
end
|
end
|
||||||
|
|
||||||
model.design_doc['views'] ||= {}
|
model.design_doc['views'] ||= {}
|
||||||
model.design_doc['views'][name.to_s] = {
|
view = model.design_doc['views'][name.to_s] = { }
|
||||||
'map' => opts[:map],
|
view['map'] = opts[:map]
|
||||||
'reduce' => opts[:reduce]
|
view['reduce'] = opts[:reduce] if opts[:reduce]
|
||||||
}
|
view
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -12,6 +12,8 @@ end
|
||||||
|
|
||||||
describe "Design View" do
|
describe "Design View" do
|
||||||
|
|
||||||
|
describe "(unit tests)" do
|
||||||
|
|
||||||
before :each do
|
before :each do
|
||||||
@klass = CouchRest::Model::Designs::View
|
@klass = CouchRest::Model::Designs::View
|
||||||
end
|
end
|
||||||
|
@ -72,14 +74,18 @@ describe "Design View" do
|
||||||
lambda { @klass.create(DesignViewModel, 'by_title') }.should_not raise_error
|
lambda { @klass.create(DesignViewModel, 'by_title') }.should_not raise_error
|
||||||
str = @design_doc['views']['by_title']['map']
|
str = @design_doc['views']['by_title']['map']
|
||||||
str.should include("((doc['couchrest-type'] == 'DesignViewModel') && (doc['title'] != null))")
|
str.should include("((doc['couchrest-type'] == 'DesignViewModel') && (doc['title'] != null))")
|
||||||
str.should include("emit(doc['title'], null);")
|
str.should include("emit(doc['title'], 1);")
|
||||||
|
str = @design_doc['views']['by_title']['reduce']
|
||||||
|
str.should include("return sum(values);")
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should auto generate mapping from name with and" do
|
it "should auto generate mapping from name with and" do
|
||||||
@klass.create(DesignViewModel, 'by_title_and_name')
|
@klass.create(DesignViewModel, 'by_title_and_name')
|
||||||
str = @design_doc['views']['by_title_and_name']['map']
|
str = @design_doc['views']['by_title_and_name']['map']
|
||||||
str.should include("(doc['title'] != null) && (doc['name'] != null)")
|
str.should include("(doc['title'] != null) && (doc['name'] != null)")
|
||||||
str.should include("emit([doc['title'], 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
|
end
|
||||||
|
@ -90,6 +96,351 @@ describe "Design View" do
|
||||||
@obj = @klass.new(DesignViewModel, {}, 'test_view')
|
@obj = @klass.new(DesignViewModel, {}, 'test_view')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
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
|
describe "#update_query" do
|
||||||
it "returns a new instance of view" do
|
it "returns a new instance of view" do
|
||||||
@obj.send(:update_query).object_id.should_not eql(@obj.object_id)
|
@obj.send(:update_query).object_id.should_not eql(@obj.object_id)
|
||||||
|
@ -99,29 +450,115 @@ describe "Design View" do
|
||||||
new_obj = @obj.send(:update_query, {:foo => :bar})
|
new_obj = @obj.send(:update_query, {:foo => :bar})
|
||||||
new_obj.query[:foo].should eql(:bar)
|
new_obj.query[:foo].should eql(:bar)
|
||||||
end
|
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
|
end
|
||||||
|
end
|
||||||
|
|
||||||
##############
|
|
||||||
|
|
||||||
describe "with real data" do
|
describe "scenarios" do
|
||||||
|
|
||||||
before :all do
|
before :all do
|
||||||
@objs = [
|
@objs = [
|
||||||
{:name => "Sam"},
|
{:name => "Judith"},
|
||||||
{:name => "Lorena"},
|
{:name => "Lorena"},
|
||||||
{:name => "Peter"},
|
{:name => "Peter"},
|
||||||
{:name => "Judith"},
|
{:name => "Sam"},
|
||||||
{:name => "Vilma"}
|
{:name => "Vilma"}
|
||||||
].map{|h| DesignViewModel.create(h)}
|
].map{|h| DesignViewModel.create(h)}
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "just documents" do
|
describe "loading documents" do
|
||||||
|
|
||||||
it "should return all" do
|
it "should return first" do
|
||||||
DesignViewModel.by_name.all.last.name.should eql("Vilma")
|
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
|
||||||
|
|
||||||
end
|
end
|
||||||
|
@ -139,7 +576,6 @@ describe "Design View" do
|
||||||
it "should provide a set of keys" do
|
it "should provide a set of keys" do
|
||||||
DesignViewModel.by_name.limit(2).keys.should eql(["Judith", "Lorena"])
|
DesignViewModel.by_name.limit(2).keys.should eql(["Judith", "Lorena"])
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -12,20 +12,21 @@ describe "Design" do
|
||||||
|
|
||||||
describe ".design" do
|
describe ".design" do
|
||||||
|
|
||||||
it "should instantiate a new DesignMapper" do
|
before :each do
|
||||||
CouchRest::Model::Designs::DesignMapper.should_receive(:new).and_return(DesignModel)
|
@mapper = mock('DesignMapper')
|
||||||
DesignModel.design() { }
|
@mapper.stub!(:create_view_method)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should instantiate a new DesignMapper with model" do
|
it "should instantiate a new DesignMapper" do
|
||||||
CouchRest::Model::Designs::DesignMapper.should_receive(:new).with(DesignModel).and_return(DesignModel)
|
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() { }
|
DesignModel.design() { }
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should allow methods to be called in mapper" do
|
it "should allow methods to be called in mapper" do
|
||||||
model = mock('Foo')
|
@mapper.should_receive(:foo)
|
||||||
model.should_receive(:foo)
|
CouchRest::Model::Designs::DesignMapper.stub!(:new).and_return(@mapper)
|
||||||
CouchRest::Model::Designs::DesignMapper.stub!(:new).and_return(model)
|
|
||||||
DesignModel.design { foo }
|
DesignModel.design { foo }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -64,10 +65,22 @@ describe "Design" do
|
||||||
DesignModel.should respond_to(:test_view)
|
DesignModel.should respond_to(:test_view)
|
||||||
end
|
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)
|
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)
|
CouchRest::Model::Designs::View.should_receive(:new).with(DesignModel, {}, 'test_view').and_return(nil)
|
||||||
|
@object.create_view_method('test_view')
|
||||||
DesignModel.test_view
|
DesignModel.test_view
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,7 @@ RSpec.configure do |config|
|
||||||
cr = TEST_SERVER
|
cr = TEST_SERVER
|
||||||
test_dbs = cr.databases.select { |db| db =~ /^#{TESTDB}/ }
|
test_dbs = cr.databases.select { |db| db =~ /^#{TESTDB}/ }
|
||||||
test_dbs.each do |db|
|
test_dbs.each do |db|
|
||||||
#cr.database(db).delete! rescue nil
|
cr.database(db).delete! rescue nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue