Lots of advances on design view code, more testing required

This commit is contained in:
Sam Lown 2011-02-06 04:32:23 +01:00
parent dc28155aa3
commit 800c2b322c
7 changed files with 105 additions and 41 deletions

View file

@ -16,7 +16,7 @@ module CouchRest
include CouchRest::Model::PropertyProtection
include CouchRest::Model::Associations
include CouchRest::Model::Validations
include CouchRest::Model::Design
include CouchRest::Model::Designs
def self.subclasses
@subclasses ||= []

View file

@ -16,7 +16,7 @@ module CouchRest
# end
# end
#
module Design
module Designs
extend ActiveSupport::Concern
module ClassMethods
@ -45,7 +45,7 @@ module CouchRest
View.create(model, name, opts)
model.class_eval <<-EOS, __FILE__, __LINE__ + 1
def self.#{name}(opts = {})
CouchRest::Model::Design::View.new(self, opts, '#{name}')
CouchRest::Model::Designs::View.new(self, opts, '#{name}')
end
EOS
end

View file

@ -1,6 +1,6 @@
module CouchRest
module Model
module Design
module Designs
#
# A proxy class that allows view queries to be created using
@ -42,15 +42,16 @@ module CouchRest
# Inmediatly send a request to the database for all documents provided by the query.
#
def all(&block)
include_docs.execute(&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.
# 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).include_docs.execute.first
limit(1).all.first
end
def info
end
@ -64,11 +65,16 @@ module CouchRest
end
def rows
@rows ||= execute['rows'].map{|v| ViewRow.new(v, model)}
return @rows if @rows
if execute && result['rows']
@rows ||= result['rows'].map{|v| ViewRow.new(v, model)}
else
[ ]
end
end
def keys
execute['rows'].map{|r| r.key}
rows.map{|r| r.key}
end
@ -175,15 +181,35 @@ module CouchRest
self.class.new(self, new_query)
end
def database
query[:database] || model.database
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)
end
def execute(&block)
self.result ||= model.view(name, query, &block)
return self.result if result
raise "Database must be defined in model or view!" if database.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?
begin
self.result = model.design_doc.view_on(database, name, query, &block)
rescue RestClient::ResourceNotFound => e
if retryable
model.save_design_doc(database)
retryable = false
retry
else
raise e
end
end
end
# Class Methods
@ -208,7 +234,6 @@ module CouchRest
# subsecuent index.
#
def create(model, name, opts = {})
views = model.design_doc['views'] ||= {}
unless opts[:map]
if opts[:by].nil? && name =~ /^by_(.+)/
@ -221,17 +246,20 @@ module CouchRest
keys = opts[:by].map{|o| "doc['#{o}']"}
emit = keys.length == 1 ? keys.first : "[#{keys.join(', ')}]"
opts[:map] =
"function(doc) {" +
" if (#{opts[:guards].join(' && ')}) {" +
" emit(#{emit}, null);" +
" }" +
"}"
opts[:guards] += keys.map{|k| "(#{k} != null)"}
opts[:map] = <<-EOF
function(doc) {
if (#{opts[:guards].join(' && ')}) {
emit(#{emit}, null);
}
}
EOF
end
views[name.to_s] = {
:map => opts[:map],
:reduce => opts[:reduce] || false,
model.design_doc['views'] ||= {}
model.design_doc['views'][name.to_s] = {
'map' => opts[:map],
'reduce' => opts[:reduce]
}
end
@ -245,21 +273,25 @@ module CouchRest
attr_accessor :model
def initialize(hash, model)
self.model = model
super(hash)
replace(hash)
end
def id
["id"]
self["id"]
end
def key
["key"]
self["key"]
end
def value
['value']
self['value']
end
def raw_doc
self['doc']
end
# Send a request for the linked document either using the "id" field's
# value, or the ["value"]["_id"] used for linked documents.
def doc
doc_id = value['_id'] || self.id
return model.create_from_database(self['doc']) if self['doc']
doc_id = (value && value['_id']) ? value['_id'] : self.id
model.get(doc_id)
end
end

View file

@ -40,8 +40,8 @@ require "couchrest/model/class_proxy"
require "couchrest/model/collection"
require "couchrest/model/associations"
require "couchrest/model/configuration"
require "couchrest/model/design"
require "couchrest/model/design/view"
require "couchrest/model/designs"
require "couchrest/model/designs/view"
# Monkey patches applied to couchrest
require "couchrest/model/support/couchrest"

View file

@ -1,7 +1,9 @@
require File.expand_path("../../../spec_helper", __FILE__)
class DesignViewModel < CouchRest::Model::Base
use_database DB
property :name
property :title
design do
view :by_name
@ -11,7 +13,7 @@ end
describe "Design View" do
before :each do
@klass = CouchRest::Model::Design::View
@klass = CouchRest::Model::Designs::View
end
describe ".new" do
@ -56,14 +58,28 @@ describe "Design View" do
describe ".create" do
before :all 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')
@design_doc['test_view'].should_not be_nil
@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
@ -110,6 +126,22 @@ describe "Design View" do
end
describe "index information" do
it "should provide total_rows" do
DesignViewModel.by_name.total_rows.should eql(5)
end
it "should provide total_rows" do
DesignViewModel.by_name.total_rows.should eql(5)
end
it "should provide an offset" do
DesignViewModel.by_name.offset.should eql(0)
end
it "should provide a set of keys" do
DesignViewModel.by_name.limit(2).keys.should eql(["Judith", "Lorena"])
end
end
end

View file

@ -13,19 +13,19 @@ describe "Design" do
describe ".design" do
it "should instantiate a new DesignMapper" do
CouchRest::Model::Design::DesignMapper.should_receive(:new).and_return(DesignModel)
CouchRest::Model::Designs::DesignMapper.should_receive(:new).and_return(DesignModel)
DesignModel.design() { }
end
it "should instantiate a new DesignMapper with model" do
CouchRest::Model::Design::DesignMapper.should_receive(:new).with(DesignModel).and_return(DesignModel)
CouchRest::Model::Designs::DesignMapper.should_receive(:new).with(DesignModel).and_return(DesignModel)
DesignModel.design() { }
end
it "should allow methods to be called in mapper" do
model = mock('Foo')
model.should_receive(:foo)
CouchRest::Model::Design::DesignMapper.stub!(:new).and_return(model)
CouchRest::Model::Designs::DesignMapper.stub!(:new).and_return(model)
DesignModel.design { foo }
end
@ -39,7 +39,7 @@ describe "Design" do
describe "DesignMapper" do
before :all do
@klass = CouchRest::Model::Design::DesignMapper
@klass = CouchRest::Model::Designs::DesignMapper
end
it "should initialize and set model" do
@ -54,20 +54,20 @@ describe "Design" do
end
it "should call create method on view" do
CouchRest::Model::Design::View.should_receive(:create).with(DesignModel, 'test', {})
CouchRest::Model::Designs::View.should_receive(:create).with(DesignModel, 'test', {})
@object.view('test')
end
it "should create a method on parent model" do
CouchRest::Model::Design::View.stub!(:create)
CouchRest::Model::Designs::View.stub!(:create)
@object.view('test_view')
DesignModel.should respond_to(:test_view)
end
it "should create a method that returns view instance" do
CouchRest::Model::Design::View.stub!(:create)
CouchRest::Model::Designs::View.stub!(:create)
@object.view('test_view')
CouchRest::Model::Design::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)
DesignModel.test_view
end

View file

@ -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