Merge remote branch 'refs/remotes/canonical/master' into design-mapper-more-auto-update-design-doc-aware
This commit is contained in:
commit
88bb413ec2
66 changed files with 866 additions and 423 deletions
30
spec/unit/active_model_lint_spec.rb
Normal file
30
spec/unit/active_model_lint_spec.rb
Normal file
|
@ -0,0 +1,30 @@
|
|||
# encoding: utf-8
|
||||
require 'spec_helper'
|
||||
require 'test/unit/assertions'
|
||||
require 'active_model/lint'
|
||||
|
||||
class CompliantModel < CouchRest::Model::Base
|
||||
end
|
||||
|
||||
|
||||
describe CouchRest::Model::Base do
|
||||
include Test::Unit::Assertions
|
||||
include ActiveModel::Lint::Tests
|
||||
|
||||
before :each do
|
||||
@model = CompliantModel.new
|
||||
end
|
||||
|
||||
describe "active model lint tests" do
|
||||
ActiveModel::Lint::Tests.public_instance_methods.map{|m| m.to_s}.grep(/^test/).each do |m|
|
||||
example m.gsub('_',' ') do
|
||||
send m
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def model
|
||||
@model
|
||||
end
|
||||
|
||||
end
|
242
spec/unit/assocations_spec.rb
Normal file
242
spec/unit/assocations_spec.rb
Normal file
|
@ -0,0 +1,242 @@
|
|||
# encoding: utf-8
|
||||
require 'spec_helper'
|
||||
|
||||
describe "Assocations" do
|
||||
|
||||
describe ".merge_belongs_to_association_options" do
|
||||
before :all do
|
||||
def SaleInvoice.merge_assoc_opts(*args)
|
||||
merge_belongs_to_association_options(*args)
|
||||
end
|
||||
end
|
||||
|
||||
it "should return a default set of options" do
|
||||
o = SaleInvoice.merge_assoc_opts(:cat)
|
||||
o[:foreign_key].should eql('cat_id')
|
||||
o[:class_name].should eql('Cat')
|
||||
o[:proxy_name].should eql('cats')
|
||||
o[:proxy].should eql('Cat') # same as class name
|
||||
end
|
||||
|
||||
it "should merge with provided options" do
|
||||
o = SaleInvoice.merge_assoc_opts(:cat, :foreign_key => 'somecat_id', :proxy => 'some_cats')
|
||||
o[:foreign_key].should eql('somecat_id')
|
||||
o[:proxy].should eql('some_cats')
|
||||
end
|
||||
|
||||
it "should generate a proxy string if proxied" do
|
||||
SaleInvoice.stub!(:proxy_owner_method).twice.and_return('company')
|
||||
o = SaleInvoice.merge_assoc_opts(:cat)
|
||||
o[:proxy].should eql('self.company.cats')
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe "of type belongs to" do
|
||||
|
||||
before :each do
|
||||
@invoice = SaleInvoice.create(:price => 2000)
|
||||
@client = Client.create(:name => "Sam Lown")
|
||||
end
|
||||
|
||||
it "should create a foreign key property with setter and getter" do
|
||||
@invoice.properties.find{|p| p.name == 'client_id'}.should_not be_nil
|
||||
@invoice.respond_to?(:client_id).should be_true
|
||||
@invoice.respond_to?("client_id=").should be_true
|
||||
end
|
||||
|
||||
it "should set the property and provide object when set" do
|
||||
@invoice.client = @client
|
||||
@invoice.client_id.should eql(@client.id)
|
||||
@invoice.client.should eql(@client)
|
||||
end
|
||||
|
||||
it "should set the attribute, save and return" do
|
||||
@invoice.client = @client
|
||||
@invoice.save
|
||||
@invoice = SaleInvoice.get(@invoice.id)
|
||||
@invoice.client.id.should eql(@client.id)
|
||||
end
|
||||
|
||||
it "should remove the association if nil is provided" do
|
||||
@invoice.client = @client
|
||||
@invoice.client = nil
|
||||
@invoice.client_id.should be_nil
|
||||
end
|
||||
|
||||
it "should not try to search for association if foreign_key is nil" do
|
||||
@invoice.client_id = nil
|
||||
Client.should_not_receive(:get)
|
||||
@invoice.client
|
||||
end
|
||||
|
||||
it "should allow override of foreign key" do
|
||||
@invoice.respond_to?(:alternate_client).should be_true
|
||||
@invoice.respond_to?("alternate_client=").should be_true
|
||||
@invoice.properties.find{|p| p.name == 'alt_client_id'}.should_not be_nil
|
||||
end
|
||||
|
||||
it "should allow override of foreign key and save" do
|
||||
@invoice.alternate_client = @client
|
||||
@invoice.save
|
||||
@invoice = SaleInvoice.get(@invoice.id)
|
||||
@invoice.alternate_client.id.should eql(@client.id)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe "of type collection_of" do
|
||||
|
||||
before(:each) do
|
||||
@invoice = SaleInvoice.create(:price => 2000)
|
||||
@entries = [
|
||||
SaleEntry.create(:description => 'test line 1', :price => 500),
|
||||
SaleEntry.create(:description => 'test line 2', :price => 500),
|
||||
SaleEntry.create(:description => 'test line 3', :price => 1000)
|
||||
]
|
||||
end
|
||||
|
||||
it "should create an associated property and collection proxy" do
|
||||
@invoice.respond_to?('entry_ids').should be_true
|
||||
@invoice.respond_to?('entry_ids=').should be_true
|
||||
@invoice.entries.class.should eql(::CouchRest::Model::CollectionOfProxy)
|
||||
end
|
||||
|
||||
it "should allow replacement of objects" do
|
||||
@invoice.entries = @entries
|
||||
@invoice.entries.length.should eql(3)
|
||||
@invoice.entry_ids.length.should eql(3)
|
||||
@invoice.entries.first.should eql(@entries.first)
|
||||
@invoice.entry_ids.first.should eql(@entries.first.id)
|
||||
end
|
||||
|
||||
it "should allow ids to be set directly and load entries" do
|
||||
@invoice.entry_ids = @entries.collect{|i| i.id}
|
||||
@invoice.entries.length.should eql(3)
|
||||
@invoice.entries.first.should eql(@entries.first)
|
||||
end
|
||||
|
||||
it "should replace collection if ids replaced" do
|
||||
@invoice.entry_ids = @entries.collect{|i| i.id}
|
||||
@invoice.entries.length.should eql(3) # load once
|
||||
@invoice.entry_ids = @entries[0..1].collect{|i| i.id}
|
||||
@invoice.entries.length.should eql(2)
|
||||
end
|
||||
|
||||
it "should allow forced collection update if ids changed" do
|
||||
@invoice.entry_ids = @entries[0..1].collect{|i| i.id}
|
||||
@invoice.entries.length.should eql(2) # load once
|
||||
@invoice.entry_ids << @entries[2].id
|
||||
@invoice.entry_ids.length.should eql(3)
|
||||
@invoice.entries.length.should eql(2) # cached!
|
||||
@invoice.entries(true).length.should eql(3)
|
||||
end
|
||||
|
||||
it "should empty arrays when nil collection provided" do
|
||||
@invoice.entries = @entries
|
||||
@invoice.entries = nil
|
||||
@invoice.entry_ids.should be_empty
|
||||
@invoice.entries.should be_empty
|
||||
end
|
||||
|
||||
it "should empty arrays when nil ids array provided" do
|
||||
@invoice.entries = @entries
|
||||
@invoice.entry_ids = nil
|
||||
@invoice.entry_ids.should be_empty
|
||||
@invoice.entries.should be_empty
|
||||
end
|
||||
|
||||
it "should ignore nil entries" do
|
||||
@invoice.entries = [ nil ]
|
||||
@invoice.entry_ids.should be_empty
|
||||
@invoice.entries.should be_empty
|
||||
end
|
||||
|
||||
# Account for dirty tracking
|
||||
describe "dirty tracking" do
|
||||
it "should register changes on push" do
|
||||
@invoice.changed?.should be_false
|
||||
@invoice.entries << @entries[0]
|
||||
@invoice.changed?.should be_true
|
||||
end
|
||||
it "should register changes on pop" do
|
||||
@invoice.entries << @entries[0]
|
||||
@invoice.save
|
||||
@invoice.changed?.should be_false
|
||||
@invoice.entries.pop
|
||||
@invoice.changed?.should be_true
|
||||
end
|
||||
it "should register id changes on push" do
|
||||
@invoice.entry_ids << @entries[0].id
|
||||
@invoice.changed?.should be_true
|
||||
end
|
||||
it "should register id changes on pop" do
|
||||
@invoice.entry_ids << @entries[0].id
|
||||
@invoice.save
|
||||
@invoice.changed?.should be_false
|
||||
@invoice.entry_ids.pop
|
||||
@invoice.changed?.should be_true
|
||||
end
|
||||
end
|
||||
|
||||
describe "proxy" do
|
||||
|
||||
it "should ensure new entries to proxy are matched" do
|
||||
@invoice.entries << @entries.first
|
||||
@invoice.entry_ids.first.should eql(@entries.first.id)
|
||||
@invoice.entries.first.should eql(@entries.first)
|
||||
@invoice.entries << @entries[1]
|
||||
@invoice.entries.count.should eql(2)
|
||||
@invoice.entry_ids.count.should eql(2)
|
||||
@invoice.entry_ids.last.should eql(@entries[1].id)
|
||||
@invoice.entries.last.should eql(@entries[1])
|
||||
end
|
||||
|
||||
it "should support push method" do
|
||||
@invoice.entries.push(@entries.first)
|
||||
@invoice.entry_ids.first.should eql(@entries.first.id)
|
||||
end
|
||||
|
||||
it "should support []= method" do
|
||||
@invoice.entries[0] = @entries.first
|
||||
@invoice.entry_ids.first.should eql(@entries.first.id)
|
||||
end
|
||||
|
||||
it "should support unshift method" do
|
||||
@invoice.entries.unshift(@entries.first)
|
||||
@invoice.entry_ids.first.should eql(@entries.first.id)
|
||||
@invoice.entries.unshift(@entries[1])
|
||||
@invoice.entry_ids.first.should eql(@entries[1].id)
|
||||
end
|
||||
|
||||
it "should support pop method" do
|
||||
@invoice.entries.push(@entries.first)
|
||||
@invoice.entries.pop.should eql(@entries.first)
|
||||
@invoice.entries.empty?.should be_true
|
||||
@invoice.entry_ids.empty?.should be_true
|
||||
end
|
||||
|
||||
it "should support shift method" do
|
||||
@invoice.entries.push(@entries[0])
|
||||
@invoice.entries.push(@entries[1])
|
||||
@invoice.entries.shift.should eql(@entries[0])
|
||||
@invoice.entries.first.should eql(@entries[1])
|
||||
@invoice.entry_ids.first.should eql(@entries[1].id)
|
||||
end
|
||||
|
||||
it "should raise error when adding un-persisted entries" do
|
||||
SaleEntry.find_by_description('test entry').should be_nil
|
||||
entry = SaleEntry.new(:description => 'test entry', :price => 500)
|
||||
lambda {
|
||||
@invoice.entries << entry
|
||||
}.should raise_error
|
||||
# In the future maybe?
|
||||
# @invoice.save.should be_true
|
||||
# SaleEntry.find_by_description('test entry').should_not be_nil
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
176
spec/unit/attachment_spec.rb
Normal file
176
spec/unit/attachment_spec.rb
Normal file
|
@ -0,0 +1,176 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe "Model attachments" do
|
||||
|
||||
describe "#has_attachment?" do
|
||||
before(:each) do
|
||||
reset_test_db!
|
||||
@obj = Basic.new
|
||||
@obj.save.should be_true
|
||||
@file = File.open(FIXTURE_PATH + '/attachments/test.html')
|
||||
@attachment_name = 'my_attachment'
|
||||
@obj.create_attachment(:file => @file, :name => @attachment_name)
|
||||
end
|
||||
|
||||
it 'should return false if there is no attachment' do
|
||||
@obj.has_attachment?('bogus').should be_false
|
||||
end
|
||||
|
||||
it 'should return true if there is an attachment' do
|
||||
@obj.has_attachment?(@attachment_name).should be_true
|
||||
end
|
||||
|
||||
it 'should return true if an object with an attachment is reloaded' do
|
||||
@obj.save.should be_true
|
||||
reloaded_obj = Basic.get(@obj.id)
|
||||
reloaded_obj.has_attachment?(@attachment_name).should be_true
|
||||
end
|
||||
|
||||
it 'should return false if an attachment has been removed' do
|
||||
@obj.delete_attachment(@attachment_name)
|
||||
@obj.has_attachment?(@attachment_name).should be_false
|
||||
end
|
||||
|
||||
it 'should return false if an attachment has been removed and reloaded' do
|
||||
@obj.delete_attachment(@attachment_name)
|
||||
reloaded_obj = Basic.get(@obj.id)
|
||||
reloaded_obj.has_attachment?(@attachment_name).should be_false
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe "creating an attachment" do
|
||||
before(:each) do
|
||||
@obj = Basic.new
|
||||
@obj.save.should be_true
|
||||
@file_ext = File.open(FIXTURE_PATH + '/attachments/test.html')
|
||||
@file_no_ext = File.open(FIXTURE_PATH + '/attachments/README')
|
||||
@attachment_name = 'my_attachment'
|
||||
@content_type = 'media/mp3'
|
||||
end
|
||||
|
||||
it "should create an attachment from file with an extension" do
|
||||
@obj.create_attachment(:file => @file_ext, :name => @attachment_name)
|
||||
@obj.save.should be_true
|
||||
reloaded_obj = Basic.get(@obj.id)
|
||||
reloaded_obj.attachments[@attachment_name].should_not be_nil
|
||||
end
|
||||
|
||||
it "should create an attachment from file without an extension" do
|
||||
@obj.create_attachment(:file => @file_no_ext, :name => @attachment_name)
|
||||
@obj.save.should be_true
|
||||
reloaded_obj = Basic.get(@obj.id)
|
||||
reloaded_obj.attachments[@attachment_name].should_not be_nil
|
||||
end
|
||||
|
||||
it 'should raise ArgumentError if :file is missing' do
|
||||
lambda{ @obj.create_attachment(:name => @attachment_name) }.should raise_error
|
||||
end
|
||||
|
||||
it 'should raise ArgumentError if :name is missing' do
|
||||
lambda{ @obj.create_attachment(:file => @file_ext) }.should raise_error
|
||||
end
|
||||
|
||||
it 'should set the content-type if passed' do
|
||||
@obj.create_attachment(:file => @file_ext, :name => @attachment_name, :content_type => @content_type)
|
||||
@obj.attachments[@attachment_name]['content_type'].should == @content_type
|
||||
end
|
||||
|
||||
it "should detect the content-type automatically" do
|
||||
@obj.create_attachment(:file => File.open(FIXTURE_PATH + '/attachments/couchdb.png'), :name => "couchdb.png")
|
||||
@obj.attachments['couchdb.png']['content_type'].should == "image/png"
|
||||
end
|
||||
|
||||
it "should use name to detect the content-type automatically if no file" do
|
||||
file = File.open(FIXTURE_PATH + '/attachments/couchdb.png')
|
||||
file.stub!(:path).and_return("badfilname")
|
||||
@obj.create_attachment(:file => File.open(FIXTURE_PATH + '/attachments/couchdb.png'), :name => "couchdb.png")
|
||||
@obj.attachments['couchdb.png']['content_type'].should == "image/png"
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe 'reading, updating, and deleting an attachment' do
|
||||
before(:each) do
|
||||
@obj = Basic.new
|
||||
@file = File.open(FIXTURE_PATH + '/attachments/test.html')
|
||||
@attachment_name = 'my_attachment'
|
||||
@obj.create_attachment(:file => @file, :name => @attachment_name)
|
||||
@obj.save.should be_true
|
||||
@file.rewind
|
||||
@content_type = 'media/mp3'
|
||||
end
|
||||
|
||||
it 'should read an attachment that exists' do
|
||||
@obj.read_attachment(@attachment_name).should == @file.read
|
||||
end
|
||||
|
||||
it 'should update an attachment that exists' do
|
||||
file = File.open(FIXTURE_PATH + '/attachments/README')
|
||||
@file.should_not == file
|
||||
@obj.update_attachment(:file => file, :name => @attachment_name)
|
||||
@obj.save
|
||||
reloaded_obj = Basic.get(@obj.id)
|
||||
file.rewind
|
||||
reloaded_obj.read_attachment(@attachment_name).should_not == @file.read
|
||||
reloaded_obj.read_attachment(@attachment_name).should == file.read
|
||||
end
|
||||
|
||||
it 'should set the content-type if passed' do
|
||||
file = File.open(FIXTURE_PATH + '/attachments/README')
|
||||
@file.should_not == file
|
||||
@obj.update_attachment(:file => file, :name => @attachment_name, :content_type => @content_type)
|
||||
@obj.attachments[@attachment_name]['content_type'].should == @content_type
|
||||
end
|
||||
|
||||
it 'should delete an attachment that exists' do
|
||||
@obj.delete_attachment(@attachment_name)
|
||||
@obj.save
|
||||
lambda{Basic.get(@obj.id).read_attachment(@attachment_name)}.should raise_error
|
||||
end
|
||||
end
|
||||
|
||||
describe "#attachment_url" do
|
||||
before(:each) do
|
||||
@obj = Basic.new
|
||||
@file = File.open(FIXTURE_PATH + '/attachments/test.html')
|
||||
@attachment_name = 'my_attachment'
|
||||
@obj.create_attachment(:file => @file, :name => @attachment_name)
|
||||
@obj.save.should be_true
|
||||
end
|
||||
|
||||
it 'should return nil if attachment does not exist' do
|
||||
@obj.attachment_url('bogus').should be_nil
|
||||
end
|
||||
|
||||
it 'should return the attachment URL as specified by CouchDB HttpDocumentApi' do
|
||||
@obj.attachment_url(@attachment_name).should == "#{Basic.database}/#{@obj.id}/#{@attachment_name}"
|
||||
end
|
||||
|
||||
it 'should return the attachment URI' do
|
||||
@obj.attachment_uri(@attachment_name).should == "#{Basic.database.uri}/#{@obj.id}/#{@attachment_name}"
|
||||
end
|
||||
end
|
||||
|
||||
describe "#attachments" do
|
||||
before(:each) do
|
||||
@obj = Basic.new
|
||||
@file = File.open(FIXTURE_PATH + '/attachments/test.html')
|
||||
@attachment_name = 'my_attachment'
|
||||
@obj.create_attachment(:file => @file, :name => @attachment_name)
|
||||
@obj.save.should be_true
|
||||
end
|
||||
|
||||
it 'should return an empty Hash when document does not have any attachment' do
|
||||
new_obj = Basic.new
|
||||
new_obj.save.should be_true
|
||||
new_obj.attachments.should == {}
|
||||
end
|
||||
|
||||
it 'should return a Hash with all attachments' do
|
||||
@file.rewind
|
||||
@obj.attachments.should == { @attachment_name =>{ "data" => "PCFET0NUWVBFIGh0bWw+CjxodG1sPgogIDxoZWFkPgogICAgPHRpdGxlPlRlc3Q8L3RpdGxlPgogIDwvaGVhZD4KICA8Ym9keT4KICAgIDxwPgogICAgICBUZXN0CiAgICA8L3A+CiAgPC9ib2R5Pgo8L2h0bWw+Cg==", "content_type" => "text/html"}}
|
||||
end
|
||||
|
||||
end
|
||||
end
|
537
spec/unit/base_spec.rb
Normal file
537
spec/unit/base_spec.rb
Normal file
|
@ -0,0 +1,537 @@
|
|||
# encoding: utf-8
|
||||
require "spec_helper"
|
||||
|
||||
describe "Model Base" do
|
||||
|
||||
before(:each) do
|
||||
@obj = WithDefaultValues.new
|
||||
end
|
||||
|
||||
describe "instance database connection" do
|
||||
it "should use the default database" do
|
||||
@obj.database.name.should == 'couchrest-model-test'
|
||||
end
|
||||
|
||||
it "should override the default db" do
|
||||
@obj.database = TEST_SERVER.database!('couchrest-extendedmodel-test')
|
||||
@obj.database.name.should == 'couchrest-extendedmodel-test'
|
||||
@obj.database.delete!
|
||||
end
|
||||
end
|
||||
|
||||
describe "a new model" do
|
||||
it "should be a new document" do
|
||||
@obj = Basic.new
|
||||
@obj.rev.should be_nil
|
||||
@obj.should be_new
|
||||
@obj.should be_new_document
|
||||
@obj.should be_new_record
|
||||
end
|
||||
|
||||
it "should not fail with nil argument" do
|
||||
@obj = Basic.new(nil)
|
||||
@obj.should_not be_nil
|
||||
end
|
||||
|
||||
it "should allow the database to be set" do
|
||||
@obj = Basic.new(nil, :database => 'database')
|
||||
@obj.database.should eql('database')
|
||||
end
|
||||
|
||||
it "should support initialization block" do
|
||||
@obj = Basic.new {|b| b.database = 'database'}
|
||||
@obj.database.should eql('database')
|
||||
end
|
||||
|
||||
it "should only set defined properties" do
|
||||
@doc = WithDefaultValues.new(:name => 'test', :foo => 'bar')
|
||||
@doc['name'].should eql('test')
|
||||
@doc['foo'].should be_nil
|
||||
end
|
||||
|
||||
it "should set all properties with :directly_set_attributes option" do
|
||||
@doc = WithDefaultValues.new({:name => 'test', :foo => 'bar'}, :directly_set_attributes => true)
|
||||
@doc['name'].should eql('test')
|
||||
@doc['foo'].should eql('bar')
|
||||
end
|
||||
|
||||
it "should set the model type" do
|
||||
@doc = WithDefaultValues.new()
|
||||
@doc[WithDefaultValues.model_type_key].should eql('WithDefaultValues')
|
||||
end
|
||||
|
||||
it "should call after_initialize method if available" do
|
||||
@doc = WithAfterInitializeMethod.new
|
||||
@doc['some_value'].should eql('value')
|
||||
end
|
||||
|
||||
it "should call after_initialize after block" do
|
||||
@doc = WithAfterInitializeMethod.new {|d| d.some_value = "foo"}
|
||||
@doc['some_value'].should eql('foo')
|
||||
end
|
||||
|
||||
it "should call after_initialize callback if available" do
|
||||
klass = Class.new(CouchRest::Model::Base)
|
||||
klass.class_eval do # for ruby 1.8.7
|
||||
property :name
|
||||
after_initialize :set_name
|
||||
def set_name; self.name = "foobar"; end
|
||||
end
|
||||
@doc = klass.new
|
||||
@doc.name.should eql("foobar")
|
||||
end
|
||||
end
|
||||
|
||||
describe "ActiveModel compatability Basic" do
|
||||
|
||||
before(:each) do
|
||||
@obj = Basic.new(nil)
|
||||
end
|
||||
|
||||
describe "#to_key" do
|
||||
context "when the document is new" do
|
||||
it "returns nil" do
|
||||
@obj.to_key.should be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context "when the document is not new" do
|
||||
it "returns id in an array" do
|
||||
@obj.save
|
||||
@obj.to_key.should eql([@obj['_id']])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#to_param" do
|
||||
context "when the document is new" do
|
||||
it "returns nil" do
|
||||
@obj.to_param.should be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context "when the document is not new" do
|
||||
it "returns id" do
|
||||
@obj.save
|
||||
@obj.to_param.should eql(@obj['_id'])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#persisted?" do
|
||||
context "when the document is new" do
|
||||
it "returns false" do
|
||||
@obj.persisted?.should be_false
|
||||
end
|
||||
end
|
||||
|
||||
context "when the document is not new" do
|
||||
it "returns id" do
|
||||
@obj.save
|
||||
@obj.persisted?.should be_true
|
||||
end
|
||||
end
|
||||
|
||||
context "when the document is destroyed" do
|
||||
it "returns false" do
|
||||
@obj.save
|
||||
@obj.destroy
|
||||
@obj.persisted?.should be_false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#model_name" do
|
||||
it "returns the name of the model" do
|
||||
@obj.class.model_name.should eql('Basic')
|
||||
WithDefaultValues.model_name.human.should eql("With default values")
|
||||
end
|
||||
end
|
||||
|
||||
describe "#destroyed?" do
|
||||
it "should be present" do
|
||||
@obj.should respond_to(:destroyed?)
|
||||
end
|
||||
it "should return false with new object" do
|
||||
@obj.destroyed?.should be_false
|
||||
end
|
||||
it "should return true after destroy" do
|
||||
@obj.save
|
||||
@obj.destroy
|
||||
@obj.destroyed?.should be_true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "comparisons" do
|
||||
describe "#==" do
|
||||
context "on saved document" do
|
||||
it "should be true on same document" do
|
||||
p = Project.create
|
||||
p.should eql(p)
|
||||
end
|
||||
it "should be true after loading" do
|
||||
p = Project.create
|
||||
p.should eql(Project.get(p.id))
|
||||
end
|
||||
it "should not be true if databases do not match" do
|
||||
p = Project.create
|
||||
p2 = p.dup
|
||||
p2.stub!(:database).and_return('other')
|
||||
p.should_not eql(p2)
|
||||
end
|
||||
it "should always be false if one document not saved" do
|
||||
p = Project.create(:name => 'test')
|
||||
o = Project.new(:name => 'test')
|
||||
p.should_not eql(o)
|
||||
end
|
||||
end
|
||||
context "with new documents" do
|
||||
it "should be true when attributes match" do
|
||||
p = Project.new(:name => 'test')
|
||||
o = Project.new(:name => 'test')
|
||||
p.should eql(o)
|
||||
end
|
||||
it "should not be true when attributes don't match" do
|
||||
p = Project.new(:name => 'test')
|
||||
o = Project.new(:name => 'testing')
|
||||
p.should_not eql(o)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "update attributes without saving" do
|
||||
before(:each) do
|
||||
a = Article.get "big-bad-danger" rescue nil
|
||||
a.destroy if a
|
||||
@art = Article.new(:title => "big bad danger")
|
||||
@art.save
|
||||
end
|
||||
it "should work for attribute= methods" do
|
||||
@art['title'].should == "big bad danger"
|
||||
@art.update_attributes_without_saving('date' => Time.now, :title => "super danger")
|
||||
@art['title'].should == "super danger"
|
||||
end
|
||||
it "should silently ignore _id" do
|
||||
@art.update_attributes_without_saving('_id' => 'foobar')
|
||||
@art['_id'].should_not == 'foobar'
|
||||
end
|
||||
it "should silently ignore _rev" do
|
||||
@art.update_attributes_without_saving('_rev' => 'foobar')
|
||||
@art['_rev'].should_not == 'foobar'
|
||||
end
|
||||
it "should silently ignore created_at" do
|
||||
@art.update_attributes_without_saving('created_at' => 'foobar')
|
||||
@art['created_at'].should_not == 'foobar'
|
||||
end
|
||||
it "should silently ignore updated_at" do
|
||||
@art.update_attributes_without_saving('updated_at' => 'foobar')
|
||||
@art['updated_at'].should_not == 'foobar'
|
||||
end
|
||||
it "should also work using attributes= alias" do
|
||||
@art.respond_to?(:attributes=).should be_true
|
||||
@art.attributes = {'date' => Time.now, :title => "something else"}
|
||||
@art['title'].should == "something else"
|
||||
end
|
||||
|
||||
it "should not flip out if an attribute= method is missing and ignore it" do
|
||||
lambda {
|
||||
@art.update_attributes_without_saving('slug' => "new-slug", :title => "super danger")
|
||||
}.should_not raise_error
|
||||
@art.slug.should == "big-bad-danger"
|
||||
end
|
||||
|
||||
#it "should not change other attributes if there is an error" do
|
||||
# lambda {
|
||||
# @art.update_attributes_without_saving('slug' => "new-slug", :title => "super danger")
|
||||
# }.should raise_error
|
||||
# @art['title'].should == "big bad danger"
|
||||
#end
|
||||
end
|
||||
|
||||
describe "update attributes" do
|
||||
before(:each) do
|
||||
a = Article.get "big-bad-danger" rescue nil
|
||||
a.destroy if a
|
||||
@art = Article.new(:title => "big bad danger")
|
||||
@art.save
|
||||
end
|
||||
it "should save" do
|
||||
@art['title'].should == "big bad danger"
|
||||
@art.update_attributes('date' => Time.now, :title => "super danger")
|
||||
loaded = Article.get(@art.id)
|
||||
loaded['title'].should == "super danger"
|
||||
end
|
||||
end
|
||||
|
||||
describe "with default" do
|
||||
it "should have the default value set at initalization" do
|
||||
@obj.preset.should == {:right => 10, :top_align => false}
|
||||
end
|
||||
|
||||
it "should have the default false value explicitly assigned" do
|
||||
@obj.default_false.should == false
|
||||
end
|
||||
|
||||
it "should automatically call a proc default at initialization" do
|
||||
@obj.set_by_proc.should be_an_instance_of(Time)
|
||||
@obj.set_by_proc.should == @obj.set_by_proc
|
||||
@obj.set_by_proc.should < Time.now
|
||||
end
|
||||
|
||||
it "should let you overwrite the default values" do
|
||||
obj = WithDefaultValues.new(:preset => 'test')
|
||||
obj.preset = 'test'
|
||||
end
|
||||
|
||||
it "should keep default values for new instances" do
|
||||
obj = WithDefaultValues.new
|
||||
obj.preset[:alpha] = 123
|
||||
obj.preset.should == {:right => 10, :top_align => false, :alpha => 123}
|
||||
another = WithDefaultValues.new
|
||||
another.preset.should == {:right => 10, :top_align => false}
|
||||
end
|
||||
|
||||
it "should work with a default empty array" do
|
||||
obj = WithDefaultValues.new(:tags => ['spec'])
|
||||
obj.tags.should == ['spec']
|
||||
end
|
||||
|
||||
it "should set default value of read-only property" do
|
||||
obj = WithDefaultValues.new
|
||||
obj.read_only_with_default.should == 'generic'
|
||||
end
|
||||
end
|
||||
|
||||
describe "simplified way of setting property types" do
|
||||
it "should set defaults" do
|
||||
obj = WithSimplePropertyType.new
|
||||
obj.preset.should eql('none')
|
||||
end
|
||||
|
||||
it "should handle arrays" do
|
||||
obj = WithSimplePropertyType.new(:tags => ['spec'])
|
||||
obj.tags.should == ['spec']
|
||||
end
|
||||
end
|
||||
|
||||
describe "a doc with template values (CR::Model spec)" do
|
||||
before(:all) do
|
||||
WithTemplateAndUniqueID.all.map{|o| o.destroy}
|
||||
WithTemplateAndUniqueID.database.bulk_delete
|
||||
@tmpl = WithTemplateAndUniqueID.new
|
||||
@tmpl2 = WithTemplateAndUniqueID.new(:preset => 'not_value', 'slug' => '1')
|
||||
end
|
||||
it "should have fields set when new" do
|
||||
@tmpl.preset.should == 'value'
|
||||
end
|
||||
it "shouldn't override explicitly set values" do
|
||||
@tmpl2.preset.should == 'not_value'
|
||||
end
|
||||
it "shouldn't override existing documents" do
|
||||
@tmpl2.save
|
||||
tmpl2_reloaded = WithTemplateAndUniqueID.get(@tmpl2.id)
|
||||
@tmpl2.preset.should == 'not_value'
|
||||
tmpl2_reloaded.preset.should == 'not_value'
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
describe "finding all instances of a model" do
|
||||
before(:all) do
|
||||
WithTemplateAndUniqueID.all.map{|o| o.destroy}
|
||||
WithTemplateAndUniqueID.database.bulk_delete
|
||||
WithTemplateAndUniqueID.new('slug' => '1').save
|
||||
WithTemplateAndUniqueID.new('slug' => '2').save
|
||||
WithTemplateAndUniqueID.new('slug' => '3').save
|
||||
WithTemplateAndUniqueID.new('slug' => '4').save
|
||||
end
|
||||
it "should find all" do
|
||||
rs = WithTemplateAndUniqueID.all
|
||||
rs.length.should == 4
|
||||
end
|
||||
end
|
||||
|
||||
describe "counting all instances of a model" do
|
||||
before(:each) do
|
||||
@db = reset_test_db!
|
||||
end
|
||||
|
||||
it ".count should return 0 if there are no docuemtns" do
|
||||
WithTemplateAndUniqueID.count.should == 0
|
||||
end
|
||||
|
||||
it ".count should return the number of documents" do
|
||||
WithTemplateAndUniqueID.new('slug' => '1').save
|
||||
WithTemplateAndUniqueID.new('slug' => '2').save
|
||||
WithTemplateAndUniqueID.new('slug' => '3').save
|
||||
|
||||
WithTemplateAndUniqueID.count.should == 3
|
||||
end
|
||||
end
|
||||
|
||||
describe "finding the first instance of a model" do
|
||||
before(:each) do
|
||||
@db = reset_test_db!
|
||||
WithTemplateAndUniqueID.new('slug' => '1').save
|
||||
WithTemplateAndUniqueID.new('slug' => '2').save
|
||||
WithTemplateAndUniqueID.new('slug' => '3').save
|
||||
WithTemplateAndUniqueID.new('slug' => '4').save
|
||||
end
|
||||
it "should find first" do
|
||||
rs = WithTemplateAndUniqueID.first
|
||||
rs['slug'].should == "1"
|
||||
end
|
||||
it "should return nil if no instances are found" do
|
||||
WithTemplateAndUniqueID.all.each {|obj| obj.destroy }
|
||||
WithTemplateAndUniqueID.first.should be_nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
describe "getting a model with a subobject field" do
|
||||
before(:all) do
|
||||
course_doc = {
|
||||
"title" => "Metaphysics 410",
|
||||
"professor" => {
|
||||
"name" => ["Mark", "Hinchliff"]
|
||||
},
|
||||
"ends_at" => "2008/12/19 13:00:00 +0800"
|
||||
}
|
||||
r = Course.database.save_doc course_doc
|
||||
@course = Course.get r['id']
|
||||
end
|
||||
it "should load the course" do
|
||||
@course["professor"]["name"][1].should == "Hinchliff"
|
||||
end
|
||||
it "should instantiate the professor as a person" do
|
||||
@course['professor'].last_name.should == "Hinchliff"
|
||||
end
|
||||
it "should instantiate the ends_at as a Time" do
|
||||
@course['ends_at'].should == Time.parse("2008/12/19 13:00:00 +0800")
|
||||
end
|
||||
end
|
||||
|
||||
describe "timestamping" do
|
||||
before(:each) do
|
||||
oldart = Article.get "saving-this" rescue nil
|
||||
oldart.destroy if oldart
|
||||
@art = Article.new(:title => "Saving this")
|
||||
@art.save
|
||||
end
|
||||
|
||||
it "should define the updated_at and created_at getters and set the values" do
|
||||
@obj.save
|
||||
obj = WithDefaultValues.get(@obj.id)
|
||||
obj.should be_an_instance_of(WithDefaultValues)
|
||||
obj.created_at.should be_an_instance_of(Time)
|
||||
obj.updated_at.should be_an_instance_of(Time)
|
||||
obj.created_at.to_s.should == @obj.updated_at.to_s
|
||||
end
|
||||
|
||||
it "should not change created_at on update" do
|
||||
2.times do
|
||||
lambda do
|
||||
@art.save
|
||||
end.should_not change(@art, :created_at)
|
||||
end
|
||||
end
|
||||
|
||||
it "should set the time on create" do
|
||||
(Time.now - @art.created_at).should < 2
|
||||
foundart = Article.get @art.id
|
||||
foundart.created_at.should == foundart.updated_at
|
||||
end
|
||||
it "should set the time on update" do
|
||||
@art.title = "new title" # only saved if @art.changed? == true
|
||||
@art.save
|
||||
@art.created_at.should < @art.updated_at
|
||||
end
|
||||
end
|
||||
|
||||
describe "getter and setter methods" do
|
||||
it "should try to call the arg= method before setting :arg in the hash" do
|
||||
@doc = WithGetterAndSetterMethods.new(:arg => "foo")
|
||||
@doc['arg'].should be_nil
|
||||
@doc[:arg].should be_nil
|
||||
@doc.other_arg.should == "foo-foo"
|
||||
end
|
||||
end
|
||||
|
||||
describe "recursive validation on a model" do
|
||||
before :each do
|
||||
reset_test_db!
|
||||
@cat = Cat.new(:name => 'Sockington')
|
||||
end
|
||||
|
||||
it "should not save if a nested casted model is invalid" do
|
||||
@cat.favorite_toy = CatToy.new
|
||||
@cat.should_not be_valid
|
||||
@cat.save.should be_false
|
||||
lambda{@cat.save!}.should raise_error
|
||||
end
|
||||
|
||||
it "should save when nested casted model is valid" do
|
||||
@cat.favorite_toy = CatToy.new(:name => 'Squeaky')
|
||||
@cat.should be_valid
|
||||
@cat.save.should be_true
|
||||
lambda{@cat.save!}.should_not raise_error
|
||||
end
|
||||
|
||||
it "should not save when nested collection contains an invalid casted model" do
|
||||
@cat.toys = [CatToy.new(:name => 'Feather'), CatToy.new]
|
||||
@cat.should_not be_valid
|
||||
@cat.save.should be_false
|
||||
lambda{@cat.save!}.should raise_error
|
||||
end
|
||||
|
||||
it "should save when nested collection contains valid casted models" do
|
||||
@cat.toys = [CatToy.new(:name => 'feather'), CatToy.new(:name => 'ball-o-twine')]
|
||||
@cat.should be_valid
|
||||
@cat.save.should be_true
|
||||
lambda{@cat.save!}.should_not raise_error
|
||||
end
|
||||
|
||||
it "should not fail if the nested casted model doesn't have validation" do
|
||||
Cat.property :trainer, Person
|
||||
Cat.validates_presence_of :name
|
||||
cat = Cat.new(:name => 'Mr Bigglesworth')
|
||||
cat.trainer = Person.new
|
||||
cat.should be_valid
|
||||
cat.save.should be_true
|
||||
end
|
||||
end
|
||||
|
||||
describe "searching the contents of a model" do
|
||||
before :each do
|
||||
@db = reset_test_db!
|
||||
|
||||
names = ["Fuzzy", "Whiskers", "Mr Bigglesworth", "Sockington", "Smitty", "Sammy", "Samson", "Simon"]
|
||||
names.each { |name| Cat.create(:name => name) }
|
||||
|
||||
search_function = { 'defaults' => {'store' => 'no', 'index' => 'analyzed_no_norms'},
|
||||
'index' => "function(doc) { ret = new Document(); ret.add(doc['name'], {'field':'name'}); return ret; }" }
|
||||
@db.save_doc({'_id' => '_design/search', 'fulltext' => {'cats' => search_function}})
|
||||
end
|
||||
|
||||
it "should be able to paginate through a large set of search results" do
|
||||
if couchdb_lucene_available?
|
||||
names = []
|
||||
Cat.paginated_each(:design_doc => "search", :view_name => "cats",
|
||||
:q => 'name:S*', :search => true, :include_docs => true, :per_page => 3) do |cat|
|
||||
cat.should_not be_nil
|
||||
names << cat.name
|
||||
end
|
||||
|
||||
names.size.should == 5
|
||||
names.should include('Sockington')
|
||||
names.should include('Smitty')
|
||||
names.should include('Sammy')
|
||||
names.should include('Samson')
|
||||
names.should include('Simon')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
72
spec/unit/casted_spec.rb
Normal file
72
spec/unit/casted_spec.rb
Normal file
|
@ -0,0 +1,72 @@
|
|||
require "spec_helper"
|
||||
|
||||
class Driver < CouchRest::Model::Base
|
||||
use_database TEST_SERVER.default_database
|
||||
# You have to add a casted_by accessor if you want to reach a casted extended doc parent
|
||||
attr_accessor :casted_by
|
||||
|
||||
property :name
|
||||
end
|
||||
|
||||
class Car < CouchRest::Model::Base
|
||||
use_database TEST_SERVER.default_database
|
||||
|
||||
property :name
|
||||
property :driver, Driver
|
||||
end
|
||||
|
||||
describe "casting an extended document" do
|
||||
|
||||
before(:each) do
|
||||
@driver = Driver.new(:name => 'Matt')
|
||||
@car = Car.new(:name => 'Renault 306', :driver => @driver)
|
||||
end
|
||||
|
||||
it "should retain all properties of the casted attribute" do
|
||||
@car.driver.should == @driver
|
||||
end
|
||||
|
||||
it "should let the casted document know who casted it" do
|
||||
@car.driver.casted_by.should == @car
|
||||
end
|
||||
end
|
||||
|
||||
describe "assigning a value to casted attribute after initializing an object" do
|
||||
|
||||
before(:each) do
|
||||
@car = Car.new(:name => 'Renault 306')
|
||||
@driver = Driver.new(:name => 'Matt')
|
||||
end
|
||||
|
||||
it "should not create an empty casted object" do
|
||||
@car.driver.should be_nil
|
||||
end
|
||||
|
||||
it "should let you assign the value" do
|
||||
@car.driver = @driver
|
||||
@car.driver.name.should == 'Matt'
|
||||
end
|
||||
|
||||
it "should cast attribute" do
|
||||
@car.driver = JSON.parse(@driver.to_json)
|
||||
@car.driver.should be_instance_of(Driver)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe "casting a model from parsed JSON" do
|
||||
|
||||
before(:each) do
|
||||
@driver = Driver.new(:name => 'Matt')
|
||||
@car = Car.new(:name => 'Renault 306', :driver => @driver)
|
||||
@new_car = Car.new(JSON.parse(@car.to_json))
|
||||
end
|
||||
|
||||
it "should cast casted attribute" do
|
||||
@new_car.driver.should be_instance_of(Driver)
|
||||
end
|
||||
|
||||
it "should retain all properties of the casted attribute" do
|
||||
@new_car.driver.should == @driver
|
||||
end
|
||||
end
|
167
spec/unit/class_proxy_spec.rb
Normal file
167
spec/unit/class_proxy_spec.rb
Normal file
|
@ -0,0 +1,167 @@
|
|||
require "spec_helper"
|
||||
|
||||
class UnattachedDoc < CouchRest::Model::Base
|
||||
# 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 allow dynamic view matching for single elements" do
|
||||
@us.should_receive(:first_from_view).with('by_title', 'bbb')
|
||||
@us.find_by_title('bbb')
|
||||
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.should == @us.all.first
|
||||
end
|
||||
|
||||
it "should get last" do
|
||||
u = @us.last
|
||||
u.should == @us.all.last
|
||||
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
|
||||
|
||||
describe "#get!" do
|
||||
it "raises exception when passed a nil" do
|
||||
expect { @us.get!(nil)}.to raise_error(CouchRest::Model::DocumentNotFound)
|
||||
end
|
||||
|
||||
it "raises exception when passed an empty string " do
|
||||
expect { @us.get!("")}.to raise_error(CouchRest::Model::DocumentNotFound)
|
||||
end
|
||||
|
||||
it "raises exception when document with provided id does not exist" do
|
||||
expect { @us.get!("thisisnotreallyadocumentid")}.to raise_error(CouchRest::Model::DocumentNotFound)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#find!" do
|
||||
it "raises exception when passed a nil" do
|
||||
expect { @us.find!(nil)}.to raise_error(CouchRest::Model::DocumentNotFound)
|
||||
end
|
||||
|
||||
it "raises exception when passed an empty string " do
|
||||
expect { @us.find!("")}.to raise_error(CouchRest::Model::DocumentNotFound)
|
||||
end
|
||||
|
||||
it "raises exception when document with provided id does not exist" do
|
||||
expect { @us.find!("thisisnotreallyadocumentid")}.to raise_error(CouchRest::Model::DocumentNotFound)
|
||||
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
|
86
spec/unit/collection_spec.rb
Normal file
86
spec/unit/collection_spec.rb
Normal file
|
@ -0,0 +1,86 @@
|
|||
require "spec_helper"
|
||||
|
||||
describe "Collections" do
|
||||
|
||||
before(:all) do
|
||||
reset_test_db!
|
||||
titles = ["very uniq one", "really interesting", "some fun",
|
||||
"really awesome", "crazy bob", "this rocks", "super rad"]
|
||||
titles.each_with_index do |title,i|
|
||||
a = Article.new(:title => title, :date => Date.today)
|
||||
a.save
|
||||
end
|
||||
|
||||
titles = ["yesterday very uniq one", "yesterday really interesting", "yesterday some fun",
|
||||
"yesterday really awesome", "yesterday crazy bob", "yesterday this rocks"]
|
||||
titles.each_with_index do |title,i|
|
||||
a = Article.new(:title => title, :date => Date.today - 1)
|
||||
a.save
|
||||
end
|
||||
end
|
||||
it "should return a proxy that looks like an array of 7 Article objects" do
|
||||
articles = Article.collection_proxy_for('Article', 'by_date', :descending => true,
|
||||
:key => Date.today, :include_docs => true)
|
||||
articles.class.should == Array
|
||||
articles.size.should == 7
|
||||
end
|
||||
it "should provide a class method for paginate" do
|
||||
articles = Article.paginate(:design_doc => 'Article', :view_name => 'by_date',
|
||||
:per_page => 3, :descending => true, :key => Date.today)
|
||||
articles.size.should == 3
|
||||
|
||||
articles = Article.paginate(:design_doc => 'Article', :view_name => 'by_date',
|
||||
:per_page => 3, :page => 2, :descending => true, :key => Date.today)
|
||||
articles.size.should == 3
|
||||
|
||||
articles = Article.paginate(:design_doc => 'Article', :view_name => 'by_date',
|
||||
:per_page => 3, :page => 3, :descending => true, :key => Date.today)
|
||||
articles.size.should == 1
|
||||
end
|
||||
it "should provide a class method for paginated_each" do
|
||||
options = { :design_doc => 'Article', :view_name => 'by_date',
|
||||
:per_page => 3, :page => 1, :descending => true, :key => Date.today }
|
||||
Article.paginated_each(options) do |a|
|
||||
a.should_not be_nil
|
||||
end
|
||||
end
|
||||
it "should provide a class method to get a collection for a view" do
|
||||
articles = Article.find_all_article_details(:key => Date.today)
|
||||
articles.class.should == Array
|
||||
articles.size.should == 7
|
||||
end
|
||||
it "should get a subset of articles using paginate" do
|
||||
articles = Article.collection_proxy_for('Article', 'by_date', :key => Date.today, :include_docs => true)
|
||||
articles.paginate(:page => 1, :per_page => 3).size.should == 3
|
||||
articles.paginate(:page => 2, :per_page => 3).size.should == 3
|
||||
articles.paginate(:page => 3, :per_page => 3).size.should == 1
|
||||
end
|
||||
it "should get all articles, a few at a time, using paginated each" do
|
||||
articles = Article.collection_proxy_for('Article', 'by_date', :key => Date.today, :include_docs => true)
|
||||
articles.paginated_each(:per_page => 3) do |a|
|
||||
a.should_not be_nil
|
||||
end
|
||||
end
|
||||
|
||||
it "should raise an exception if design_doc is not provided" do
|
||||
lambda{Article.collection_proxy_for(nil, 'by_date')}.should raise_error
|
||||
lambda{Article.paginate(:view_name => 'by_date')}.should raise_error
|
||||
end
|
||||
it "should raise an exception if view_name is not provided" do
|
||||
lambda{Article.collection_proxy_for('Article', nil)}.should raise_error
|
||||
lambda{Article.paginate(:design_doc => 'Article')}.should raise_error
|
||||
end
|
||||
it "should be able to span multiple keys" do
|
||||
articles = Article.collection_proxy_for('Article', 'by_date', :startkey => Date.today - 1, :endkey => Date.today, :include_docs => true)
|
||||
articles.paginate(:page => 1, :per_page => 3).size.should == 3
|
||||
articles.paginate(:page => 3, :per_page => 3).size.should == 3
|
||||
articles.paginate(:page => 5, :per_page => 3).size.should == 1
|
||||
end
|
||||
it "should pass database parameter to pager" do
|
||||
proxy = mock(:proxy)
|
||||
proxy.stub!(:paginate)
|
||||
::CouchRest::Model::Collection::CollectionProxy.should_receive(:new).with('database', anything(), anything(), anything(), anything()).and_return(proxy)
|
||||
Article.paginate(:design_doc => 'Article', :view_name => 'by_date', :database => 'database')
|
||||
end
|
||||
|
||||
end
|
77
spec/unit/configuration_spec.rb
Normal file
77
spec/unit/configuration_spec.rb
Normal file
|
@ -0,0 +1,77 @@
|
|||
# encoding: utf-8
|
||||
require "spec_helper"
|
||||
|
||||
describe CouchRest::Model::Configuration do
|
||||
|
||||
before do
|
||||
@class = Class.new(CouchRest::Model::Base)
|
||||
end
|
||||
|
||||
describe '.configure' do
|
||||
it "should set a configuration parameter" do
|
||||
@class.add_config :foo_bar
|
||||
@class.configure do |config|
|
||||
config.foo_bar = 'monkey'
|
||||
end
|
||||
@class.foo_bar.should == 'monkey'
|
||||
end
|
||||
end
|
||||
|
||||
describe '.add_config' do
|
||||
|
||||
it "should add a class level accessor" do
|
||||
@class.add_config :foo_bar
|
||||
@class.foo_bar = 'foo'
|
||||
@class.foo_bar.should == 'foo'
|
||||
end
|
||||
|
||||
['foo', :foo, 45, ['foo', :bar]].each do |val|
|
||||
it "should be inheritable for a #{val.class}" do
|
||||
@class.add_config :foo_bar
|
||||
@child_class = Class.new(@class)
|
||||
|
||||
@class.foo_bar = val
|
||||
@class.foo_bar.should == val
|
||||
@child_class.foo_bar.should == val
|
||||
|
||||
@child_class.foo_bar = "bar"
|
||||
@child_class.foo_bar.should == "bar"
|
||||
|
||||
@class.foo_bar.should == val
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
it "should add an instance level accessor" do
|
||||
@class.add_config :foo_bar
|
||||
@class.foo_bar = 'foo'
|
||||
@class.new.foo_bar.should == 'foo'
|
||||
end
|
||||
|
||||
it "should add a convenient in-class setter" do
|
||||
@class.add_config :foo_bar
|
||||
@class.foo_bar "monkey"
|
||||
@class.foo_bar.should == "monkey"
|
||||
end
|
||||
end
|
||||
|
||||
describe "General examples" do
|
||||
|
||||
before(:all) do
|
||||
@default_model_key = 'model-type'
|
||||
end
|
||||
|
||||
|
||||
it "should be possible to override on class using configure method" do
|
||||
default_model_key = Cat.model_type_key
|
||||
Cat.instance_eval do
|
||||
model_type_key 'cat-type'
|
||||
end
|
||||
CouchRest::Model::Base.model_type_key.should eql(default_model_key)
|
||||
Cat.model_type_key.should eql('cat-type')
|
||||
cat = Cat.new
|
||||
cat.model_type_key.should eql('cat-type')
|
||||
end
|
||||
end
|
||||
|
||||
end
|
148
spec/unit/connection_spec.rb
Normal file
148
spec/unit/connection_spec.rb
Normal file
|
@ -0,0 +1,148 @@
|
|||
# encoding: utf-8
|
||||
require 'spec_helper'
|
||||
|
||||
describe CouchRest::Model::Connection do
|
||||
|
||||
before do
|
||||
@class = Class.new(CouchRest::Model::Base)
|
||||
end
|
||||
|
||||
describe "instance methods" do
|
||||
before :each do
|
||||
@obj = @class.new
|
||||
end
|
||||
|
||||
describe "#database" do
|
||||
it "should respond to" do
|
||||
@obj.should respond_to(:database)
|
||||
end
|
||||
it "should provided class's database" do
|
||||
@obj.class.should_receive :database
|
||||
@obj.database
|
||||
end
|
||||
end
|
||||
|
||||
describe "#server" do
|
||||
it "should respond to method" do
|
||||
@obj.should respond_to(:server)
|
||||
end
|
||||
it "should return class's server" do
|
||||
@obj.class.should_receive :server
|
||||
@obj.server
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "default configuration" do
|
||||
|
||||
it "should provide environment" do
|
||||
@class.environment.should eql(:development)
|
||||
end
|
||||
it "should provide connection config file" do
|
||||
@class.connection_config_file.should eql(File.join(Dir.pwd, 'config', 'couchdb.yml'))
|
||||
end
|
||||
it "should provided simple connection details" do
|
||||
@class.connection[:prefix].should eql('couchrest')
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe "class methods" do
|
||||
|
||||
describe ".use_database" do
|
||||
it "should respond to" do
|
||||
@class.should respond_to(:use_database)
|
||||
end
|
||||
end
|
||||
|
||||
describe ".database" do
|
||||
it "should respond to" do
|
||||
@class.should respond_to(:database)
|
||||
end
|
||||
it "should provide a database object" do
|
||||
@class.database.should be_a(CouchRest::Database)
|
||||
end
|
||||
it "should provide a database with default name" do
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe ".server" do
|
||||
it "should respond to" do
|
||||
@class.should respond_to(:server)
|
||||
end
|
||||
it "should provide a server object" do
|
||||
@class.server.should be_a(CouchRest::Server)
|
||||
end
|
||||
it "should provide a server with default config" do
|
||||
@class.server.uri.should eql("http://localhost:5984")
|
||||
end
|
||||
it "should allow the configuration to be overwritten" do
|
||||
@class.connection = {
|
||||
:protocol => "https",
|
||||
:host => "127.0.0.1",
|
||||
:port => '5985',
|
||||
:prefix => 'sample',
|
||||
:suffix => 'test',
|
||||
:username => 'foo',
|
||||
:password => 'bar'
|
||||
}
|
||||
@class.server.uri.should eql("https://foo:bar@127.0.0.1:5985")
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe ".prepare_database" do
|
||||
|
||||
it "should respond to" do
|
||||
@class.should respond_to(:prepare_database)
|
||||
end
|
||||
|
||||
it "should join the database name correctly" do
|
||||
@class.connection[:suffix] = 'db'
|
||||
db = @class.prepare_database('test')
|
||||
db.name.should eql('couchrest_test_db')
|
||||
end
|
||||
|
||||
it "should ignore nil values in database name" do
|
||||
@class.connection[:suffix] = nil
|
||||
db = @class.prepare_database('test')
|
||||
db.name.should eql('couchrest_test')
|
||||
end
|
||||
end
|
||||
|
||||
describe "protected methods" do
|
||||
|
||||
describe ".connection_configuration" do
|
||||
it "should provide main config by default" do
|
||||
@class.send(:connection_configuration).should eql(@class.connection)
|
||||
end
|
||||
it "should load file if available" do
|
||||
@class.connection_config_file = File.join(FIXTURE_PATH, 'config', 'couchdb.yml')
|
||||
hash = @class.send(:connection_configuration)
|
||||
hash[:protocol].should eql('https')
|
||||
hash[:host].should eql('sample.cloudant.com')
|
||||
hash[:join].should eql('_')
|
||||
end
|
||||
end
|
||||
|
||||
describe ".load_connection_config_file" do
|
||||
it "should provide an empty hash if config not found" do
|
||||
@class.send(:load_connection_config_file).should eql({})
|
||||
end
|
||||
it "should load file if available" do
|
||||
@class.connection_config_file = File.join(FIXTURE_PATH, 'config', 'couchdb.yml')
|
||||
hash = @class.send(:load_connection_config_file)
|
||||
hash[:development].should_not be_nil
|
||||
@class.server.uri.should eql("https://test:user@sample.cloudant.com:443")
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
end
|
77
spec/unit/core_extensions/time_parsing.rb
Normal file
77
spec/unit/core_extensions/time_parsing.rb
Normal file
|
@ -0,0 +1,77 @@
|
|||
# encoding: utf-8
|
||||
require File.expand_path('../../../spec_helper', __FILE__)
|
||||
|
||||
describe "Time Parsing core extension" do
|
||||
|
||||
describe "Time" do
|
||||
|
||||
it "should respond to .parse_iso8601" do
|
||||
Time.respond_to?("parse_iso8601").should be_true
|
||||
end
|
||||
|
||||
describe ".parse_iso8601" do
|
||||
|
||||
describe "parsing" do
|
||||
|
||||
before :each do
|
||||
# Time.parse should not be called for these tests!
|
||||
Time.stub!(:parse).and_return(nil)
|
||||
end
|
||||
|
||||
it "should parse JSON time" do
|
||||
txt = "2011-04-01T19:05:30Z"
|
||||
Time.parse_iso8601(txt).should eql(Time.utc(2011, 04, 01, 19, 05, 30))
|
||||
end
|
||||
|
||||
it "should parse JSON time as UTC without Z" do
|
||||
txt = "2011-04-01T19:05:30"
|
||||
Time.parse_iso8601(txt).should eql(Time.utc(2011, 04, 01, 19, 05, 30))
|
||||
end
|
||||
|
||||
it "should parse basic time as UTC" do
|
||||
txt = "2011-04-01 19:05:30"
|
||||
Time.parse_iso8601(txt).should eql(Time.utc(2011, 04, 01, 19, 05, 30))
|
||||
end
|
||||
|
||||
it "should parse JSON time with zone" do
|
||||
txt = "2011-04-01T19:05:30 +02:00"
|
||||
Time.parse_iso8601(txt).should eql(Time.new(2011, 04, 01, 19, 05, 30, "+02:00"))
|
||||
end
|
||||
|
||||
it "should parse JSON time with zone 2" do
|
||||
txt = "2011-04-01T19:05:30-0200"
|
||||
Time.parse_iso8601(txt).should eql(Time.new(2011, 04, 01, 19, 05, 30, "-02:00"))
|
||||
end
|
||||
|
||||
it "should parse dodgy time with zone" do
|
||||
txt = "2011-04-01 19:05:30 +0200"
|
||||
Time.parse_iso8601(txt).should eql(Time.new(2011, 04, 01, 19, 05, 30, "+02:00"))
|
||||
end
|
||||
|
||||
it "should parse dodgy time with zone 2" do
|
||||
txt = "2011-04-01 19:05:30+0230"
|
||||
Time.parse_iso8601(txt).should eql(Time.new(2011, 04, 01, 19, 05, 30, "+02:30"))
|
||||
end
|
||||
|
||||
it "should parse dodgy time with zone 3" do
|
||||
txt = "2011-04-01 19:05:30 0230"
|
||||
Time.parse_iso8601(txt).should eql(Time.new(2011, 04, 01, 19, 05, 30, "+02:30"))
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe "resorting back to normal parse" do
|
||||
before :each do
|
||||
Time.should_receive(:parse)
|
||||
end
|
||||
it "should work with weird time" do
|
||||
txt = "16/07/1981 05:04:00"
|
||||
Time.parse_iso8601(txt)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
241
spec/unit/design_doc_spec.rb
Normal file
241
spec/unit/design_doc_spec.rb
Normal file
|
@ -0,0 +1,241 @@
|
|||
# encoding: utf-8
|
||||
require 'spec_helper'
|
||||
|
||||
describe CouchRest::Model::DesignDoc do
|
||||
|
||||
before :all do
|
||||
reset_test_db!
|
||||
end
|
||||
|
||||
describe "CouchRest Extension" do
|
||||
|
||||
it "should have created a checksum! method" do
|
||||
::CouchRest::Design.new.should respond_to(:checksum!)
|
||||
end
|
||||
|
||||
it "should calculate a consistent checksum for model" do
|
||||
WithTemplateAndUniqueID.design_doc.checksum!.should eql('caa2b4c27abb82b4e37421de76d96ffc')
|
||||
end
|
||||
|
||||
it "should calculate checksum for complex model" do
|
||||
Article.design_doc.checksum!.should eql('70dff8caea143bf40fad09adf0701104')
|
||||
end
|
||||
|
||||
it "should cache the generated checksum value" do
|
||||
Article.design_doc.checksum!
|
||||
Article.design_doc['couchrest-hash'].should_not be_blank
|
||||
end
|
||||
end
|
||||
|
||||
describe "class methods" do
|
||||
|
||||
describe ".design_doc" do
|
||||
it "should provide Design document" do
|
||||
Article.design_doc.should be_a(::CouchRest::Design)
|
||||
end
|
||||
end
|
||||
|
||||
describe ".design_doc_id" do
|
||||
it "should provide a reasonable id" do
|
||||
Article.design_doc_id.should eql("_design/Article")
|
||||
end
|
||||
end
|
||||
|
||||
describe ".design_doc_slug" do
|
||||
it "should provide slug part of design doc" do
|
||||
Article.design_doc_slug.should eql('Article')
|
||||
end
|
||||
end
|
||||
|
||||
describe ".design_doc_uri" do
|
||||
it "should provide complete url" do
|
||||
Article.design_doc_uri.should eql("#{COUCHHOST}/#{TESTDB}/_design/Article")
|
||||
end
|
||||
it "should provide complete url for new DB" do
|
||||
db = mock("Database")
|
||||
db.should_receive(:root).and_return('db')
|
||||
Article.design_doc_uri(db).should eql("db/_design/Article")
|
||||
end
|
||||
end
|
||||
|
||||
describe ".stored_design_doc" do
|
||||
it "should load a stored design from the database" do
|
||||
Article.by_date
|
||||
Article.stored_design_doc['_rev'].should_not be_blank
|
||||
end
|
||||
it "should return nil if not already stored" do
|
||||
WithDefaultValues.stored_design_doc.should be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe ".save_design_doc" do
|
||||
it "should call up the design updater" do
|
||||
Article.should_receive(:update_design_doc).with('db', false)
|
||||
Article.save_design_doc('db')
|
||||
end
|
||||
end
|
||||
|
||||
describe ".save_design_doc!" do
|
||||
it "should call save_design_doc with force" do
|
||||
Article.should_receive(:save_design_doc).with('db', true)
|
||||
Article.save_design_doc!('db')
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe "basics" do
|
||||
|
||||
before :all do
|
||||
reset_test_db!
|
||||
end
|
||||
|
||||
it "should have been instantiated with views" do
|
||||
d = Article.design_doc
|
||||
d['views']['all']['map'].should include('Article')
|
||||
end
|
||||
|
||||
it "should not have been saved yet" do
|
||||
lambda { Article.database.get(Article.design_doc.id) }.should raise_error(RestClient::ResourceNotFound)
|
||||
end
|
||||
|
||||
describe "after requesting a view" do
|
||||
before :each do
|
||||
Article.all
|
||||
end
|
||||
it "should have saved the design doc after view request" do
|
||||
Article.database.get(Article.design_doc.id).should_not be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe "model with simple views" do
|
||||
before(:all) do
|
||||
Article.all.map{|a| a.destroy(true)}
|
||||
Article.database.bulk_delete
|
||||
written_at = Time.now - 24 * 3600 * 7
|
||||
@titles = ["this and that", "also interesting", "more fun", "some junk"]
|
||||
@titles.each do |title|
|
||||
a = Article.new(:title => title)
|
||||
a.date = written_at
|
||||
a.save
|
||||
written_at += 24 * 3600
|
||||
end
|
||||
end
|
||||
|
||||
it "will send request for the saved design doc on view request" do
|
||||
reset_test_db!
|
||||
Article.should_receive(:stored_design_doc).and_return(nil)
|
||||
Article.by_date
|
||||
end
|
||||
|
||||
it "should have generated a design doc" do
|
||||
Article.design_doc["views"]["by_date"].should_not be_nil
|
||||
end
|
||||
it "should save the design doc when view requested" do
|
||||
Article.by_date
|
||||
doc = Article.database.get Article.design_doc.id
|
||||
doc['views']['by_date'].should_not be_nil
|
||||
end
|
||||
it "should save design doc if a view changed" do
|
||||
Article.by_date
|
||||
orig = Article.stored_design_doc
|
||||
design = Article.design_doc
|
||||
view = design['views']['by_date']['map']
|
||||
design['views']['by_date']['map'] = view + ' ' # little bit of white space
|
||||
Article.by_date
|
||||
Article.stored_design_doc['_rev'].should_not eql(orig['_rev'])
|
||||
orig['views']['by_date']['map'].should_not eql(Article.design_doc['views']['by_date']['map'])
|
||||
end
|
||||
it "should not save design doc if not changed" do
|
||||
Article.by_date
|
||||
orig = Article.stored_design_doc['_rev']
|
||||
Article.by_date
|
||||
Article.stored_design_doc['_rev'].should eql(orig)
|
||||
end
|
||||
it "should recreate the design doc if database deleted" do
|
||||
Article.database.recreate!
|
||||
lambda { Article.by_date }.should_not raise_error(RestClient::ResourceNotFound)
|
||||
end
|
||||
end
|
||||
|
||||
describe "when auto_update_design_doc false" do
|
||||
# We really do need a new class for each of example. If we try
|
||||
# to use the same class the examples interact with each in ways
|
||||
# that can hide failures because the design document gets cached
|
||||
# at the class level.
|
||||
let(:model_class) {
|
||||
class_name = "#{example.metadata[:full_description].gsub(/\s+/,'_').camelize}Model"
|
||||
doc = CouchRest::Document.new("_id" => "_design/#{class_name}")
|
||||
doc["language"] = "javascript"
|
||||
doc["views"] = {"all" => {"map" =>
|
||||
"function(doc) {
|
||||
if (doc['type'] == 'Article') {
|
||||
emit(doc['_id'],1);
|
||||
}
|
||||
}"},
|
||||
"by_name" => {"map" =>
|
||||
"function(doc) {
|
||||
if ((doc['type'] == '#{class_name}') && (doc['name'] != null)) {
|
||||
emit(doc['name'], null);
|
||||
}",
|
||||
"reduce" =>
|
||||
"function(keys, values, rereduce) {
|
||||
return sum(values);
|
||||
}"}}
|
||||
|
||||
DB.save_doc doc
|
||||
|
||||
eval <<-KLASS
|
||||
class ::#{class_name} < CouchRest::Model::Base
|
||||
use_database DB
|
||||
self.auto_update_design_doc = false
|
||||
design do
|
||||
view :by_name
|
||||
end
|
||||
property :name, String
|
||||
end
|
||||
KLASS
|
||||
|
||||
class_name.constantize
|
||||
}
|
||||
|
||||
it "will not update stored design doc if view changed" do
|
||||
model_class.by_name
|
||||
orig = model_class.stored_design_doc
|
||||
design = model_class.design_doc
|
||||
view = design['views']['by_name']['map']
|
||||
design['views']['by_name']['map'] = view + ' '
|
||||
model_class.by_name
|
||||
model_class.stored_design_doc['_rev'].should eql(orig['_rev'])
|
||||
end
|
||||
|
||||
it "will update stored design if forced" do
|
||||
model_class.by_name
|
||||
orig = model_class.stored_design_doc
|
||||
design = model_class.design_doc
|
||||
view = design['views']['by_name']['map']
|
||||
design['views']['by_name']['map'] = view + ' '
|
||||
model_class.save_design_doc!
|
||||
model_class.stored_design_doc['_rev'].should_not eql(orig['_rev'])
|
||||
end
|
||||
|
||||
it "is able to use predefined views" do
|
||||
model_class.by_name(key: "special").all
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "lazily refreshing the design document" do
|
||||
before(:all) do
|
||||
@db = reset_test_db!
|
||||
WithTemplateAndUniqueID.new('slug' => '1').save
|
||||
end
|
||||
it "should not save the design doc twice" do
|
||||
WithTemplateAndUniqueID.all
|
||||
rev = WithTemplateAndUniqueID.design_doc['_rev']
|
||||
WithTemplateAndUniqueID.design_doc['_rev'].should eql(rev)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
end
|
831
spec/unit/designs/view_spec.rb
Normal file
831
spec/unit/designs/view_spec.rb
Normal file
|
@ -0,0 +1,831 @@
|
|||
require File.expand_path("../../../spec_helper", __FILE__)
|
||||
|
||||
class DesignViewModel < CouchRest::Model::Base
|
||||
use_database DB
|
||||
property :name
|
||||
property :title
|
||||
|
||||
design do
|
||||
view :by_name
|
||||
view :by_just_name, :map => "function(doc) { emit(doc['name'], null); }"
|
||||
end
|
||||
end
|
||||
|
||||
describe "Design View" do
|
||||
|
||||
describe "(unit tests)" do
|
||||
|
||||
before :each do
|
||||
@klass = CouchRest::Model::Designs::View
|
||||
end
|
||||
|
||||
describe ".new" do
|
||||
|
||||
describe "with invalid parent model" do
|
||||
it "should burn" do
|
||||
lambda { @klass.new(String) }.should raise_exception
|
||||
end
|
||||
end
|
||||
|
||||
describe "with CouchRest Model" do
|
||||
|
||||
it "should setup attributes" do
|
||||
@obj = @klass.new(DesignViewModel, {}, 'test_view')
|
||||
@obj.model.should eql(DesignViewModel)
|
||||
@obj.name.should eql('test_view')
|
||||
@obj.query.should be_empty
|
||||
end
|
||||
|
||||
it "should complain if there is no name" do
|
||||
lambda { @klass.new(DesignViewModel, {}, nil) }.should raise_error
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe "with previous view instance" do
|
||||
|
||||
before :each do
|
||||
first = @klass.new(DesignViewModel, {}, 'test_view')
|
||||
@obj = @klass.new(first, {:foo => :bar})
|
||||
end
|
||||
|
||||
it "should copy attributes" do
|
||||
@obj.model.should eql(DesignViewModel)
|
||||
@obj.name.should eql('test_view')
|
||||
@obj.query.should eql({:foo => :bar})
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe ".create" 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', :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['#{DesignViewModel.model_type_key}'] == 'DesignViewModel') && (doc['title'] != null))")
|
||||
str.should include("emit(doc['title'], 1);")
|
||||
str = @design_doc['views']['by_title']['reduce']
|
||||
str.should include("return sum(values);")
|
||||
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']], 1);")
|
||||
str = @design_doc['views']['by_title_and_name']['reduce']
|
||||
str.should include("return sum(values);")
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe "instance methods" do
|
||||
|
||||
before :each do
|
||||
@obj = @klass.new(DesignViewModel, {}, 'test_view')
|
||||
end
|
||||
|
||||
describe "#rows" do
|
||||
it "should execute query" do
|
||||
@obj.should_receive(:execute).and_return(true)
|
||||
@obj.should_receive(:result).twice.and_return({'rows' => []})
|
||||
@obj.rows.should be_empty
|
||||
end
|
||||
|
||||
it "should wrap rows in ViewRow class" do
|
||||
@obj.should_receive(:execute).and_return(true)
|
||||
@obj.should_receive(:result).twice.and_return({'rows' => [{:foo => :bar}]})
|
||||
CouchRest::Model::Designs::ViewRow.should_receive(:new).with({:foo => :bar}, @obj.model)
|
||||
@obj.rows
|
||||
end
|
||||
end
|
||||
|
||||
describe "#all" do
|
||||
it "should ensure docs included and call docs" do
|
||||
@obj.should_receive(:include_docs!)
|
||||
@obj.should_receive(:docs)
|
||||
@obj.all
|
||||
end
|
||||
end
|
||||
|
||||
describe "#docs" do
|
||||
it "should provide docs from rows" do
|
||||
@obj.should_receive(:rows).and_return([])
|
||||
@obj.docs
|
||||
end
|
||||
it "should cache the results" do
|
||||
@obj.should_receive(:rows).once.and_return([])
|
||||
@obj.docs
|
||||
@obj.docs
|
||||
end
|
||||
end
|
||||
|
||||
describe "#first" do
|
||||
it "should provide the first result of loaded query" do
|
||||
@obj.should_receive(:result).and_return(true)
|
||||
@obj.should_receive(:all).and_return([:foo])
|
||||
@obj.first.should eql(:foo)
|
||||
end
|
||||
it "should perform a query if no results cached" do
|
||||
view = mock('SubView')
|
||||
@obj.should_receive(:result).and_return(nil)
|
||||
@obj.should_receive(:limit).with(1).and_return(view)
|
||||
view.should_receive(:all).and_return([:foo])
|
||||
@obj.first.should eql(:foo)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#last" do
|
||||
it "should provide the last result of loaded query" do
|
||||
@obj.should_receive(:result).and_return(true)
|
||||
@obj.should_receive(:all).and_return([:foo, :bar])
|
||||
@obj.first.should eql(:foo)
|
||||
end
|
||||
it "should perform a query if no results cached" do
|
||||
view = mock('SubView')
|
||||
@obj.should_receive(:result).and_return(nil)
|
||||
@obj.should_receive(:limit).with(1).and_return(view)
|
||||
view.should_receive(:descending).and_return(view)
|
||||
view.should_receive(:all).and_return([:foo, :bar])
|
||||
@obj.last.should eql(:bar)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#length" do
|
||||
it "should provide a length from the docs array" do
|
||||
@obj.should_receive(:docs).and_return([1, 2, 3])
|
||||
@obj.length.should eql(3)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#count" do
|
||||
it "should raise an error if view prepared for group" do
|
||||
@obj.should_receive(:query).and_return({:group => true})
|
||||
lambda { @obj.count }.should raise_error(/group/)
|
||||
end
|
||||
|
||||
it "should return first row value if reduce possible" do
|
||||
view = mock("SubView")
|
||||
row = mock("Row")
|
||||
@obj.should_receive(:can_reduce?).and_return(true)
|
||||
@obj.should_receive(:reduce).and_return(view)
|
||||
view.should_receive(:skip).with(0).and_return(view)
|
||||
view.should_receive(:limit).with(1).and_return(view)
|
||||
view.should_receive(:rows).and_return([row])
|
||||
row.should_receive(:value).and_return(2)
|
||||
@obj.count.should eql(2)
|
||||
end
|
||||
it "should return 0 if no rows and reduce possible" do
|
||||
view = mock("SubView")
|
||||
@obj.should_receive(:can_reduce?).and_return(true)
|
||||
@obj.should_receive(:reduce).and_return(view)
|
||||
view.should_receive(:skip).with(0).and_return(view)
|
||||
view.should_receive(:limit).with(1).and_return(view)
|
||||
view.should_receive(:rows).and_return([])
|
||||
@obj.count.should eql(0)
|
||||
end
|
||||
|
||||
it "should perform limit request for total_rows" do
|
||||
view = mock("SubView")
|
||||
@obj.should_receive(:limit).with(0).and_return(view)
|
||||
view.should_receive(:total_rows).and_return(4)
|
||||
@obj.should_receive(:can_reduce?).and_return(false)
|
||||
@obj.count.should eql(4)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#empty?" do
|
||||
it "should check the #all method for any results" do
|
||||
all = mock("All")
|
||||
all.should_receive(:empty?).and_return('win')
|
||||
@obj.should_receive(:all).and_return(all)
|
||||
@obj.empty?.should eql('win')
|
||||
end
|
||||
end
|
||||
|
||||
describe "#each" do
|
||||
it "should call each method on all" do
|
||||
@obj.should_receive(:all).and_return([])
|
||||
@obj.each
|
||||
end
|
||||
it "should call each and pass block" do
|
||||
set = [:foo, :bar]
|
||||
@obj.should_receive(:all).and_return(set)
|
||||
result = []
|
||||
@obj.each do |s|
|
||||
result << s
|
||||
end
|
||||
result.should eql(set)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#offset" do
|
||||
it "should excute" do
|
||||
@obj.should_receive(:execute).and_return({'offset' => 3})
|
||||
@obj.offset.should eql(3)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#total_rows" do
|
||||
it "should excute" do
|
||||
@obj.should_receive(:execute).and_return({'total_rows' => 3})
|
||||
@obj.total_rows.should eql(3)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#values" do
|
||||
it "should request each row and provide value" do
|
||||
row = mock("Row")
|
||||
row.should_receive(:value).twice.and_return('foo')
|
||||
@obj.should_receive(:rows).and_return([row, row])
|
||||
@obj.values.should eql(['foo', 'foo'])
|
||||
end
|
||||
end
|
||||
|
||||
describe "#[]" do
|
||||
it "should execute and provide requested field" do
|
||||
@obj.should_receive(:execute).and_return({'total_rows' => 2})
|
||||
@obj['total_rows'].should eql(2)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#info" do
|
||||
it "should raise error" do
|
||||
lambda { @obj.info }.should raise_error
|
||||
end
|
||||
end
|
||||
|
||||
describe "#database" do
|
||||
it "should update query with value" do
|
||||
@obj.should_receive(:update_query).with({:database => 'foo'})
|
||||
@obj.database('foo')
|
||||
end
|
||||
end
|
||||
|
||||
describe "#key" do
|
||||
it "should update query with value" do
|
||||
@obj.should_receive(:update_query).with({:key => 'foo'})
|
||||
@obj.key('foo')
|
||||
end
|
||||
it "should raise error if startkey set" do
|
||||
@obj.query[:startkey] = 'bar'
|
||||
lambda { @obj.key('foo') }.should raise_error
|
||||
end
|
||||
it "should raise error if endkey set" do
|
||||
@obj.query[:endkey] = 'bar'
|
||||
lambda { @obj.key('foo') }.should raise_error
|
||||
end
|
||||
it "should raise error if both startkey and endkey set" do
|
||||
@obj.query[:startkey] = 'bar'
|
||||
@obj.query[:endkey] = 'bar'
|
||||
lambda { @obj.key('foo') }.should raise_error
|
||||
end
|
||||
it "should raise error if keys set" do
|
||||
@obj.query[:keys] = 'bar'
|
||||
lambda { @obj.key('foo') }.should raise_error
|
||||
end
|
||||
end
|
||||
|
||||
describe "#startkey" do
|
||||
it "should update query with value" do
|
||||
@obj.should_receive(:update_query).with({:startkey => 'foo'})
|
||||
@obj.startkey('foo')
|
||||
end
|
||||
it "should raise and error if key set" do
|
||||
@obj.query[:key] = 'bar'
|
||||
lambda { @obj.startkey('foo') }.should raise_error(/View#startkey/)
|
||||
end
|
||||
it "should raise and error if keys set" do
|
||||
@obj.query[:keys] = 'bar'
|
||||
lambda { @obj.startkey('foo') }.should raise_error(/View#startkey/)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#startkey_doc" do
|
||||
it "should update query with value" do
|
||||
@obj.should_receive(:update_query).with({:startkey_docid => 'foo'})
|
||||
@obj.startkey_doc('foo')
|
||||
end
|
||||
it "should update query with object id if available" do
|
||||
doc = mock("Document")
|
||||
doc.should_receive(:id).and_return(44)
|
||||
@obj.should_receive(:update_query).with({:startkey_docid => 44})
|
||||
@obj.startkey_doc(doc)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#endkey" do
|
||||
it "should update query with value" do
|
||||
@obj.should_receive(:update_query).with({:endkey => 'foo'})
|
||||
@obj.endkey('foo')
|
||||
end
|
||||
it "should raise and error if key set" do
|
||||
@obj.query[:key] = 'bar'
|
||||
lambda { @obj.endkey('foo') }.should raise_error(/View#endkey/)
|
||||
end
|
||||
it "should raise and error if keys set" do
|
||||
@obj.query[:keys] = 'bar'
|
||||
lambda { @obj.endkey('foo') }.should raise_error(/View#endkey/)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#endkey_doc" do
|
||||
it "should update query with value" do
|
||||
@obj.should_receive(:update_query).with({:endkey_docid => 'foo'})
|
||||
@obj.endkey_doc('foo')
|
||||
end
|
||||
it "should update query with object id if available" do
|
||||
doc = mock("Document")
|
||||
doc.should_receive(:id).and_return(44)
|
||||
@obj.should_receive(:update_query).with({:endkey_docid => 44})
|
||||
@obj.endkey_doc(doc)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#keys" do
|
||||
it "should update the query" do
|
||||
@obj.should_receive(:update_query).with({:keys => ['foo', 'bar']})
|
||||
@obj.keys(['foo', 'bar'])
|
||||
end
|
||||
it "should raise and error if key set" do
|
||||
@obj.query[:key] = 'bar'
|
||||
lambda { @obj.keys('foo') }.should raise_error(/View#keys/)
|
||||
end
|
||||
it "should raise and error if startkey or endkey set" do
|
||||
@obj.query[:startkey] = 'bar'
|
||||
lambda { @obj.keys('foo') }.should raise_error(/View#keys/)
|
||||
@obj.query.delete(:startkey)
|
||||
@obj.query[:endkey] = 'bar'
|
||||
lambda { @obj.keys('foo') }.should raise_error(/View#keys/)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#keys (without parameters)" do
|
||||
it "should request each row and provide key value" do
|
||||
row = mock("Row")
|
||||
row.should_receive(:key).twice.and_return('foo')
|
||||
@obj.should_receive(:rows).and_return([row, row])
|
||||
@obj.keys.should eql(['foo', 'foo'])
|
||||
end
|
||||
end
|
||||
|
||||
describe "#descending" do
|
||||
it "should update query" do
|
||||
@obj.should_receive(:update_query).with({:descending => true})
|
||||
@obj.descending
|
||||
end
|
||||
it "should reverse start and end keys if given" do
|
||||
@obj = @obj.startkey('a').endkey('z')
|
||||
@obj = @obj.descending
|
||||
@obj.query[:endkey].should eql('a')
|
||||
@obj.query[:startkey].should eql('z')
|
||||
end
|
||||
it "should reverse even if start or end nil" do
|
||||
@obj = @obj.startkey('a')
|
||||
@obj = @obj.descending
|
||||
@obj.query[:endkey].should eql('a')
|
||||
@obj.query[:startkey].should be_nil
|
||||
end
|
||||
it "should reverse start_doc and end_doc keys if given" do
|
||||
@obj = @obj.startkey_doc('a').endkey_doc('z')
|
||||
@obj = @obj.descending
|
||||
@obj.query[:endkey_docid].should eql('a')
|
||||
@obj.query[:startkey_docid].should eql('z')
|
||||
end
|
||||
end
|
||||
|
||||
describe "#limit" do
|
||||
it "should update query with value" do
|
||||
@obj.should_receive(:update_query).with({:limit => 3})
|
||||
@obj.limit(3)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#skip" do
|
||||
it "should update query with value" do
|
||||
@obj.should_receive(:update_query).with({:skip => 3})
|
||||
@obj.skip(3)
|
||||
end
|
||||
it "should update query with default value" do
|
||||
@obj.should_receive(:update_query).with({:skip => 0})
|
||||
@obj.skip
|
||||
end
|
||||
end
|
||||
|
||||
describe "#reduce" do
|
||||
it "should update query" do
|
||||
@obj.should_receive(:can_reduce?).and_return(true)
|
||||
@obj.should_receive(:update_query).with({:reduce => true, :include_docs => nil})
|
||||
@obj.reduce
|
||||
end
|
||||
it "should raise error if query cannot be reduced" do
|
||||
@obj.should_receive(:can_reduce?).and_return(false)
|
||||
lambda { @obj.reduce }.should raise_error
|
||||
end
|
||||
end
|
||||
|
||||
describe "#group" do
|
||||
it "should update query" do
|
||||
@obj.should_receive(:query).and_return({:reduce => true})
|
||||
@obj.should_receive(:update_query).with({:group => true})
|
||||
@obj.group
|
||||
end
|
||||
it "should raise error if query not prepared for reduce" do
|
||||
@obj.should_receive(:query).and_return({:reduce => false})
|
||||
lambda { @obj.group }.should raise_error
|
||||
end
|
||||
end
|
||||
|
||||
describe "#group" do
|
||||
it "should update query" do
|
||||
@obj.should_receive(:query).and_return({:reduce => true})
|
||||
@obj.should_receive(:update_query).with({:group => true})
|
||||
@obj.group
|
||||
end
|
||||
it "should raise error if query not prepared for reduce" do
|
||||
@obj.should_receive(:query).and_return({:reduce => false})
|
||||
lambda { @obj.group }.should raise_error
|
||||
end
|
||||
end
|
||||
|
||||
describe "#group_level" do
|
||||
it "should update query" do
|
||||
@obj.should_receive(:group).and_return(@obj)
|
||||
@obj.should_receive(:update_query).with({:group_level => 3})
|
||||
@obj.group_level(3)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#include_docs" do
|
||||
it "should call include_docs! on new view" do
|
||||
@obj.should_receive(:update_query).and_return(@obj)
|
||||
@obj.should_receive(:include_docs!)
|
||||
@obj.include_docs
|
||||
end
|
||||
end
|
||||
|
||||
describe "#reset!" do
|
||||
it "should empty all cached data" do
|
||||
@obj.should_receive(:result=).with(nil)
|
||||
@obj.instance_exec { @rows = 'foo'; @docs = 'foo' }
|
||||
@obj.reset!
|
||||
@obj.instance_exec { @rows }.should be_nil
|
||||
@obj.instance_exec { @docs }.should be_nil
|
||||
end
|
||||
end
|
||||
|
||||
#### PROTECTED METHODS
|
||||
|
||||
describe "#include_docs!" do
|
||||
it "should set query value" do
|
||||
@obj.should_receive(:result).and_return(false)
|
||||
@obj.should_not_receive(:reset!)
|
||||
@obj.send(:include_docs!)
|
||||
@obj.query[:include_docs].should be_true
|
||||
end
|
||||
it "should reset if result and no docs" do
|
||||
@obj.should_receive(:result).and_return(true)
|
||||
@obj.should_receive(:include_docs?).and_return(false)
|
||||
@obj.should_receive(:reset!)
|
||||
@obj.send(:include_docs!)
|
||||
@obj.query[:include_docs].should be_true
|
||||
end
|
||||
it "should raise an error if view is reduced" do
|
||||
@obj.query[:reduce] = true
|
||||
lambda { @obj.send(:include_docs!) }.should raise_error
|
||||
end
|
||||
end
|
||||
|
||||
describe "#include_docs?" do
|
||||
it "should return true if set" do
|
||||
@obj.should_receive(:query).and_return({:include_docs => true})
|
||||
@obj.send(:include_docs?).should be_true
|
||||
end
|
||||
it "should return false if not set" do
|
||||
@obj.should_receive(:query).and_return({})
|
||||
@obj.send(:include_docs?).should be_false
|
||||
@obj.should_receive(:query).and_return({:include_docs => false})
|
||||
@obj.send(:include_docs?).should be_false
|
||||
end
|
||||
end
|
||||
|
||||
describe "#update_query" do
|
||||
it "returns a new instance of view" do
|
||||
@obj.send(:update_query).object_id.should_not eql(@obj.object_id)
|
||||
end
|
||||
|
||||
it "returns a new instance of view with extra parameters" do
|
||||
new_obj = @obj.send(:update_query, {:foo => :bar})
|
||||
new_obj.query[:foo].should eql(:bar)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#design_doc" do
|
||||
it "should call design_doc on model" do
|
||||
@obj.model.should_receive(:design_doc)
|
||||
@obj.send(:design_doc)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#can_reduce?" do
|
||||
it "should check and prove true" do
|
||||
@obj.should_receive(:name).and_return('test_view')
|
||||
@obj.should_receive(:design_doc).and_return({'views' => {'test_view' => {'reduce' => 'foo'}}})
|
||||
@obj.send(:can_reduce?).should be_true
|
||||
end
|
||||
it "should check and prove false" do
|
||||
@obj.should_receive(:name).and_return('test_view')
|
||||
@obj.should_receive(:design_doc).and_return({'views' => {'test_view' => {'reduce' => nil}}})
|
||||
@obj.send(:can_reduce?).should be_false
|
||||
end
|
||||
end
|
||||
|
||||
describe "#execute" do
|
||||
before :each do
|
||||
# disable real execution!
|
||||
@design_doc = mock("DesignDoc")
|
||||
@design_doc.stub!(:view_on)
|
||||
@obj.model.stub!(:save_design_doc)
|
||||
@obj.model.stub!(:design_doc).and_return(@design_doc)
|
||||
end
|
||||
|
||||
it "should return previous result if set" do
|
||||
@obj.result = "foo"
|
||||
@obj.send(:execute).should eql('foo')
|
||||
end
|
||||
|
||||
it "should raise issue if no database" do
|
||||
@obj.should_receive(:query).and_return({:database => nil})
|
||||
model = mock("SomeModel")
|
||||
model.should_receive(:database).and_return(nil)
|
||||
@obj.should_receive(:model).and_return(model)
|
||||
lambda { @obj.send(:execute) }.should raise_error
|
||||
end
|
||||
|
||||
it "should delete the reduce option if not going to be used" do
|
||||
@obj.should_receive(:can_reduce?).and_return(false)
|
||||
@obj.query.should_receive(:delete).with(:reduce)
|
||||
@obj.send(:execute)
|
||||
end
|
||||
|
||||
it "should call to save the design document" do
|
||||
@obj.should_receive(:can_reduce?).and_return(false)
|
||||
@obj.model.should_receive(:save_design_doc).with(DB)
|
||||
@obj.send(:execute)
|
||||
end
|
||||
|
||||
it "should populate the results" do
|
||||
@obj.should_receive(:can_reduce?).and_return(true)
|
||||
@design_doc.should_receive(:view_on).and_return('foos')
|
||||
@obj.send(:execute)
|
||||
@obj.result.should eql('foos')
|
||||
end
|
||||
|
||||
it "should remove nil values from query" do
|
||||
@obj.should_receive(:can_reduce?).and_return(true)
|
||||
@obj.stub!(:use_database).and_return('database')
|
||||
@obj.query = {:reduce => true, :limit => nil, :skip => nil}
|
||||
@design_doc.should_receive(:view_on).with('database', 'test_view', {:reduce => true})
|
||||
@obj.send(:execute)
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
describe "pagination methods" do
|
||||
|
||||
describe "#page" do
|
||||
it "should call limit and skip" do
|
||||
@obj.should_receive(:limit).with(25).and_return(@obj)
|
||||
@obj.should_receive(:skip).with(25).and_return(@obj)
|
||||
@obj.page(2)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#per" do
|
||||
it "should raise an error if page not called before hand" do
|
||||
lambda { @obj.per(12) }.should raise_error
|
||||
end
|
||||
it "should not do anything if number less than or eql 0" do
|
||||
view = @obj.page(1)
|
||||
view.per(0).should eql(view)
|
||||
end
|
||||
it "should set limit and update skip" do
|
||||
view = @obj.page(2).per(10)
|
||||
view.query[:skip].should eql(10)
|
||||
view.query[:limit].should eql(10)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#total_count" do
|
||||
it "set limit and skip to nill and perform count" do
|
||||
@obj.should_receive(:limit).with(nil).and_return(@obj)
|
||||
@obj.should_receive(:skip).with(nil).and_return(@obj)
|
||||
@obj.should_receive(:count).and_return(5)
|
||||
@obj.total_count.should eql(5)
|
||||
@obj.total_count.should eql(5) # Second to test caching
|
||||
end
|
||||
end
|
||||
|
||||
describe "#num_pages" do
|
||||
it "should use total_count and limit_value" do
|
||||
@obj.should_receive(:total_count).and_return(200)
|
||||
@obj.should_receive(:limit_value).and_return(25)
|
||||
@obj.num_pages.should eql(8)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#current_page" do
|
||||
it "should use offset and limit" do
|
||||
@obj.should_receive(:offset_value).and_return(25)
|
||||
@obj.should_receive(:limit_value).and_return(25)
|
||||
@obj.current_page.should eql(2)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "ViewRow" do
|
||||
|
||||
before :all do
|
||||
@klass = CouchRest::Model::Designs::ViewRow
|
||||
end
|
||||
|
||||
describe "intialize" do
|
||||
it "should store reference to model" do
|
||||
obj = @klass.new({}, "model")
|
||||
obj.model.should eql('model')
|
||||
end
|
||||
it "should copy details from hash" do
|
||||
obj = @klass.new({:foo => :bar, :test => :example}, "")
|
||||
obj[:foo].should eql(:bar)
|
||||
obj[:test].should eql(:example)
|
||||
end
|
||||
end
|
||||
|
||||
describe "running" do
|
||||
before :each do
|
||||
end
|
||||
|
||||
it "should provide id" do
|
||||
obj = @klass.new({'id' => '123456'}, 'model')
|
||||
obj.id.should eql('123456')
|
||||
end
|
||||
|
||||
it "should provide key" do
|
||||
obj = @klass.new({'key' => 'thekey'}, 'model')
|
||||
obj.key.should eql('thekey')
|
||||
end
|
||||
|
||||
it "should provide the value" do
|
||||
obj = @klass.new({'value' => 'thevalue'}, 'model')
|
||||
obj.value.should eql('thevalue')
|
||||
end
|
||||
|
||||
it "should provide the raw document" do
|
||||
obj = @klass.new({'doc' => 'thedoc'}, 'model')
|
||||
obj.raw_doc.should eql('thedoc')
|
||||
end
|
||||
|
||||
it "should instantiate a new document" do
|
||||
hash = {'doc' => {'_id' => '12345', 'name' => 'sam'}}
|
||||
obj = @klass.new(hash, DesignViewModel)
|
||||
doc = mock('DesignViewModel')
|
||||
obj.model.should_receive(:build_from_database).with(hash['doc']).and_return(doc)
|
||||
obj.doc.should eql(doc)
|
||||
end
|
||||
|
||||
it "should try to load from id if no document" do
|
||||
hash = {'id' => '12345', 'value' => 5}
|
||||
obj = @klass.new(hash, DesignViewModel)
|
||||
doc = mock('DesignViewModel')
|
||||
obj.model.should_receive(:get).with('12345').and_return(doc)
|
||||
obj.doc.should eql(doc)
|
||||
end
|
||||
|
||||
it "should try to load linked document if available" do
|
||||
hash = {'id' => '12345', 'value' => {'_id' => '54321'}}
|
||||
obj = @klass.new(hash, DesignViewModel)
|
||||
doc = mock('DesignViewModel')
|
||||
obj.model.should_receive(:get).with('54321').and_return(doc)
|
||||
obj.doc.should eql(doc)
|
||||
end
|
||||
|
||||
it "should try to return nil for document if none available" do
|
||||
hash = {'value' => 23} # simulate reduce
|
||||
obj = @klass.new(hash, DesignViewModel)
|
||||
doc = mock('DesignViewModel')
|
||||
obj.model.should_not_receive(:get)
|
||||
obj.doc.should be_nil
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
describe "scenarios" do
|
||||
|
||||
before :all do
|
||||
@objs = [
|
||||
{:name => "Judith"},
|
||||
{:name => "Lorena"},
|
||||
{:name => "Peter"},
|
||||
{:name => "Sam"},
|
||||
{:name => "Vilma"}
|
||||
].map{|h| DesignViewModel.create(h)}
|
||||
end
|
||||
|
||||
describe "loading documents" do
|
||||
it "should return first" do
|
||||
DesignViewModel.by_name.first.name.should eql("Judith")
|
||||
end
|
||||
|
||||
it "should return last" do
|
||||
DesignViewModel.by_name.last.name.should eql("Vilma")
|
||||
end
|
||||
|
||||
it "should allow multiple results" do
|
||||
view = DesignViewModel.by_name.limit(3)
|
||||
view.total_rows.should eql(5)
|
||||
view.last.name.should eql("Peter")
|
||||
view.all.length.should eql(3)
|
||||
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
|
||||
|
||||
describe "viewing" do
|
||||
it "should load views with no reduce method" do
|
||||
docs = DesignViewModel.by_just_name.all
|
||||
docs.length.should eql(5)
|
||||
end
|
||||
it "should load documents by specific keys" do
|
||||
docs = DesignViewModel.by_name.keys(["Judith", "Peter"]).all
|
||||
docs[0].name.should eql("Judith")
|
||||
docs[1].name.should eql("Peter")
|
||||
end
|
||||
it "should provide count even if limit or skip set" do
|
||||
docs = DesignViewModel.by_name.limit(20).skip(2)
|
||||
docs.count.should eql(5)
|
||||
end
|
||||
end
|
||||
|
||||
describe "pagination" do
|
||||
before :all do
|
||||
DesignViewModel.paginates_per 3
|
||||
end
|
||||
before :each do
|
||||
@view = DesignViewModel.by_name.page(1)
|
||||
end
|
||||
|
||||
it "should calculate number of pages" do
|
||||
@view.num_pages.should eql(2)
|
||||
end
|
||||
it "should return results from first page" do
|
||||
@view.all.first.name.should eql('Judith')
|
||||
@view.all.last.name.should eql('Peter')
|
||||
end
|
||||
it "should return results from second page" do
|
||||
@view.page(2).all.first.name.should eql('Sam')
|
||||
@view.page(2).all.last.name.should eql('Vilma')
|
||||
end
|
||||
|
||||
it "should allow overriding per page count" do
|
||||
@view = @view.per(10)
|
||||
@view.num_pages.should eql(1)
|
||||
@view.all.last.name.should eql('Vilma')
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
end
|
134
spec/unit/designs_spec.rb
Normal file
134
spec/unit/designs_spec.rb
Normal file
|
@ -0,0 +1,134 @@
|
|||
require "spec_helper"
|
||||
|
||||
class DesignModel < CouchRest::Model::Base
|
||||
end
|
||||
|
||||
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
|
||||
@mapper = mock('DesignMapper')
|
||||
@mapper.stub!(:create_view_method)
|
||||
end
|
||||
|
||||
it "should instantiate a new DesignMapper" do
|
||||
CouchRest::Model::Designs::DesignMapper.should_receive(:new).with(DesignModel).and_return(@mapper)
|
||||
@mapper.should_receive(:create_view_method).with(:all)
|
||||
@mapper.should_receive(:instance_eval)
|
||||
DesignModel.design() { }
|
||||
end
|
||||
|
||||
it "should allow methods to be called in mapper" do
|
||||
@mapper.should_receive(:foo)
|
||||
CouchRest::Model::Designs::DesignMapper.stub!(:new).and_return(@mapper)
|
||||
DesignModel.design { foo }
|
||||
end
|
||||
|
||||
it "should work even if a block is not provided" do
|
||||
lambda { DesignModel.design }.should_not raise_error
|
||||
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 "DesignMapper" do
|
||||
|
||||
before :all do
|
||||
@klass = CouchRest::Model::Designs::DesignMapper
|
||||
end
|
||||
|
||||
it "should initialize and set model" do
|
||||
object = @klass.new(DesignModel)
|
||||
object.send(:model).should eql(DesignModel)
|
||||
end
|
||||
|
||||
describe "#view" do
|
||||
|
||||
before :each do
|
||||
@object = @klass.new(DesignModel)
|
||||
end
|
||||
|
||||
it "should call create method on view" do
|
||||
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::Designs::View.stub!(:create)
|
||||
@object.view('test_view')
|
||||
DesignModel.should respond_to(:test_view)
|
||||
end
|
||||
|
||||
it "should create a method for view instance" do
|
||||
CouchRest::Model::Designs::View.stub!(:create)
|
||||
@object.should_receive(:create_view_method).with('test')
|
||||
@object.view('test')
|
||||
end
|
||||
end
|
||||
|
||||
context "for model with auto_update_design_doc disabled " do
|
||||
class ::DesignModelAutoUpdateDesignDocDisabled < ::CouchRest::Model::Base
|
||||
self.auto_update_design_doc = false
|
||||
end
|
||||
|
||||
describe "#view" do
|
||||
before :each do
|
||||
@object = @klass.new(DesignModelAutoUpdateDesignDocDisabled)
|
||||
end
|
||||
|
||||
it "does not attempt to create view" do
|
||||
CouchRest::Model::Designs::View.should_not_receive(:create)
|
||||
@object.view('test')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#filter" do
|
||||
|
||||
before :each do
|
||||
@object = @klass.new(DesignModel)
|
||||
end
|
||||
|
||||
it "should add the provided function to the design doc" do
|
||||
@object.filter(:important, "function(doc, req) { return doc.priority == 'high'; }")
|
||||
DesignModel.design_doc['filters'].should_not be_empty
|
||||
DesignModel.design_doc['filters']['important'].should_not be_blank
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe "#create_view_method" do
|
||||
before :each do
|
||||
@object = @klass.new(DesignModel)
|
||||
end
|
||||
|
||||
it "should create a method that returns view instance" do
|
||||
CouchRest::Model::Designs::View.should_receive(:new).with(DesignModel, {}, 'test_view').and_return(nil)
|
||||
@object.create_view_method('test_view')
|
||||
DesignModel.test_view
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
436
spec/unit/dirty_spec.rb
Normal file
436
spec/unit/dirty_spec.rb
Normal file
|
@ -0,0 +1,436 @@
|
|||
require "spec_helper"
|
||||
|
||||
class WithCastedModelMixin
|
||||
include CouchRest::Model::CastedModel
|
||||
property :name
|
||||
property :details, Object, :default => {}
|
||||
property :casted_attribute, WithCastedModelMixin
|
||||
end
|
||||
|
||||
class DirtyModel < CouchRest::Model::Base
|
||||
use_database DB
|
||||
|
||||
property :casted_attribute, WithCastedModelMixin
|
||||
property :title, :default => 'Sample Title'
|
||||
property :details, Object, :default => { 'color' => 'blue' }
|
||||
property :keywords, [String], :default => ['default-keyword']
|
||||
property :sub_models do
|
||||
property :title
|
||||
end
|
||||
end
|
||||
|
||||
class DirtyUniqueIdModel < CouchRest::Model::Base
|
||||
use_database DB
|
||||
attr_accessor :code
|
||||
unique_id :code
|
||||
property :title, String, :default => "Sample Title"
|
||||
timestamps!
|
||||
|
||||
def code; self['_id'] || @code; end
|
||||
end
|
||||
|
||||
describe "Dirty" do
|
||||
|
||||
describe "changes" do
|
||||
|
||||
it "should return changes on an attribute" do
|
||||
@card = Card.new(:first_name => "matt")
|
||||
@card.first_name = "andrew"
|
||||
@card.first_name_changed?.should be_true
|
||||
@card.changes.should == { "first_name" => ["matt", "andrew"] }
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe "save" do
|
||||
|
||||
it "should not save unchanged records" do
|
||||
card_id = Card.create!(:first_name => "matt").id
|
||||
@card = Card.find(card_id)
|
||||
@card.database.should_not_receive(:save_doc)
|
||||
@card.save
|
||||
end
|
||||
|
||||
it "should save changed records" do
|
||||
card_id = Card.create!(:first_name => "matt").id
|
||||
@card = Card.find(card_id)
|
||||
@card.first_name = "andrew"
|
||||
@card.database.should_receive(:save_doc).and_return({"ok" => true})
|
||||
@card.save
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe "changed?" do
|
||||
|
||||
# match activerecord behaviour
|
||||
it "should report no changes on a new object with no attributes set" do
|
||||
@card = Card.new
|
||||
@card.changed?.should be_false
|
||||
end
|
||||
|
||||
it "should report no changes on a hash property with a default value" do
|
||||
@obj = DirtyModel.new
|
||||
@obj.details.changed?.should be_false
|
||||
end
|
||||
|
||||
# match activerecord behaviour
|
||||
it "should report changes on a new object with attributes set" do
|
||||
@card = Card.new(:first_name => "matt")
|
||||
@card.changed?.should be_true
|
||||
end
|
||||
|
||||
it "should report no changes on new object with 'unique_id' set" do
|
||||
@obj = DirtyUniqueIdModel.new
|
||||
@obj.changed?.should be_false
|
||||
@obj.changes.should be_empty
|
||||
end
|
||||
|
||||
it "should report no changes on objects fetched from the database" do
|
||||
card_id = Card.create!(:first_name => "matt").id
|
||||
@card = Card.find(card_id)
|
||||
@card.changed?.should be_false
|
||||
end
|
||||
|
||||
it "should report changes if the record is modified" do
|
||||
@card = Card.new
|
||||
@card.first_name = "andrew"
|
||||
@card.changed?.should be_true
|
||||
@card.first_name_changed?.should be_true
|
||||
end
|
||||
|
||||
it "should report no changes for unmodified records" do
|
||||
card_id = Card.create!(:first_name => "matt").id
|
||||
@card = Card.find(card_id)
|
||||
@card.first_name = "matt"
|
||||
@card.changed?.should be_false
|
||||
@card.first_name_changed?.should be_false
|
||||
end
|
||||
|
||||
it "should report no changes after a new record has been saved" do
|
||||
@card = Card.new(:first_name => "matt")
|
||||
@card.save!
|
||||
@card.changed?.should be_false
|
||||
end
|
||||
|
||||
it "should report no changes after a record has been saved" do
|
||||
card_id = Card.create!(:first_name => "matt").id
|
||||
@card = Card.find(card_id)
|
||||
@card.first_name = "andrew"
|
||||
@card.save!
|
||||
@card.changed?.should be_false
|
||||
end
|
||||
|
||||
# test changing list properties
|
||||
|
||||
it "should report changes if a list property is modified" do
|
||||
cat_id = Cat.create!(:name => "Felix", :toys => [{:name => "Mouse"}]).id
|
||||
@cat = Cat.find(cat_id)
|
||||
@cat.toys = [{:name => "Feather"}]
|
||||
@cat.changed?.should be_true
|
||||
end
|
||||
|
||||
it "should report no changes if a list property is unmodified" do
|
||||
cat_id = Cat.create!(:name => "Felix", :toys => [{:name => "Mouse"}]).id
|
||||
@cat = Cat.find(cat_id)
|
||||
@cat.toys = [{:name => "Mouse"}] # same as original list
|
||||
@cat.changed?.should be_false
|
||||
end
|
||||
|
||||
# attachments
|
||||
|
||||
it "should report changes if an attachment is added" do
|
||||
cat_id = Cat.create!(:name => "Felix", :toys => [{:name => "Mouse"}]).id
|
||||
@file = File.open(FIXTURE_PATH + '/attachments/test.html')
|
||||
@cat = Cat.find(cat_id)
|
||||
@cat.create_attachment(:file => @file, :name => "my_attachment")
|
||||
@cat.changed?.should be_true
|
||||
end
|
||||
|
||||
it "should report changes if an attachment is deleted" do
|
||||
@cat = Cat.create!(:name => "Felix", :toys => [{:name => "Mouse"}])
|
||||
@file = File.open(FIXTURE_PATH + '/attachments/test.html')
|
||||
@attachment_name = "my_attachment"
|
||||
@cat.create_attachment(:file => @file, :name => @attachment_name)
|
||||
@cat.save
|
||||
@cat = Cat.find(@cat.id)
|
||||
@cat.delete_attachment(@attachment_name)
|
||||
@cat.changed?.should be_true
|
||||
end
|
||||
|
||||
# casted models
|
||||
|
||||
it "should report changes to casted models" do
|
||||
@cat = Cat.create!(:name => "Felix", :favorite_toy => { :name => "Mouse" })
|
||||
@cat = Cat.find(@cat.id)
|
||||
@cat.favorite_toy.name = 'Feather'
|
||||
@cat.changed?.should be_true
|
||||
end
|
||||
|
||||
it "should report changes to casted model in array" do
|
||||
@obj = Cat.create!(:name => 'felix', :toys => [{:name => "Catnip"}])
|
||||
@obj = Cat.get(@obj.id)
|
||||
@obj.toys.first.name.should eql('Catnip')
|
||||
@obj.toys.first.changed?.should be_false
|
||||
@obj.changed?.should be_false
|
||||
@obj.toys.first.name = "Super Catnip"
|
||||
@obj.toys.first.changed?.should be_true
|
||||
@obj.changed?.should be_true
|
||||
end
|
||||
|
||||
it "should report changes to anonymous casted models in array" do
|
||||
@obj = DirtyModel.create!(:sub_models => [{:title => "Sample"}])
|
||||
@obj = DirtyModel.get(@obj.id)
|
||||
@obj.sub_models.first.title.should eql("Sample")
|
||||
@obj.sub_models.first.changed?.should be_false
|
||||
@obj.changed?.should be_false
|
||||
@obj.sub_models.first.title = "Another Sample"
|
||||
@obj.sub_models.first.changed?.should be_true
|
||||
@obj.changed?.should be_true
|
||||
end
|
||||
|
||||
# casted arrays
|
||||
|
||||
def test_casted_array(change_expected)
|
||||
obj = DirtyModel.create!
|
||||
obj = DirtyModel.get(obj.id)
|
||||
array = obj.keywords
|
||||
yield array, obj
|
||||
if change_expected
|
||||
obj.changed?.should be_true
|
||||
else
|
||||
obj.changed?.should be_false
|
||||
end
|
||||
end
|
||||
|
||||
def should_change_array
|
||||
test_casted_array(true) { |a,b| yield a,b }
|
||||
end
|
||||
|
||||
def should_not_change_array
|
||||
test_casted_array(false) { |a,b| yield a,b }
|
||||
end
|
||||
|
||||
it "should report changes if an array index is modified" do
|
||||
should_change_array do |array, obj|
|
||||
array[0] = "keyword"
|
||||
end
|
||||
end
|
||||
|
||||
it "should report no changes if an array index is unmodified" do
|
||||
should_not_change_array do |array, obj|
|
||||
array[0] = array[0]
|
||||
end
|
||||
end
|
||||
|
||||
it "should report changes if an array is appended with <<" do
|
||||
should_change_array do |array, obj|
|
||||
array << 'keyword'
|
||||
end
|
||||
end
|
||||
|
||||
it "should report changes if an array is popped" do
|
||||
should_change_array do |array, obj|
|
||||
array.pop
|
||||
end
|
||||
end
|
||||
|
||||
it "should report changes if an array is popped after reload" do
|
||||
should_change_array do |array, obj|
|
||||
obj.reload
|
||||
obj.keywords.pop
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
it "should report no changes if an empty array is popped" do
|
||||
should_not_change_array do |array, obj|
|
||||
array.clear
|
||||
obj.save! # clears changes
|
||||
array.pop
|
||||
end
|
||||
end
|
||||
|
||||
it "should report changes on deletion from an array" do
|
||||
should_change_array do |array, obj|
|
||||
array << "keyword"
|
||||
obj.save!
|
||||
array.delete_at(0)
|
||||
end
|
||||
|
||||
should_change_array do |array, obj|
|
||||
array << "keyword"
|
||||
obj.save!
|
||||
array.delete("keyword")
|
||||
end
|
||||
end
|
||||
|
||||
it "should report changes on deletion from an array after reload" do
|
||||
should_change_array do |array, obj|
|
||||
array << "keyword"
|
||||
obj.save!
|
||||
obj.reload
|
||||
array.delete_at(0)
|
||||
end
|
||||
|
||||
should_change_array do |array, obj|
|
||||
array << "keyword"
|
||||
obj.save!
|
||||
obj.reload
|
||||
array.delete("keyword")
|
||||
end
|
||||
end
|
||||
|
||||
it "should report no changes on deletion from an empty array" do
|
||||
should_not_change_array do |array, obj|
|
||||
array.clear
|
||||
obj.save!
|
||||
array.delete_at(0)
|
||||
end
|
||||
|
||||
should_not_change_array do |array, obj|
|
||||
array.clear
|
||||
obj.save!
|
||||
array.delete("keyword")
|
||||
end
|
||||
end
|
||||
|
||||
it "should report changes if an array is pushed" do
|
||||
should_change_array do |array, obj|
|
||||
array.push("keyword")
|
||||
end
|
||||
end
|
||||
|
||||
it "should report changes if an array is shifted" do
|
||||
should_change_array do |array, obj|
|
||||
array.shift
|
||||
end
|
||||
end
|
||||
|
||||
it "should report no changes if an empty array is shifted" do
|
||||
should_not_change_array do |array, obj|
|
||||
array.clear
|
||||
obj.save! # clears changes
|
||||
array.shift
|
||||
end
|
||||
end
|
||||
|
||||
it "should report changes if an array is unshifted" do
|
||||
should_change_array do |array, obj|
|
||||
array.unshift("keyword")
|
||||
end
|
||||
end
|
||||
|
||||
it "should report changes if an array is cleared" do
|
||||
should_change_array do |array, obj|
|
||||
array.clear
|
||||
end
|
||||
end
|
||||
|
||||
# Object, {} (casted hash)
|
||||
|
||||
def test_casted_hash(change_expected)
|
||||
obj = DirtyModel.create!
|
||||
obj = DirtyModel.get(obj.id)
|
||||
hash = obj.details
|
||||
yield hash, obj
|
||||
if change_expected
|
||||
obj.changed?.should be_true
|
||||
else
|
||||
obj.changed?.should be_false
|
||||
end
|
||||
end
|
||||
|
||||
def should_change_hash
|
||||
test_casted_hash(true) { |a,b| yield a,b }
|
||||
end
|
||||
|
||||
def should_not_change_hash
|
||||
test_casted_hash(false) { |a,b| yield a,b }
|
||||
end
|
||||
|
||||
it "should report changes if a hash is modified" do
|
||||
should_change_hash do |hash, obj|
|
||||
hash['color'] = 'orange'
|
||||
end
|
||||
end
|
||||
|
||||
it "should report no changes if a hash is unmodified" do
|
||||
should_not_change_hash do |hash, obj|
|
||||
hash['color'] = hash['color']
|
||||
end
|
||||
end
|
||||
|
||||
it "should report changes when deleting from a hash" do
|
||||
should_change_hash do |hash, obj|
|
||||
hash.delete('color')
|
||||
end
|
||||
end
|
||||
|
||||
it "should report no changes when deleting a non existent key from a hash" do
|
||||
should_not_change_hash do |hash, obj|
|
||||
hash.delete('non-existent-key')
|
||||
end
|
||||
end
|
||||
|
||||
it "should report changes when clearing a hash" do
|
||||
should_change_hash do |hash, obj|
|
||||
hash.clear
|
||||
end
|
||||
end
|
||||
|
||||
it "should report changes when merging changes to a hash" do
|
||||
should_change_hash do |hash, obj|
|
||||
hash.merge!('foo' => 'bar')
|
||||
end
|
||||
end
|
||||
|
||||
it "should report no changes when merging no changes to a hash" do
|
||||
should_not_change_hash do |hash, obj|
|
||||
hash.merge!('color' => hash['color'])
|
||||
end
|
||||
end
|
||||
|
||||
it "should report changes when replacing hash content" do
|
||||
should_change_hash do |hash, obj|
|
||||
hash.replace('foo' => 'bar')
|
||||
end
|
||||
end
|
||||
|
||||
it "should report no changes when replacing hash content with same content" do
|
||||
should_not_change_hash do |hash, obj|
|
||||
hash.replace(hash)
|
||||
end
|
||||
end
|
||||
|
||||
it "should report changes when removing records with delete_if" do
|
||||
should_change_hash do |hash, obj|
|
||||
hash.delete_if { true }
|
||||
end
|
||||
end
|
||||
|
||||
it "should report no changes when removing no records with delete_if" do
|
||||
should_not_change_hash do |hash, obj|
|
||||
hash.delete_if { false }
|
||||
end
|
||||
end
|
||||
|
||||
if {}.respond_to?(:keep_if)
|
||||
|
||||
it "should report changes when removing records with keep_if" do
|
||||
should_change_hash do |hash, obj|
|
||||
hash.keep_if { false }
|
||||
end
|
||||
end
|
||||
|
||||
it "should report no changes when removing no records with keep_if" do
|
||||
should_not_change_hash do |hash, obj|
|
||||
hash.keep_if { true }
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
498
spec/unit/embeddable_spec.rb
Normal file
498
spec/unit/embeddable_spec.rb
Normal file
|
@ -0,0 +1,498 @@
|
|||
# encoding: utf-8
|
||||
require "spec_helper"
|
||||
|
||||
class WithCastedModelMixin
|
||||
include CouchRest::Model::Embeddable
|
||||
property :name
|
||||
property :no_value
|
||||
property :details, Object, :default => {}
|
||||
property :casted_attribute, WithCastedModelMixin
|
||||
end
|
||||
|
||||
class OldFashionedMixin < Hash
|
||||
include CouchRest::Model::CastedModel
|
||||
property :name
|
||||
end
|
||||
|
||||
class DummyModel < CouchRest::Model::Base
|
||||
use_database TEST_SERVER.default_database
|
||||
raise "Default DB not set" if TEST_SERVER.default_database.nil?
|
||||
property :casted_attribute, WithCastedModelMixin
|
||||
property :keywords, [String]
|
||||
property :old_casted_attribute, OldFashionedMixin
|
||||
property :sub_models do |child|
|
||||
child.property :title
|
||||
end
|
||||
property :param_free_sub_models do
|
||||
property :title
|
||||
end
|
||||
end
|
||||
|
||||
class WithCastedCallBackModel
|
||||
include CouchRest::Model::Embeddable
|
||||
property :name
|
||||
property :run_before_validation
|
||||
property :run_after_validation
|
||||
|
||||
validates_presence_of :run_before_validation
|
||||
|
||||
before_validation do |object|
|
||||
object.run_before_validation = true
|
||||
end
|
||||
after_validation do |object|
|
||||
object.run_after_validation = true
|
||||
end
|
||||
end
|
||||
|
||||
class CastedCallbackDoc < CouchRest::Model::Base
|
||||
use_database TEST_SERVER.default_database
|
||||
raise "Default DB not set" if TEST_SERVER.default_database.nil?
|
||||
property :callback_model, WithCastedCallBackModel
|
||||
end
|
||||
|
||||
describe CouchRest::Model::Embeddable do
|
||||
|
||||
describe "isolated" do
|
||||
before(:each) do
|
||||
@obj = WithCastedModelMixin.new
|
||||
end
|
||||
it "should automatically include the property mixin and define getters and setters" do
|
||||
@obj.name = 'Matt'
|
||||
@obj.name.should == 'Matt'
|
||||
end
|
||||
|
||||
it "should allow override of default" do
|
||||
@obj = WithCastedModelMixin.new(:name => 'Eric', :details => {'color' => 'orange'})
|
||||
@obj.name.should == 'Eric'
|
||||
@obj.details['color'].should == 'orange'
|
||||
end
|
||||
it "should always return base_doc? as false" do
|
||||
@obj.base_doc?.should be_false
|
||||
end
|
||||
it "should call after_initialize callback if available" do
|
||||
klass = Class.new do
|
||||
include CouchRest::Model::CastedModel
|
||||
after_initialize :set_name
|
||||
property :name
|
||||
def set_name; self.name = "foobar"; end
|
||||
end
|
||||
@obj = klass.new
|
||||
@obj.name.should eql("foobar")
|
||||
end
|
||||
it "should allow override of initialize with super" do
|
||||
klass = Class.new do
|
||||
include CouchRest::Model::Embeddable
|
||||
after_initialize :set_name
|
||||
property :name
|
||||
def set_name; self.name = "foobar"; end
|
||||
def initialize(attrs = {}); super(); end
|
||||
end
|
||||
@obj = klass.new
|
||||
@obj.name.should eql("foobar")
|
||||
end
|
||||
end
|
||||
|
||||
describe "casted as an attribute, but without a value" do
|
||||
before(:each) do
|
||||
@obj = DummyModel.new
|
||||
@casted_obj = @obj.casted_attribute
|
||||
end
|
||||
it "should be nil" do
|
||||
@casted_obj.should == nil
|
||||
end
|
||||
end
|
||||
|
||||
describe "anonymous sub casted models" do
|
||||
before :each do
|
||||
@obj = DummyModel.new
|
||||
end
|
||||
it "should be empty initially" do
|
||||
@obj.sub_models.should_not be_nil
|
||||
@obj.sub_models.should be_empty
|
||||
end
|
||||
it "should be updatable using a hash" do
|
||||
@obj.sub_models << {:title => 'test'}
|
||||
@obj.sub_models.first.title.should eql('test')
|
||||
end
|
||||
it "should be empty intitally (without params)" do
|
||||
@obj.param_free_sub_models.should_not be_nil
|
||||
@obj.param_free_sub_models.should be_empty
|
||||
end
|
||||
it "should be updatable using a hash (without params)" do
|
||||
@obj.param_free_sub_models << {:title => 'test'}
|
||||
@obj.param_free_sub_models.first.title.should eql('test')
|
||||
end
|
||||
end
|
||||
|
||||
describe "casted as attribute" do
|
||||
before(:each) do
|
||||
casted = {:name => 'not whatever'}
|
||||
@obj = DummyModel.new(:casted_attribute => {:name => 'whatever', :casted_attribute => casted})
|
||||
@casted_obj = @obj.casted_attribute
|
||||
end
|
||||
|
||||
it "should be available from its parent" do
|
||||
@casted_obj.should be_an_instance_of(WithCastedModelMixin)
|
||||
end
|
||||
|
||||
it "should have the getters defined" do
|
||||
@casted_obj.name.should == 'whatever'
|
||||
end
|
||||
|
||||
it "should know who casted it" do
|
||||
@casted_obj.casted_by.should == @obj
|
||||
end
|
||||
|
||||
it "should know which property casted it" do
|
||||
@casted_obj.casted_by_property.should == @obj.properties.detect{|p| p.to_s == 'casted_attribute'}
|
||||
end
|
||||
|
||||
it "should return nil for the 'no_value' attribute" do
|
||||
@casted_obj.no_value.should be_nil
|
||||
end
|
||||
|
||||
it "should return nil for the unknown attribute" do
|
||||
@casted_obj["unknown"].should be_nil
|
||||
end
|
||||
|
||||
it "should return {} for the hash attribute" do
|
||||
@casted_obj.details.should == {}
|
||||
end
|
||||
|
||||
it "should cast its own attributes" do
|
||||
@casted_obj.casted_attribute.should be_instance_of(WithCastedModelMixin)
|
||||
end
|
||||
|
||||
it "should raise an error if save or update_attributes called" do
|
||||
expect { @casted_obj.casted_attribute.save }.to raise_error(NoMethodError)
|
||||
expect { @casted_obj.casted_attribute.update_attributes(:name => "Fubar") }.to raise_error(NoMethodError)
|
||||
end
|
||||
end
|
||||
|
||||
# Basic testing for an old fashioned casted hash
|
||||
describe "old hash casted as attribute" do
|
||||
before :each do
|
||||
@obj = DummyModel.new(:old_casted_attribute => {:name => 'Testing'})
|
||||
@casted_obj = @obj.old_casted_attribute
|
||||
end
|
||||
it "should be available from its parent" do
|
||||
@casted_obj.should be_an_instance_of(OldFashionedMixin)
|
||||
end
|
||||
|
||||
it "should have the getters defined" do
|
||||
@casted_obj.name.should == 'Testing'
|
||||
end
|
||||
|
||||
it "should know who casted it" do
|
||||
@casted_obj.casted_by.should == @obj
|
||||
end
|
||||
|
||||
it "should know which property casted it" do
|
||||
@casted_obj.casted_by_property.should == @obj.properties.detect{|p| p.to_s == 'old_casted_attribute'}
|
||||
end
|
||||
|
||||
it "should return nil for the unknown attribute" do
|
||||
@casted_obj["unknown"].should be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe "casted as an array of a different type" do
|
||||
before(:each) do
|
||||
@obj = DummyModel.new(:keywords => ['couch', 'sofa', 'relax', 'canapé'])
|
||||
end
|
||||
|
||||
it "should cast the array properly" do
|
||||
@obj.keywords.should be_kind_of(Array)
|
||||
@obj.keywords.first.should == 'couch'
|
||||
end
|
||||
end
|
||||
|
||||
describe "update attributes without saving" do
|
||||
before(:each) do
|
||||
@question = Question.new(:q => "What is your quest?", :a => "To seek the Holy Grail")
|
||||
end
|
||||
it "should work for attribute= methods" do
|
||||
@question.q.should == "What is your quest?"
|
||||
@question['a'].should == "To seek the Holy Grail"
|
||||
@question.update_attributes_without_saving(:q => "What is your favorite color?", 'a' => "Blue")
|
||||
@question['q'].should == "What is your favorite color?"
|
||||
@question.a.should == "Blue"
|
||||
end
|
||||
|
||||
it "should also work for attributes= alias" do
|
||||
@question.respond_to?(:attributes=).should be_true
|
||||
@question.attributes = {:q => "What is your favorite color?", 'a' => "Blue"}
|
||||
@question['q'].should == "What is your favorite color?"
|
||||
@question.a.should == "Blue"
|
||||
end
|
||||
|
||||
it "should flip out if an attribute= method is missing" do
|
||||
lambda {
|
||||
@q.update_attributes_without_saving('foo' => "something", :a => "No green")
|
||||
}.should raise_error(NoMethodError)
|
||||
end
|
||||
|
||||
it "should not change any attributes if there is an error" do
|
||||
lambda {
|
||||
@q.update_attributes_without_saving('foo' => "something", :a => "No green")
|
||||
}.should raise_error(NoMethodError)
|
||||
@question.q.should == "What is your quest?"
|
||||
@question.a.should == "To seek the Holy Grail"
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe "saved document with casted models" do
|
||||
before(:each) do
|
||||
reset_test_db!
|
||||
@obj = DummyModel.new(:casted_attribute => {:name => 'whatever'})
|
||||
@obj.save.should be_true
|
||||
@obj = DummyModel.get(@obj.id)
|
||||
end
|
||||
|
||||
it "should be able to load with the casted models" do
|
||||
casted_obj = @obj.casted_attribute
|
||||
casted_obj.should_not be_nil
|
||||
casted_obj.should be_an_instance_of(WithCastedModelMixin)
|
||||
end
|
||||
|
||||
it "should have defined getters for the casted model" do
|
||||
casted_obj = @obj.casted_attribute
|
||||
casted_obj.name.should == "whatever"
|
||||
end
|
||||
|
||||
it "should have defined setters for the casted model" do
|
||||
casted_obj = @obj.casted_attribute
|
||||
casted_obj.name = "test"
|
||||
casted_obj.name.should == "test"
|
||||
end
|
||||
|
||||
it "should retain an override of a casted model attribute's default" do
|
||||
casted_obj = @obj.casted_attribute
|
||||
casted_obj.details['color'] = 'orange'
|
||||
@obj.save
|
||||
casted_obj = DummyModel.get(@obj.id).casted_attribute
|
||||
casted_obj.details['color'].should == 'orange'
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe "saving document with array of casted models and validation" do
|
||||
before :each do
|
||||
@cat = Cat.new :name => "felix"
|
||||
@cat.save
|
||||
end
|
||||
|
||||
it "should save" do
|
||||
toy = CatToy.new :name => "Mouse"
|
||||
@cat.toys.push(toy)
|
||||
@cat.save.should be_true
|
||||
@cat = Cat.get @cat.id
|
||||
@cat.toys.class.should == CouchRest::Model::CastedArray
|
||||
@cat.toys.first.class.should == CatToy
|
||||
@cat.toys.first.should === toy
|
||||
end
|
||||
|
||||
it "should fail because name is not present" do
|
||||
toy = CatToy.new
|
||||
@cat.toys.push(toy)
|
||||
@cat.should_not be_valid
|
||||
@cat.save.should be_false
|
||||
end
|
||||
|
||||
it "should not fail if the casted model doesn't have validation" do
|
||||
Cat.property :masters, [Person], :default => []
|
||||
Cat.validates_presence_of :name
|
||||
cat = Cat.new(:name => 'kitty')
|
||||
cat.should be_valid
|
||||
cat.masters.push Person.new
|
||||
cat.should be_valid
|
||||
end
|
||||
end
|
||||
|
||||
describe "calling valid?" do
|
||||
before :each do
|
||||
@cat = Cat.new
|
||||
@toy1 = CatToy.new
|
||||
@toy2 = CatToy.new
|
||||
@toy3 = CatToy.new
|
||||
@cat.favorite_toy = @toy1
|
||||
@cat.toys << @toy2
|
||||
@cat.toys << @toy3
|
||||
end
|
||||
|
||||
describe "on the top document" do
|
||||
it "should put errors on all invalid casted models" do
|
||||
@cat.should_not be_valid
|
||||
@cat.errors.should_not be_empty
|
||||
@toy1.errors.should_not be_empty
|
||||
@toy2.errors.should_not be_empty
|
||||
@toy3.errors.should_not be_empty
|
||||
end
|
||||
|
||||
it "should not put errors on valid casted models" do
|
||||
@toy1.name = "Feather"
|
||||
@toy2.name = "Twine"
|
||||
@cat.should_not be_valid
|
||||
@cat.errors.should_not be_empty
|
||||
@toy1.errors.should be_empty
|
||||
@toy2.errors.should be_empty
|
||||
@toy3.errors.should_not be_empty
|
||||
end
|
||||
|
||||
it "should not use dperecated ActiveModel options" do
|
||||
ActiveSupport::Deprecation.should_not_receive(:warn)
|
||||
@cat.should_not be_valid
|
||||
end
|
||||
end
|
||||
|
||||
describe "on a casted model property" do
|
||||
it "should only validate itself" do
|
||||
@toy1.should_not be_valid
|
||||
@toy1.errors.should_not be_empty
|
||||
@cat.errors.should be_empty
|
||||
@toy2.errors.should be_empty
|
||||
@toy3.errors.should be_empty
|
||||
end
|
||||
end
|
||||
|
||||
describe "on a casted model inside a casted collection" do
|
||||
it "should only validate itself" do
|
||||
@toy2.should_not be_valid
|
||||
@toy2.errors.should_not be_empty
|
||||
@cat.errors.should be_empty
|
||||
@toy1.errors.should be_empty
|
||||
@toy3.errors.should be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "calling new? on a casted model" do
|
||||
before :each do
|
||||
reset_test_db!
|
||||
@cat = Cat.new(:name => 'Sockington')
|
||||
@favorite_toy = CatToy.new(:name => 'Catnip Ball')
|
||||
@cat.favorite_toy = @favorite_toy
|
||||
@cat.toys << CatToy.new(:name => 'Fuzzy Stick')
|
||||
end
|
||||
|
||||
it "should be true on new" do
|
||||
CatToy.new.should be_new
|
||||
CatToy.new.new_record?.should be_true
|
||||
end
|
||||
|
||||
it "should be true after assignment" do
|
||||
@cat.should be_new
|
||||
@cat.favorite_toy.should be_new
|
||||
@cat.toys.first.should be_new
|
||||
end
|
||||
|
||||
it "should not be true after create or save" do
|
||||
@cat.create
|
||||
@cat.save
|
||||
@cat.favorite_toy.should_not be_new
|
||||
@cat.toys.first.casted_by.should eql(@cat)
|
||||
@cat.toys.first.should_not be_new
|
||||
end
|
||||
|
||||
it "should not be true after get from the database" do
|
||||
@cat.save
|
||||
@cat = Cat.get(@cat.id)
|
||||
@cat.favorite_toy.should_not be_new
|
||||
@cat.toys.first.should_not be_new
|
||||
end
|
||||
|
||||
it "should still be true after a failed create or save" do
|
||||
@cat.name = nil
|
||||
@cat.create.should be_false
|
||||
@cat.save.should be_false
|
||||
@cat.favorite_toy.should be_new
|
||||
@cat.toys.first.should be_new
|
||||
end
|
||||
end
|
||||
|
||||
describe "calling base_doc from a nested casted model" do
|
||||
before :each do
|
||||
@course = Course.new(:title => 'Science 101')
|
||||
@professor = Person.new(:name => ['Professor', 'Plum'])
|
||||
@cat = Cat.new(:name => 'Scratchy')
|
||||
@toy1 = CatToy.new
|
||||
@toy2 = CatToy.new
|
||||
@course.professor = @professor
|
||||
@professor.pet = @cat
|
||||
@cat.favorite_toy = @toy1
|
||||
@cat.toys << @toy2
|
||||
end
|
||||
|
||||
it 'should let you copy over casted arrays' do
|
||||
question = Question.new
|
||||
@course.questions << question
|
||||
new_course = Course.new
|
||||
new_course.questions = @course.questions
|
||||
new_course.questions.should include(question)
|
||||
end
|
||||
|
||||
it "should reference the top document for" do
|
||||
@course.base_doc.should === @course
|
||||
@professor.casted_by.should === @course
|
||||
@professor.base_doc.should === @course
|
||||
@cat.base_doc.should === @course
|
||||
@toy1.base_doc.should === @course
|
||||
@toy2.base_doc.should === @course
|
||||
end
|
||||
|
||||
it "should call setter on top document" do
|
||||
@toy1.base_doc.should_not be_nil
|
||||
@toy1.base_doc.title = 'Tom Foolery'
|
||||
@course.title.should == 'Tom Foolery'
|
||||
end
|
||||
|
||||
it "should return nil if not yet casted" do
|
||||
person = Person.new
|
||||
person.base_doc.should == nil
|
||||
end
|
||||
end
|
||||
|
||||
describe "calling base_doc.save from a nested casted model" do
|
||||
before :each do
|
||||
reset_test_db!
|
||||
@cat = Cat.new(:name => 'Snowball')
|
||||
@toy = CatToy.new
|
||||
@cat.favorite_toy = @toy
|
||||
end
|
||||
|
||||
it "should not save parent document when casted model is invalid" do
|
||||
@toy.should_not be_valid
|
||||
@toy.base_doc.save.should be_false
|
||||
lambda{@toy.base_doc.save!}.should raise_error
|
||||
end
|
||||
|
||||
it "should save parent document when nested casted model is valid" do
|
||||
@toy.name = "Mr Squeaks"
|
||||
@toy.should be_valid
|
||||
@toy.base_doc.save.should be_true
|
||||
lambda{@toy.base_doc.save!}.should_not raise_error
|
||||
end
|
||||
end
|
||||
|
||||
describe "callbacks" do
|
||||
before(:each) do
|
||||
@doc = CastedCallbackDoc.new
|
||||
@model = WithCastedCallBackModel.new
|
||||
@doc.callback_model = @model
|
||||
end
|
||||
|
||||
describe "validate" do
|
||||
it "should run before_validation before validating" do
|
||||
@model.run_before_validation.should be_nil
|
||||
@model.should be_valid
|
||||
@model.run_before_validation.should be_true
|
||||
end
|
||||
it "should run after_validation after validating" do
|
||||
@model.run_after_validation.should be_nil
|
||||
@model.should be_valid
|
||||
@model.run_after_validation.should be_true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
33
spec/unit/inherited_spec.rb
Normal file
33
spec/unit/inherited_spec.rb
Normal file
|
@ -0,0 +1,33 @@
|
|||
require 'spec_helper'
|
||||
|
||||
class PlainParent
|
||||
class_inheritable_accessor :foo
|
||||
self.foo = :bar
|
||||
end
|
||||
|
||||
class PlainChild < PlainParent
|
||||
end
|
||||
|
||||
class ExtendedParent < CouchRest::Model::Base
|
||||
class_inheritable_accessor :foo
|
||||
self.foo = :bar
|
||||
end
|
||||
|
||||
class ExtendedChild < ExtendedParent
|
||||
end
|
||||
|
||||
describe "Using chained inheritance without CouchRest::Model::Base" do
|
||||
it "should preserve inheritable attributes" do
|
||||
PlainParent.foo.should == :bar
|
||||
PlainChild.foo.should == :bar
|
||||
end
|
||||
end
|
||||
|
||||
describe "Using chained inheritance with CouchRest::Model::Base" do
|
||||
it "should preserve inheritable attributes" do
|
||||
ExtendedParent.foo.should == :bar
|
||||
ExtendedChild.foo.should == :bar
|
||||
end
|
||||
end
|
||||
|
||||
|
481
spec/unit/persistence_spec.rb
Normal file
481
spec/unit/persistence_spec.rb
Normal file
|
@ -0,0 +1,481 @@
|
|||
# encoding: utf-8
|
||||
require 'spec_helper'
|
||||
|
||||
describe CouchRest::Model::Persistence do
|
||||
|
||||
before(:each) do
|
||||
@obj = WithDefaultValues.new
|
||||
end
|
||||
|
||||
describe "creating a new document from database" do
|
||||
|
||||
it "should instantialize" do
|
||||
doc = Article.build_from_database({'_id' => 'testitem1', '_rev' => 123, 'couchrest-type' => 'Article', 'name' => 'my test'})
|
||||
doc.class.should eql(Article)
|
||||
end
|
||||
|
||||
it "should instantialize of same class if no couchrest-type included from DB" do
|
||||
doc = Article.build_from_database({'_id' => 'testitem1', '_rev' => 123, 'name' => 'my test'})
|
||||
doc.class.should eql(Article)
|
||||
end
|
||||
|
||||
it "should instantialize document of different type" do
|
||||
doc = Article.build_from_database({'_id' => 'testitem2', '_rev' => 123, Article.model_type_key => 'WithTemplateAndUniqueID', 'name' => 'my test'})
|
||||
doc.class.should eql(WithTemplateAndUniqueID)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe "basic saving and retrieving" do
|
||||
it "should work fine" do
|
||||
@obj.name = "should be easily saved and retrieved"
|
||||
@obj.save!
|
||||
saved_obj = WithDefaultValues.get!(@obj.id)
|
||||
saved_obj.should_not be_nil
|
||||
end
|
||||
|
||||
it "should parse the Time attributes automatically" do
|
||||
@obj.name = "should parse the Time attributes automatically"
|
||||
@obj.set_by_proc.should be_an_instance_of(Time)
|
||||
@obj.save
|
||||
@obj.set_by_proc.should be_an_instance_of(Time)
|
||||
saved_obj = WithDefaultValues.get(@obj.id)
|
||||
saved_obj.set_by_proc.should be_an_instance_of(Time)
|
||||
end
|
||||
end
|
||||
|
||||
describe "creating a model" do
|
||||
|
||||
before(:each) do
|
||||
@sobj = Basic.new
|
||||
end
|
||||
|
||||
it "should accept true or false on save for validation" do
|
||||
@sobj.should_receive(:valid?)
|
||||
@sobj.save(true)
|
||||
end
|
||||
|
||||
it "should accept hash with validation option" do
|
||||
@sobj.should_receive(:valid?)
|
||||
@sobj.save(:validate => true)
|
||||
end
|
||||
|
||||
it "should not call validation when option is false" do
|
||||
@sobj.should_not_receive(:valid?)
|
||||
@sobj.save(false)
|
||||
end
|
||||
|
||||
it "should not call validation when option :validate is false" do
|
||||
@sobj.should_not_receive(:valid?)
|
||||
@sobj.save(:validate => false)
|
||||
end
|
||||
|
||||
it "should instantialize and save a document" do
|
||||
article = Article.create(:title => 'my test')
|
||||
article.title.should == 'my test'
|
||||
article.should_not be_new
|
||||
end
|
||||
|
||||
it "yields new instance to block before saving (#create)" do
|
||||
article = Article.create{|a| a.title = 'my create init block test'}
|
||||
article.title.should == 'my create init block test'
|
||||
article.should_not be_new
|
||||
end
|
||||
|
||||
it "yields new instance to block before saving (#create!)" do
|
||||
article = Article.create{|a| a.title = 'my create bang init block test'}
|
||||
article.title.should == 'my create bang init block test'
|
||||
article.should_not be_new
|
||||
end
|
||||
|
||||
it "should trigger the create callbacks" do
|
||||
doc = WithCallBacks.create(:name => 'my other test')
|
||||
doc.run_before_create.should be_true
|
||||
doc.run_after_create.should be_true
|
||||
doc.run_before_save.should be_true
|
||||
doc.run_after_save.should be_true
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe "saving a model" do
|
||||
before(:all) do
|
||||
@sobj = Basic.new
|
||||
@sobj.save.should be_true
|
||||
end
|
||||
|
||||
it "should save the doc" do
|
||||
doc = Basic.get(@sobj.id)
|
||||
doc['_id'].should == @sobj.id
|
||||
end
|
||||
|
||||
it "should be set for resaving" do
|
||||
rev = @obj.rev
|
||||
@sobj['another-key'] = "some value"
|
||||
@sobj.save
|
||||
@sobj.rev.should_not == rev
|
||||
end
|
||||
|
||||
it "should set the id" do
|
||||
@sobj.id.should be_an_instance_of(String)
|
||||
end
|
||||
|
||||
it "should set the type" do
|
||||
@sobj[@sobj.model_type_key].should == 'Basic'
|
||||
end
|
||||
|
||||
it "should accept true or false on save for validation" do
|
||||
@sobj.should_receive(:valid?)
|
||||
@sobj.save(true)
|
||||
end
|
||||
|
||||
it "should accept hash with validation option" do
|
||||
@sobj.should_receive(:valid?)
|
||||
@sobj.save(:validate => true)
|
||||
end
|
||||
|
||||
it "should not call validation when option is false" do
|
||||
@sobj.should_not_receive(:valid?)
|
||||
@sobj.save(false)
|
||||
end
|
||||
|
||||
it "should not call validation when option :validate is false" do
|
||||
@sobj.should_not_receive(:valid?)
|
||||
@sobj.save(:validate => false)
|
||||
end
|
||||
|
||||
describe "save!" do
|
||||
|
||||
before(:each) do
|
||||
@sobj = Card.new(:first_name => "Marcos", :last_name => "Tapajós")
|
||||
end
|
||||
|
||||
it "should return true if save the document" do
|
||||
@sobj.save!.should be_true
|
||||
end
|
||||
|
||||
it "should raise error if don't save the document" do
|
||||
@sobj.first_name = nil
|
||||
lambda { @sobj.save! }.should raise_error(CouchRest::Model::Errors::Validations)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
describe "saving a model with a unique_id configured" do
|
||||
before(:each) do
|
||||
@art = Article.new
|
||||
@old = Article.database.get('this-is-the-title') rescue nil
|
||||
Article.database.delete_doc(@old) if @old
|
||||
end
|
||||
|
||||
it "should be a new document" do
|
||||
@art.should be_new
|
||||
@art.title.should be_nil
|
||||
end
|
||||
|
||||
it "should require the title" do
|
||||
lambda{@art.save}.should raise_error
|
||||
@art.title = 'This is the title'
|
||||
@art.save.should be_true
|
||||
end
|
||||
|
||||
it "should not change the slug on update" do
|
||||
@art.title = 'This is the title'
|
||||
@art.save.should be_true
|
||||
@art.title = 'new title'
|
||||
@art.save.should be_true
|
||||
@art.slug.should == 'this-is-the-title'
|
||||
end
|
||||
|
||||
it "should raise an error when the slug is taken" do
|
||||
@art.title = 'This is the title'
|
||||
@art.save.should be_true
|
||||
@art2 = Article.new(:title => 'This is the title!')
|
||||
lambda{@art2.save}.should raise_error
|
||||
end
|
||||
|
||||
it "should set the slug" do
|
||||
@art.title = 'This is the title'
|
||||
@art.save.should be_true
|
||||
@art.slug.should == 'this-is-the-title'
|
||||
end
|
||||
|
||||
it "should set the id" do
|
||||
@art.title = 'This is the title'
|
||||
@art.save.should be_true
|
||||
@art.id.should == 'this-is-the-title'
|
||||
end
|
||||
end
|
||||
|
||||
describe "saving a model with a unique_id lambda" do
|
||||
before(:each) do
|
||||
@templated = WithTemplateAndUniqueID.new
|
||||
@old = WithTemplateAndUniqueID.get('very-important') rescue nil
|
||||
@old.destroy if @old
|
||||
end
|
||||
|
||||
it "should require the field" do
|
||||
lambda{@templated.save}.should raise_error
|
||||
@templated['slug'] = 'very-important'
|
||||
@templated.save.should be_true
|
||||
end
|
||||
|
||||
it "should save with the id" do
|
||||
@templated['slug'] = 'very-important'
|
||||
@templated.save.should be_true
|
||||
t = WithTemplateAndUniqueID.get('very-important')
|
||||
t.should == @templated
|
||||
end
|
||||
|
||||
it "should not change the id on update" do
|
||||
@templated['slug'] = 'very-important'
|
||||
@templated.save.should be_true
|
||||
@templated['slug'] = 'not-important'
|
||||
@templated.save.should be_true
|
||||
t = WithTemplateAndUniqueID.get('very-important')
|
||||
t.id.should == @templated.id
|
||||
end
|
||||
|
||||
it "should raise an error when the id is taken" do
|
||||
@templated['slug'] = 'very-important'
|
||||
@templated.save.should be_true
|
||||
lambda{WithTemplateAndUniqueID.new('slug' => 'very-important').save}.should raise_error
|
||||
end
|
||||
|
||||
it "should set the id" do
|
||||
@templated['slug'] = 'very-important'
|
||||
@templated.save.should be_true
|
||||
@templated.id.should == 'very-important'
|
||||
end
|
||||
end
|
||||
|
||||
describe "destroying an instance" do
|
||||
before(:each) do
|
||||
@dobj = Event.new
|
||||
@dobj.save.should be_true
|
||||
end
|
||||
it "should return true" do
|
||||
result = @dobj.destroy
|
||||
result.should be_true
|
||||
end
|
||||
it "should make it go away" do
|
||||
@dobj.destroy
|
||||
lambda{Basic.get!(@dobj.id)}.should raise_error(CouchRest::Model::DocumentNotFound)
|
||||
end
|
||||
it "should freeze the object" do
|
||||
@dobj.destroy
|
||||
# In Ruby 1.9.2 this raises RuntimeError, in 1.8.7 TypeError, D'OH!
|
||||
lambda { @dobj.subject = "Test" }.should raise_error(StandardError)
|
||||
end
|
||||
it "trying to save after should fail" do
|
||||
@dobj.destroy
|
||||
lambda { @dobj.save }.should raise_error(StandardError)
|
||||
lambda{Basic.get!(@dobj.id)}.should raise_error(CouchRest::Model::DocumentNotFound)
|
||||
end
|
||||
it "should make destroyed? true" do
|
||||
@dobj.destroyed?.should be_false
|
||||
@dobj.destroy
|
||||
@dobj.destroyed?.should be_true
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
describe "getting a model" do
|
||||
before(:all) do
|
||||
@art = Article.new(:title => 'All About Getting')
|
||||
@art.save
|
||||
end
|
||||
it "should load and instantiate it" do
|
||||
foundart = Article.get @art.id
|
||||
foundart.title.should == "All About Getting"
|
||||
end
|
||||
it "should load and instantiate with find" do
|
||||
foundart = Article.find @art.id
|
||||
foundart.title.should == "All About Getting"
|
||||
end
|
||||
it "should return nil if `get` is used and the document doesn't exist" do
|
||||
foundart = Article.get 'matt aimonetti'
|
||||
foundart.should be_nil
|
||||
end
|
||||
it "should return nil if a blank id is requested" do
|
||||
Article.get("").should be_nil
|
||||
end
|
||||
it "should raise an error if `get!` is used and the document doesn't exist" do
|
||||
expect{ Article.get!('matt aimonetti') }.to raise_error
|
||||
end
|
||||
it "should raise an error if `get!` is requested with a blank id" do
|
||||
expect{ Article.get!("") }.to raise_error
|
||||
end
|
||||
it "should raise an error if `find!` is used and the document doesn't exist" do
|
||||
expect{ Article.find!('matt aimonetti') }.to raise_error
|
||||
end
|
||||
end
|
||||
|
||||
describe "getting a model with a subobjects array" do
|
||||
before(:all) do
|
||||
course_doc = {
|
||||
"title" => "Metaphysics 200",
|
||||
"questions" => [
|
||||
{
|
||||
"q" => "Carve the ___ of reality at the ___.",
|
||||
"a" => ["beast","joints"]
|
||||
},{
|
||||
"q" => "Who layed the smack down on Leibniz's Law?",
|
||||
"a" => "Willard Van Orman Quine"
|
||||
}
|
||||
]
|
||||
}
|
||||
r = Course.database.save_doc course_doc
|
||||
@course = Course.get r['id']
|
||||
end
|
||||
it "should load the course" do
|
||||
@course.title.should == "Metaphysics 200"
|
||||
end
|
||||
it "should instantiate them as such" do
|
||||
@course["questions"][0].a[0].should == "beast"
|
||||
end
|
||||
end
|
||||
|
||||
describe "callbacks" do
|
||||
|
||||
before(:each) do
|
||||
@doc = WithCallBacks.new
|
||||
end
|
||||
|
||||
describe "validation" do
|
||||
it "should run before_validation before validating" do
|
||||
@doc.run_before_validation.should be_nil
|
||||
@doc.should be_valid
|
||||
@doc.run_before_validation.should be_true
|
||||
end
|
||||
it "should run after_validation after validating" do
|
||||
@doc.run_after_validation.should be_nil
|
||||
@doc.should be_valid
|
||||
@doc.run_after_validation.should be_true
|
||||
end
|
||||
end
|
||||
|
||||
describe "with contextual validation on ”create”" do
|
||||
it "should validate only within ”create” context" do
|
||||
doc = WithContextualValidationOnCreate.new
|
||||
doc.save.should be_false
|
||||
doc.name = "Alice"
|
||||
doc.save.should be_true
|
||||
|
||||
doc.update_attributes(:name => nil).should be_true
|
||||
end
|
||||
end
|
||||
|
||||
describe "with contextual validation on ”update”" do
|
||||
it "should validate only within ”update” context" do
|
||||
doc = WithContextualValidationOnUpdate.new
|
||||
doc.save.should be_true
|
||||
|
||||
doc.update_attributes(:name => nil).should be_false
|
||||
doc.update_attributes(:name => "Bob").should be_true
|
||||
end
|
||||
end
|
||||
|
||||
describe "save" do
|
||||
it "should run the after filter after saving" do
|
||||
@doc.run_after_save.should be_nil
|
||||
@doc.save.should be_true
|
||||
@doc.run_after_save.should be_true
|
||||
end
|
||||
it "should run the grouped callbacks before saving" do
|
||||
@doc.run_one.should be_nil
|
||||
@doc.run_two.should be_nil
|
||||
@doc.run_three.should be_nil
|
||||
@doc.save.should be_true
|
||||
@doc.run_one.should be_true
|
||||
@doc.run_two.should be_true
|
||||
@doc.run_three.should be_true
|
||||
end
|
||||
it "should not run conditional callbacks" do
|
||||
@doc.run_it = false
|
||||
@doc.save.should be_true
|
||||
@doc.conditional_one.should be_nil
|
||||
@doc.conditional_two.should be_nil
|
||||
end
|
||||
it "should run conditional callbacks" do
|
||||
@doc.run_it = true
|
||||
@doc.save.should be_true
|
||||
@doc.conditional_one.should be_true
|
||||
@doc.conditional_two.should be_true
|
||||
end
|
||||
end
|
||||
describe "create" do
|
||||
it "should run the before save filter when creating" do
|
||||
@doc.run_before_save.should be_nil
|
||||
@doc.create.should_not be_nil
|
||||
@doc.run_before_save.should be_true
|
||||
end
|
||||
it "should run the before create filter" do
|
||||
@doc.run_before_create.should be_nil
|
||||
@doc.create.should_not be_nil
|
||||
@doc.create
|
||||
@doc.run_before_create.should be_true
|
||||
end
|
||||
it "should run the after create filter" do
|
||||
@doc.run_after_create.should be_nil
|
||||
@doc.create.should_not be_nil
|
||||
@doc.create
|
||||
@doc.run_after_create.should be_true
|
||||
end
|
||||
end
|
||||
describe "update" do
|
||||
|
||||
before(:each) do
|
||||
@doc.save
|
||||
end
|
||||
it "should run the before update filter when updating an existing document" do
|
||||
@doc.run_before_update.should be_nil
|
||||
@doc.update
|
||||
@doc.run_before_update.should be_true
|
||||
end
|
||||
it "should run the after update filter when updating an existing document" do
|
||||
@doc.run_after_update.should be_nil
|
||||
@doc.update
|
||||
@doc.run_after_update.should be_true
|
||||
end
|
||||
it "should run the before update filter when saving an existing document" do
|
||||
@doc.run_before_update.should be_nil
|
||||
@doc.save
|
||||
@doc.run_before_update.should be_true
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
describe "#reload" do
|
||||
it "reloads defined attributes" do
|
||||
i = Article.create!(:title => "Reload when changed")
|
||||
i.title.should == "Reload when changed"
|
||||
|
||||
i.title = "..."
|
||||
i.title.should == "..."
|
||||
|
||||
i.reload
|
||||
i.title.should == "Reload when changed"
|
||||
end
|
||||
|
||||
it "reloads defined attributes set to nil" do
|
||||
i = Article.create!(:title => "Reload when nil")
|
||||
i.title.should == "Reload when nil"
|
||||
|
||||
i.title = nil
|
||||
i.title.should be_nil
|
||||
|
||||
i.reload
|
||||
i.title.should == "Reload when nil"
|
||||
end
|
||||
|
||||
it "returns self" do
|
||||
i = Article.create!(:title => "Reload return self")
|
||||
i.reload.should be(i)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
192
spec/unit/property_protection_spec.rb
Normal file
192
spec/unit/property_protection_spec.rb
Normal file
|
@ -0,0 +1,192 @@
|
|||
require "spec_helper"
|
||||
|
||||
describe "Model Attributes" do
|
||||
|
||||
describe "no declarations" do
|
||||
class NoProtection < CouchRest::Model::Base
|
||||
use_database TEST_SERVER.default_database
|
||||
property :name
|
||||
property :phone
|
||||
end
|
||||
|
||||
it "should not protect anything through new" do
|
||||
user = NoProtection.new(:name => "will", :phone => "555-5555")
|
||||
|
||||
user.name.should == "will"
|
||||
user.phone.should == "555-5555"
|
||||
end
|
||||
|
||||
it "should not protect anything through attributes=" do
|
||||
user = NoProtection.new
|
||||
user.attributes = {:name => "will", :phone => "555-5555"}
|
||||
|
||||
user.name.should == "will"
|
||||
user.phone.should == "555-5555"
|
||||
end
|
||||
|
||||
it "should recreate from the database properly" do
|
||||
user = NoProtection.new
|
||||
user.name = "will"
|
||||
user.phone = "555-5555"
|
||||
user.save!
|
||||
|
||||
user = NoProtection.get(user.id)
|
||||
user.name.should == "will"
|
||||
user.phone.should == "555-5555"
|
||||
end
|
||||
|
||||
it "should provide a list of all properties as accessible" do
|
||||
user = NoProtection.new(:name => "will", :phone => "555-5555")
|
||||
user.accessible_properties.length.should eql(2)
|
||||
user.protected_properties.should be_empty
|
||||
end
|
||||
end
|
||||
|
||||
describe "Model Base", "accessible flag" do
|
||||
class WithAccessible < CouchRest::Model::Base
|
||||
use_database TEST_SERVER.default_database
|
||||
property :name, :accessible => true
|
||||
property :admin, :default => false
|
||||
end
|
||||
|
||||
it { expect { WithAccessible.new(nil) }.to_not raise_error }
|
||||
|
||||
it "should recognize accessible properties" do
|
||||
props = WithAccessible.accessible_properties.map { |prop| prop.name}
|
||||
props.should include("name")
|
||||
props.should_not include("admin")
|
||||
end
|
||||
|
||||
it "should protect non-accessible properties set through new" do
|
||||
user = WithAccessible.new(:name => "will", :admin => true)
|
||||
|
||||
user.name.should == "will"
|
||||
user.admin.should == false
|
||||
end
|
||||
|
||||
it "should protect non-accessible properties set through attributes=" do
|
||||
user = WithAccessible.new
|
||||
user.attributes = {:name => "will", :admin => true}
|
||||
|
||||
user.name.should == "will"
|
||||
user.admin.should == false
|
||||
end
|
||||
|
||||
it "should provide correct accessible and protected property lists" do
|
||||
user = WithAccessible.new(:name => 'will', :admin => true)
|
||||
user.accessible_properties.map{|p| p.to_s}.should eql(['name'])
|
||||
user.protected_properties.map{|p| p.to_s}.should eql(['admin'])
|
||||
end
|
||||
end
|
||||
|
||||
describe "Model Base", "protected flag" do
|
||||
class WithProtected < CouchRest::Model::Base
|
||||
use_database TEST_SERVER.default_database
|
||||
property :name
|
||||
property :admin, :default => false, :protected => true
|
||||
end
|
||||
|
||||
it { expect { WithProtected.new(nil) }.to_not raise_error }
|
||||
|
||||
it "should recognize protected properties" do
|
||||
props = WithProtected.protected_properties.map { |prop| prop.name}
|
||||
props.should_not include("name")
|
||||
props.should include("admin")
|
||||
end
|
||||
|
||||
it "should protect non-accessible properties set through new" do
|
||||
user = WithProtected.new(:name => "will", :admin => true)
|
||||
|
||||
user.name.should == "will"
|
||||
user.admin.should == false
|
||||
end
|
||||
|
||||
it "should protect non-accessible properties set through attributes=" do
|
||||
user = WithProtected.new
|
||||
user.attributes = {:name => "will", :admin => true}
|
||||
|
||||
user.name.should == "will"
|
||||
user.admin.should == false
|
||||
end
|
||||
|
||||
it "should not modify the provided attribute hash" do
|
||||
user = WithProtected.new
|
||||
attrs = {:name => "will", :admin => true}
|
||||
user.attributes = attrs
|
||||
attrs[:admin].should be_true
|
||||
attrs[:name].should eql('will')
|
||||
end
|
||||
|
||||
it "should provide correct accessible and protected property lists" do
|
||||
user = WithProtected.new(:name => 'will', :admin => true)
|
||||
user.accessible_properties.map{|p| p.to_s}.should eql(['name'])
|
||||
user.protected_properties.map{|p| p.to_s}.should eql(['admin'])
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe "Model Base", "mixing protected and accessible flags" do
|
||||
class WithBothAndUnspecified < CouchRest::Model::Base
|
||||
use_database TEST_SERVER.default_database
|
||||
property :name, :accessible => true
|
||||
property :admin, :default => false, :protected => true
|
||||
property :phone, :default => 'unset phone number'
|
||||
end
|
||||
|
||||
it { expect { WithBothAndUnspecified.new }.to_not raise_error }
|
||||
|
||||
it 'should assume that any unspecified property is protected by default' do
|
||||
user = WithBothAndUnspecified.new(:name => 'will', :admin => true, :phone => '555-1234')
|
||||
|
||||
user.name.should == 'will'
|
||||
user.admin.should == false
|
||||
user.phone.should == 'unset phone number'
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe "from database" do
|
||||
class WithProtected < CouchRest::Model::Base
|
||||
use_database TEST_SERVER.default_database
|
||||
property :name
|
||||
property :admin, :default => false, :protected => true
|
||||
view_by :name
|
||||
end
|
||||
|
||||
before(:each) do
|
||||
@user = WithProtected.new
|
||||
@user.name = "will"
|
||||
@user.admin = true
|
||||
@user.save!
|
||||
end
|
||||
|
||||
def verify_attrs(user)
|
||||
user.name.should == "will"
|
||||
user.admin.should == true
|
||||
end
|
||||
|
||||
it "Base#get should not strip protected attributes" do
|
||||
reloaded = WithProtected.get( @user.id )
|
||||
verify_attrs reloaded
|
||||
end
|
||||
|
||||
it "Base#get! should not strip protected attributes" do
|
||||
reloaded = WithProtected.get!( @user.id )
|
||||
verify_attrs reloaded
|
||||
end
|
||||
|
||||
it "Base#all should not strip protected attributes" do
|
||||
# all creates a CollectionProxy
|
||||
docs = WithProtected.all(:key => @user.id)
|
||||
docs.length.should == 1
|
||||
reloaded = docs.first
|
||||
verify_attrs reloaded
|
||||
end
|
||||
|
||||
it "views should not strip protected attributes" do
|
||||
docs = WithProtected.by_name(:startkey => "will", :endkey => "will")
|
||||
reloaded = docs.first
|
||||
verify_attrs reloaded
|
||||
end
|
||||
end
|
||||
end
|
481
spec/unit/property_spec.rb
Normal file
481
spec/unit/property_spec.rb
Normal file
|
@ -0,0 +1,481 @@
|
|||
# encoding: utf-8
|
||||
require 'spec_helper'
|
||||
|
||||
describe CouchRest::Model::Property do
|
||||
|
||||
before(:each) do
|
||||
reset_test_db!
|
||||
@card = Card.new(:first_name => "matt")
|
||||
end
|
||||
|
||||
it "should be accessible from the object" do
|
||||
@card.properties.should be_an_instance_of(Array)
|
||||
@card.properties.map{|p| p.name}.should include("first_name")
|
||||
end
|
||||
|
||||
it "should list object properties with values" do
|
||||
@card.properties_with_values.should be_an_instance_of(Hash)
|
||||
@card.properties_with_values["first_name"].should == "matt"
|
||||
end
|
||||
|
||||
it "should let you access a property value (getter)" do
|
||||
@card.first_name.should == "matt"
|
||||
end
|
||||
|
||||
it "should let you set a property value (setter)" do
|
||||
@card.last_name = "Aimonetti"
|
||||
@card.last_name.should == "Aimonetti"
|
||||
end
|
||||
|
||||
it "should not let you set a property value if it's read only" do
|
||||
lambda{@card.read_only_value = "test"}.should raise_error
|
||||
end
|
||||
|
||||
it "should let you use an alias for an attribute" do
|
||||
@card.last_name = "Aimonetti"
|
||||
@card.family_name.should == "Aimonetti"
|
||||
@card.family_name.should == @card.last_name
|
||||
end
|
||||
|
||||
it "should let you use an alias for a casted attribute" do
|
||||
@card.cast_alias = Person.new(:name => ["Aimonetti"])
|
||||
@card.cast_alias.name.should == ["Aimonetti"]
|
||||
@card.calias.name.should == ["Aimonetti"]
|
||||
card = Card.new(:first_name => "matt", :cast_alias => {:name => ["Aimonetti"]})
|
||||
card.cast_alias.name.should == ["Aimonetti"]
|
||||
card.calias.name.should == ["Aimonetti"]
|
||||
end
|
||||
|
||||
it "should raise error if property name coincides with model type key" do
|
||||
lambda { Cat.property(Cat.model_type_key) }.should raise_error(/already used/)
|
||||
end
|
||||
|
||||
it "should not raise error if property name coincides with model type key on non-model" do
|
||||
lambda { Person.property(Article.model_type_key) }.should_not raise_error
|
||||
end
|
||||
|
||||
it "should be auto timestamped" do
|
||||
@card.created_at.should be_nil
|
||||
@card.updated_at.should be_nil
|
||||
@card.save.should be_true
|
||||
@card.created_at.should_not be_nil
|
||||
@card.updated_at.should_not be_nil
|
||||
end
|
||||
|
||||
describe "#as_couch_json" do
|
||||
|
||||
it "should provide a simple hash from model" do
|
||||
@card.as_couch_json.class.should eql(Hash)
|
||||
end
|
||||
|
||||
it "should remove properties from Hash if value is nil" do
|
||||
@card.last_name = nil
|
||||
@card.as_couch_json.keys.include?('last_name').should be_false
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe "#as_json" do
|
||||
|
||||
it "should provide a simple hash from model" do
|
||||
@card.as_json.class.should eql(Hash)
|
||||
end
|
||||
|
||||
it "should pass options to Active Support's as_json" do
|
||||
@card.last_name = "Aimonetti"
|
||||
@card.as_json(:only => 'last_name').should eql('last_name' => 'Aimonetti')
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe '#read_attribute' do
|
||||
it "should let you use read_attribute method" do
|
||||
@card.last_name = "Aimonetti"
|
||||
@card.read_attribute(:last_name).should eql('Aimonetti')
|
||||
@card.read_attribute('last_name').should eql('Aimonetti')
|
||||
last_name_prop = @card.properties.find{|p| p.name == 'last_name'}
|
||||
@card.read_attribute(last_name_prop).should eql('Aimonetti')
|
||||
end
|
||||
|
||||
it 'should raise an error if the property does not exist' do
|
||||
expect { @card.read_attribute(:this_property_should_not_exist) }.to raise_error(ArgumentError)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#write_attribute' do
|
||||
it "should let you use write_attribute method" do
|
||||
@card.write_attribute(:last_name, 'Aimonetti 1')
|
||||
@card.last_name.should eql('Aimonetti 1')
|
||||
@card.write_attribute('last_name', 'Aimonetti 2')
|
||||
@card.last_name.should eql('Aimonetti 2')
|
||||
last_name_prop = @card.properties.find{|p| p.name == 'last_name'}
|
||||
@card.write_attribute(last_name_prop, 'Aimonetti 3')
|
||||
@card.last_name.should eql('Aimonetti 3')
|
||||
end
|
||||
|
||||
it 'should raise an error if the property does not exist' do
|
||||
expect { @card.write_attribute(:this_property_should_not_exist, 823) }.to raise_error(ArgumentError)
|
||||
end
|
||||
|
||||
|
||||
it "should let you use write_attribute on readonly properties" do
|
||||
lambda {
|
||||
@card.read_only_value = "foo"
|
||||
}.should raise_error
|
||||
@card.write_attribute(:read_only_value, "foo")
|
||||
@card.read_only_value.should == 'foo'
|
||||
end
|
||||
|
||||
it "should cast via write_attribute" do
|
||||
@card.write_attribute(:cast_alias, {:name => ["Sam", "Lown"]})
|
||||
@card.cast_alias.class.should eql(Person)
|
||||
@card.cast_alias.name.last.should eql("Lown")
|
||||
end
|
||||
|
||||
it "should not cast via write_attribute if property not casted" do
|
||||
@card.write_attribute(:first_name, {:name => "Sam"})
|
||||
@card.first_name.class.should eql(Hash)
|
||||
@card.first_name[:name].should eql("Sam")
|
||||
end
|
||||
end
|
||||
|
||||
describe "mass updating attributes without property" do
|
||||
|
||||
describe "when mass_assign_any_attribute false" do
|
||||
|
||||
it "should not allow them to be set" do
|
||||
@card.attributes = {:test => 'fooobar'}
|
||||
@card['test'].should be_nil
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe "when mass_assign_any_attribute true" do
|
||||
before(:each) do
|
||||
# dup Card class so that no other tests are effected
|
||||
card_class = Card.dup
|
||||
card_class.class_eval do
|
||||
mass_assign_any_attribute true
|
||||
end
|
||||
@card = card_class.new(:first_name => 'Sam')
|
||||
end
|
||||
|
||||
it 'should allow them to be updated' do
|
||||
@card.attributes = {:test => 'fooobar'}
|
||||
@card['test'].should eql('fooobar')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
describe "mass assignment protection" do
|
||||
|
||||
it "should not store protected attribute using mass assignment" do
|
||||
cat_toy = CatToy.new(:name => "Zorro")
|
||||
cat = Cat.create(:name => "Helena", :toys => [cat_toy], :favorite_toy => cat_toy, :number => 1)
|
||||
cat.number.should be_nil
|
||||
cat.number = 1
|
||||
cat.save
|
||||
cat.number.should == 1
|
||||
end
|
||||
|
||||
it "should not store protected attribute when 'declare accessible poperties, assume all the rest are protected'" do
|
||||
user = User.create(:name => "Marcos Tapajós", :admin => true)
|
||||
user.admin.should be_nil
|
||||
end
|
||||
|
||||
it "should not store protected attribute when 'declare protected properties, assume all the rest are accessible'" do
|
||||
user = SpecialUser.create(:name => "Marcos Tapajós", :admin => true)
|
||||
user.admin.should be_nil
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe "validation" do
|
||||
before(:each) do
|
||||
@invoice = Invoice.new(:client_name => "matt", :employee_name => "Chris", :location => "San Diego, CA")
|
||||
end
|
||||
|
||||
it "should be able to be validated" do
|
||||
@card.valid?.should == true
|
||||
end
|
||||
|
||||
it "should let you validate the presence of an attribute" do
|
||||
@card.first_name = nil
|
||||
@card.should_not be_valid
|
||||
@card.errors.should_not be_empty
|
||||
@card.errors[:first_name].should == ["can't be blank"]
|
||||
end
|
||||
|
||||
it "should let you look up errors for a field by a string name" do
|
||||
@card.first_name = nil
|
||||
@card.should_not be_valid
|
||||
@card.errors['first_name'].should == ["can't be blank"]
|
||||
end
|
||||
|
||||
it "should validate the presence of 2 attributes" do
|
||||
@invoice.clear
|
||||
@invoice.should_not be_valid
|
||||
@invoice.errors.should_not be_empty
|
||||
@invoice.errors[:client_name].should == ["can't be blank"]
|
||||
@invoice.errors[:employee_name].should_not be_empty
|
||||
end
|
||||
|
||||
it "should let you set an error message" do
|
||||
@invoice.location = nil
|
||||
@invoice.valid?
|
||||
@invoice.errors[:location].should == ["Hey stupid!, you forgot the location"]
|
||||
end
|
||||
|
||||
it "should validate before saving" do
|
||||
@invoice.location = nil
|
||||
@invoice.should_not be_valid
|
||||
@invoice.save.should be_false
|
||||
@invoice.should be_new
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe "properties of hash of casted models" do
|
||||
it "should be able to assign a casted hash to a hash property" do
|
||||
chain = KeyChain.new
|
||||
keys = {"House" => "8==$", "Office" => "<>==U"}
|
||||
chain.keys = keys
|
||||
chain.keys = chain.keys
|
||||
chain.keys.should == keys
|
||||
end
|
||||
end
|
||||
|
||||
describe "properties of array of casted models" do
|
||||
|
||||
before(:each) do
|
||||
@course = Course.new :title => 'Test Course'
|
||||
end
|
||||
|
||||
it "should allow attribute to be set from an array of objects" do
|
||||
@course.questions = [Question.new(:q => "works?"), Question.new(:q => "Meaning of Life?")]
|
||||
@course.questions.length.should eql(2)
|
||||
end
|
||||
|
||||
it "should allow attribute to be set from an array of hashes" do
|
||||
@course.questions = [{:q => "works?"}, {:q => "Meaning of Life?"}]
|
||||
@course.questions.length.should eql(2)
|
||||
@course.questions.last.q.should eql("Meaning of Life?")
|
||||
@course.questions.last.class.should eql(Question) # typecasting
|
||||
end
|
||||
|
||||
it "should allow attribute to be set from hash with ordered keys and objects" do
|
||||
@course.questions = { '0' => Question.new(:q => "Test1"), '1' => Question.new(:q => 'Test2') }
|
||||
@course.questions.length.should eql(2)
|
||||
@course.questions.last.q.should eql('Test2')
|
||||
@course.questions.last.class.should eql(Question)
|
||||
end
|
||||
|
||||
it "should allow attribute to be set from hash with ordered keys and sub-hashes" do
|
||||
@course.questions = { '10' => {:q => 'Test10'}, '0' => {:q => "Test1"}, '1' => {:q => 'Test2'} }
|
||||
@course.questions.length.should eql(3)
|
||||
@course.questions.last.q.should eql('Test10')
|
||||
@course.questions.last.class.should eql(Question)
|
||||
end
|
||||
|
||||
it "should allow attribute to be set from hash with ordered keys and HashWithIndifferentAccess" do
|
||||
# This is similar to what you'd find in an HTML POST parameters
|
||||
hash = HashWithIndifferentAccess.new({ '0' => {:q => "Test1"}, '1' => {:q => 'Test2'} })
|
||||
@course.questions = hash
|
||||
@course.questions.length.should eql(2)
|
||||
@course.questions.last.q.should eql('Test2')
|
||||
@course.questions.last.class.should eql(Question)
|
||||
end
|
||||
|
||||
|
||||
it "should raise an error if attempting to set single value for array type" do
|
||||
lambda {
|
||||
@course.questions = Question.new(:q => 'test1')
|
||||
}.should raise_error(/Expecting an array/)
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
describe "a casted model retrieved from the database" do
|
||||
before(:each) do
|
||||
reset_test_db!
|
||||
@cat = Cat.new(:name => 'Stimpy')
|
||||
@cat.favorite_toy = CatToy.new(:name => 'Stinky')
|
||||
@cat.toys << CatToy.new(:name => 'Feather')
|
||||
@cat.toys << CatToy.new(:name => 'Mouse')
|
||||
@cat.save
|
||||
@cat = Cat.get(@cat.id)
|
||||
end
|
||||
|
||||
describe "as a casted property" do
|
||||
it "should already be casted_by its parent" do
|
||||
@cat.favorite_toy.casted_by.should === @cat
|
||||
end
|
||||
end
|
||||
|
||||
describe "from a casted collection" do
|
||||
it "should already be casted_by its parent" do
|
||||
@cat.toys[0].casted_by.should === @cat
|
||||
@cat.toys[1].casted_by.should === @cat
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "nested models (not casted)" do
|
||||
before(:each) do
|
||||
reset_test_db!
|
||||
@cat = ChildCat.new(:name => 'Stimpy')
|
||||
@cat.mother = {:name => 'Stinky'}
|
||||
@cat.siblings = [{:name => 'Feather'}, {:name => 'Felix'}]
|
||||
@cat.save
|
||||
@cat = ChildCat.get(@cat.id)
|
||||
end
|
||||
|
||||
it "should correctly save single relation" do
|
||||
@cat.mother.name.should eql('Stinky')
|
||||
@cat.mother.casted_by.should eql(@cat)
|
||||
end
|
||||
|
||||
it "should correctly save collection" do
|
||||
@cat.siblings.first.name.should eql("Feather")
|
||||
@cat.siblings.last.casted_by.should eql(@cat)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe "Property Class" do
|
||||
|
||||
it "should provide name as string" do
|
||||
property = CouchRest::Model::Property.new(:test, String)
|
||||
property.name.should eql('test')
|
||||
property.to_s.should eql('test')
|
||||
end
|
||||
|
||||
it "should provide class from type" do
|
||||
property = CouchRest::Model::Property.new(:test, String)
|
||||
property.type_class.should eql(String)
|
||||
end
|
||||
|
||||
it "should provide base class from type in array" do
|
||||
property = CouchRest::Model::Property.new(:test, [String])
|
||||
property.type_class.should eql(String)
|
||||
end
|
||||
|
||||
it "should raise error if type as string requested" do
|
||||
lambda {
|
||||
property = CouchRest::Model::Property.new(:test, 'String')
|
||||
}.should raise_error
|
||||
end
|
||||
|
||||
it "should leave type nil and return class as nil also" do
|
||||
property = CouchRest::Model::Property.new(:test, nil)
|
||||
property.type.should be_nil
|
||||
property.type_class.should be_nil
|
||||
end
|
||||
|
||||
it "should convert empty type array to [Object]" do
|
||||
property = CouchRest::Model::Property.new(:test, [])
|
||||
property.type_class.should eql(Object)
|
||||
end
|
||||
|
||||
it "should set init method option or leave as 'new'" do
|
||||
# (bad example! Time already typecast)
|
||||
property = CouchRest::Model::Property.new(:test, Time)
|
||||
property.init_method.should eql('new')
|
||||
property = CouchRest::Model::Property.new(:test, Time, :init_method => 'parse')
|
||||
property.init_method.should eql('parse')
|
||||
end
|
||||
|
||||
describe "#build" do
|
||||
it "should allow instantiation of new object" do
|
||||
property = CouchRest::Model::Property.new(:test, Date)
|
||||
obj = property.build(2011, 05, 21)
|
||||
obj.should eql(Date.new(2011, 05, 21))
|
||||
end
|
||||
it "should use init_method if provided" do
|
||||
property = CouchRest::Model::Property.new(:test, Date, :init_method => 'parse')
|
||||
obj = property.build("2011-05-21")
|
||||
obj.should eql(Date.new(2011, 05, 21))
|
||||
end
|
||||
it "should use init_method Proc if provided" do
|
||||
property = CouchRest::Model::Property.new(:test, Date, :init_method => Proc.new{|v| Date.parse(v)})
|
||||
obj = property.build("2011-05-21")
|
||||
obj.should eql(Date.new(2011, 05, 21))
|
||||
end
|
||||
it "should raise error if no class" do
|
||||
property = CouchRest::Model::Property.new(:test)
|
||||
lambda { property.build }.should raise_error(StandardError, /Cannot build/)
|
||||
end
|
||||
end
|
||||
|
||||
## Property Casting method. More thoroughly tested in typecast_spec.
|
||||
|
||||
describe "casting" do
|
||||
it "should cast a value" do
|
||||
property = CouchRest::Model::Property.new(:test, Date)
|
||||
parent = mock("FooObject")
|
||||
property.cast(parent, "2010-06-16").should eql(Date.new(2010, 6, 16))
|
||||
property.cast_value(parent, "2010-06-16").should eql(Date.new(2010, 6, 16))
|
||||
end
|
||||
|
||||
it "should cast an array of values" do
|
||||
property = CouchRest::Model::Property.new(:test, [Date])
|
||||
parent = mock("FooObject")
|
||||
property.cast(parent, ["2010-06-01", "2010-06-02"]).should eql([Date.new(2010, 6, 1), Date.new(2010, 6, 2)])
|
||||
end
|
||||
|
||||
it "should set a CastedArray on array of Objects" do
|
||||
property = CouchRest::Model::Property.new(:test, [Object])
|
||||
parent = mock("FooObject")
|
||||
property.cast(parent, ["2010-06-01", "2010-06-02"]).class.should eql(CouchRest::Model::CastedArray)
|
||||
end
|
||||
|
||||
it "should set a CastedArray on array of Strings" do
|
||||
property = CouchRest::Model::Property.new(:test, [String])
|
||||
parent = mock("FooObject")
|
||||
property.cast(parent, ["2010-06-01", "2010-06-02"]).class.should eql(CouchRest::Model::CastedArray)
|
||||
end
|
||||
|
||||
it "should allow instantion of model via CastedArray#build" do
|
||||
property = CouchRest::Model::Property.new(:dates, [Date])
|
||||
parent = Article.new
|
||||
ary = property.cast(parent, [])
|
||||
obj = ary.build(2011, 05, 21)
|
||||
ary.length.should eql(1)
|
||||
ary.first.should eql(Date.new(2011, 05, 21))
|
||||
obj = ary.build(2011, 05, 22)
|
||||
ary.length.should eql(2)
|
||||
ary.last.should eql(Date.new(2011, 05, 22))
|
||||
end
|
||||
|
||||
it "should cast an object that provides an array" do
|
||||
prop = Class.new do
|
||||
attr_accessor :ary
|
||||
def initialize(val); self.ary = val; end
|
||||
def as_json; ary; end
|
||||
end
|
||||
property = CouchRest::Model::Property.new(:test, prop)
|
||||
parent = mock("FooClass")
|
||||
cast = property.cast(parent, [1, 2])
|
||||
cast.ary.should eql([1, 2])
|
||||
end
|
||||
|
||||
it "should set parent as casted_by object in CastedArray" do
|
||||
property = CouchRest::Model::Property.new(:test, [Object])
|
||||
parent = mock("FooObject")
|
||||
property.cast(parent, ["2010-06-01", "2010-06-02"]).casted_by.should eql(parent)
|
||||
end
|
||||
|
||||
it "should set casted_by on new value" do
|
||||
property = CouchRest::Model::Property.new(:test, CatToy)
|
||||
parent = mock("CatObject")
|
||||
cast = property.cast(parent, {:name => 'catnip'})
|
||||
cast.casted_by.should eql(parent)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
376
spec/unit/proxyable_spec.rb
Normal file
376
spec/unit/proxyable_spec.rb
Normal file
|
@ -0,0 +1,376 @@
|
|||
require "spec_helper"
|
||||
|
||||
class DummyProxyable < CouchRest::Model::Base
|
||||
proxy_database_method :db
|
||||
def db
|
||||
'db'
|
||||
end
|
||||
end
|
||||
|
||||
class ProxyKitten < CouchRest::Model::Base
|
||||
end
|
||||
|
||||
describe CouchRest::Model::Proxyable do
|
||||
|
||||
describe "#proxy_database" do
|
||||
|
||||
before do
|
||||
@class = Class.new(CouchRest::Model::Base)
|
||||
@class.class_eval do
|
||||
def slug; 'proxy'; end
|
||||
end
|
||||
@obj = @class.new
|
||||
end
|
||||
|
||||
it "should respond to method" do
|
||||
@obj.should respond_to(:proxy_database)
|
||||
end
|
||||
|
||||
it "should provide proxy database from method" do
|
||||
@class.stub!(:proxy_database_method).twice.and_return(:slug)
|
||||
@obj.proxy_database.should be_a(CouchRest::Database)
|
||||
@obj.proxy_database.name.should eql('couchrest_proxy')
|
||||
end
|
||||
|
||||
it "should raise an error if called and no proxy_database_method set" do
|
||||
lambda { @obj.proxy_database }.should raise_error(StandardError, /Please set/)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe "class methods" do
|
||||
|
||||
|
||||
describe ".proxy_owner_method" do
|
||||
before(:each) do
|
||||
@class = DummyProxyable.clone
|
||||
end
|
||||
it "should provide proxy_owner_method accessors" do
|
||||
@class.should respond_to(:proxy_owner_method)
|
||||
@class.should respond_to(:proxy_owner_method=)
|
||||
end
|
||||
it "should work as expected" do
|
||||
@class.proxy_owner_method = "foo"
|
||||
@class.proxy_owner_method.should eql("foo")
|
||||
end
|
||||
end
|
||||
|
||||
describe ".proxy_database_method" do
|
||||
before do
|
||||
@class = Class.new(CouchRest::Model::Base)
|
||||
end
|
||||
it "should be possible to set the proxy database method" do
|
||||
@class.proxy_database_method :db
|
||||
@class.proxy_database_method.should eql(:db)
|
||||
end
|
||||
end
|
||||
|
||||
describe ".proxy_for" do
|
||||
before(:each) do
|
||||
@class = DummyProxyable.clone
|
||||
end
|
||||
|
||||
it "should be provided" do
|
||||
@class.should respond_to(:proxy_for)
|
||||
end
|
||||
|
||||
it "should create a new method" do
|
||||
DummyProxyable.stub!(:method_defined?).and_return(true)
|
||||
DummyProxyable.proxy_for(:cats)
|
||||
DummyProxyable.new.should respond_to(:cats)
|
||||
end
|
||||
|
||||
describe "generated method" do
|
||||
it "should call ModelProxy" do
|
||||
DummyProxyable.proxy_for(:cats)
|
||||
@obj = DummyProxyable.new
|
||||
CouchRest::Model::Proxyable::ModelProxy.should_receive(:new).with(Cat, @obj, 'dummy_proxyable', 'db').and_return(true)
|
||||
@obj.should_receive(:proxy_database).and_return('db')
|
||||
@obj.cats
|
||||
end
|
||||
|
||||
it "should call class on root namespace" do
|
||||
class ::Document < CouchRest::Model::Base
|
||||
def self.foo; puts 'bar'; end
|
||||
end
|
||||
DummyProxyable.proxy_for(:documents)
|
||||
@obj = DummyProxyable.new
|
||||
CouchRest::Model::Proxyable::ModelProxy.should_receive(:new).with(::Document, @obj, 'dummy_proxyable', 'db').and_return(true)
|
||||
@obj.should_receive('proxy_database').and_return('db')
|
||||
@obj.documents
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe ".proxied_by" do
|
||||
before do
|
||||
@class = Class.new(CouchRest::Model::Base)
|
||||
end
|
||||
|
||||
it "should be provided" do
|
||||
@class.should respond_to(:proxied_by)
|
||||
end
|
||||
|
||||
it "should add an attribute accessor" do
|
||||
@class.proxied_by(:foobar)
|
||||
@class.new.should respond_to(:foobar)
|
||||
end
|
||||
|
||||
it "should provide #model_proxy method" do
|
||||
@class.proxied_by(:foobar)
|
||||
@class.new.should respond_to(:model_proxy)
|
||||
end
|
||||
|
||||
it "should set the proxy_owner_method" do
|
||||
@class.proxied_by(:foobar)
|
||||
@class.proxy_owner_method.should eql(:foobar)
|
||||
end
|
||||
|
||||
it "should raise an error if model name pre-defined" do
|
||||
lambda { @class.proxied_by(:object_id) }.should raise_error
|
||||
end
|
||||
|
||||
it "should raise an error if object already has a proxy" do
|
||||
@class.proxied_by(:department)
|
||||
lambda { @class.proxied_by(:company) }.should raise_error
|
||||
end
|
||||
|
||||
it "should overwrite the database method to provide an error" do
|
||||
@class.proxied_by(:company)
|
||||
lambda { @class.database }.should raise_error(StandardError, /database must be accessed via/)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "ModelProxy" do
|
||||
|
||||
before :all do
|
||||
@klass = CouchRest::Model::Proxyable::ModelProxy
|
||||
end
|
||||
|
||||
it "should initialize and set variables" do
|
||||
@obj = @klass.new(Cat, 'owner', 'owner_name', 'database')
|
||||
@obj.model.should eql(Cat)
|
||||
@obj.owner.should eql('owner')
|
||||
@obj.owner_name.should eql('owner_name')
|
||||
@obj.database.should eql('database')
|
||||
end
|
||||
|
||||
describe "instance" do
|
||||
|
||||
before :each do
|
||||
@obj = @klass.new(Cat, 'owner', 'owner_name', 'database')
|
||||
end
|
||||
|
||||
it "should proxy new call" do
|
||||
@obj.should_receive(:proxy_block_update).with(:new, 'attrs', 'opts')
|
||||
@obj.new('attrs', 'opts')
|
||||
end
|
||||
|
||||
it "should proxy build_from_database" do
|
||||
@obj.should_receive(:proxy_block_update).with(:build_from_database, 'attrs', 'opts')
|
||||
@obj.build_from_database('attrs', 'opts')
|
||||
end
|
||||
|
||||
describe "#method_missing" do
|
||||
it "should return design view object" do
|
||||
m = "by_some_property"
|
||||
inst = mock('DesignView')
|
||||
inst.stub!(:proxy).and_return(inst)
|
||||
@obj.should_receive(:has_view?).with(m).and_return(true)
|
||||
Cat.should_receive(:respond_to?).with(m).and_return(true)
|
||||
Cat.should_receive(:send).with(m).and_return(inst)
|
||||
@obj.method_missing(m).should eql(inst)
|
||||
end
|
||||
|
||||
it "should call view if necessary" do
|
||||
m = "by_some_property"
|
||||
@obj.should_receive(:has_view?).with(m).and_return(true)
|
||||
Cat.should_receive(:respond_to?).with(m).and_return(false)
|
||||
@obj.should_receive(:view).with(m, {}).and_return('view')
|
||||
@obj.method_missing(m).should eql('view')
|
||||
end
|
||||
|
||||
it "should provide wrapper for #first_from_view" do
|
||||
m = "find_by_some_property"
|
||||
view = "by_some_property"
|
||||
@obj.should_receive(:has_view?).with(m).and_return(false)
|
||||
@obj.should_receive(:has_view?).with(view).and_return(true)
|
||||
@obj.should_receive(:first_from_view).with(view).and_return('view')
|
||||
@obj.method_missing(m).should eql('view')
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
it "should proxy #all" do
|
||||
Cat.should_receive(:all).with({:database => 'database'})
|
||||
@obj.should_receive(:proxy_update_all)
|
||||
@obj.all
|
||||
end
|
||||
|
||||
it "should proxy #count" do
|
||||
Cat.should_receive(:all).with({:database => 'database', :raw => true, :limit => 0}).and_return({'total_rows' => 3})
|
||||
@obj.count.should eql(3)
|
||||
end
|
||||
|
||||
it "should proxy #first" do
|
||||
Cat.should_receive(:first).with({:database => 'database'})
|
||||
@obj.should_receive(:proxy_update)
|
||||
@obj.first
|
||||
end
|
||||
|
||||
it "should proxy #last" do
|
||||
Cat.should_receive(:last).with({:database => 'database'})
|
||||
@obj.should_receive(:proxy_update)
|
||||
@obj.last
|
||||
end
|
||||
|
||||
it "should proxy #get" do
|
||||
Cat.should_receive(:get).with(32, 'database')
|
||||
@obj.should_receive(:proxy_update)
|
||||
@obj.get(32)
|
||||
end
|
||||
it "should proxy #find" do
|
||||
Cat.should_receive(:get).with(32, 'database')
|
||||
@obj.should_receive(:proxy_update)
|
||||
@obj.find(32)
|
||||
end
|
||||
|
||||
it "should proxy #has_view?" do
|
||||
Cat.should_receive(:has_view?).with('view').and_return(false)
|
||||
@obj.has_view?('view')
|
||||
end
|
||||
|
||||
it "should proxy #view_by" do
|
||||
Cat.should_receive(:view_by).with('name').and_return(false)
|
||||
@obj.view_by('name')
|
||||
end
|
||||
|
||||
it "should proxy #view" do
|
||||
Cat.should_receive(:view).with('view', {:database => 'database'})
|
||||
@obj.should_receive(:proxy_update_all)
|
||||
@obj.view('view')
|
||||
end
|
||||
|
||||
it "should proxy #first_from_view" do
|
||||
Cat.should_receive(:first_from_view).with('view', {:database => 'database'})
|
||||
@obj.should_receive(:proxy_update)
|
||||
@obj.first_from_view('view')
|
||||
end
|
||||
|
||||
it "should proxy design_doc" do
|
||||
Cat.should_receive(:design_doc)
|
||||
@obj.design_doc
|
||||
end
|
||||
|
||||
describe "#save_design_doc" do
|
||||
it "should be proxied without args" do
|
||||
Cat.should_receive(:save_design_doc).with('database')
|
||||
@obj.save_design_doc
|
||||
end
|
||||
|
||||
it "should be proxied with database arg" do
|
||||
Cat.should_receive(:save_design_doc).with('db')
|
||||
@obj.save_design_doc('db')
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
### Updating methods
|
||||
|
||||
describe "#proxy_update" do
|
||||
it "should set returned doc fields" do
|
||||
doc = mock(:Document)
|
||||
doc.should_receive(:is_a?).with(Cat).and_return(true)
|
||||
doc.should_receive(:database=).with('database')
|
||||
doc.should_receive(:model_proxy=).with(@obj)
|
||||
doc.should_receive(:send).with('owner_name=', 'owner')
|
||||
@obj.send(:proxy_update, doc).should eql(doc)
|
||||
end
|
||||
|
||||
it "should not set anything if matching document not provided" do
|
||||
doc = mock(:DocumentFoo)
|
||||
doc.should_receive(:is_a?).with(Cat).and_return(false)
|
||||
doc.should_not_receive(:database=)
|
||||
doc.should_not_receive(:model_proxy=)
|
||||
doc.should_not_receive(:owner_name=)
|
||||
@obj.send(:proxy_update, doc).should eql(doc)
|
||||
end
|
||||
|
||||
it "should pass nil straight through without errors" do
|
||||
lambda { @obj.send(:proxy_update, nil).should eql(nil) }.should_not raise_error
|
||||
end
|
||||
end
|
||||
|
||||
it "#proxy_update_all should update array of docs" do
|
||||
docs = [{}, {}]
|
||||
@obj.should_receive(:proxy_update).twice.with({})
|
||||
@obj.send(:proxy_update_all, docs)
|
||||
end
|
||||
|
||||
describe "#proxy_block_update" do
|
||||
it "should proxy block updates" do
|
||||
doc = { }
|
||||
@obj.model.should_receive(:new).and_yield(doc)
|
||||
@obj.should_receive(:proxy_update).with(doc)
|
||||
@obj.send(:proxy_block_update, :new)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe "scenarios" do
|
||||
|
||||
before :all do
|
||||
class ProxyableCompany < CouchRest::Model::Base
|
||||
use_database DB
|
||||
property :slug
|
||||
proxy_for :proxyable_invoices
|
||||
def proxy_database
|
||||
@db ||= TEST_SERVER.database!(TESTDB + "-#{slug}")
|
||||
end
|
||||
end
|
||||
|
||||
class ProxyableInvoice < CouchRest::Model::Base
|
||||
property :client
|
||||
property :total
|
||||
proxied_by :proxyable_company
|
||||
validates_uniqueness_of :client
|
||||
design do
|
||||
view :by_total
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@company = ProxyableCompany.create(:slug => 'samco')
|
||||
end
|
||||
|
||||
it "should create the new database" do
|
||||
@company.proxyable_invoices.all.should be_empty
|
||||
TEST_SERVER.databases.find{|db| db =~ /#{TESTDB}-samco/}.should_not be_nil
|
||||
end
|
||||
|
||||
it "should allow creation of new entries" do
|
||||
inv = @company.proxyable_invoices.new(:client => "Lorena", :total => 35)
|
||||
# inv.database.should_not be_nil
|
||||
inv.save.should be_true
|
||||
@company.proxyable_invoices.count.should eql(1)
|
||||
@company.proxyable_invoices.first.client.should eql("Lorena")
|
||||
end
|
||||
|
||||
it "should validate uniqueness" do
|
||||
inv = @company.proxyable_invoices.new(:client => "Lorena", :total => 40)
|
||||
inv.save.should be_false
|
||||
end
|
||||
|
||||
it "should allow design views" do
|
||||
item = @company.proxyable_invoices.by_total.key(35).first
|
||||
item.client.should eql('Lorena')
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
85
spec/unit/subclass_spec.rb
Normal file
85
spec/unit/subclass_spec.rb
Normal file
|
@ -0,0 +1,85 @@
|
|||
require "spec_helper"
|
||||
|
||||
# add a default value
|
||||
Card.property :bg_color, :default => '#ccc'
|
||||
|
||||
class BusinessCard < Card
|
||||
property :extension_code
|
||||
property :job_title
|
||||
|
||||
validates_presence_of :extension_code
|
||||
validates_presence_of :job_title
|
||||
end
|
||||
|
||||
class DesignBusinessCard < BusinessCard
|
||||
property :bg_color, :default => '#eee'
|
||||
end
|
||||
|
||||
class OnlineCourse < Course
|
||||
property :url
|
||||
view_by :url
|
||||
end
|
||||
|
||||
class Animal < CouchRest::Model::Base
|
||||
use_database TEST_SERVER.default_database
|
||||
property :name
|
||||
view_by :name
|
||||
end
|
||||
|
||||
class Dog < Animal; end
|
||||
|
||||
describe "Subclassing a Model" do
|
||||
|
||||
before(:each) do
|
||||
@card = BusinessCard.new
|
||||
end
|
||||
|
||||
it "shouldn't messup the parent's properties" do
|
||||
Card.properties.should_not == BusinessCard.properties
|
||||
end
|
||||
|
||||
it "should share the same db default" do
|
||||
@card.database.uri.should == Card.database.uri
|
||||
end
|
||||
|
||||
it "should have kept the validation details" do
|
||||
@card.should_not be_valid
|
||||
end
|
||||
|
||||
it "should have added the new validation details" do
|
||||
validated_fields = @card.class.validators.map{|v| v.attributes}.flatten
|
||||
validated_fields.should include(:extension_code)
|
||||
validated_fields.should include(:job_title)
|
||||
end
|
||||
|
||||
it "should not add to the parent's validations" do
|
||||
validated_fields = Card.validators.map{|v| v.attributes}.flatten
|
||||
validated_fields.should_not include(:extension_code)
|
||||
validated_fields.should_not include(:job_title)
|
||||
end
|
||||
|
||||
it "should inherit default property values" do
|
||||
@card.bg_color.should == '#ccc'
|
||||
end
|
||||
|
||||
it "should be able to overwrite a default property" do
|
||||
DesignBusinessCard.new.bg_color.should == '#eee'
|
||||
end
|
||||
|
||||
it "should have a design doc slug based on the subclass name" do
|
||||
OnlineCourse.design_doc_slug.should =~ /^OnlineCourse/
|
||||
end
|
||||
|
||||
it "should not add views to the parent's design_doc" do
|
||||
Course.design_doc['views'].keys.should_not include('by_url')
|
||||
end
|
||||
|
||||
it "should not add the parent's views to its design doc" do
|
||||
OnlineCourse.design_doc['views'].keys.should_not include('by_title')
|
||||
end
|
||||
|
||||
it "should have an all view with a guard clause for model == subclass name in the map function" do
|
||||
OnlineCourse.design_doc['views']['all']['map'].should =~ /if \(doc\['#{OnlineCourse.model_type_key}'\] == 'OnlineCourse'\)/
|
||||
end
|
||||
end
|
||||
|
521
spec/unit/typecast_spec.rb
Normal file
521
spec/unit/typecast_spec.rb
Normal file
|
@ -0,0 +1,521 @@
|
|||
# encoding: utf-8
|
||||
require 'spec_helper'
|
||||
|
||||
describe "Type Casting" do
|
||||
|
||||
before(:each) do
|
||||
@course = Course.new(:title => 'Relaxation')
|
||||
end
|
||||
|
||||
describe "when value is nil" do
|
||||
it "leaves the value unchanged" do
|
||||
@course.title = nil
|
||||
@course['title'].should == nil
|
||||
end
|
||||
end
|
||||
|
||||
describe "when type primitive is an Object" do
|
||||
it "it should not cast given value" do
|
||||
@course.participants = [{}, 'q', 1]
|
||||
@course['participants'].should == [{}, 'q', 1]
|
||||
end
|
||||
|
||||
it "should cast started_on to Date" do
|
||||
@course.started_on = Date.today
|
||||
@course['started_on'].should be_an_instance_of(Date)
|
||||
end
|
||||
end
|
||||
|
||||
describe "when type primitive is a String" do
|
||||
it "keeps string value unchanged" do
|
||||
value = "1.0"
|
||||
@course.title = value
|
||||
@course['title'].should equal(value)
|
||||
end
|
||||
|
||||
it "it casts to string representation of the value" do
|
||||
@course.title = 1.0
|
||||
@course['title'].should eql("1.0")
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when type primitive is a Float' do
|
||||
it 'returns same value if a float' do
|
||||
value = 24.0
|
||||
@course.estimate = value
|
||||
@course['estimate'].should equal(value)
|
||||
end
|
||||
|
||||
it 'returns float representation of a zero string integer' do
|
||||
@course.estimate = '0'
|
||||
@course['estimate'].should eql(0.0)
|
||||
end
|
||||
|
||||
it 'returns float representation of a positive string integer' do
|
||||
@course.estimate = '24'
|
||||
@course['estimate'].should eql(24.0)
|
||||
end
|
||||
|
||||
it 'returns float representation of a negative string integer' do
|
||||
@course.estimate = '-24'
|
||||
@course['estimate'].should eql(-24.0)
|
||||
end
|
||||
|
||||
it 'returns float representation of a zero string float' do
|
||||
@course.estimate = '0.0'
|
||||
@course['estimate'].should eql(0.0)
|
||||
end
|
||||
|
||||
it 'returns float representation of a positive string float' do
|
||||
@course.estimate = '24.35'
|
||||
@course['estimate'].should eql(24.35)
|
||||
end
|
||||
|
||||
it 'returns float representation of a negative string float' do
|
||||
@course.estimate = '-24.35'
|
||||
@course['estimate'].should eql(-24.35)
|
||||
end
|
||||
|
||||
it 'returns float representation of a zero string float, with no leading digits' do
|
||||
@course.estimate = '.0'
|
||||
@course['estimate'].should eql(0.0)
|
||||
end
|
||||
|
||||
it 'returns float representation of a positive string float, with no leading digits' do
|
||||
@course.estimate = '.41'
|
||||
@course['estimate'].should eql(0.41)
|
||||
end
|
||||
|
||||
it 'returns float representation of a zero integer' do
|
||||
@course.estimate = 0
|
||||
@course['estimate'].should eql(0.0)
|
||||
end
|
||||
|
||||
it 'returns float representation of a positive integer' do
|
||||
@course.estimate = 24
|
||||
@course['estimate'].should eql(24.0)
|
||||
end
|
||||
|
||||
it 'returns float representation of a negative integer' do
|
||||
@course.estimate = -24
|
||||
@course['estimate'].should eql(-24.0)
|
||||
end
|
||||
|
||||
it 'returns float representation of a zero decimal' do
|
||||
@course.estimate = BigDecimal('0.0')
|
||||
@course['estimate'].should eql(0.0)
|
||||
end
|
||||
|
||||
it 'returns float representation of a positive decimal' do
|
||||
@course.estimate = BigDecimal('24.35')
|
||||
@course['estimate'].should eql(24.35)
|
||||
end
|
||||
|
||||
it 'returns float representation of a negative decimal' do
|
||||
@course.estimate = BigDecimal('-24.35')
|
||||
@course['estimate'].should eql(-24.35)
|
||||
end
|
||||
|
||||
it 'return float of a number with commas instead of points for decimals' do
|
||||
@course.estimate = '23,35'
|
||||
@course['estimate'].should eql(23.35)
|
||||
end
|
||||
|
||||
it "should handle numbers with commas and points" do
|
||||
@course.estimate = '1,234.00'
|
||||
@course.estimate.should eql(1234.00)
|
||||
end
|
||||
|
||||
it "should handle a mis-match of commas and points and maintain the last one" do
|
||||
@course.estimate = "1,232.434.123,323"
|
||||
@course.estimate.should eql(1232434123.323)
|
||||
end
|
||||
|
||||
it "should handle numbers with whitespace" do
|
||||
@course.estimate = " 24.35 "
|
||||
@course.estimate.should eql(24.35)
|
||||
end
|
||||
|
||||
[ Object.new, true, '00.0', '0.', '-.0', 'string' ].each do |value|
|
||||
it "does not typecast non-numeric value #{value.inspect}" do
|
||||
@course.estimate = value
|
||||
@course['estimate'].should equal(value)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe 'when type primitive is a Integer' do
|
||||
it 'returns same value if an integer' do
|
||||
value = 24
|
||||
@course.hours = value
|
||||
@course['hours'].should equal(value)
|
||||
end
|
||||
|
||||
it 'returns integer representation of a zero string integer' do
|
||||
@course.hours = '0'
|
||||
@course['hours'].should eql(0)
|
||||
end
|
||||
|
||||
it 'returns integer representation of a positive string integer' do
|
||||
@course.hours = '24'
|
||||
@course['hours'].should eql(24)
|
||||
end
|
||||
|
||||
it 'returns integer representation of a negative string integer' do
|
||||
@course.hours = '-24'
|
||||
@course['hours'].should eql(-24)
|
||||
end
|
||||
|
||||
it 'returns integer representation of a zero string float' do
|
||||
@course.hours = '0.0'
|
||||
@course['hours'].should eql(0)
|
||||
end
|
||||
|
||||
it 'returns integer representation of a positive string float' do
|
||||
@course.hours = '24.35'
|
||||
@course['hours'].should eql(24)
|
||||
end
|
||||
|
||||
it 'returns integer representation of a negative string float' do
|
||||
@course.hours = '-24.35'
|
||||
@course['hours'].should eql(-24)
|
||||
end
|
||||
|
||||
it 'returns integer representation of a zero string float, with no leading digits' do
|
||||
@course.hours = '.0'
|
||||
@course['hours'].should eql(0)
|
||||
end
|
||||
|
||||
it 'returns integer representation of a positive string float, with no leading digits' do
|
||||
@course.hours = '.41'
|
||||
@course['hours'].should eql(0)
|
||||
end
|
||||
|
||||
it 'returns integer representation of a zero float' do
|
||||
@course.hours = 0.0
|
||||
@course['hours'].should eql(0)
|
||||
end
|
||||
|
||||
it 'returns integer representation of a positive float' do
|
||||
@course.hours = 24.35
|
||||
@course['hours'].should eql(24)
|
||||
end
|
||||
|
||||
it 'returns integer representation of a negative float' do
|
||||
@course.hours = -24.35
|
||||
@course['hours'].should eql(-24)
|
||||
end
|
||||
|
||||
it 'returns integer representation of a zero decimal' do
|
||||
@course.hours = '0.0'
|
||||
@course['hours'].should eql(0)
|
||||
end
|
||||
|
||||
it 'returns integer representation of a positive decimal' do
|
||||
@course.hours = '24.35'
|
||||
@course['hours'].should eql(24)
|
||||
end
|
||||
|
||||
it 'returns integer representation of a negative decimal' do
|
||||
@course.hours = '-24.35'
|
||||
@course['hours'].should eql(-24)
|
||||
end
|
||||
|
||||
it "should handle numbers with whitespace" do
|
||||
@course.hours = " 24 "
|
||||
@course['hours'].should eql(24)
|
||||
end
|
||||
|
||||
[ Object.new, true, '00.0', '0.', '-.0', 'string' ].each do |value|
|
||||
it "does not typecast non-numeric value #{value.inspect}" do
|
||||
@course.hours = value
|
||||
@course['hours'].should equal(value)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when type primitive is a BigDecimal' do
|
||||
it 'returns same value if a decimal' do
|
||||
value = BigDecimal('24.0')
|
||||
@course.profit = value
|
||||
@course['profit'].should equal(value)
|
||||
end
|
||||
|
||||
it 'returns decimal representation of a zero string integer' do
|
||||
@course.profit = '0'
|
||||
@course['profit'].should eql(BigDecimal('0.0'))
|
||||
end
|
||||
|
||||
it 'returns decimal representation of a positive string integer' do
|
||||
@course.profit = '24'
|
||||
@course['profit'].should eql(BigDecimal('24.0'))
|
||||
end
|
||||
|
||||
it 'returns decimal representation of a negative string integer' do
|
||||
@course.profit = '-24'
|
||||
@course['profit'].should eql(BigDecimal('-24.0'))
|
||||
end
|
||||
|
||||
it 'returns decimal representation of a zero string float' do
|
||||
@course.profit = '0.0'
|
||||
@course['profit'].should eql(BigDecimal('0.0'))
|
||||
end
|
||||
|
||||
it 'returns decimal representation of a positive string float' do
|
||||
@course.profit = '24.35'
|
||||
@course['profit'].should eql(BigDecimal('24.35'))
|
||||
end
|
||||
|
||||
it 'returns decimal representation of a negative string float' do
|
||||
@course.profit = '-24.35'
|
||||
@course['profit'].should eql(BigDecimal('-24.35'))
|
||||
end
|
||||
|
||||
it 'returns decimal representation of a zero string float, with no leading digits' do
|
||||
@course.profit = '.0'
|
||||
@course['profit'].should eql(BigDecimal('0.0'))
|
||||
end
|
||||
|
||||
it 'returns decimal representation of a positive string float, with no leading digits' do
|
||||
@course.profit = '.41'
|
||||
@course['profit'].should eql(BigDecimal('0.41'))
|
||||
end
|
||||
|
||||
it 'returns decimal representation of a zero integer' do
|
||||
@course.profit = 0
|
||||
@course['profit'].should eql(BigDecimal('0.0'))
|
||||
end
|
||||
|
||||
it 'returns decimal representation of a positive integer' do
|
||||
@course.profit = 24
|
||||
@course['profit'].should eql(BigDecimal('24.0'))
|
||||
end
|
||||
|
||||
it 'returns decimal representation of a negative integer' do
|
||||
@course.profit = -24
|
||||
@course['profit'].should eql(BigDecimal('-24.0'))
|
||||
end
|
||||
|
||||
it 'returns decimal representation of a zero float' do
|
||||
@course.profit = 0.0
|
||||
@course['profit'].should eql(BigDecimal('0.0'))
|
||||
end
|
||||
|
||||
it 'returns decimal representation of a positive float' do
|
||||
@course.profit = 24.35
|
||||
@course['profit'].should eql(BigDecimal('24.35'))
|
||||
end
|
||||
|
||||
it 'returns decimal representation of a negative float' do
|
||||
@course.profit = -24.35
|
||||
@course['profit'].should eql(BigDecimal('-24.35'))
|
||||
end
|
||||
|
||||
it "should handle numbers with whitespace" do
|
||||
@course.profit = " 24.35 "
|
||||
@course['profit'].should eql(BigDecimal('24.35'))
|
||||
end
|
||||
|
||||
[ Object.new, true, '00.0', '0.', '-.0', 'string' ].each do |value|
|
||||
it "does not typecast non-numeric value #{value.inspect}" do
|
||||
@course.profit = value
|
||||
@course['profit'].should equal(value)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when type primitive is a DateTime' do
|
||||
describe 'and value given as a hash with keys like :year, :month, etc' do
|
||||
it 'builds a DateTime instance from hash values' do
|
||||
@course.updated_at = {
|
||||
:year => '2006',
|
||||
:month => '11',
|
||||
:day => '23',
|
||||
:hour => '12',
|
||||
:min => '0',
|
||||
:sec => '0'
|
||||
}
|
||||
result = @course['updated_at']
|
||||
|
||||
result.should be_kind_of(DateTime)
|
||||
result.year.should eql(2006)
|
||||
result.month.should eql(11)
|
||||
result.day.should eql(23)
|
||||
result.hour.should eql(12)
|
||||
result.min.should eql(0)
|
||||
result.sec.should eql(0)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'and value is a string' do
|
||||
it 'parses the string' do
|
||||
@course.updated_at = 'Dec, 2006'
|
||||
@course['updated_at'].month.should == 12
|
||||
end
|
||||
end
|
||||
|
||||
it 'does not typecast non-datetime values' do
|
||||
@course.updated_at = 'not-datetime'
|
||||
@course['updated_at'].should eql('not-datetime')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when type primitive is a Date' do
|
||||
describe 'and value given as a hash with keys like :year, :month, etc' do
|
||||
it 'builds a Date instance from hash values' do
|
||||
@course.started_on = {
|
||||
:year => '2007',
|
||||
:month => '3',
|
||||
:day => '25'
|
||||
}
|
||||
result = @course['started_on']
|
||||
|
||||
result.should be_kind_of(Date)
|
||||
result.year.should eql(2007)
|
||||
result.month.should eql(3)
|
||||
result.day.should eql(25)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'and value is a string' do
|
||||
it 'parses the string' do
|
||||
@course.started_on = 'Dec 20th, 2006'
|
||||
@course.started_on.month.should == 12
|
||||
@course.started_on.day.should == 20
|
||||
@course.started_on.year.should == 2006
|
||||
end
|
||||
end
|
||||
|
||||
it 'does not typecast non-date values' do
|
||||
@course.started_on = 'not-date'
|
||||
@course['started_on'].should eql('not-date')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when type primitive is a Time' do
|
||||
describe 'and value given as a hash with keys like :year, :month, etc' do
|
||||
it 'builds a Time instance from hash values' do
|
||||
@course.ends_at = {
|
||||
:year => '2006',
|
||||
:month => '11',
|
||||
:day => '23',
|
||||
:hour => '12',
|
||||
:min => '0',
|
||||
:sec => '0'
|
||||
}
|
||||
result = @course['ends_at']
|
||||
|
||||
result.should be_kind_of(Time)
|
||||
result.year.should eql(2006)
|
||||
result.month.should eql(11)
|
||||
result.day.should eql(23)
|
||||
result.hour.should eql(12)
|
||||
result.min.should eql(0)
|
||||
result.sec.should eql(0)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'and value is a string' do
|
||||
it 'parses the string' do
|
||||
t = Time.new(2011, 4, 1, 18, 50, 32, "+02:00")
|
||||
@course.ends_at = t.strftime('%Y/%m/%d %H:%M:%S %z')
|
||||
@course['ends_at'].year.should eql(t.year)
|
||||
@course['ends_at'].month.should eql(t.month)
|
||||
@course['ends_at'].day.should eql(t.day)
|
||||
@course['ends_at'].hour.should eql(t.hour)
|
||||
@course['ends_at'].min.should eql(t.min)
|
||||
@course['ends_at'].sec.should eql(t.sec)
|
||||
end
|
||||
it 'parses the string without offset as UTC' do
|
||||
t = Time.now.utc
|
||||
@course.ends_at = t.strftime("%Y-%m-%d %H:%M:%S")
|
||||
@course.ends_at.utc?.should be_true
|
||||
@course['ends_at'].year.should eql(t.year)
|
||||
@course['ends_at'].month.should eql(t.month)
|
||||
@course['ends_at'].day.should eql(t.day)
|
||||
@course['ends_at'].hour.should eql(t.hour)
|
||||
@course['ends_at'].min.should eql(t.min)
|
||||
@course['ends_at'].sec.should eql(t.sec)
|
||||
end
|
||||
end
|
||||
|
||||
it "converts a time value into utc" do
|
||||
t = Time.new(2011, 4, 1, 18, 50, 32, "+02:00")
|
||||
@course.ends_at = t
|
||||
@course.ends_at.utc?.should be_true
|
||||
@course.ends_at.to_i.should eql(Time.utc(2011, 4, 1, 16, 50, 32).to_i)
|
||||
end
|
||||
|
||||
if RUBY_VERSION >= "1.9.1"
|
||||
# In ruby 1.8.7 Time.parse will always return a value. D'OH
|
||||
it 'does not typecast non-time values' do
|
||||
@course.ends_at = 'not-time'
|
||||
@course['ends_at'].should eql('not-time')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when type primitive is a Class' do
|
||||
it 'returns same value if a class' do
|
||||
value = Course
|
||||
@course.klass = value
|
||||
@course['klass'].should equal(value)
|
||||
end
|
||||
|
||||
it 'returns the class if found' do
|
||||
@course.klass = 'Course'
|
||||
@course['klass'].should eql(Course)
|
||||
end
|
||||
|
||||
it 'does not typecast non-class values' do
|
||||
@course.klass = 'NoClass'
|
||||
@course['klass'].should eql('NoClass')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when type primitive is a Boolean' do
|
||||
|
||||
[ true, 'true', 'TRUE', '1', 1, 't', 'T' ].each do |value|
|
||||
it "returns true when value is #{value.inspect}" do
|
||||
@course.active = value
|
||||
@course['active'].should be_true
|
||||
end
|
||||
end
|
||||
|
||||
[ false, 'false', 'FALSE', '0', 0, 'f', 'F' ].each do |value|
|
||||
it "returns false when value is #{value.inspect}" do
|
||||
@course.active = value
|
||||
@course['active'].should be_false
|
||||
end
|
||||
end
|
||||
|
||||
[ 'string', 2, 1.0, BigDecimal('1.0'), DateTime.now, Time.now, Date.today, Class, Object.new, ].each do |value|
|
||||
it "does not typecast value #{value.inspect}" do
|
||||
@course.active = value
|
||||
@course['active'].should equal(value)
|
||||
end
|
||||
end
|
||||
|
||||
it "should respond to requests with ? modifier" do
|
||||
@course.active = nil
|
||||
@course.active?.should be_false
|
||||
@course.active = false
|
||||
@course.active?.should be_false
|
||||
@course.active = true
|
||||
@course.active?.should be_true
|
||||
end
|
||||
|
||||
it "should respond to requests with ? modifier on TrueClass" do
|
||||
@course.very_active = nil
|
||||
@course.very_active?.should be_false
|
||||
@course.very_active = false
|
||||
@course.very_active?.should be_false
|
||||
@course.very_active = true
|
||||
@course.very_active?.should be_true
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
140
spec/unit/validations_spec.rb
Normal file
140
spec/unit/validations_spec.rb
Normal file
|
@ -0,0 +1,140 @@
|
|||
require "spec_helper"
|
||||
|
||||
describe CouchRest::Model::Validations do
|
||||
|
||||
describe "Uniqueness" do
|
||||
|
||||
context "basic" do
|
||||
before(:all) do
|
||||
@objs = ['title 1', 'title 2', 'title 3'].map{|t| WithUniqueValidation.create(:title => t)}
|
||||
end
|
||||
|
||||
it "should create a new view if none defined before performing" do
|
||||
WithUniqueValidation.has_view?(:by_title).should be_true
|
||||
end
|
||||
|
||||
it "should validate a new unique document" do
|
||||
@obj = WithUniqueValidation.create(:title => 'title 4')
|
||||
@obj.new?.should_not be_true
|
||||
@obj.should be_valid
|
||||
end
|
||||
|
||||
it "should not validate a non-unique document" do
|
||||
@obj = WithUniqueValidation.create(:title => 'title 1')
|
||||
@obj.should_not be_valid
|
||||
@obj.errors[:title].should == ["has already been taken"]
|
||||
end
|
||||
|
||||
it "should save already created document" do
|
||||
@obj = @objs.first
|
||||
@obj.save.should_not be_false
|
||||
@obj.should be_valid
|
||||
end
|
||||
|
||||
|
||||
it "should allow own view to be specified" do
|
||||
# validates_uniqueness_of :code, :view => 'all'
|
||||
WithUniqueValidationView.create(:title => 'title 1', :code => '1234')
|
||||
@obj = WithUniqueValidationView.new(:title => 'title 5', :code => '1234')
|
||||
@obj.should_not be_valid
|
||||
end
|
||||
|
||||
it "should raise an error if specified view does not exist" do
|
||||
WithUniqueValidationView.validates_uniqueness_of :title, :view => 'fooobar'
|
||||
@obj = WithUniqueValidationView.new(:title => 'title 2', :code => '12345')
|
||||
lambda {
|
||||
@obj.valid?
|
||||
}.should raise_error
|
||||
end
|
||||
|
||||
it "should not try to create a defined view" do
|
||||
WithUniqueValidationView.validates_uniqueness_of :title, :view => 'fooobar'
|
||||
WithUniqueValidationView.has_view?('fooobar').should be_false
|
||||
WithUniqueValidationView.has_view?('by_title').should be_false
|
||||
end
|
||||
|
||||
|
||||
it "should not try to create new view when already defined" do
|
||||
@obj = @objs[1]
|
||||
@obj.class.should_not_receive('view_by')
|
||||
@obj.class.should_receive('has_view?').and_return(true)
|
||||
@obj.class.should_receive('view').and_return({'rows' => [ ]})
|
||||
@obj.valid?
|
||||
end
|
||||
end
|
||||
|
||||
context "with a proxy parameter" do
|
||||
|
||||
it "should create a new view despite proxy" do
|
||||
WithUniqueValidationProxy.has_view?(:by_title).should be_true
|
||||
end
|
||||
|
||||
it "should be used" do
|
||||
@obj = WithUniqueValidationProxy.new(:title => 'test 6')
|
||||
proxy = @obj.should_receive('proxy').and_return(@obj.class)
|
||||
@obj.valid?.should be_true
|
||||
end
|
||||
|
||||
it "should allow specific view" do
|
||||
@obj = WithUniqueValidationProxy.new(:title => 'test 7')
|
||||
@obj.class.should_not_receive('view_by')
|
||||
proxy = mock('Proxy')
|
||||
@obj.should_receive('proxy').and_return(proxy)
|
||||
proxy.should_receive('has_view?').and_return(true)
|
||||
proxy.should_receive('view').and_return({'rows' => [ ]})
|
||||
@obj.valid?
|
||||
end
|
||||
end
|
||||
|
||||
context "when proxied" do
|
||||
it "should lookup the model_proxy" do
|
||||
mp = mock(:ModelProxy)
|
||||
mp.should_receive(:view).and_return({'rows' => []})
|
||||
@obj = WithUniqueValidation.new(:title => 'test 8')
|
||||
@obj.stub!(:model_proxy).twice.and_return(mp)
|
||||
@obj.valid?
|
||||
end
|
||||
end
|
||||
|
||||
context "with a scope" do
|
||||
before(:all) do
|
||||
@objs = [['title 1', 1], ['title 2', 1], ['title 3', 1]].map{|t| WithScopedUniqueValidation.create(:title => t[0], :parent_id => t[1])}
|
||||
@objs_nil = [['title 1', nil], ['title 2', nil], ['title 3', nil]].map{|t| WithScopedUniqueValidation.create(:title => t[0], :parent_id => t[1])}
|
||||
end
|
||||
|
||||
it "should create the view" do
|
||||
@objs.first.class.has_view?('by_parent_id_and_title')
|
||||
end
|
||||
|
||||
it "should validate unique document" do
|
||||
@obj = WithScopedUniqueValidation.create(:title => 'title 4', :parent_id => 1)
|
||||
@obj.should be_valid
|
||||
end
|
||||
|
||||
it "should validate unique document outside of scope" do
|
||||
@obj = WithScopedUniqueValidation.create(:title => 'title 1', :parent_id => 2)
|
||||
@obj.should be_valid
|
||||
end
|
||||
|
||||
it "should validate non-unique document" do
|
||||
@obj = WithScopedUniqueValidation.create(:title => 'title 1', :parent_id => 1)
|
||||
@obj.should_not be_valid
|
||||
@obj.errors[:title].should == ["has already been taken"]
|
||||
end
|
||||
|
||||
it "should validate unique document will nil scope" do
|
||||
@obj = WithScopedUniqueValidation.create(:title => 'title 4', :parent_id => nil)
|
||||
@obj.should be_valid
|
||||
end
|
||||
|
||||
it "should validate non-unique document with nil scope" do
|
||||
@obj = WithScopedUniqueValidation.create(:title => 'title 1', :parent_id => nil)
|
||||
@obj.should_not be_valid
|
||||
@obj.errors[:title].should == ["has already been taken"]
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
367
spec/unit/view_spec.rb
Normal file
367
spec/unit/view_spec.rb
Normal file
|
@ -0,0 +1,367 @@
|
|||
require "spec_helper"
|
||||
|
||||
describe CouchRest::Model::Views do
|
||||
|
||||
class Unattached < CouchRest::Model::Base
|
||||
property :title
|
||||
property :questions
|
||||
property :professor
|
||||
view_by :title
|
||||
|
||||
# Force the database to always be nil
|
||||
def self.database
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
describe "ClassMethods" do
|
||||
# NOTE! Add more unit tests!
|
||||
|
||||
describe "#view" do
|
||||
|
||||
it "should not alter original query" do
|
||||
options = { :database => DB }
|
||||
view = Article.view('by_date', options)
|
||||
options[:database].should eql(DB)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe "#has_view?" do
|
||||
it "should check the design doc" do
|
||||
Article.design_doc.should_receive(:has_view?).with(:test).and_return(true)
|
||||
Article.has_view?(:test).should be_true
|
||||
end
|
||||
end
|
||||
|
||||
describe "#can_reduce_view?" do
|
||||
it "should check if view has a reduce method" do
|
||||
Article.design_doc.should_receive(:can_reduce_view?).with(:test).and_return(true)
|
||||
Article.can_reduce_view?(:test).should be_true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "a model with simple views and a default param" do
|
||||
before(:all) do
|
||||
Article.all.map{|a| a.destroy(true)}
|
||||
Article.database.bulk_delete
|
||||
written_at = Time.now - 24 * 3600 * 7
|
||||
@titles = ["this and that", "also interesting", "more fun", "some junk"]
|
||||
@titles.each do |title|
|
||||
a = Article.new(:title => title)
|
||||
a.date = written_at
|
||||
a.save
|
||||
written_at += 24 * 3600
|
||||
end
|
||||
end
|
||||
it "should return the matching raw view result" do
|
||||
view = Article.by_date :raw => true
|
||||
view['rows'].length.should == 4
|
||||
end
|
||||
it "should not include non-Articles" do
|
||||
Article.database.save_doc({"date" => 1})
|
||||
view = Article.by_date :raw => true
|
||||
view['rows'].length.should == 4
|
||||
end
|
||||
it "should return the matching objects (with default argument :descending => true)" do
|
||||
articles = Article.by_date
|
||||
articles.collect{|a|a.title}.should == @titles.reverse
|
||||
end
|
||||
it "should allow you to override default args" do
|
||||
articles = Article.by_date :descending => false
|
||||
articles.collect{|a|a.title}.should == @titles
|
||||
end
|
||||
it "should allow you to create a new view on the fly" do
|
||||
lambda{Article.by_title}.should raise_error
|
||||
Article.view_by :title
|
||||
lambda{Article.by_title}.should_not raise_error
|
||||
end
|
||||
end
|
||||
|
||||
describe "another model with a simple view" do
|
||||
before(:all) do
|
||||
reset_test_db!
|
||||
%w{aaa bbb ddd eee}.each do |title|
|
||||
Course.new(:title => title).save
|
||||
end
|
||||
end
|
||||
it "should make the design doc upon first query" do
|
||||
Course.by_title
|
||||
doc = Course.design_doc
|
||||
doc['views']['all']['map'].should include('Course')
|
||||
end
|
||||
it "should can query via view" do
|
||||
# register methods with method-missing, for local dispatch. method
|
||||
# missing lookup table, no heuristics.
|
||||
view = Course.view :by_title
|
||||
designed = Course.by_title
|
||||
view.should == designed
|
||||
end
|
||||
it "should get them" do
|
||||
rs = Course.by_title
|
||||
rs.length.should == 4
|
||||
end
|
||||
it "should yield" do
|
||||
courses = []
|
||||
Course.view(:by_title) do |course|
|
||||
courses << course
|
||||
end
|
||||
courses[0]["doc"]["title"].should =='aaa'
|
||||
end
|
||||
it "should yield with by_key method" do
|
||||
courses = []
|
||||
Course.by_title do |course|
|
||||
courses << course
|
||||
end
|
||||
courses[0]["doc"]["title"].should =='aaa'
|
||||
end
|
||||
end
|
||||
|
||||
describe "find a single item using a view" 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
|
||||
|
||||
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
|
||||
|
||||
it "should perform a search for first when reduce method present" do
|
||||
course = Course.first_from_view('by_active')
|
||||
course.should_not be_nil
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe "#method_missing for find_by methods" do
|
||||
before(:all) { reset_test_db! }
|
||||
|
||||
specify { Course.should respond_to :find_by_title_and_active }
|
||||
specify { Course.should respond_to :by_title }
|
||||
|
||||
specify "#method should work in ruby 1.9, but not 1.8" do
|
||||
if RUBY_VERSION >= "1.9"
|
||||
Course.method(:find_by_title_and_active).should be_a Method
|
||||
else
|
||||
expect { Course.method(:find_by_title_and_active) }.to raise_error(NameError)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "a ducktype view" do
|
||||
before(:all) do
|
||||
reset_test_db!
|
||||
@id = DB.save_doc({:dept => true})['id']
|
||||
end
|
||||
it "should setup" do
|
||||
duck = Course.get(@id) # from a different db
|
||||
duck["dept"].should == true
|
||||
end
|
||||
it "should make the design doc" do
|
||||
@as = Course.by_dept
|
||||
@doc = Course.design_doc
|
||||
@doc["views"]["by_dept"]["map"].should_not include("couchrest")
|
||||
end
|
||||
it "should not look for class" do
|
||||
@as = Course.by_dept
|
||||
@as[0]['_id'].should == @id
|
||||
end
|
||||
end
|
||||
|
||||
describe "a model class with database provided manually" do
|
||||
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}.should raise_error
|
||||
end
|
||||
it "should query all" do
|
||||
rs = Unattached.all :database => @db
|
||||
rs.length.should == 4
|
||||
end
|
||||
it "should barf on query if no database given" do
|
||||
lambda{Unattached.view :by_title}.should raise_error
|
||||
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 query via view" do
|
||||
view = Unattached.view :by_title, :database=>@db
|
||||
designed = Unattached.by_title :database=>@db
|
||||
view.should == designed
|
||||
end
|
||||
it "should yield" do
|
||||
things = []
|
||||
Unattached.view(:by_title, :database=>@db) do |thing|
|
||||
things << thing
|
||||
end
|
||||
things[0]["doc"]["title"].should =='aaa'
|
||||
end
|
||||
it "should yield with by_key method" do
|
||||
things = []
|
||||
Unattached.by_title(:database=>@db) do |thing|
|
||||
things << thing
|
||||
end
|
||||
things[0]["doc"]["title"].should =='aaa'
|
||||
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.first :database=>@db
|
||||
u.title.should =~ /\A...\z/
|
||||
end
|
||||
|
||||
it "should get last" do
|
||||
u = Unattached.last :database=>@db
|
||||
u.title.should == "aaa"
|
||||
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)}
|
||||
Article.database.bulk_delete
|
||||
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
|
||||
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'
|
||||
articles.length.should == 1
|
||||
articles[0].title.should == "even more interesting"
|
||||
end
|
||||
end
|
||||
|
||||
describe "with a custom view" do
|
||||
before(:all) do
|
||||
@titles = ["very uniq one", "even less interesting", "some fun",
|
||||
"really junk", "crazy bob"]
|
||||
@tags = ["cool", "lame"]
|
||||
@titles.each_with_index do |title,i|
|
||||
u = i % 2
|
||||
a = Article.new(:title => title, :tags => [@tags[u]])
|
||||
a.save
|
||||
end
|
||||
end
|
||||
it "should be available raw" do
|
||||
view = Article.by_tags :raw => true
|
||||
view['rows'].length.should == 5
|
||||
end
|
||||
|
||||
it "should be default to :reduce => false" do
|
||||
ars = Article.by_tags
|
||||
ars.first.tags.first.should == 'cool'
|
||||
end
|
||||
|
||||
it "should be raw when reduce is true" do
|
||||
view = Article.by_tags :reduce => true, :group => true
|
||||
view['rows'].find{|r|r['key'] == 'cool'}['value'].should == 3
|
||||
end
|
||||
end
|
||||
|
||||
# TODO: moved to Design, delete
|
||||
describe "adding a view" do
|
||||
before(:each) do
|
||||
reset_test_db!
|
||||
Article.by_date
|
||||
@original_doc_rev = Article.stored_design_doc['_rev']
|
||||
@design_docs = Article.database.documents :startkey => "_design/", :endkey => "_design/\u9999"
|
||||
end
|
||||
it "should not create a design doc on view definition" do
|
||||
Article.view_by :created_at
|
||||
newdocs = Article.database.documents :startkey => "_design/", :endkey => "_design/\u9999"
|
||||
newdocs["rows"].length.should == @design_docs["rows"].length
|
||||
end
|
||||
it "should create a new version of the design document on view access" do
|
||||
Article.view_by :updated_at
|
||||
Article.by_updated_at
|
||||
@original_doc_rev.should_not == Article.stored_design_doc['_rev']
|
||||
Article.design_doc["views"].keys.should include("by_updated_at")
|
||||
end
|
||||
end
|
||||
|
||||
end
|
Loading…
Add table
Add a link
Reference in a new issue