require "spec_helper"


describe CouchRest::Model::Designs do

  it "should accessable from model" do
    DesignModel.respond_to?(:design).should be_true
  end

  describe "class methods" do

    describe ".design" do

      before :each do
        @klass = DesignsModel.dup
      end

      describe "without block" do
        it "should create design_doc and all methods" do
          @klass.design
          @klass.should respond_to(:design_doc)
          @klass.should respond_to(:all)
        end

        it "should created named design_doc method and not all" do
          @klass.design :stats
          @klass.should respond_to(:stats_design_doc)
          @klass.should_not respond_to(:all)
        end

        it "should have added itself to a design_blocks array" do
          @klass.design
          blocks = @klass.instance_variable_get(:@_design_blocks)
          blocks.length.should eql(1)
          blocks.first.should eql({:args => [], :block => nil})
        end

        it "should have added itself to a design_blocks array" do
          @klass.design
          blocks = @klass.instance_variable_get(:@_design_blocks)
          blocks.length.should eql(1)
          blocks.first.should eql({:args => [], :block => nil})
        end

        it "should have added itself to a design_blocks array with prefix" do
          @klass.design :stats
          blocks = @klass.instance_variable_get(:@_design_blocks)
          blocks.length.should eql(1)
          blocks.first.should eql({:args => [:stats], :block => nil})
        end
      end

      describe "with block" do
        before :each do
          @block = Proc.new do
            disable_auto_update
          end
          @klass.design &@block
        end

        it "should pass calls to mapper" do
          @klass.design_doc.auto_update.should be_false
        end

        it "should have added itself to a design_blocks array" do
          blocks = @klass.instance_variable_get(:@_design_blocks)
          blocks.length.should eql(1)
          blocks.first.should eql({:args => [], :block => @block})
        end

        it "should handle multiple designs" do
          @block2 = Proc.new do
            view :by_name
          end
          @klass.design :stats, &@block2
          blocks = @klass.instance_variable_get(:@_design_blocks)
          blocks.length.should eql(2)
          blocks.first.should eql({:args => [], :block => @block})
          blocks.last.should eql({:args => [:stats], :block => @block2})
        end
      end

    end

    describe "inheritance" do
      before :each do
        klass = DesignModel.dup
        klass.design do
          view :by_name
        end
        @klass = Class.new(klass)
      end

      it "should add designs to sub module" do
        @klass.should respond_to(:design_doc)
      end

    end

    describe "default_per_page" do
      it "should return 25 default" do
        DesignModel.default_per_page.should eql(25)
      end
    end

    describe ".paginates_per" do
      it "should set the default per page value" do
        DesignModel.paginates_per(21)
        DesignModel.default_per_page.should eql(21)
      end
    end
  end

  describe "Scenario testing" do

    describe "with auto update disabled" do

      before :all do
        reset_test_db!
        @mod = DesignsNoAutoUpdate
      end

      before(:all) do
        id = @mod.to_s
        doc = CouchRest::Document.new("_id" => "_design/#{id}")
        doc["language"] = "javascript"
        doc["views"] = {"all"     => {"map" => "function(doc) { if (doc['type'] == '#{id}') { emit(doc['_id'],1); } }"},
                        "by_title" => {"map" => 
                                  "function(doc) {
                                     if ((doc['type'] == '#{id}') && (doc['title'] != null)) {
                                       emit(doc['title'], 1);
                                     }
                                   }", "reduce" => "function(k,v,r) { return sum(v); }"}}
        DB.save_doc doc
      end

      it "will fail if reduce is not specific in view" do
        @mod.create(:title => 'This is a test')
        lambda { @mod.by_title_fail.first }.should raise_error(RestClient::ResourceNotFound)
      end

      it "will perform view request" do
        @mod.create(:title => 'This is a test')
        @mod.by_title.first.title.should eql("This is a test")
      end

    end

    describe "using views" do

      describe "to find a single item" do
  
        before(:all) do
          reset_test_db!
          %w{aaa bbb ddd eee}.each do |title|
            Course.new(:title => title, :active => (title == 'bbb')).save
          end
        end

        it "should return single matched record with find helper" do
          course = Course.find_by_title('bbb')
          course.should_not be_nil
          course.title.should eql('bbb') # Ensure really is a Course!
        end

        it "should return nil if not found" do
          course = Course.find_by_title('fff')
          course.should be_nil
        end

        it "should peform search on view with two properties" do
          course = Course.find_by_title_and_active(['bbb', true])
          course.should_not be_nil
          course.title.should eql('bbb') # Ensure really is a Course!
        end

        it "should return nil if not found" do
          course = Course.find_by_title_and_active(['bbb', false])
          course.should be_nil
        end

        it "should raise exception if view not present" do
          lambda { Course.find_by_foobar('123') }.should raise_error(NoMethodError)
        end

      end

      describe "a model class with database provided manually" do

        class Unattached < CouchRest::Model::Base
          property :title
          property :questions
          property :professor
          design do
            view :by_title
          end

          # Force the database to always be nil
          def self.database
            nil
          end
        end

        before(:all) do
          reset_test_db!
          @db = DB 
          %w{aaa bbb ddd eee}.each do |title|
            u = Unattached.new(:title => title)
            u.database = @db
            u.save
            @first_id ||= u.id
          end
        end
        it "should barf on all if no database given" do
          lambda{Unattached.all.first}.should raise_error
        end
        it "should query all" do
          rs = Unattached.all.database(@db).all
          rs.length.should == 4
        end
        it "should barf on query if no database given" do
          lambda{Unattached.by_title.all}.should raise_error /Database must be defined/
        end
        it "should make the design doc upon first query" do
          Unattached.by_title.database(@db)
          doc = Unattached.design_doc
          doc['views']['all']['map'].should include('Unattached')
        end
        it "should merge query params" do
          rs = Unattached.by_title.database(@db).startkey("bbb").endkey("eee")
          rs.length.should == 3
        end
        it "should return nil on get if no database given" do
          Unattached.get("aaa").should be_nil
        end
        it "should barf on get! if no database given" do
          lambda{Unattached.get!("aaa")}.should raise_error
        end
        it "should get from specific database" do
          u = Unattached.get(@first_id, @db)
          u.title.should == "aaa"
        end
        it "should barf on first if no database given" do
          lambda{Unattached.first}.should raise_error
        end
        it "should get first" do
          u = Unattached.all.database(@db).first
          u.title.should =~ /\A...\z/
        end
        it "should get last" do
          u = Unattached.all.database(@db).last
          u.title.should == "aaa"
        end

      end

      describe "a model with a compound key view" do
        before(:all) do
          reset_test_db!
          written_at = Time.now - 24 * 3600 * 7
          @titles    = ["uniq one", "even more interesting", "less fun", "not junk"]
          @user_ids  = ["quentin", "aaron"]
          @titles.each_with_index do |title,i|
            u = i % 2
            a = Article.new(:title => title, :user_id => @user_ids[u])
            a.date = written_at
            a.save
            written_at += 24 * 3600
          end
        end
        it "should create the design doc" do
          Article.by_user_id_and_date rescue nil
          doc = Article.design_doc
          doc['views']['by_date'].should_not be_nil
        end
        it "should sort correctly" do
          articles = Article.by_user_id_and_date.all
          articles.collect{|a|a['user_id']}.should == ['aaron', 'aaron', 'quentin', 
            'quentin']
          articles[1].title.should == 'not junk'
        end
        it "should be queryable with couchrest options" do
          articles = Article.by_user_id_and_date(:limit => 1, :startkey => 'quentin').all
          articles.length.should == 1
          articles[0].title.should == "even more interesting"
        end
      end


    end

  end

end