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::PropertyProtection
include CouchRest::Model::Associations include CouchRest::Model::Associations
include CouchRest::Model::Validations include CouchRest::Model::Validations
include CouchRest::Model::Design include CouchRest::Model::Designs
def self.subclasses def self.subclasses
@subclasses ||= [] @subclasses ||= []

View file

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

View file

@ -1,6 +1,6 @@
module CouchRest module CouchRest
module Model module Model
module Design module Designs
# #
# A proxy class that allows view queries to be created using # 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. # Inmediatly send a request to the database for all documents provided by the query.
# #
def all(&block) def all(&block)
include_docs.execute(&block) include_docs.rows.map{|r| r.doc}
end end
# Inmediatly send a request for the first result of the dataset. This will override # Inmediatly send a request for the first result of the dataset.
# any limit set in the view previously. # This will override any limit set in the view previously.
def first def first
limit(1).include_docs.execute.first limit(1).all.first
end end
def info def info
end end
@ -64,11 +65,16 @@ module CouchRest
end end
def rows 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 end
def keys def keys
execute['rows'].map{|r| r.key} rows.map{|r| r.key}
end end
@ -175,15 +181,35 @@ module CouchRest
self.class.new(self, new_query) self.class.new(self, new_query)
end end
def database
query[:database] || model.database
end
# Used internally to ensure that docs are provided. Should not be used outside of # Used internally to ensure that docs are provided. Should not be used outside of
# the view class under normal circumstances. # the view class under normal circumstances.
def include_docs def include_docs
raise "Documents cannot be returned from a view that is prepared for a reduce" if query[:reduce] 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) update_query(:include_docs => true)
end end
def execute(&block) 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 end
# Class Methods # Class Methods
@ -208,7 +234,6 @@ module CouchRest
# subsecuent index. # subsecuent index.
# #
def create(model, name, opts = {}) def create(model, name, opts = {})
views = model.design_doc['views'] ||= {}
unless opts[:map] unless opts[:map]
if opts[:by].nil? && name =~ /^by_(.+)/ if opts[:by].nil? && name =~ /^by_(.+)/
@ -221,17 +246,20 @@ module CouchRest
keys = opts[:by].map{|o| "doc['#{o}']"} keys = opts[:by].map{|o| "doc['#{o}']"}
emit = keys.length == 1 ? keys.first : "[#{keys.join(', ')}]" emit = keys.length == 1 ? keys.first : "[#{keys.join(', ')}]"
opts[:map] = opts[:guards] += keys.map{|k| "(#{k} != null)"}
"function(doc) {" + opts[:map] = <<-EOF
" if (#{opts[:guards].join(' && ')}) {" + function(doc) {
" emit(#{emit}, null);" + if (#{opts[:guards].join(' && ')}) {
" }" + emit(#{emit}, null);
"}" }
}
EOF
end end
views[name.to_s] = { model.design_doc['views'] ||= {}
:map => opts[:map], model.design_doc['views'][name.to_s] = {
:reduce => opts[:reduce] || false, 'map' => opts[:map],
'reduce' => opts[:reduce]
} }
end end
@ -245,21 +273,25 @@ module CouchRest
attr_accessor :model attr_accessor :model
def initialize(hash, model) def initialize(hash, model)
self.model = model self.model = model
super(hash) replace(hash)
end end
def id def id
["id"] self["id"]
end end
def key def key
["key"] self["key"]
end end
def value def value
['value'] self['value']
end
def raw_doc
self['doc']
end end
# Send a request for the linked document either using the "id" field's # Send a request for the linked document either using the "id" field's
# value, or the ["value"]["_id"] used for linked documents. # value, or the ["value"]["_id"] used for linked documents.
def doc 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) model.get(doc_id)
end end
end end

View file

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

View file

@ -1,7 +1,9 @@
require File.expand_path("../../../spec_helper", __FILE__) require File.expand_path("../../../spec_helper", __FILE__)
class DesignViewModel < CouchRest::Model::Base class DesignViewModel < CouchRest::Model::Base
use_database DB
property :name property :name
property :title
design do design do
view :by_name view :by_name
@ -11,7 +13,7 @@ end
describe "Design View" do describe "Design View" do
before :each do before :each do
@klass = CouchRest::Model::Design::View @klass = CouchRest::Model::Designs::View
end end
describe ".new" do describe ".new" do
@ -56,14 +58,28 @@ describe "Design View" do
describe ".create" do describe ".create" do
before :all do before :each do
@design_doc = {} @design_doc = {}
DesignViewModel.stub!(:design_doc).and_return(@design_doc) DesignViewModel.stub!(:design_doc).and_return(@design_doc)
end end
it "should add a basic view" do it "should add a basic view" do
@klass.create(DesignViewModel, 'test_view') @klass.create(DesignViewModel, 'test_view', :map => 'foo')
@design_doc['test_view'].should_not be_nil @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
end end
@ -110,6 +126,22 @@ describe "Design View" do
end 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 end

View file

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

View file

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