diff --git a/lib/couchrest/model/base.rb b/lib/couchrest/model/base.rb index f555c9f..b49c675 100644 --- a/lib/couchrest/model/base.rb +++ b/lib/couchrest/model/base.rb @@ -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 ||= [] diff --git a/lib/couchrest/model/design.rb b/lib/couchrest/model/designs.rb similarity index 92% rename from lib/couchrest/model/design.rb rename to lib/couchrest/model/designs.rb index 9249d31..cd5e7d4 100644 --- a/lib/couchrest/model/design.rb +++ b/lib/couchrest/model/designs.rb @@ -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 diff --git a/lib/couchrest/model/design/view.rb b/lib/couchrest/model/designs/view.rb similarity index 82% rename from lib/couchrest/model/design/view.rb rename to lib/couchrest/model/designs/view.rb index 5196703..da7c219 100644 --- a/lib/couchrest/model/design/view.rb +++ b/lib/couchrest/model/designs/view.rb @@ -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 diff --git a/lib/couchrest_model.rb b/lib/couchrest_model.rb index a0e3069..bc5faf5 100644 --- a/lib/couchrest_model.rb +++ b/lib/couchrest_model.rb @@ -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" diff --git a/spec/couchrest/design/view_spec.rb b/spec/couchrest/designs/view_spec.rb similarity index 62% rename from spec/couchrest/design/view_spec.rb rename to spec/couchrest/designs/view_spec.rb index de8fb40..c83fd5b 100644 --- a/spec/couchrest/design/view_spec.rb +++ b/spec/couchrest/designs/view_spec.rb @@ -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 diff --git a/spec/couchrest/design_spec.rb b/spec/couchrest/designs_spec.rb similarity index 69% rename from spec/couchrest/design_spec.rb rename to spec/couchrest/designs_spec.rb index 5543964..cee7665 100644 --- a/spec/couchrest/design_spec.rb +++ b/spec/couchrest/designs_spec.rb @@ -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 diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 3a865b5..415a2c7 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -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