Renaming to CouchRest Model

Refactored basic directory structure.
Moved to ActiveSupport for Validations and Callbacks.
Cleaned up older code, and removed support for text property types.
This commit is contained in:
Sam Lown 2010-06-20 22:01:11 +02:00
parent 9f1eea8d32
commit c280b3a29b
70 changed files with 1725 additions and 3733 deletions

View file

@ -1,21 +1,21 @@
# encoding: utf-8
require File.expand_path('../../spec_helper', __FILE__)
class Client < CouchRest::ExtendedDocument
class Client < CouchRest::Model::Base
use_database DB
property :name
property :tax_code
end
class SaleEntry < CouchRest::ExtendedDocument
class SaleEntry < CouchRest::Model::Base
use_database DB
property :description
property :price
end
class SaleInvoice < CouchRest::ExtendedDocument
class SaleInvoice < CouchRest::Model::Base
use_database DB
belongs_to :client
@ -64,7 +64,7 @@ describe "Assocations" do
it "should raise error if class name does not exist" do
lambda {
class TestBadAssoc < CouchRest::ExtendedDocument
class TestBadAssoc < CouchRest::Model::Base
belongs_to :test_bad_item
end
}.should raise_error
@ -99,7 +99,7 @@ describe "Assocations" do
it "should create an associated property and collection proxy" do
@invoice.respond_to?('entry_ids')
@invoice.respond_to?('entry_ids=')
@invoice.entries.class.should eql(::CouchRest::CollectionProxy)
@invoice.entries.class.should eql(::CouchRest::CollectionOfProxy)
end
it "should allow replacement of objects" do

View file

@ -1,12 +1,12 @@
require File.expand_path('../../spec_helper', __FILE__)
describe "ExtendedDocument attachments" do
describe "Model attachments" do
describe "#has_attachment?" do
before(:each) do
reset_test_db!
@obj = Basic.new
@obj.save.should == true
@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)
@ -35,7 +35,7 @@ describe "ExtendedDocument attachments" do
describe "creating an attachment" do
before(:each) do
@obj = Basic.new
@obj.save.should == true
@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'
@ -44,14 +44,14 @@ describe "ExtendedDocument attachments" do
it "should create an attachment from file with an extension" do
@obj.create_attachment(:file => @file_ext, :name => @attachment_name)
@obj.save.should == true
@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 == true
@obj.save.should be_true
reloaded_obj = Basic.get(@obj.id)
reloaded_obj['_attachments'][@attachment_name].should_not be_nil
end
@ -89,7 +89,7 @@ describe "ExtendedDocument attachments" do
@file = File.open(FIXTURE_PATH + '/attachments/test.html')
@attachment_name = 'my_attachment'
@obj.create_attachment(:file => @file, :name => @attachment_name)
@obj.save.should == true
@obj.save.should be_true
@file.rewind
@content_type = 'media/mp3'
end
@ -129,7 +129,7 @@ describe "ExtendedDocument attachments" do
@file = File.open(FIXTURE_PATH + '/attachments/test.html')
@attachment_name = 'my_attachment'
@obj.create_attachment(:file => @file, :name => @attachment_name)
@obj.save.should == true
@obj.save.should be_true
end
it 'should return nil if attachment does not exist' do

View file

