djsun-mongomapper 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +7 -0
- data/History +51 -0
- data/LICENSE +20 -0
- data/README.rdoc +39 -0
- data/Rakefile +71 -0
- data/VERSION +1 -0
- data/bin/mmconsole +56 -0
- data/lib/mongomapper.rb +96 -0
- data/lib/mongomapper/associations.rb +61 -0
- data/lib/mongomapper/associations/base.rb +71 -0
- data/lib/mongomapper/associations/belongs_to_polymorphic_proxy.rb +32 -0
- data/lib/mongomapper/associations/belongs_to_proxy.rb +22 -0
- data/lib/mongomapper/associations/many_documents_proxy.rb +85 -0
- data/lib/mongomapper/associations/many_embedded_polymorphic_proxy.rb +33 -0
- data/lib/mongomapper/associations/many_embedded_proxy.rb +17 -0
- data/lib/mongomapper/associations/many_polymorphic_proxy.rb +11 -0
- data/lib/mongomapper/associations/many_proxy.rb +6 -0
- data/lib/mongomapper/associations/proxy.rb +67 -0
- data/lib/mongomapper/callbacks.rb +106 -0
- data/lib/mongomapper/document.rb +278 -0
- data/lib/mongomapper/embedded_document.rb +237 -0
- data/lib/mongomapper/finder_options.rb +96 -0
- data/lib/mongomapper/key.rb +80 -0
- data/lib/mongomapper/observing.rb +50 -0
- data/lib/mongomapper/pagination.rb +52 -0
- data/lib/mongomapper/rails_compatibility/document.rb +15 -0
- data/lib/mongomapper/rails_compatibility/embedded_document.rb +25 -0
- data/lib/mongomapper/save_with_validation.rb +19 -0
- data/lib/mongomapper/serialization.rb +55 -0
- data/lib/mongomapper/serializers/json_serializer.rb +79 -0
- data/lib/mongomapper/validations.rb +47 -0
- data/mongomapper.gemspec +139 -0
- data/test/NOTE_ON_TESTING +1 -0
- data/test/functional/associations/test_belongs_to_polymorphic_proxy.rb +39 -0
- data/test/functional/associations/test_belongs_to_proxy.rb +35 -0
- data/test/functional/associations/test_many_embedded_polymorphic_proxy.rb +131 -0
- data/test/functional/associations/test_many_embedded_proxy.rb +106 -0
- data/test/functional/associations/test_many_polymorphic_proxy.rb +267 -0
- data/test/functional/associations/test_many_proxy.rb +236 -0
- data/test/functional/test_associations.rb +40 -0
- data/test/functional/test_callbacks.rb +85 -0
- data/test/functional/test_document.rb +691 -0
- data/test/functional/test_pagination.rb +81 -0
- data/test/functional/test_rails_compatibility.rb +31 -0
- data/test/functional/test_validations.rb +172 -0
- data/test/models.rb +108 -0
- data/test/test_helper.rb +67 -0
- data/test/unit/serializers/test_json_serializer.rb +103 -0
- data/test/unit/test_association_base.rb +136 -0
- data/test/unit/test_document.rb +125 -0
- data/test/unit/test_embedded_document.rb +370 -0
- data/test/unit/test_finder_options.rb +214 -0
- data/test/unit/test_key.rb +217 -0
- data/test/unit/test_mongo_id.rb +35 -0
- data/test/unit/test_mongomapper.rb +28 -0
- data/test/unit/test_observing.rb +101 -0
- data/test/unit/test_pagination.rb +113 -0
- data/test/unit/test_rails_compatibility.rb +34 -0
- data/test/unit/test_serializations.rb +52 -0
- data/test/unit/test_validations.rb +259 -0
- metadata +189 -0
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class JsonSerializationTest < Test::Unit::TestCase
|
4
|
+
class Contact
|
5
|
+
include MongoMapper::Document
|
6
|
+
key :name, String
|
7
|
+
key :age, Integer
|
8
|
+
key :created_at, Time
|
9
|
+
key :awesome, Boolean
|
10
|
+
key :preferences, Hash
|
11
|
+
end
|
12
|
+
|
13
|
+
def setup
|
14
|
+
Contact.include_root_in_json = false
|
15
|
+
@contact = Contact.new(
|
16
|
+
:name => 'Konata Izumi',
|
17
|
+
:age => 16,
|
18
|
+
:created_at => Time.utc(2006, 8, 1),
|
19
|
+
:awesome => true,
|
20
|
+
:preferences => { :shows => 'anime' }
|
21
|
+
)
|
22
|
+
end
|
23
|
+
|
24
|
+
should "include demodulized root" do
|
25
|
+
Contact.include_root_in_json = true
|
26
|
+
assert_match %r{^\{"contact": \{}, @contact.to_json
|
27
|
+
end
|
28
|
+
|
29
|
+
should "encode all encodable attributes" do
|
30
|
+
json = @contact.to_json
|
31
|
+
|
32
|
+
assert_match %r{"name":"Konata Izumi"}, json
|
33
|
+
assert_match %r{"age":16}, json
|
34
|
+
assert json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))}))
|
35
|
+
assert_match %r{"awesome":true}, json
|
36
|
+
assert_match %r{"preferences":\{"shows":"anime"\}}, json
|
37
|
+
end
|
38
|
+
|
39
|
+
should "allow attribute filtering with only" do
|
40
|
+
json = @contact.to_json(:only => [:name, :age])
|
41
|
+
|
42
|
+
assert_match %r{"name":"Konata Izumi"}, json
|
43
|
+
assert_match %r{"age":16}, json
|
44
|
+
assert_no_match %r{"awesome"}, json
|
45
|
+
assert_no_match %r{"created_at"}, json
|
46
|
+
assert_no_match %r{"preferences"}, json
|
47
|
+
end
|
48
|
+
|
49
|
+
should "allow attribute filtering with except" do
|
50
|
+
json = @contact.to_json(:except => [:name, :age])
|
51
|
+
|
52
|
+
assert_no_match %r{"name"}, json
|
53
|
+
assert_no_match %r{"age"}, json
|
54
|
+
assert_match %r{"awesome"}, json
|
55
|
+
assert_match %r{"created_at"}, json
|
56
|
+
assert_match %r{"preferences"}, json
|
57
|
+
end
|
58
|
+
|
59
|
+
context "including methods" do
|
60
|
+
setup do
|
61
|
+
def @contact.label; "Has cheezburger"; end
|
62
|
+
def @contact.favorite_quote; "Constraints are liberating"; end
|
63
|
+
end
|
64
|
+
|
65
|
+
should "include single method" do
|
66
|
+
assert_match %r{"label":"Has cheezburger"}, @contact.to_json(:only => :name, :methods => :label)
|
67
|
+
end
|
68
|
+
|
69
|
+
should "include multiple methods" do
|
70
|
+
json = @contact.to_json(:only => :name, :methods => [:label, :favorite_quote])
|
71
|
+
assert_match %r{"label":"Has cheezburger"}, json
|
72
|
+
assert_match %r{"favorite_quote":"Constraints are liberating"}, json
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
context "array of records" do
|
77
|
+
setup do
|
78
|
+
@contacts = [
|
79
|
+
Contact.new(:name => 'David', :age => 39),
|
80
|
+
Contact.new(:name => 'Mary', :age => 14)
|
81
|
+
]
|
82
|
+
end
|
83
|
+
|
84
|
+
should "allow attribute filtering with only" do
|
85
|
+
assert_equal %([{"name":"David"},{"name":"Mary"}]), @contacts.to_json(:only => :name)
|
86
|
+
end
|
87
|
+
|
88
|
+
should "allow attribute filtering with except" do
|
89
|
+
json = @contacts.to_json(:except => [:name, :preferences, :awesome, :created_at, :updated_at, :_id])
|
90
|
+
assert_equal %([{"id":"","age":39},{"id":"","age":14}]), json
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
should "allow options for hash of records" do
|
95
|
+
contacts = {
|
96
|
+
1 => Contact.new(:name => 'David', :age => 39),
|
97
|
+
2 => Contact.new(:name => 'Mary', :age => 14)
|
98
|
+
}
|
99
|
+
|
100
|
+
assert_equal %({"1":{"name":"David"}}), contacts.to_json(:only => [1, :name])
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'models'
|
3
|
+
|
4
|
+
class FooMonster; end
|
5
|
+
|
6
|
+
class AssociationBaseTest < Test::Unit::TestCase
|
7
|
+
include MongoMapper::Associations
|
8
|
+
|
9
|
+
should "initialize with type and name" do
|
10
|
+
base = Base.new(:many, :foos)
|
11
|
+
base.type.should == :many
|
12
|
+
base.name.should == :foos
|
13
|
+
end
|
14
|
+
|
15
|
+
should "also allow options when initializing" do
|
16
|
+
base = Base.new(:many, :foos, :polymorphic => true)
|
17
|
+
base.options[:polymorphic].should be_true
|
18
|
+
end
|
19
|
+
|
20
|
+
context "class_name" do
|
21
|
+
should "work for belongs_to" do
|
22
|
+
Base.new(:belongs_to, :user).class_name.should == 'User'
|
23
|
+
end
|
24
|
+
|
25
|
+
should "work for many" do
|
26
|
+
Base.new(:many, :smart_people).class_name.should == 'SmartPerson'
|
27
|
+
end
|
28
|
+
|
29
|
+
should "be changeable using class_name option" do
|
30
|
+
base = Base.new(:many, :smart_people, :class_name => 'IntelligentPerson')
|
31
|
+
base.class_name.should == 'IntelligentPerson'
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context "klass" do
|
36
|
+
should "be class_name constantized" do
|
37
|
+
Base.new(:belongs_to, :foo_monster).klass.should == FooMonster
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
context "many?" do
|
42
|
+
should "be true if many" do
|
43
|
+
Base.new(:many, :foos).many?.should be_true
|
44
|
+
end
|
45
|
+
|
46
|
+
should "be false if not many" do
|
47
|
+
Base.new(:belongs_to, :foo).many?.should be_false
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
context "belongs_to?" do
|
52
|
+
should "be true if belongs_to" do
|
53
|
+
Base.new(:belongs_to, :foo).belongs_to?.should be_true
|
54
|
+
end
|
55
|
+
|
56
|
+
should "be false if not belongs_to" do
|
57
|
+
Base.new(:many, :foos).belongs_to?.should be_false
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
context "polymorphic?" do
|
62
|
+
should "be true if polymorphic" do
|
63
|
+
Base.new(:many, :foos, :polymorphic => true).polymorphic?.should be_true
|
64
|
+
end
|
65
|
+
|
66
|
+
should "be false if not polymorphic" do
|
67
|
+
Base.new(:many, :bars).polymorphic?.should be_false
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
context "type_key_name" do
|
72
|
+
should "be _type for many" do
|
73
|
+
Base.new(:many, :foos).type_key_name.should == '_type'
|
74
|
+
end
|
75
|
+
|
76
|
+
should "be association name _ type for belongs_to" do
|
77
|
+
Base.new(:belongs_to, :foo).type_key_name.should == 'foo_type'
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
should "have belongs_to_key_name" do
|
82
|
+
Base.new(:belongs_to, :foo).belongs_to_key_name.should == 'foo_id'
|
83
|
+
end
|
84
|
+
|
85
|
+
should "have ivar that is association name" do
|
86
|
+
Base.new(:belongs_to, :foo).ivar.should == '@_foo'
|
87
|
+
end
|
88
|
+
|
89
|
+
context "embeddable?" do
|
90
|
+
should "be true if class is embeddable" do
|
91
|
+
base = Base.new(:many, :medias)
|
92
|
+
base.embeddable?.should be_true
|
93
|
+
end
|
94
|
+
|
95
|
+
should "be false if class is not embeddable" do
|
96
|
+
base = Base.new(:many, :statuses)
|
97
|
+
base.embeddable?.should be_false
|
98
|
+
|
99
|
+
base = Base.new(:belongs_to, :project)
|
100
|
+
base.embeddable?.should be_false
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
context "proxy_class" do
|
105
|
+
should "be ManyProxy for many" do
|
106
|
+
base = Base.new(:many, :statuses)
|
107
|
+
base.proxy_class.should == ManyProxy
|
108
|
+
end
|
109
|
+
|
110
|
+
should "be ManyPolymorphicProxy for polymorphic many" do
|
111
|
+
base = Base.new(:many, :messages, :polymorphic => true)
|
112
|
+
base.proxy_class.should == ManyPolymorphicProxy
|
113
|
+
end
|
114
|
+
|
115
|
+
should "be ManyEmbeddedProxy for many embedded" do
|
116
|
+
base = Base.new(:many, :medias)
|
117
|
+
base.proxy_class.should == ManyEmbeddedProxy
|
118
|
+
end
|
119
|
+
|
120
|
+
should "be ManyEmbeddedPolymorphicProxy for polymorphic many embedded" do
|
121
|
+
base = Base.new(:many, :medias, :polymorphic => true)
|
122
|
+
base.proxy_class.should == ManyEmbeddedPolymorphicProxy
|
123
|
+
end
|
124
|
+
|
125
|
+
should "be BelongsToProxy for belongs_to" do
|
126
|
+
base = Base.new(:belongs_to, :project)
|
127
|
+
base.proxy_class.should == BelongsToProxy
|
128
|
+
end
|
129
|
+
|
130
|
+
should "be BelongsToPolymorphicProxy for polymorphic belongs_to" do
|
131
|
+
base = Base.new(:belongs_to, :target, :polymorphic => true)
|
132
|
+
base.proxy_class.should == BelongsToPolymorphicProxy
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'models'
|
3
|
+
|
4
|
+
class DocumentTest < Test::Unit::TestCase
|
5
|
+
context "The Document Class" do
|
6
|
+
setup do
|
7
|
+
@document = Class.new do
|
8
|
+
include MongoMapper::Document
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
should "track its descendants" do
|
13
|
+
MongoMapper::Document.descendants.should include(@document)
|
14
|
+
end
|
15
|
+
|
16
|
+
should "use default database by default" do
|
17
|
+
@document.database.should == MongoMapper.database
|
18
|
+
end
|
19
|
+
|
20
|
+
should "have a connection" do
|
21
|
+
@document.connection.should be_instance_of(XGen::Mongo::Driver::Mongo)
|
22
|
+
end
|
23
|
+
|
24
|
+
should "allow setting different connection without affecting the default" do
|
25
|
+
conn = XGen::Mongo::Driver::Mongo.new
|
26
|
+
@document.connection conn
|
27
|
+
@document.connection.should == conn
|
28
|
+
@document.connection.should_not == MongoMapper.connection
|
29
|
+
end
|
30
|
+
|
31
|
+
should "allow setting a different database without affecting the default" do
|
32
|
+
@document.database AlternateDatabase
|
33
|
+
@document.database.name.should == AlternateDatabase
|
34
|
+
|
35
|
+
another_document = Class.new do
|
36
|
+
include MongoMapper::Document
|
37
|
+
end
|
38
|
+
another_document.database.should == MongoMapper.database
|
39
|
+
end
|
40
|
+
|
41
|
+
should "default collection name to class name tableized" do
|
42
|
+
class Item
|
43
|
+
include MongoMapper::Document
|
44
|
+
end
|
45
|
+
|
46
|
+
Item.collection.should be_instance_of(XGen::Mongo::Driver::Collection)
|
47
|
+
Item.collection.name.should == 'items'
|
48
|
+
end
|
49
|
+
|
50
|
+
should "allow setting the collection name" do
|
51
|
+
@document.collection('foobar')
|
52
|
+
@document.collection.should be_instance_of(XGen::Mongo::Driver::Collection)
|
53
|
+
@document.collection.name.should == 'foobar'
|
54
|
+
end
|
55
|
+
end # Document class
|
56
|
+
|
57
|
+
context "Documents that inherit from other documents" do
|
58
|
+
should "default collection to inherited class" do
|
59
|
+
Message.collection.name.should == 'messages'
|
60
|
+
Enter.collection.name.should == 'messages'
|
61
|
+
Exit.collection.name.should == 'messages'
|
62
|
+
Chat.collection.name.should == 'messages'
|
63
|
+
end
|
64
|
+
|
65
|
+
should "track subclasses" do
|
66
|
+
Message.subclasses.should == [Enter, Exit, Chat]
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
context "An instance of a document" do
|
71
|
+
setup do
|
72
|
+
@document = Class.new do
|
73
|
+
include MongoMapper::Document
|
74
|
+
|
75
|
+
key :name, String
|
76
|
+
key :age, Integer
|
77
|
+
end
|
78
|
+
@document.collection.clear
|
79
|
+
end
|
80
|
+
|
81
|
+
should "have access to the class's collection" do
|
82
|
+
doc = @document.new
|
83
|
+
doc.collection.should == @document.collection
|
84
|
+
end
|
85
|
+
|
86
|
+
should "automatically have a created_at key" do
|
87
|
+
@document.keys.keys.should include('created_at')
|
88
|
+
end
|
89
|
+
|
90
|
+
should "automatically have an updated_at key" do
|
91
|
+
@document.keys.keys.should include('updated_at')
|
92
|
+
end
|
93
|
+
|
94
|
+
should "use default values if defined for keys" do
|
95
|
+
@document.key :active, Boolean, :default => true
|
96
|
+
|
97
|
+
@document.new.active.should be_true
|
98
|
+
@document.new(:active => false).active.should be_false
|
99
|
+
end
|
100
|
+
|
101
|
+
context "new?" do
|
102
|
+
should "be true if no id" do
|
103
|
+
@document.new.new?.should be(true)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
context "equality" do
|
108
|
+
should "be equal if id and class are the same" do
|
109
|
+
(@document.new('_id' => 1) == @document.new('_id' => 1)).should be(true)
|
110
|
+
end
|
111
|
+
|
112
|
+
should "not be equal if class same but id different" do
|
113
|
+
(@document.new('_id' => 1) == @document.new('_id' => 2)).should be(false)
|
114
|
+
end
|
115
|
+
|
116
|
+
should "not be equal if id same but class different" do
|
117
|
+
@another_document = Class.new do
|
118
|
+
include MongoMapper::Document
|
119
|
+
end
|
120
|
+
|
121
|
+
(@document.new('_id' => 1) == @another_document.new('_id' => 1)).should be(false)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end # instance of a document
|
125
|
+
end # DocumentTest
|
@@ -0,0 +1,370 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class Grandparent
|
4
|
+
include MongoMapper::EmbeddedDocument
|
5
|
+
key :grandparent, String
|
6
|
+
end
|
7
|
+
|
8
|
+
class Parent < Grandparent
|
9
|
+
include MongoMapper::EmbeddedDocument
|
10
|
+
key :parent, String
|
11
|
+
end
|
12
|
+
|
13
|
+
class Child < Parent
|
14
|
+
include MongoMapper::EmbeddedDocument
|
15
|
+
key :child, String
|
16
|
+
end
|
17
|
+
|
18
|
+
class OtherChild < Parent
|
19
|
+
include MongoMapper::EmbeddedDocument
|
20
|
+
key :other_child, String
|
21
|
+
end
|
22
|
+
|
23
|
+
class EmbeddedDocumentTest < Test::Unit::TestCase
|
24
|
+
context "Including MongoMapper::EmbeddedDocument" do
|
25
|
+
setup do
|
26
|
+
@klass = Class.new do
|
27
|
+
include MongoMapper::EmbeddedDocument
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
should "add _id key" do
|
32
|
+
@klass.keys['_id'].should_not be_nil
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context "parent_model" do
|
37
|
+
should "be nil if none of parents ancestors include EmbeddedDocument" do
|
38
|
+
parent = Class.new
|
39
|
+
document = Class.new(parent) do
|
40
|
+
include MongoMapper::EmbeddedDocument
|
41
|
+
end
|
42
|
+
document.parent_model.should be_nil
|
43
|
+
end
|
44
|
+
|
45
|
+
should "find parent" do
|
46
|
+
Parent.parent_model.should == Grandparent
|
47
|
+
Child.parent_model.should == Parent
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
context "defining a key" do
|
52
|
+
setup do
|
53
|
+
@document = Class.new do
|
54
|
+
include MongoMapper::EmbeddedDocument
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
should "work" do
|
59
|
+
key = @document.key(:name, String)
|
60
|
+
key.name.should == 'name'
|
61
|
+
key.type.should == String
|
62
|
+
key.should be_instance_of(MongoMapper::Key)
|
63
|
+
end
|
64
|
+
|
65
|
+
should "work with options" do
|
66
|
+
key = @document.key(:name, String, :required => true)
|
67
|
+
key.options[:required].should be(true)
|
68
|
+
end
|
69
|
+
|
70
|
+
should "be tracked per document" do
|
71
|
+
@document.key(:name, String)
|
72
|
+
@document.key(:age, Integer)
|
73
|
+
@document.keys['name'].name.should == 'name'
|
74
|
+
@document.keys['name'].type.should == String
|
75
|
+
@document.keys['age'].name.should == 'age'
|
76
|
+
@document.keys['age'].type.should == Integer
|
77
|
+
end
|
78
|
+
|
79
|
+
should "be redefinable" do
|
80
|
+
@document.key(:foo, String)
|
81
|
+
@document.keys['foo'].type.should == String
|
82
|
+
@document.key(:foo, Integer)
|
83
|
+
@document.keys['foo'].type.should == Integer
|
84
|
+
end
|
85
|
+
|
86
|
+
should "create reader method" do
|
87
|
+
@document.new.should_not respond_to(:foo)
|
88
|
+
@document.key(:foo, String)
|
89
|
+
@document.new.should respond_to(:foo)
|
90
|
+
end
|
91
|
+
|
92
|
+
should "create reader before typecast method" do
|
93
|
+
@document.new.should_not respond_to(:foo_before_typecast)
|
94
|
+
@document.key(:foo, String)
|
95
|
+
@document.new.should respond_to(:foo_before_typecast)
|
96
|
+
end
|
97
|
+
|
98
|
+
should "create writer method" do
|
99
|
+
@document.new.should_not respond_to(:foo=)
|
100
|
+
@document.key(:foo, String)
|
101
|
+
@document.new.should respond_to(:foo=)
|
102
|
+
end
|
103
|
+
|
104
|
+
should "create boolean method" do
|
105
|
+
@document.new.should_not respond_to(:foo?)
|
106
|
+
@document.key(:foo, String)
|
107
|
+
@document.new.should respond_to(:foo?)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
context "keys" do
|
112
|
+
should "be inherited" do
|
113
|
+
Grandparent.keys.keys.sort.should == ['_id', 'grandparent']
|
114
|
+
Parent.keys.keys.sort.should == ['_id', 'grandparent', 'parent']
|
115
|
+
Child.keys.keys.sort.should == ['_id', 'child', 'grandparent', 'parent']
|
116
|
+
end
|
117
|
+
|
118
|
+
should "propogate to subclasses if key added after class definition" do
|
119
|
+
Grandparent.key :_type, String
|
120
|
+
|
121
|
+
Grandparent.keys.keys.sort.should == ['_id', '_type', 'grandparent']
|
122
|
+
Parent.keys.keys.sort.should == ['_id', '_type', 'grandparent', 'parent']
|
123
|
+
Child.keys.keys.sort.should == ['_id', '_type', 'child', 'grandparent', 'parent']
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
context "subclasses" do
|
128
|
+
should "default to nil" do
|
129
|
+
Child.subclasses.should be_nil
|
130
|
+
end
|
131
|
+
|
132
|
+
should "be recorded" do
|
133
|
+
Grandparent.subclasses.should == [Parent]
|
134
|
+
Parent.subclasses.should == [Child, OtherChild]
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
context "An instance of an embedded document" do
|
139
|
+
setup do
|
140
|
+
@document = Class.new do
|
141
|
+
include MongoMapper::EmbeddedDocument
|
142
|
+
|
143
|
+
key :name, String
|
144
|
+
key :age, Integer
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
should "automatically have an _id key" do
|
149
|
+
@document.keys.keys.should include('_id')
|
150
|
+
end
|
151
|
+
|
152
|
+
should "have id method that is string representation of _id" do
|
153
|
+
doc = @document.new
|
154
|
+
doc.id.should == doc._id.to_s
|
155
|
+
end
|
156
|
+
|
157
|
+
should "be able to set _id using id=" do
|
158
|
+
id = MongoID.new
|
159
|
+
doc = @document.new(:id => id.to_s)
|
160
|
+
doc._id.should == id
|
161
|
+
doc.id.should == id.to_s
|
162
|
+
end
|
163
|
+
|
164
|
+
context "being initialized" do
|
165
|
+
should "accept a hash that sets keys and values" do
|
166
|
+
doc = @document.new(:name => 'John', :age => 23)
|
167
|
+
doc.attributes.keys.sort.should == ['_id', 'age', 'name']
|
168
|
+
doc.attributes['name'].should == 'John'
|
169
|
+
doc.attributes['age'].should == 23
|
170
|
+
end
|
171
|
+
|
172
|
+
should "not throw error if initialized with nil" do
|
173
|
+
lambda {
|
174
|
+
@document.new(nil)
|
175
|
+
}.should_not raise_error
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
context "mass assigning keys" do
|
180
|
+
should "update values for keys provided" do
|
181
|
+
doc = @document.new(:name => 'foobar', :age => 10)
|
182
|
+
doc.attributes = {:name => 'new value', :age => 5}
|
183
|
+
doc.attributes[:name].should == 'new value'
|
184
|
+
doc.attributes[:age].should == 5
|
185
|
+
end
|
186
|
+
|
187
|
+
should "not update values for keys that were not provided" do
|
188
|
+
doc = @document.new(:name => 'foobar', :age => 10)
|
189
|
+
doc.attributes = {:name => 'new value'}
|
190
|
+
doc.attributes[:name].should == 'new value'
|
191
|
+
doc.attributes[:age].should == 10
|
192
|
+
end
|
193
|
+
|
194
|
+
should "raise undefined method if no key exists" do
|
195
|
+
doc = @document.new(:name => 'foobar', :age => 10)
|
196
|
+
lambda {
|
197
|
+
doc.attributes = {:name => 'new value', :foobar => 'baz'}
|
198
|
+
}.should raise_error(NoMethodError)
|
199
|
+
end
|
200
|
+
|
201
|
+
should "not ignore keys that have methods defined" do
|
202
|
+
@document.class_eval do
|
203
|
+
attr_writer :password
|
204
|
+
|
205
|
+
def passwd
|
206
|
+
@password
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
doc = @document.new(:name => 'foobar', :password => 'secret')
|
211
|
+
doc.passwd.should == 'secret'
|
212
|
+
end
|
213
|
+
|
214
|
+
should "typecast key values" do
|
215
|
+
doc = @document.new(:name => 1234, :age => '21')
|
216
|
+
doc.name.should == '1234'
|
217
|
+
doc.age.should == 21
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
context "attributes" do
|
222
|
+
should "default to hash with _id" do
|
223
|
+
doc = @document.new
|
224
|
+
doc.attributes.keys.should == ['_id']
|
225
|
+
end
|
226
|
+
|
227
|
+
should "return all keys that aren't nil" do
|
228
|
+
doc = @document.new(:name => 'string', :age => nil)
|
229
|
+
doc.attributes.keys.sort.should == ['_id', 'name']
|
230
|
+
doc.attributes.values.should include('string')
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
context "key shorcut access" do
|
235
|
+
should "be able to read key with []" do
|
236
|
+
doc = @document.new(:name => 'string')
|
237
|
+
doc[:name].should == 'string'
|
238
|
+
end
|
239
|
+
|
240
|
+
should "be able to write key value with []=" do
|
241
|
+
doc = @document.new
|
242
|
+
doc[:name] = 'string'
|
243
|
+
doc[:name].should == 'string'
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
context "indifferent access" do
|
248
|
+
should "be enabled for keys" do
|
249
|
+
doc = @document.new(:name => 'string')
|
250
|
+
doc.attributes[:name].should == 'string'
|
251
|
+
doc.attributes['name'].should == 'string'
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
context "reading an attribute" do
|
256
|
+
should "work for defined keys" do
|
257
|
+
doc = @document.new(:name => 'string')
|
258
|
+
doc.name.should == 'string'
|
259
|
+
end
|
260
|
+
|
261
|
+
should "raise no method error for undefined keys" do
|
262
|
+
doc = @document.new
|
263
|
+
lambda { doc.fart }.should raise_error(NoMethodError)
|
264
|
+
end
|
265
|
+
|
266
|
+
should "be accessible for use in the model" do
|
267
|
+
@document.class_eval do
|
268
|
+
def name_and_age
|
269
|
+
"#{read_attribute(:name)} (#{read_attribute(:age)})"
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
doc = @document.new(:name => 'John', :age => 27)
|
274
|
+
doc.name_and_age.should == 'John (27)'
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
context "reading an attribute before typcasting" do
|
279
|
+
should "work for defined keys" do
|
280
|
+
doc = @document.new(:name => 12)
|
281
|
+
doc.name_before_typecast.should == 12
|
282
|
+
end
|
283
|
+
|
284
|
+
should "raise no method error for undefined keys" do
|
285
|
+
doc = @document.new
|
286
|
+
lambda { doc.foo_before_typecast }.should raise_error(NoMethodError)
|
287
|
+
end
|
288
|
+
|
289
|
+
should "be accessible for use in a document" do
|
290
|
+
@document.class_eval do
|
291
|
+
def untypcasted_name
|
292
|
+
read_attribute_before_typecast(:name)
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
doc = @document.new(:name => 12)
|
297
|
+
doc.name.should == '12'
|
298
|
+
doc.untypcasted_name.should == 12
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
context "writing an attribute" do
|
303
|
+
should "work for defined keys" do
|
304
|
+
doc = @document.new
|
305
|
+
doc.name = 'John'
|
306
|
+
doc.name.should == 'John'
|
307
|
+
end
|
308
|
+
|
309
|
+
should "raise no method error for undefined keys" do
|
310
|
+
doc = @document.new
|
311
|
+
lambda { doc.fart = 'poof!' }.should raise_error(NoMethodError)
|
312
|
+
end
|
313
|
+
|
314
|
+
should "typecast value" do
|
315
|
+
doc = @document.new
|
316
|
+
doc.name = 1234
|
317
|
+
doc.name.should == '1234'
|
318
|
+
doc.age = '21'
|
319
|
+
doc.age.should == 21
|
320
|
+
end
|
321
|
+
|
322
|
+
should "be accessible for use in the model" do
|
323
|
+
@document.class_eval do
|
324
|
+
def name_and_age=(new_value)
|
325
|
+
new_value.match(/([^\(\s]+) \((.*)\)/)
|
326
|
+
write_attribute :name, $1
|
327
|
+
write_attribute :age, $2
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
doc = @document.new
|
332
|
+
doc.name_and_age = 'Frank (62)'
|
333
|
+
doc.name.should == 'Frank'
|
334
|
+
doc.age.should == 62
|
335
|
+
end
|
336
|
+
end # writing an attribute
|
337
|
+
|
338
|
+
context "checking if an attributes value is present" do
|
339
|
+
should "work for defined keys" do
|
340
|
+
doc = @document.new
|
341
|
+
doc.name?.should be_false
|
342
|
+
doc.name = 'John'
|
343
|
+
doc.name?.should be_true
|
344
|
+
end
|
345
|
+
|
346
|
+
should "raise no method error for undefined keys" do
|
347
|
+
doc = @document.new
|
348
|
+
lambda { doc.fart? }.should raise_error(NoMethodError)
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
context "equality" do
|
353
|
+
should "be equal if id and class are the same" do
|
354
|
+
(@document.new('_id' => 1) == @document.new('_id' => 1)).should be(true)
|
355
|
+
end
|
356
|
+
|
357
|
+
should "not be equal if class same but id different" do
|
358
|
+
(@document.new('_id' => 1) == @document.new('_id' => 2)).should be(false)
|
359
|
+
end
|
360
|
+
|
361
|
+
should "not be equal if id same but class different" do
|
362
|
+
@another_document = Class.new do
|
363
|
+
include MongoMapper::Document
|
364
|
+
end
|
365
|
+
|
366
|
+
(@document.new('_id' => 1) == @another_document.new('_id' => 1)).should be(false)
|
367
|
+
end
|
368
|
+
end
|
369
|
+
end # instance of a embedded document
|
370
|
+
end
|