diff --git a/lib/couchrest/extended_document.rb b/lib/couchrest/extended_document.rb index 808c530..84c86d1 100644 --- a/lib/couchrest/extended_document.rb +++ b/lib/couchrest/extended_document.rb @@ -140,7 +140,7 @@ module CouchRest elsif m.to_s =~ /^find_(by_.+)/ view_name = $1 if has_view?(view_name) - return find_first_from_view(view_name, *args) + return first_from_view(view_name, *args) end end super diff --git a/lib/couchrest/mixins/class_proxy.rb b/lib/couchrest/mixins/class_proxy.rb index adc208d..ab8daf6 100644 --- a/lib/couchrest/mixins/class_proxy.rb +++ b/lib/couchrest/mixins/class_proxy.rb @@ -94,9 +94,10 @@ module CouchRest docs end - def find_first_from_view(name, *args) - (args[1] ||= {})[:database] = @database - doc = @klass.find_first_from_view(name, args) + def first_from_view(name, *args) + # add to first hash available, or add to end + (args.last.is_a?(Hash) ? args.last : (args << {}).last)[:database] = @database + doc = @klass.first_from_view(name, *args) doc.database = @database if doc && doc.respond_to?(:database) doc end diff --git a/lib/couchrest/mixins/views.rb b/lib/couchrest/mixins/views.rb index a5ea221..f4b55a8 100644 --- a/lib/couchrest/mixins/views.rb +++ b/lib/couchrest/mixins/views.rb @@ -102,15 +102,24 @@ module CouchRest fetch_view_with_docs(db, name, query, raw, &block) end - # Find the first entry that matches the provided key. - # Request like: + # Find the first entry in the view. If the second parameter is a string + # it will be used as the key for the request, for example: # - # Course.find_first_from_view('teachers', 'Fred') + # Course.first_from_view('by_teacher', 'Fred') # - def find_first_from_view(name, *args) - key = args[0] - query = args[1] || {} - query.update(:limit => 1, :key => key) + # More advanced requests can be performed by providing a hash: + # + # Course.first_from_view('by_teacher', :startkey => 'bbb', :endkey => 'eee') + # + def first_from_view(name, *args) + query = {:limit => 1} + case args.first + when String, Array + query.update(args[1]) unless args[1].nil? + query[:key] = args.first + when Hash + query.update(args.first) + end view(name, query).first end diff --git a/spec/couchrest/class_proxy_spec.rb b/spec/couchrest/class_proxy_spec.rb new file mode 100644 index 0000000..f8d5f92 --- /dev/null +++ b/spec/couchrest/class_proxy_spec.rb @@ -0,0 +1,128 @@ +require File.expand_path("../../spec_helper", __FILE__) + +class UnattachedDoc < CouchRest::ExtendedDocument + # Note: no use_database here + property :title + property :questions + property :professor + view_by :title +end + + +describe "Proxy Class" do + + before(:all) do + reset_test_db! + # setup the class default doc to save the design doc + UnattachedDoc.use_database nil # just to be sure it is really unattached + @us = UnattachedDoc.on(DB) + %w{aaa bbb ddd eee}.each do |title| + u = @us.new(:title => title) + u.save + @first_id ||= u.id + end + end + + it "should query all" do + rs = @us.all + rs.length.should == 4 + end + it "should count" do + @us.count.should == 4 + end + it "should make the design doc upon first query" do + @us.by_title + doc = @us.design_doc + doc['views']['all']['map'].should include('UnattachedDoc') + end + it "should merge query params" do + rs = @us.by_title :startkey=>"bbb", :endkey=>"eee" + rs.length.should == 3 + end + it "should query via view" do + view = @us.view :by_title + designed = @us.by_title + view.should == designed + end + + it "should query via first_from_view" do + UnattachedDoc.should_receive(:first_from_view).with('by_title', 'bbb', {:database => DB}) + @us.first_from_view('by_title', 'bbb') + end + + it "should query via first_from_view with complex options" do + UnattachedDoc.should_receive(:first_from_view).with('by_title', {:key => 'bbb', :database => DB}) + @us.first_from_view('by_title', :key => 'bbb') + end + + it "should query via first_from_view with complex extra options" do + UnattachedDoc.should_receive(:first_from_view).with('by_title', 'bbb', {:limit => 1, :database => DB}) + @us.first_from_view('by_title', 'bbb', :limit => 1) + end + + + it "should yield" do + things = [] + @us.view(:by_title) do |thing| + things << thing + end + things[0]["doc"]["title"].should =='aaa' + end + it "should yield with by_key method" do + things = [] + @us.by_title do |thing| + things << thing + end + things[0]["doc"]["title"].should =='aaa' + end + it "should get from specific database" do + u = @us.get(@first_id) + u.title.should == "aaa" + end + it "should get first" do + u = @us.first + u.title.should =~ /\A...\z/ + end + it "should set database on first retreived document" do + u = @us.first + u.database.should === DB + end + it "should set database on all retreived documents" do + @us.all.each do |u| + u.database.should === DB + end + end + it "should set database on each retreived document" do + rs = @us.by_title :startkey=>"bbb", :endkey=>"eee" + rs.length.should == 3 + rs.each do |u| + u.database.should === DB + end + end + it "should set database on document retreived by id" do + u = @us.get(@first_id) + u.database.should === DB + end + it "should not attempt to set database on raw results using :all" do + @us.all(:raw => true).each do |u| + u.respond_to?(:database).should be_false + end + end + it "should not attempt to set database on raw results using view" do + @us.by_title(:raw => true).each do |u| + u.respond_to?(:database).should be_false + end + end + # Sam Lown 2010-04-07 + # Removed as unclear why this should happen as before my changes + # this happend by accident, not explicitly. + # If requested, this feature should be added as a specific method. + # + #it "should clean up design docs left around on specific database" do + # @us.by_title + # original_id = @us.model_design_doc['_rev'] + # Unattached.view_by :professor + # @us.by_professor + # @us.model_design_doc['_rev'].should_not == original_id + #end +end diff --git a/spec/couchrest/extended_doc_view_spec.rb b/spec/couchrest/extended_doc_view_spec.rb index 124347b..53115f6 100644 --- a/spec/couchrest/extended_doc_view_spec.rb +++ b/spec/couchrest/extended_doc_view_spec.rb @@ -152,6 +152,21 @@ describe "ExtendedDocument views" do lambda { Course.find_by_foobar('123') }.should raise_error(NoMethodError) end + it "should perform a search directly with specific key" do + course = Course.first_from_view('by_title', 'bbb') + course.title.should eql('bbb') + end + + it "should perform a search directly with specific key with options" do + course = Course.first_from_view('by_title', 'bbb', :reverse => true) + course.title.should eql('bbb') + end + + it "should perform a search directly with range" do + course = Course.first_from_view('by_title', :startkey => 'bbb', :endkey => 'eee') + course.title.should eql('bbb') + end + end describe "a ducktype view" do @@ -255,105 +270,6 @@ describe "ExtendedDocument views" do end end - describe "class proxy" do - before(:all) do - reset_test_db! - # setup the class default doc to save the design doc - Unattached.use_database nil # just to be sure it is really unattached - @us = Unattached.on(DB) - %w{aaa bbb ddd eee}.each do |title| - u = @us.new(:title => title) - u.save - @first_id ||= u.id - end - end - it "should query all" do - rs = @us.all - rs.length.should == 4 - end - it "should count" do - @us.count.should == 4 - end - it "should make the design doc upon first query" do - @us.by_title - doc = @us.design_doc - doc['views']['all']['map'].should include('Unattached') - end - it "should merge query params" do - rs = @us.by_title :startkey=>"bbb", :endkey=>"eee" - rs.length.should == 3 - end - it "should query via view" do - view = @us.view :by_title - designed = @us.by_title - view.should == designed - end - it "should yield" do - things = [] - @us.view(:by_title) do |thing| - things << thing - end - things[0]["doc"]["title"].should =='aaa' - end - it "should yield with by_key method" do - things = [] - @us.by_title do |thing| - things << thing - end - things[0]["doc"]["title"].should =='aaa' - end - it "should get from specific database" do - u = @us.get(@first_id) - u.title.should == "aaa" - end - it "should get first" do - u = @us.first - u.title.should =~ /\A...\z/ - end - it "should set database on first retreived document" do - u = @us.first - u.database.should === DB - end - it "should set database on all retreived documents" do - @us.all.each do |u| - u.database.should === DB - end - end - it "should set database on each retreived document" do - rs = @us.by_title :startkey=>"bbb", :endkey=>"eee" - rs.length.should == 3 - rs.each do |u| - u.database.should === DB - end - end - it "should set database on document retreived by id" do - u = @us.get(@first_id) - u.database.should === DB - end - it "should not attempt to set database on raw results using :all" do - @us.all(:raw => true).each do |u| - u.respond_to?(:database).should be_false - end - end - it "should not attempt to set database on raw results using view" do - @us.by_title(:raw => true).each do |u| - u.respond_to?(:database).should be_false - end - end - # Sam Lown 2010-04-07 - # Removed as unclear why this should happen as before my changes - # this happend by accident, not explicitly. - # If requested, this feature should be added as a specific method. - # - #it "should clean up design docs left around on specific database" do - # @us.by_title - # original_id = @us.model_design_doc['_rev'] - # Unattached.view_by :professor - # @us.by_professor - # @us.model_design_doc['_rev'].should_not == original_id - #end - end - describe "a model with a compound key view" do before(:all) do Article.by_user_id_and_date.each{|a| a.destroy(true)}