@ -1,150 +1,153 @@
require File.expand_path("../../spec_helper", __FILE__)
describe "ExtendedDocument", "no declarations" do
class NoProtection < CouchRest::ExtendedDocument
use_database TEST_SERVER.default_database
property :name
property :phone
end
describe "Model Attributes" do
it "should not protect anything through new" do
user = NoProtection.new(:name => "will", :phone => "555-5555")
describe "no declarations" do
class NoProtection < CouchRest::Model::Base
use_database TEST_SERVER.default_database
property :name
property :phone
end
user.name.should == "will"
user.phone.should == "555-5555"
end
it "should not protect anything through new" do
user = NoProtection.new(:name => "will", :phone => "555-5555")
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
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!
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
user = NoProtection.get(user.id)
user.name.should == "will"
user.phone.should == "555-5555"
end
end
describe "ExtendedDocument", "accessible flag" do
class WithAccessible < CouchRest::ExtendedDocument
use_database TEST_SERVER.default_database
property :name, :accessible => true
property :admin, :default => false
end
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
end
describe "ExtendedDocument", "protected flag" do
class WithProtected < CouchRest::ExtendedDocument
use_database TEST_SERVER.default_database
property :name
property :admin, :default => false, :protected => true
end
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
end
describe "ExtendedDocument", "protected flag" do
class WithBoth < CouchRest::ExtendedDocument
use_database TEST_SERVER.default_database
property :name, :accessible => true
property :admin, :default => false, :protected => true
end
it "should raise an error when both are set" do
lambda { WithBoth.new }.should raise_error
end
end
describe "ExtendedDocument", "from database" do
class WithProtected < CouchRest::ExtendedDocument
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 "ExtendedDocument#get should not strip protected attributes" do
reloaded = WithProtected.get( @user.id )
verify_attrs reloaded
end
it "ExtendedDocument#get! should not strip protected attributes" do
reloaded = WithProtected.get!( @user.id )
verify_attrs reloaded
end
it "ExtendedDocument#all should not strip protected attributes" do
# all creates a CollectionProxy
docs = WithProtected.all(:key => @user.id)
docs.size.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
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
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 "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
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 "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
end
describe "protected flag" do
class WithBoth < CouchRest::Model::Base
use_database TEST_SERVER.default_database
property :name, :accessible => true
property :admin, :default => false, :protected => true
end
it "should raise an error when both are set" do
lambda { WithBoth.new }.should raise_error
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.size.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

403
spec/couchrest/base_spec.rb Normal file
View file

@ -0,0 +1,403 @@
# encoding: utf-8
require File.expand_path("../../spec_helper", __FILE__)
require File.join(FIXTURE_PATH, 'more', 'cat')
require File.join(FIXTURE_PATH, 'more', 'article')
require File.join(FIXTURE_PATH, 'more', 'course')
require File.join(FIXTURE_PATH, 'more', 'card')
require File.join(FIXTURE_PATH, 'base')
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 failed on a nil value in argument" do
@obj = Basic.new(nil)
@obj.should == { 'couchrest-type' => 'Basic' }
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 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(true)}
WithTemplateAndUniqueID.database.bulk_delete
@tmpl = WithTemplateAndUniqueID.new
@tmpl2 = WithTemplateAndUniqueID.new(:preset => 'not_value', 'important-field' => '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.req_design_doc_refresh
WithTemplateAndUniqueID.all.map{|o| o.destroy(true)}
WithTemplateAndUniqueID.database.bulk_delete
WithTemplateAndUniqueID.new('important-field' => '1').save
WithTemplateAndUniqueID.new('important-field' => '2').save
WithTemplateAndUniqueID.new('important-field' => '3').save
WithTemplateAndUniqueID.new('important-field' => '4').save
end
it "should make the design doc" do
WithTemplateAndUniqueID.all
d = WithTemplateAndUniqueID.design_doc
d['views']['all']['map'].should include('WithTemplateAndUniqueID')
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!
WithTemplateAndUniqueID.req_design_doc_refresh
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('important-field' => '1').save
WithTemplateAndUniqueID.new('important-field' => '2').save
WithTemplateAndUniqueID.new('important-field' => '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.req_design_doc_refresh # Removed by Sam Lown, design doc should be loaded automatically
WithTemplateAndUniqueID.new('important-field' => '1').save
WithTemplateAndUniqueID.new('important-field' => '2').save
WithTemplateAndUniqueID.new('important-field' => '3').save
WithTemplateAndUniqueID.new('important-field' => '4').save
end
it "should make the design doc" do
WithTemplateAndUniqueID.all
d = WithTemplateAndUniqueID.design_doc
d['views']['all']['map'].should include('WithTemplateAndUniqueID')
end
it "should find first" do
rs = WithTemplateAndUniqueID.first
rs['important-field'].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 "lazily refreshing the design document" do
before(:all) do
@db = reset_test_db!
WithTemplateAndUniqueID.new('important-field' => '1').save
end
it "should not save the design doc twice" do
WithTemplateAndUniqueID.all
WithTemplateAndUniqueID.req_design_doc_refresh
WithTemplateAndUniqueID.refresh_design_doc
rev = WithTemplateAndUniqueID.design_doc['_rev']
WithTemplateAndUniqueID.req_design_doc_refresh
WithTemplateAndUniqueID.refresh_design_doc
WithTemplateAndUniqueID.design_doc['_rev'].should eql(rev)
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.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 "initialization" do
it "should call after_initialize method if available" do
@doc = WithAfterInitializeMethod.new
@doc['some_value'].should eql('value')
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 an extended document" 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

View file

@ -1,39 +1,33 @@
# encoding: utf-8
require File.expand_path('../../spec_helper', __FILE__)
require File.join(FIXTURE_PATH, 'more', 'cat')
require File.join(FIXTURE_PATH, 'more', 'person')
require File.join(FIXTURE_PATH, 'more', 'card')
require File.join(FIXTURE_PATH, 'more', 'cat')
require File.join(FIXTURE_PATH, 'more', 'question')
require File.join(FIXTURE_PATH, 'more', 'course')
class WithCastedModelMixin < Hash
include CouchRest::CastedModel
include CouchRest::Model::CastedModel
property :name
property :no_value
property :details, :type => 'Object', :default => {}
property :casted_attribute, :cast_as => 'WithCastedModelMixin'
property :details, Object, :default => {}
property :casted_attribute, WithCastedModelMixin
end
class DummyModel < CouchRest::ExtendedDocument
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 :keywords, [String]
property :sub_models do |child|
child.property :title
end
end
class CastedCallbackDoc < CouchRest::ExtendedDocument
use_database TEST_SERVER.default_database
raise "Default DB not set" if TEST_SERVER.default_database.nil?
property :callback_model, :cast_as => 'WithCastedCallBackModel'
end
class WithCastedCallBackModel < Hash
include CouchRest::CastedModel
include CouchRest::Validation
include CouchRest::Model::CastedModel
property :name
property :run_before_validate
property :run_after_validate
@ -46,7 +40,13 @@ class WithCastedCallBackModel < Hash
end
end
describe CouchRest::CastedModel do
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::CastedModel do
describe "A non hash class including CastedModel" do
it "should fail raising and include error" do
@ -227,7 +227,7 @@ describe CouchRest::CastedModel do
@cat.toys.push(toy)
@cat.save.should be_true
@cat = Cat.get @cat.id
@cat.toys.class.should == CouchRest::CastedArray
@cat.toys.class.should == CouchRest::Model::CastedArray
@cat.toys.first.class.should == CatToy
@cat.toys.first.should === toy
end
@ -240,7 +240,7 @@ describe CouchRest::CastedModel do
end
it "should not fail if the casted model doesn't have validation" do
Cat.property :masters, :cast_as => ['Person'], :default => []
Cat.property :masters, [Person], :default => []
Cat.validates_presence_of :name
cat = Cat.new(:name => 'kitty')
cat.should be_valid

View file

@ -3,7 +3,7 @@ require File.join(FIXTURE_PATH, 'more', 'cat')
require File.join(FIXTURE_PATH, 'more', 'person')
require File.join(FIXTURE_PATH, 'more', 'card')
class Driver < CouchRest::ExtendedDocument
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
@ -11,12 +11,11 @@ class Driver < CouchRest::ExtendedDocument
property :name
end
class Car < CouchRest::ExtendedDocument
class Car < CouchRest::Model::Base
use_database TEST_SERVER.default_database
property :name
property :driver, :cast_as => 'Driver'
property :backseat_driver, :cast_as => Driver
property :driver, Driver
end
describe "casting an extended document" do
@ -24,17 +23,14 @@ describe "casting an extended document" do
before(:each) do
@driver = Driver.new(:name => 'Matt')
@car = Car.new(:name => 'Renault 306', :driver => @driver)
@car2 = Car.new(:name => 'Renault 306', :backseat_driver => @driver.dup)
end
it "should retain all properties of the casted attribute" do
@car.driver.should == @driver
@car2.backseat_driver.should == @driver
end
it "should let the casted document know who casted it" do
@car.driver.casted_by.should == @car
@car2.backseat_driver.casted_by.should == @car2
end
end
@ -61,7 +57,7 @@ describe "assigning a value to casted attribute after initializing an object" do
end
describe "casting an extended document from parsed JSON" do
describe "casting a model from parsed JSON" do
before(:each) do
@driver = Driver.new(:name => 'Matt')

View file

@ -1,6 +1,6 @@
require File.expand_path("../../spec_helper", __FILE__)
class UnattachedDoc < CouchRest::ExtendedDocument
class UnattachedDoc < CouchRest::Model::Base
# Note: no use_database here
property :title
property :questions

View file

@ -1,869 +0,0 @@
# encoding: utf-8
require File.expand_path("../../spec_helper", __FILE__)
require File.join(FIXTURE_PATH, 'more', 'article')
require File.join(FIXTURE_PATH, 'more', 'course')
require File.join(FIXTURE_PATH, 'more', 'card')
require File.join(FIXTURE_PATH, 'more', 'cat')
describe "ExtendedDocument" do
class WithDefaultValues < CouchRest::ExtendedDocument
use_database TEST_SERVER.default_database
property :preset, :type => 'Object', :default => {:right => 10, :top_align => false}
property :set_by_proc, :default => Proc.new{Time.now}, :cast_as => 'Time'
property :tags, :type => ['String'], :default => []
property :read_only_with_default, :default => 'generic', :read_only => true
property :default_false, :type => 'Boolean', :default => false
property :name
timestamps!
end
class WithSimplePropertyType < CouchRest::ExtendedDocument
use_database TEST_SERVER.default_database
property :name, String
property :preset, String, :default => 'none'
property :tags, [String]
timestamps!
end
class WithCallBacks < CouchRest::ExtendedDocument
include ::CouchRest::Validation
use_database TEST_SERVER.default_database
property :name
property :run_before_validate
property :run_after_validate
property :run_before_save
property :run_after_save
property :run_before_create
property :run_after_create
property :run_before_update
property :run_after_update
before_validate do |object|
object.run_before_validate = true
end
after_validate do |object|
object.run_after_validate = true
end
before_save do |object|
object.run_before_save = true
end
after_save do |object|
object.run_after_save = true
end
before_create do |object|
object.run_before_create = true
end
after_create do |object|
object.run_after_create = true
end
before_update do |object|
object.run_before_update = true
end
after_update do |object|
object.run_after_update = true
end
property :run_one
property :run_two
property :run_three
before_save :run_one_method, :run_two_method do |object|
object.run_three = true
end
def run_one_method
self.run_one = true
end
def run_two_method
self.run_two = true
end
attr_accessor :run_it
property :conditional_one
property :conditional_two
before_save :conditional_one_method, :conditional_two_method, :if => proc { self.run_it }
def conditional_one_method
self.conditional_one = true
end
def conditional_two_method
self.conditional_two = true
end
end
class WithTemplateAndUniqueID < CouchRest::ExtendedDocument
use_database TEST_SERVER.default_database
unique_id do |model|
model['important-field']
end
property :preset, :default => 'value'
property :has_no_default
end
class WithGetterAndSetterMethods < CouchRest::ExtendedDocument
use_database TEST_SERVER.default_database
property :other_arg
def arg
other_arg
end
def arg=(value)
self.other_arg = "foo-#{value}"
end
end
class WithAfterInitializeMethod < CouchRest::ExtendedDocument
use_database TEST_SERVER.default_database
property :some_value
def after_initialize
self.some_value ||= "value"
end
end
before(:each) do
@obj = WithDefaultValues.new
end
describe "instance database connection" do
it "should use the default database" do
@obj.database.name.should == 'couchrest-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 failed on a nil value in argument" do
@obj = Basic.new(nil)
@obj.should == { 'couchrest-type' => 'Basic' }
end
end
describe "creating a new document" do
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 "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 "creating a new document from database" do
it "should instantialize" do
doc = Article.create_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.create_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.create_from_database({'_id' => 'testitem2', '_rev' => 123, 'couchrest-type' => 'WithCallBacks', 'name' => 'my test'})
doc.class.should eql(WithCallBacks)
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 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(true)}
WithTemplateAndUniqueID.database.bulk_delete
@tmpl = WithTemplateAndUniqueID.new
@tmpl2 = WithTemplateAndUniqueID.new(:preset => 'not_value', 'important-field' => '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 "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 raise an error if `get!` is used and the document doesn't exist" do
lambda{foundart = Article.get!('matt aimonetti')}.should raise_error
end
it "should raise an error if `find!` is used and the document doesn't exist" do
lambda{foundart = Article.find!('matt aimonetti')}.should 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 "finding all instances of a model" do
before(:all) do
WithTemplateAndUniqueID.req_design_doc_refresh
WithTemplateAndUniqueID.all.map{|o| o.destroy(true)}
WithTemplateAndUniqueID.database.bulk_delete
WithTemplateAndUniqueID.new('important-field' => '1').save
WithTemplateAndUniqueID.new('important-field' => '2').save
WithTemplateAndUniqueID.new('important-field' => '3').save
WithTemplateAndUniqueID.new('important-field' => '4').save
end
it "should make the design doc" do
WithTemplateAndUniqueID.all
d = WithTemplateAndUniqueID.design_doc
d['views']['all']['map'].should include('WithTemplateAndUniqueID')
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!
WithTemplateAndUniqueID.req_design_doc_refresh
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('important-field' => '1').save
WithTemplateAndUniqueID.new('important-field' => '2').save
WithTemplateAndUniqueID.new('important-field' => '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.req_design_doc_refresh # Removed by Sam Lown, design doc should be loaded automatically
WithTemplateAndUniqueID.new('important-field' => '1').save
WithTemplateAndUniqueID.new('important-field' => '2').save
WithTemplateAndUniqueID.new('important-field' => '3').save
WithTemplateAndUniqueID.new('important-field' => '4').save
end
it "should make the design doc" do
WithTemplateAndUniqueID.all
d = WithTemplateAndUniqueID.design_doc
d['views']['all']['map'].should include('WithTemplateAndUniqueID')
end
it "should find first" do
rs = WithTemplateAndUniqueID.first
rs['important-field'].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 "lazily refreshing the design document" do
before(:all) do
@db = reset_test_db!
WithTemplateAndUniqueID.new('important-field' => '1').save
end
it "should not save the design doc twice" do
WithTemplateAndUniqueID.all
WithTemplateAndUniqueID.req_design_doc_refresh
WithTemplateAndUniqueID.refresh_design_doc
rev = WithTemplateAndUniqueID.design_doc['_rev']
WithTemplateAndUniqueID.req_design_doc_refresh
WithTemplateAndUniqueID.refresh_design_doc
WithTemplateAndUniqueID.design_doc['_rev'].should eql(rev)
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.save
@art.created_at.should < @art.updated_at
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 "saving a model" do
before(:all) do
@sobj = Basic.new
@sobj.save.should == 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['couchrest-type'].should == 'Basic'
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 == true
end
it "should raise error if don't save the document" do
@sobj.first_name = nil
lambda { @sobj.save!.should == true }.should raise_error(RuntimeError)
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 == true
end
it "should not change the slug on update" do
@art.title = 'This is the title'
@art.save.should == true
@art.title = 'new title'
@art.save.should == 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 == 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 == true
@art.slug.should == 'this-is-the-title'
end
it "should set the id" do
@art.title = 'This is the title'
@art.save.should == 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['important-field'] = 'very-important'
@templated.save.should == true
end
it "should save with the id" do
@templated['important-field'] = 'very-important'
@templated.save.should == true
t = WithTemplateAndUniqueID.get('very-important')
t.should == @templated
end
it "should not change the id on update" do
@templated['important-field'] = 'very-important'
@templated.save.should == true
@templated['important-field'] = 'not-important'
@templated.save.should == true
t = WithTemplateAndUniqueID.get('very-important')
t.should == @templated
end
it "should raise an error when the id is taken" do
@templated['important-field'] = 'very-important'
@templated.save.should == true
lambda{WithTemplateAndUniqueID.new('important-field' => 'very-important').save}.should raise_error
end
it "should set the id" do
@templated['important-field'] = 'very-important'
@templated.save.should == true
@templated.id.should == 'very-important'
end
end
describe "destroying an instance" do
before(:each) do
@dobj = Basic.new
@dobj.save.should == true
end
it "should return true" do
result = @dobj.destroy
result.should == true
end
it "should be resavable" do
@dobj.destroy
@dobj.rev.should be_nil
@dobj.id.should be_nil
@dobj.save.should == true
end
it "should make it go away" do
@dobj.destroy
lambda{Basic.get!(@dobj.id)}.should raise_error
end
end
describe "callbacks" do
before(:each) do
@doc = WithCallBacks.new
end
describe "validate" do
it "should run before_validate before validating" do
@doc.run_before_validate.should be_nil
@doc.should be_valid
@doc.run_before_validate.should be_true
end
it "should run after_validate after validating" do
@doc.run_after_validate.should be_nil
@doc.should be_valid
@doc.run_after_validate.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 "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 "initialization" do
it "should call after_initialize method if available" do
@doc = WithAfterInitializeMethod.new
@doc['some_value'].should eql('value')
end
end
describe "recursive validation on an extended document" 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, :cast_as => 'Person'
Cat.validates_presence_of :name
cat = Cat.new(:name => 'Mr Bigglesworth')
cat.trainer = Person.new
cat.trainer.validatable?.should be_false
cat.should be_valid
cat.save.should be_true
end
end
describe "searching the contents of an extended document" 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

View file

@ -13,7 +13,7 @@ begin
class PlainChild < PlainParent
end
class ExtendedParent < CouchRest::ExtendedDocument
class ExtendedParent < CouchRest::Model::Base
class_inheritable_accessor :foo
self.foo = :bar
end

View file

@ -0,0 +1,409 @@
# encoding: utf-8
require File.expand_path('../../spec_helper', __FILE__)
require File.join(FIXTURE_PATH, 'base')
require File.join(FIXTURE_PATH, 'more', 'cat')
require File.join(FIXTURE_PATH, 'more', 'article')
require File.join(FIXTURE_PATH, 'more', 'course')
require File.join(FIXTURE_PATH, 'more', 'card')
describe "Model Persistence" do
before(:each) do
@obj = WithDefaultValues.new
end
describe "creating a new document from database" do
it "should instantialize" do
doc = Article.create_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.create_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.create_from_database({'_id' => 'testitem2', '_rev' => 123, 'couchrest-type' => '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 "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['couchrest-type'].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['important-field'] = 'very-important'
@templated.save.should be_true
end
it "should save with the id" do
@templated['important-field'] = '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['important-field'] = 'very-important'
@templated.save.should be_true
@templated['important-field'] = 'not-important'
@templated.save.should be_true
t = WithTemplateAndUniqueID.get('very-important')
t.should == @templated
end
it "should raise an error when the id is taken" do
@templated['important-field'] = 'very-important'
@templated.save.should be_true
lambda{WithTemplateAndUniqueID.new('important-field' => 'very-important').save}.should raise_error
end
it "should set the id" do
@templated['important-field'] = 'very-important'
@templated.save.should be_true
@templated.id.should == 'very-important'
end
end
describe "destroying an instance" do
before(:each) do
@dobj = Basic.new
@dobj.save.should be_true
end
it "should return true" do
result = @dobj.destroy
result.should be_true
end
it "should be resavable" do
@dobj.destroy
@dobj.rev.should be_nil
@dobj.id.should be_nil
@dobj.save.should be_true
end
it "should make it go away" do
@dobj.destroy
lambda{Basic.get!(@dobj.id)}.should raise_error
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 raise an error if `get!` is used and the document doesn't exist" do
lambda{foundart = Article.get!('matt aimonetti')}.should raise_error
end
it "should raise an error if `find!` is used and the document doesn't exist" do
lambda{foundart = Article.find!('matt aimonetti')}.should 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_validate.should be_nil
@doc.should be_valid
@doc.run_before_validate.should be_true
end
it "should run after_validation after validating" do
@doc.run_after_validate.should be_nil
@doc.should be_valid
@doc.run_after_validate.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
end

View file

@ -1,16 +1,16 @@
# encoding: utf-8
require File.expand_path('../../spec_helper', __FILE__)
require File.join(FIXTURE_PATH, 'more', 'cat')
require File.join(FIXTURE_PATH, 'more', 'person')
require File.join(FIXTURE_PATH, 'more', 'card')
require File.join(FIXTURE_PATH, 'more', 'invoice')
require File.join(FIXTURE_PATH, 'more', 'service')
require File.join(FIXTURE_PATH, 'more', 'event')
require File.join(FIXTURE_PATH, 'more', 'cat')
require File.join(FIXTURE_PATH, 'more', 'user')
require File.join(FIXTURE_PATH, 'more', 'course')
describe "ExtendedDocument properties" do
describe "Model properties" do
before(:each) do
reset_test_db!
@ -134,27 +134,27 @@ describe "ExtendedDocument properties" do
@card.first_name = nil
@card.should_not be_valid
@card.errors.should_not be_empty
@card.errors.on(:first_name).should == ["First name must not be blank"]
@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.on('first_name').should == ["First name must not be blank"]
@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.on(:client_name).first.should == "Client name must not be blank"
@invoice.errors.on(:employee_name).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.on(:location).should == ["Hey stupid!, you forgot the location"]
@invoice.errors[:location].should == ["Hey stupid!, you forgot the location"]
end
it "should validate before saving" do
@ -165,37 +165,6 @@ describe "ExtendedDocument properties" do
end
end
describe "autovalidation" do
before(:each) do
@service = Service.new(:name => "Coumpound analysis", :price => 3_000)
end
it "should be valid" do
@service.should be_valid
end
it "should not respond to properties not setup" do
@service.respond_to?(:client_name).should be_false
end
describe "property :name, :length => 4...20" do
it "should autovalidate the presence when length is set" do
@service.name = nil
@service.should_not be_valid
@service.errors.should_not be_nil
@service.errors.on(:name).first.should == "Name must be between 4 and 19 characters long"
end
it "should autovalidate the correct length" do
@service.name = "a"
@service.should_not be_valid
@service.errors.should_not be_nil
@service.errors.on(:name).first.should == "Name must be between 4 and 19 characters long"
end
end
end
describe "casting" do
before(:each) do
@course = Course.new(:title => 'Relaxation')
@ -740,50 +709,43 @@ end
describe "Property Class" do
it "should provide name as string" do
property = CouchRest::Property.new(:test, String)
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::Property.new(:test, String)
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::Property.new(:test, [String])
property = CouchRest::Model::Property.new(:test, [String])
property.type_class.should eql(String)
end
it "should leave type as string if requested" do
property = CouchRest::Property.new(:test, 'String')
property.type.should eql('String')
property.type_class.should eql(String)
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 string by default" do
property = CouchRest::Property.new(:test, nil)
it "should leave type nil and return class as nil also" do
property = CouchRest::Model::Property.new(:test, nil)
property.type.should be_nil
# Type cast should never be used on non-casted property!
property.type_class.should eql(String)
property.type_class.should be_nil
end
it "should convert empty type array to [String]" do
property = CouchRest::Property.new(:test, [])
property.type_class.should eql(String)
end
it "should convert boolean text-type TrueClass" do
property = CouchRest::Property.new(:test, 'boolean')
property.type.should eql('boolean') # no change
property.type_class.should eql(TrueClass)
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::Property.new(:test, Time)
property = CouchRest::Model::Property.new(:test, Time)
property.init_method.should eql('new')
property = CouchRest::Property.new(:test, Time, :init_method => 'parse')
property = CouchRest::Model::Property.new(:test, Time, :init_method => 'parse')
property.init_method.should eql('parse')
end
@ -791,32 +753,32 @@ describe "Property Class" do
describe "casting" do
it "should cast a value" do
property = CouchRest::Property.new(:test, Date)
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::Property.new(:test, [Date])
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::Property.new(:test, [Object])
property = CouchRest::Model::Property.new(:test, [Object])
parent = mock("FooObject")
property.cast(parent, ["2010-06-01", "2010-06-02"]).class.should eql(::CouchRest::CastedArray)
property.cast(parent, ["2010-06-01", "2010-06-02"]).class.should eql(CouchRest::Model::CastedArray)
end
it "should not set a CastedArray on array of Strings" do
property = CouchRest::Property.new(:test, [String])
property = CouchRest::Model::Property.new(:test, [String])
parent = mock("FooObject")
property.cast(parent, ["2010-06-01", "2010-06-02"]).class.should_not eql(::CouchRest::CastedArray)
property.cast(parent, ["2010-06-01", "2010-06-02"]).class.should_not eql(CouchRest::Model::CastedArray)
end
it "should raise and error if value is array when type is not" do
property = CouchRest::Property.new(:test, Date)
property = CouchRest::Model::Property.new(:test, Date)
parent = mock("FooClass")
lambda {
cast = property.cast(parent, [Date.new(2010, 6, 1)])
@ -825,13 +787,13 @@ describe "Property Class" do
it "should set parent as casted_by object in CastedArray" do
property = CouchRest::Property.new(:test, [Object])
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::Property.new(:test, CatToy)
property = CouchRest::Model::Property.new(:test, CatToy)
parent = mock("CatObject")
cast = property.cast(parent, {:name => 'catnip'})
cast.casted_by.should eql(parent)

View file

@ -1,4 +1,5 @@
require File.expand_path("../../spec_helper", __FILE__)
require File.join(FIXTURE_PATH, 'more', 'cat')
require File.join(FIXTURE_PATH, 'more', 'person')
require File.join(FIXTURE_PATH, 'more', 'card')
require File.join(FIXTURE_PATH, 'more', 'course')
@ -9,6 +10,9 @@ 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
@ -20,7 +24,7 @@ class OnlineCourse < Course
view_by :url
end
class Animal < CouchRest::ExtendedDocument
class Animal < CouchRest::Model::Base
use_database TEST_SERVER.default_database
property :name
view_by :name
@ -28,7 +32,7 @@ end
class Dog < Animal; end
describe "Subclassing an ExtendedDocument" do
describe "Subclassing a Model" do
before(:each) do
@card = BusinessCard.new
@ -42,22 +46,18 @@ describe "Subclassing an ExtendedDocument" do
@card.database.uri.should == Card.database.uri
end
it "should share the same autovalidation details" do
@card.auto_validation.should be_true
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.contexts[:default].map{|v| v.field_name}
validated_fields.should include(:extension_code)
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.contexts[:default].map{|v| v.field_name}
validated_fields = Card.validators.map{|v| v.attributes}.flatten
validated_fields.should_not include(:extension_code)
validated_fields.should_not include(:job_title)
end

View file

@ -1,10 +1,12 @@
require File.expand_path("../../spec_helper", __FILE__)
require File.join(FIXTURE_PATH, 'more', 'cat')
require File.join(FIXTURE_PATH, 'more', 'person')
require File.join(FIXTURE_PATH, 'more', 'article')
require File.join(FIXTURE_PATH, 'more', 'course')
describe "ExtendedDocument views" do
describe "Model views" do
class Unattached < CouchRest::ExtendedDocument
class Unattached < CouchRest::Model::Base
# Note: no use_database here
property :title
property :questions
@ -438,7 +440,7 @@ describe "ExtendedDocument views" do
it "should pass database parameter to pager" do
proxy = mock(:proxy)
proxy.stub!(:paginate)
::CouchRest::Mixins::Collection::CollectionProxy.should_receive(:new).with('database', anything(), anything(), anything(), anything()).and_return(proxy)
::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