mongo_mapper 0.5.0
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/LICENSE +20 -0
- data/README.rdoc +39 -0
- data/Rakefile +87 -0
- data/VERSION +1 -0
- data/bin/mmconsole +55 -0
- data/lib/mongo_mapper.rb +92 -0
- data/lib/mongo_mapper/associations.rb +86 -0
- data/lib/mongo_mapper/associations/base.rb +83 -0
- data/lib/mongo_mapper/associations/belongs_to_polymorphic_proxy.rb +34 -0
- data/lib/mongo_mapper/associations/belongs_to_proxy.rb +22 -0
- data/lib/mongo_mapper/associations/many_documents_as_proxy.rb +27 -0
- data/lib/mongo_mapper/associations/many_documents_proxy.rb +116 -0
- data/lib/mongo_mapper/associations/many_embedded_polymorphic_proxy.rb +33 -0
- data/lib/mongo_mapper/associations/many_embedded_proxy.rb +67 -0
- data/lib/mongo_mapper/associations/many_polymorphic_proxy.rb +11 -0
- data/lib/mongo_mapper/associations/many_proxy.rb +6 -0
- data/lib/mongo_mapper/associations/proxy.rb +64 -0
- data/lib/mongo_mapper/callbacks.rb +106 -0
- data/lib/mongo_mapper/document.rb +317 -0
- data/lib/mongo_mapper/dynamic_finder.rb +35 -0
- data/lib/mongo_mapper/embedded_document.rb +354 -0
- data/lib/mongo_mapper/finder_options.rb +94 -0
- data/lib/mongo_mapper/key.rb +32 -0
- data/lib/mongo_mapper/observing.rb +50 -0
- data/lib/mongo_mapper/pagination.rb +51 -0
- data/lib/mongo_mapper/rails_compatibility/document.rb +15 -0
- data/lib/mongo_mapper/rails_compatibility/embedded_document.rb +27 -0
- data/lib/mongo_mapper/save_with_validation.rb +19 -0
- data/lib/mongo_mapper/serialization.rb +55 -0
- data/lib/mongo_mapper/serializers/json_serializer.rb +92 -0
- data/lib/mongo_mapper/support.rb +157 -0
- data/lib/mongo_mapper/validations.rb +69 -0
- data/mongo_mapper.gemspec +156 -0
- data/test/NOTE_ON_TESTING +1 -0
- data/test/custom_matchers.rb +48 -0
- data/test/functional/associations/test_belongs_to_polymorphic_proxy.rb +54 -0
- data/test/functional/associations/test_belongs_to_proxy.rb +46 -0
- data/test/functional/associations/test_many_documents_as_proxy.rb +244 -0
- data/test/functional/associations/test_many_embedded_polymorphic_proxy.rb +132 -0
- data/test/functional/associations/test_many_embedded_proxy.rb +174 -0
- data/test/functional/associations/test_many_polymorphic_proxy.rb +297 -0
- data/test/functional/associations/test_many_proxy.rb +331 -0
- data/test/functional/test_associations.rb +48 -0
- data/test/functional/test_binary.rb +18 -0
- data/test/functional/test_callbacks.rb +85 -0
- data/test/functional/test_document.rb +951 -0
- data/test/functional/test_embedded_document.rb +97 -0
- data/test/functional/test_pagination.rb +87 -0
- data/test/functional/test_rails_compatibility.rb +30 -0
- data/test/functional/test_validations.rb +279 -0
- data/test/models.rb +169 -0
- data/test/test_helper.rb +29 -0
- data/test/unit/serializers/test_json_serializer.rb +189 -0
- data/test/unit/test_association_base.rb +144 -0
- data/test/unit/test_document.rb +165 -0
- data/test/unit/test_dynamic_finder.rb +125 -0
- data/test/unit/test_embedded_document.rb +645 -0
- data/test/unit/test_finder_options.rb +193 -0
- data/test/unit/test_key.rb +163 -0
- data/test/unit/test_mongomapper.rb +28 -0
- data/test/unit/test_observing.rb +101 -0
- data/test/unit/test_pagination.rb +109 -0
- data/test/unit/test_rails_compatibility.rb +39 -0
- data/test/unit/test_serializations.rb +52 -0
- data/test/unit/test_support.rb +272 -0
- data/test/unit/test_time_zones.rb +40 -0
- data/test/unit/test_validations.rb +503 -0
- metadata +204 -0
@@ -0,0 +1,165 @@
|
|
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
|
+
set_collection_name 'test'
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
should "track its descendants" do
|
14
|
+
MongoMapper::Document.descendants.should include(@document)
|
15
|
+
end
|
16
|
+
|
17
|
+
should "use default database by default" do
|
18
|
+
@document.database.should == MongoMapper.database
|
19
|
+
end
|
20
|
+
|
21
|
+
should "have a connection" do
|
22
|
+
@document.connection.should be_instance_of(Mongo::Connection)
|
23
|
+
end
|
24
|
+
|
25
|
+
should "allow setting different connection without affecting the default" do
|
26
|
+
conn = Mongo::Connection.new
|
27
|
+
@document.connection conn
|
28
|
+
@document.connection.should == conn
|
29
|
+
@document.connection.should_not == MongoMapper.connection
|
30
|
+
end
|
31
|
+
|
32
|
+
should "allow setting a different database without affecting the default" do
|
33
|
+
@document.database AlternateDatabase
|
34
|
+
@document.database.name.should == AlternateDatabase
|
35
|
+
|
36
|
+
another_document = Class.new do
|
37
|
+
include MongoMapper::Document
|
38
|
+
set_collection_name 'test'
|
39
|
+
end
|
40
|
+
another_document.database.should == MongoMapper.database
|
41
|
+
end
|
42
|
+
|
43
|
+
should "default collection name to class name tableized" do
|
44
|
+
class Item
|
45
|
+
include MongoMapper::Document
|
46
|
+
end
|
47
|
+
|
48
|
+
Item.collection.should be_instance_of(Mongo::Collection)
|
49
|
+
Item.collection.name.should == 'items'
|
50
|
+
end
|
51
|
+
|
52
|
+
should "allow setting the collection name" do
|
53
|
+
@document.set_collection_name('foobar')
|
54
|
+
@document.collection.should be_instance_of(Mongo::Collection)
|
55
|
+
@document.collection.name.should == 'foobar'
|
56
|
+
end
|
57
|
+
end # Document class
|
58
|
+
|
59
|
+
context "Documents that inherit from other documents" do
|
60
|
+
should "default collection name to inherited class" do
|
61
|
+
Message.collection_name.should == 'messages'
|
62
|
+
Enter.collection_name.should == 'messages'
|
63
|
+
Exit.collection_name.should == 'messages'
|
64
|
+
Chat.collection_name.should == 'messages'
|
65
|
+
end
|
66
|
+
|
67
|
+
should "default associations to inherited class" do
|
68
|
+
Message.associations.keys.should include("room")
|
69
|
+
Enter.associations.keys.should include("room")
|
70
|
+
Exit.associations.keys.should include("room")
|
71
|
+
Chat.associations.keys.should include("room")
|
72
|
+
end
|
73
|
+
|
74
|
+
should "track subclasses" do
|
75
|
+
Message.subclasses.should == [Enter, Exit, Chat]
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
context "An instance of a document" do
|
80
|
+
setup do
|
81
|
+
@document = Class.new do
|
82
|
+
include MongoMapper::Document
|
83
|
+
set_collection_name 'test'
|
84
|
+
|
85
|
+
key :name, String
|
86
|
+
key :age, Integer
|
87
|
+
end
|
88
|
+
@document.collection.clear
|
89
|
+
end
|
90
|
+
|
91
|
+
should "have access to the class's collection" do
|
92
|
+
doc = @document.new
|
93
|
+
doc.collection.should == @document.collection
|
94
|
+
end
|
95
|
+
|
96
|
+
should "use default values if defined for keys" do
|
97
|
+
@document.key :active, Boolean, :default => true
|
98
|
+
|
99
|
+
@document.new.active.should be_true
|
100
|
+
@document.new(:active => false).active.should be_false
|
101
|
+
end
|
102
|
+
|
103
|
+
context "root document" do
|
104
|
+
should "have a nil _root_document" do
|
105
|
+
@document.new._root_document.should be_nil
|
106
|
+
end
|
107
|
+
|
108
|
+
should "set self to the root document on embedded documents" do
|
109
|
+
document = Class.new(RealPerson) do
|
110
|
+
many :pets
|
111
|
+
end
|
112
|
+
|
113
|
+
doc = document.new 'pets' => [{}]
|
114
|
+
doc.pets.first._root_document.should == doc
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
context "new?" do
|
119
|
+
should "be true if no id" do
|
120
|
+
@document.new.new?.should be_true
|
121
|
+
end
|
122
|
+
|
123
|
+
should "be true if id but using custom id and not saved yet" do
|
124
|
+
doc = @document.new
|
125
|
+
doc.id = '1234'
|
126
|
+
doc.new?.should be_true
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
context "clone" do
|
131
|
+
should "not set the id" do
|
132
|
+
doc = @document.create(:name => "foo", :age => 27)
|
133
|
+
clone = doc.clone
|
134
|
+
clone.should be_new
|
135
|
+
end
|
136
|
+
|
137
|
+
should "copy the attributes" do
|
138
|
+
doc = @document.create(:name => "foo", :age => 27)
|
139
|
+
clone = doc.clone
|
140
|
+
clone.name.should == "foo"
|
141
|
+
clone.age.should == 27
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
|
146
|
+
context "equality" do
|
147
|
+
should "be equal if id and class are the same" do
|
148
|
+
(@document.new('_id' => 1) == @document.new('_id' => 1)).should be(true)
|
149
|
+
end
|
150
|
+
|
151
|
+
should "not be equal if class same but id different" do
|
152
|
+
(@document.new('_id' => 1) == @document.new('_id' => 2)).should be(false)
|
153
|
+
end
|
154
|
+
|
155
|
+
should "not be equal if id same but class different" do
|
156
|
+
@another_document = Class.new do
|
157
|
+
include MongoMapper::Document
|
158
|
+
set_collection_name 'test'
|
159
|
+
end
|
160
|
+
|
161
|
+
(@document.new('_id' => 1) == @another_document.new('_id' => 1)).should be(false)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end # instance of a document
|
165
|
+
end # DocumentTest
|
@@ -0,0 +1,125 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class DynamicFinderTest < Test::Unit::TestCase
|
4
|
+
include MongoMapper
|
5
|
+
|
6
|
+
should "initialize with method" do
|
7
|
+
finder = DynamicFinder.new(:foobar)
|
8
|
+
finder.method.should == :foobar
|
9
|
+
end
|
10
|
+
|
11
|
+
context "found?" do
|
12
|
+
should "be true for find_by" do
|
13
|
+
DynamicFinder.new(:find_by_foo).found?.should be_true
|
14
|
+
end
|
15
|
+
|
16
|
+
should "be true for find_by with !" do
|
17
|
+
DynamicFinder.new(:find_by_foo!).found?.should be_true
|
18
|
+
end
|
19
|
+
|
20
|
+
should "be true for find_all_by" do
|
21
|
+
DynamicFinder.new(:find_all_by_foo).found?.should be_true
|
22
|
+
end
|
23
|
+
|
24
|
+
should "be true for find_or_initialize_by" do
|
25
|
+
DynamicFinder.new(:find_or_initialize_by_foo).found?.should be_true
|
26
|
+
end
|
27
|
+
|
28
|
+
should "be true for find_or_create_by" do
|
29
|
+
DynamicFinder.new(:find_or_create_by_foo).found?.should be_true
|
30
|
+
end
|
31
|
+
|
32
|
+
should "be false for anything else" do
|
33
|
+
[:foobar, :bazwick].each do |method|
|
34
|
+
DynamicFinder.new(method).found?.should be_false
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
context "find_all_by" do
|
40
|
+
should "parse one attribute" do
|
41
|
+
DynamicFinder.new(:find_all_by_foo).attributes.should == %w(foo)
|
42
|
+
end
|
43
|
+
|
44
|
+
should "parse multiple attributes" do
|
45
|
+
DynamicFinder.new(:find_all_by_foo_and_bar).attributes.should == %w(foo bar)
|
46
|
+
DynamicFinder.new(:find_all_by_foo_and_bar_and_baz).attributes.should == %w(foo bar baz)
|
47
|
+
end
|
48
|
+
|
49
|
+
should "set finder to :all" do
|
50
|
+
DynamicFinder.new(:find_all_by_foo_and_bar).finder.should == :all
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context "find_by" do
|
55
|
+
should "parse one attribute" do
|
56
|
+
DynamicFinder.new(:find_by_foo).attributes.should == %w(foo)
|
57
|
+
end
|
58
|
+
|
59
|
+
should "parse multiple attributes" do
|
60
|
+
DynamicFinder.new(:find_by_foo_and_bar).attributes.should == %w(foo bar)
|
61
|
+
end
|
62
|
+
|
63
|
+
should "set finder to :first" do
|
64
|
+
DynamicFinder.new(:find_by_foo).finder.should == :first
|
65
|
+
end
|
66
|
+
|
67
|
+
should "set bang to false" do
|
68
|
+
DynamicFinder.new(:find_by_foo).bang.should be_false
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
context "find_by with !" do
|
73
|
+
should "parse one attribute" do
|
74
|
+
DynamicFinder.new(:find_by_foo!).attributes.should == %w(foo)
|
75
|
+
end
|
76
|
+
|
77
|
+
should "parse multiple attributes" do
|
78
|
+
DynamicFinder.new(:find_by_foo_and_bar!).attributes.should == %w(foo bar)
|
79
|
+
end
|
80
|
+
|
81
|
+
should "set finder to :first" do
|
82
|
+
DynamicFinder.new(:find_by_foo!).finder.should == :first
|
83
|
+
end
|
84
|
+
|
85
|
+
should "set bang to true" do
|
86
|
+
DynamicFinder.new(:find_by_foo!).bang.should be_true
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
context "find_or_initialize_by" do
|
91
|
+
should "parse one attribute" do
|
92
|
+
DynamicFinder.new(:find_or_initialize_by_foo).attributes.should == %w(foo)
|
93
|
+
end
|
94
|
+
|
95
|
+
should "parse multiple attributes" do
|
96
|
+
DynamicFinder.new(:find_or_initialize_by_foo_and_bar).attributes.should == %w(foo bar)
|
97
|
+
end
|
98
|
+
|
99
|
+
should "set finder to :first" do
|
100
|
+
DynamicFinder.new(:find_or_initialize_by_foo).finder.should == :first
|
101
|
+
end
|
102
|
+
|
103
|
+
should "set instantiator to new" do
|
104
|
+
DynamicFinder.new(:find_or_initialize_by_foo).instantiator.should == :new
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
context "find_or_create_by" do
|
109
|
+
should "parse one attribute" do
|
110
|
+
DynamicFinder.new(:find_or_create_by_foo).attributes.should == %w(foo)
|
111
|
+
end
|
112
|
+
|
113
|
+
should "parse multiple attributes" do
|
114
|
+
DynamicFinder.new(:find_or_create_by_foo_and_bar).attributes.should == %w(foo bar)
|
115
|
+
end
|
116
|
+
|
117
|
+
should "set finder to :first" do
|
118
|
+
DynamicFinder.new(:find_or_create_by_foo).finder.should == :first
|
119
|
+
end
|
120
|
+
|
121
|
+
should "set instantiator to new" do
|
122
|
+
DynamicFinder.new(:find_or_create_by_foo).instantiator.should == :create
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,645 @@
|
|
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
|
+
module KeyOverride
|
19
|
+
def other_child
|
20
|
+
read_attribute(:other_child) || "special result"
|
21
|
+
end
|
22
|
+
|
23
|
+
def other_child=(value)
|
24
|
+
super(value + " modified")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class OtherChild < Parent
|
29
|
+
include MongoMapper::EmbeddedDocument
|
30
|
+
include KeyOverride
|
31
|
+
|
32
|
+
key :other_child, String
|
33
|
+
end
|
34
|
+
|
35
|
+
class EmbeddedDocumentTest < Test::Unit::TestCase
|
36
|
+
context "Including MongoMapper::EmbeddedDocument in a class" do
|
37
|
+
setup do
|
38
|
+
@klass = Class.new do
|
39
|
+
include MongoMapper::EmbeddedDocument
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
should "add _id key" do
|
44
|
+
@klass.keys['_id'].should_not be_nil
|
45
|
+
end
|
46
|
+
|
47
|
+
context "#to_mongo" do
|
48
|
+
should "be nil if nil" do
|
49
|
+
@klass.to_mongo(nil).should be_nil
|
50
|
+
end
|
51
|
+
|
52
|
+
should "convert to_mongo for other values" do
|
53
|
+
doc = @klass.new(:foo => 'bar')
|
54
|
+
to_mongo = @klass.to_mongo(doc)
|
55
|
+
to_mongo.is_a?(Hash).should be_true
|
56
|
+
to_mongo['foo'].should == 'bar'
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
context "#from_mongo" do
|
61
|
+
should "be nil if nil" do
|
62
|
+
@klass.from_mongo(nil).should be_nil
|
63
|
+
end
|
64
|
+
|
65
|
+
should "be instance if instance of class" do
|
66
|
+
doc = @klass.new
|
67
|
+
@klass.from_mongo(doc).should == doc
|
68
|
+
end
|
69
|
+
|
70
|
+
should "be instance if hash of attributes" do
|
71
|
+
doc = @klass.from_mongo({:foo => 'bar'})
|
72
|
+
doc.instance_of?(@klass).should be_true
|
73
|
+
doc.foo.should == 'bar'
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
context "parent_model" do
|
79
|
+
should "be nil if none of parents ancestors include EmbeddedDocument" do
|
80
|
+
parent = Class.new
|
81
|
+
document = Class.new(parent) do
|
82
|
+
include MongoMapper::EmbeddedDocument
|
83
|
+
end
|
84
|
+
document.parent_model.should be_nil
|
85
|
+
end
|
86
|
+
|
87
|
+
should "work when other modules have been included" do
|
88
|
+
grandparent = Class.new
|
89
|
+
parent = Class.new grandparent do
|
90
|
+
include MongoMapper::EmbeddedDocument
|
91
|
+
end
|
92
|
+
|
93
|
+
example_module = Module.new
|
94
|
+
document = Class.new(parent) do
|
95
|
+
include MongoMapper::EmbeddedDocument
|
96
|
+
include example_module
|
97
|
+
end
|
98
|
+
|
99
|
+
document.parent_model.should == parent
|
100
|
+
end
|
101
|
+
|
102
|
+
should "find parent" do
|
103
|
+
Parent.parent_model.should == Grandparent
|
104
|
+
Child.parent_model.should == Parent
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
context "defining a key" do
|
109
|
+
setup do
|
110
|
+
@document = Class.new do
|
111
|
+
include MongoMapper::EmbeddedDocument
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
should "work with name" do
|
116
|
+
key = @document.key(:name)
|
117
|
+
key.name.should == 'name'
|
118
|
+
end
|
119
|
+
|
120
|
+
should "work with name and type" do
|
121
|
+
key = @document.key(:name, String)
|
122
|
+
key.name.should == 'name'
|
123
|
+
key.type.should == String
|
124
|
+
end
|
125
|
+
|
126
|
+
should "work with name, type and options" do
|
127
|
+
key = @document.key(:name, String, :required => true)
|
128
|
+
key.name.should == 'name'
|
129
|
+
key.type.should == String
|
130
|
+
key.options[:required].should be_true
|
131
|
+
end
|
132
|
+
|
133
|
+
should "work with name and options" do
|
134
|
+
key = @document.key(:name, :required => true)
|
135
|
+
key.name.should == 'name'
|
136
|
+
key.options[:required].should be_true
|
137
|
+
end
|
138
|
+
|
139
|
+
should "be tracked per document" do
|
140
|
+
@document.key(:name, String)
|
141
|
+
@document.key(:age, Integer)
|
142
|
+
@document.keys['name'].name.should == 'name'
|
143
|
+
@document.keys['name'].type.should == String
|
144
|
+
@document.keys['age'].name.should == 'age'
|
145
|
+
@document.keys['age'].type.should == Integer
|
146
|
+
end
|
147
|
+
|
148
|
+
should "not be redefinable" do
|
149
|
+
@document.key(:foo, String)
|
150
|
+
@document.keys['foo'].type.should == String
|
151
|
+
@document.key(:foo, Integer)
|
152
|
+
@document.keys['foo'].type.should == String
|
153
|
+
end
|
154
|
+
|
155
|
+
should "create reader method" do
|
156
|
+
@document.new.should_not respond_to(:foo)
|
157
|
+
@document.key(:foo, String)
|
158
|
+
@document.new.should respond_to(:foo)
|
159
|
+
end
|
160
|
+
|
161
|
+
should "create reader before typecast method" do
|
162
|
+
@document.new.should_not respond_to(:foo_before_typecast)
|
163
|
+
@document.key(:foo, String)
|
164
|
+
@document.new.should respond_to(:foo_before_typecast)
|
165
|
+
end
|
166
|
+
|
167
|
+
should "create writer method" do
|
168
|
+
@document.new.should_not respond_to(:foo=)
|
169
|
+
@document.key(:foo, String)
|
170
|
+
@document.new.should respond_to(:foo=)
|
171
|
+
end
|
172
|
+
|
173
|
+
should "create boolean method" do
|
174
|
+
@document.new.should_not respond_to(:foo?)
|
175
|
+
@document.key(:foo, String)
|
176
|
+
@document.new.should respond_to(:foo?)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
context "keys" do
|
181
|
+
should "be inherited" do
|
182
|
+
Grandparent.keys.keys.sort.should == ['_id', 'grandparent']
|
183
|
+
Parent.keys.keys.sort.should == ['_id', 'grandparent', 'parent']
|
184
|
+
Child.keys.keys.sort.should == ['_id', 'child', 'grandparent', 'parent']
|
185
|
+
end
|
186
|
+
|
187
|
+
should "propogate to subclasses if key added after class definition" do
|
188
|
+
Grandparent.key :_type, String
|
189
|
+
|
190
|
+
Grandparent.keys.keys.sort.should == ['_id', '_type', 'grandparent']
|
191
|
+
Parent.keys.keys.sort.should == ['_id', '_type', 'grandparent', 'parent']
|
192
|
+
Child.keys.keys.sort.should == ['_id', '_type', 'child', 'grandparent', 'parent']
|
193
|
+
end
|
194
|
+
|
195
|
+
should "not add anonymous objects to the ancestor tree" do
|
196
|
+
OtherChild.ancestors.any? { |a| a.name.blank? }.should be_false
|
197
|
+
end
|
198
|
+
|
199
|
+
should "not include descendant keys" do
|
200
|
+
lambda { Parent.new.other_child }.should raise_error
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
context "#inspect" do
|
205
|
+
setup do
|
206
|
+
@document = Class.new do
|
207
|
+
include MongoMapper::Document
|
208
|
+
key :animals
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
should "call inspect on the document's attributes instead of to_s" do
|
213
|
+
doc = @document.new
|
214
|
+
doc.animals = %w(dog cat)
|
215
|
+
doc.inspect.should include(%(animals: ["dog", "cat"]))
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
context "subclasses" do
|
220
|
+
should "default to nil" do
|
221
|
+
Child.subclasses.should be_nil
|
222
|
+
end
|
223
|
+
|
224
|
+
should "be recorded" do
|
225
|
+
Grandparent.subclasses.should == [Parent]
|
226
|
+
Parent.subclasses.should == [Child, OtherChild]
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
context "Applying default values for keys" do
|
231
|
+
setup do
|
232
|
+
@document = Class.new do
|
233
|
+
include MongoMapper::EmbeddedDocument
|
234
|
+
|
235
|
+
key :name, String, :default => 'foo'
|
236
|
+
key :age, Integer, :default => 20
|
237
|
+
key :net_worth, Float, :default => 100.00
|
238
|
+
key :active, Boolean, :default => true
|
239
|
+
key :smart, Boolean, :default => false
|
240
|
+
key :skills, Array, :default => [1]
|
241
|
+
key :options, Hash, :default => {'foo' => 'bar'}
|
242
|
+
end
|
243
|
+
|
244
|
+
@doc = @document.new
|
245
|
+
end
|
246
|
+
|
247
|
+
should "work for strings" do
|
248
|
+
@doc.name.should == 'foo'
|
249
|
+
end
|
250
|
+
|
251
|
+
should "work for integers" do
|
252
|
+
@doc.age.should == 20
|
253
|
+
end
|
254
|
+
|
255
|
+
should "work for floats" do
|
256
|
+
@doc.net_worth.should == 100.00
|
257
|
+
end
|
258
|
+
|
259
|
+
should "work for booleans" do
|
260
|
+
@doc.active.should == true
|
261
|
+
@doc.smart.should == false
|
262
|
+
end
|
263
|
+
|
264
|
+
should "work for arrays" do
|
265
|
+
@doc.skills.should == [1]
|
266
|
+
@doc.skills << 2
|
267
|
+
@doc.skills.should == [1, 2]
|
268
|
+
end
|
269
|
+
|
270
|
+
should "work for hashes" do
|
271
|
+
@doc.options['foo'].should == 'bar'
|
272
|
+
@doc.options['baz'] = 'wick'
|
273
|
+
@doc.options['baz'].should == 'wick'
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
context "An instance of an embedded document" do
|
278
|
+
setup do
|
279
|
+
@document = Class.new do
|
280
|
+
include MongoMapper::EmbeddedDocument
|
281
|
+
|
282
|
+
key :name, String
|
283
|
+
key :age, Integer
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
should "automatically have an _id key" do
|
288
|
+
@document.keys.keys.should include('_id')
|
289
|
+
end
|
290
|
+
|
291
|
+
should "have id method that sets _id" do
|
292
|
+
doc = @document.new
|
293
|
+
doc.id.should == doc._id.to_s
|
294
|
+
end
|
295
|
+
|
296
|
+
should "have a nil _root_document" do
|
297
|
+
@document.new._root_document.should be_nil
|
298
|
+
end
|
299
|
+
|
300
|
+
context "setting custom id" do
|
301
|
+
should "set _id" do
|
302
|
+
doc = @document.new(:id => '1234')
|
303
|
+
doc._id.should == '1234'
|
304
|
+
end
|
305
|
+
|
306
|
+
should "know that custom id is set" do
|
307
|
+
doc = @document.new
|
308
|
+
doc.using_custom_id?.should be_false
|
309
|
+
doc.id = '1234'
|
310
|
+
doc.using_custom_id?.should be_true
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
context "being initialized" do
|
315
|
+
should "accept a hash that sets keys and values" do
|
316
|
+
doc = @document.new(:name => 'John', :age => 23)
|
317
|
+
doc.attributes.keys.sort.should == ['_id', 'age', 'name']
|
318
|
+
doc.attributes['name'].should == 'John'
|
319
|
+
doc.attributes['age'].should == 23
|
320
|
+
end
|
321
|
+
|
322
|
+
should "be able to assign keys dynamically" do
|
323
|
+
doc = @document.new(:name => 'John', :skills => ['ruby', 'rails'])
|
324
|
+
doc.name.should == 'John'
|
325
|
+
doc.skills.should == ['ruby', 'rails']
|
326
|
+
end
|
327
|
+
|
328
|
+
should "set the root on embedded documents" do
|
329
|
+
document = Class.new(@document) do
|
330
|
+
many :children
|
331
|
+
end
|
332
|
+
|
333
|
+
doc = document.new :_root_document => 'document', 'children' => [{}]
|
334
|
+
doc.children.first._root_document.should == 'document'
|
335
|
+
end
|
336
|
+
|
337
|
+
should "not throw error if initialized with nil" do
|
338
|
+
lambda {
|
339
|
+
@document.new(nil)
|
340
|
+
}.should_not raise_error
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
344
|
+
context "initialized when _type key present" do
|
345
|
+
setup do
|
346
|
+
::FooBar = Class.new do
|
347
|
+
include MongoMapper::EmbeddedDocument
|
348
|
+
key :_type, String
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
teardown do
|
353
|
+
Object.send(:remove_const, :FooBar)
|
354
|
+
end
|
355
|
+
|
356
|
+
should "set _type to class name" do
|
357
|
+
FooBar.new._type.should == 'FooBar'
|
358
|
+
end
|
359
|
+
|
360
|
+
should "not change _type if already set" do
|
361
|
+
FooBar.new(:_type => 'Foo')._type.should == 'Foo'
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
365
|
+
context "mass assigning keys" do
|
366
|
+
should "update values for keys provided" do
|
367
|
+
doc = @document.new(:name => 'foobar', :age => 10)
|
368
|
+
doc.attributes = {:name => 'new value', :age => 5}
|
369
|
+
doc.attributes[:name].should == 'new value'
|
370
|
+
doc.attributes[:age].should == 5
|
371
|
+
end
|
372
|
+
|
373
|
+
should "not update values for keys that were not provided" do
|
374
|
+
doc = @document.new(:name => 'foobar', :age => 10)
|
375
|
+
doc.attributes = {:name => 'new value'}
|
376
|
+
doc.attributes[:name].should == 'new value'
|
377
|
+
doc.attributes[:age].should == 10
|
378
|
+
end
|
379
|
+
|
380
|
+
should "not ignore keys that have methods defined" do
|
381
|
+
@document.class_eval do
|
382
|
+
attr_writer :password
|
383
|
+
|
384
|
+
def passwd
|
385
|
+
@password
|
386
|
+
end
|
387
|
+
end
|
388
|
+
|
389
|
+
doc = @document.new(:name => 'foobar', :password => 'secret')
|
390
|
+
doc.passwd.should == 'secret'
|
391
|
+
end
|
392
|
+
|
393
|
+
should "typecast key values" do
|
394
|
+
doc = @document.new(:name => 1234, :age => '21')
|
395
|
+
doc.name.should == '1234'
|
396
|
+
doc.age.should == 21
|
397
|
+
end
|
398
|
+
end
|
399
|
+
|
400
|
+
context "attributes" do
|
401
|
+
should "default to hash with all keys" do
|
402
|
+
doc = @document.new
|
403
|
+
doc.attributes.keys.sort.should == ['_id', 'age', 'name']
|
404
|
+
end
|
405
|
+
|
406
|
+
should "return all keys with values" do
|
407
|
+
doc = @document.new(:name => 'string', :age => nil)
|
408
|
+
doc.attributes.keys.sort.should == ['_id', 'age', 'name']
|
409
|
+
doc.attributes.values.should include('string')
|
410
|
+
doc.attributes.values.should include(nil)
|
411
|
+
end
|
412
|
+
end
|
413
|
+
|
414
|
+
context "to_mongo" do
|
415
|
+
should "default to hash with _id key" do
|
416
|
+
doc = @document.new
|
417
|
+
doc.to_mongo.keys.should == ['_id']
|
418
|
+
end
|
419
|
+
|
420
|
+
should "return all keys with non nil values" do
|
421
|
+
doc = @document.new(:name => 'string', :age => nil)
|
422
|
+
doc.to_mongo.keys.sort.should == ['_id', 'name']
|
423
|
+
doc.to_mongo.values.should include('string')
|
424
|
+
doc.to_mongo.values.should_not include(nil)
|
425
|
+
end
|
426
|
+
end
|
427
|
+
|
428
|
+
should "convert dates into times" do
|
429
|
+
document = Class.new(@document) do
|
430
|
+
key :start_date, Date
|
431
|
+
end
|
432
|
+
doc = document.new :start_date => "12/05/2009"
|
433
|
+
doc.start_date.should == Date.new(2009, 12, 05)
|
434
|
+
end
|
435
|
+
|
436
|
+
context "clone" do
|
437
|
+
should "regenerate the id" do
|
438
|
+
doc = @document.new(:name => "foo", :age => 27)
|
439
|
+
doc_id = doc.id
|
440
|
+
clone = doc.clone
|
441
|
+
clone_id = clone.id
|
442
|
+
clone_id.should_not == doc_id
|
443
|
+
end
|
444
|
+
|
445
|
+
should "copy the attributes" do
|
446
|
+
doc = @document.new(:name => "foo", :age => 27)
|
447
|
+
clone = doc.clone
|
448
|
+
clone.name.should == "foo"
|
449
|
+
clone.age.should == 27
|
450
|
+
end
|
451
|
+
end
|
452
|
+
|
453
|
+
context "key shorcut access" do
|
454
|
+
should "be able to read key with []" do
|
455
|
+
doc = @document.new(:name => 'string')
|
456
|
+
doc[:name].should == 'string'
|
457
|
+
end
|
458
|
+
|
459
|
+
context "[]=" do
|
460
|
+
should "write key value for existing key" do
|
461
|
+
doc = @document.new
|
462
|
+
doc[:name] = 'string'
|
463
|
+
doc[:name].should == 'string'
|
464
|
+
end
|
465
|
+
|
466
|
+
should "create key and write value for missing key" do
|
467
|
+
doc = @document.new
|
468
|
+
doc[:foo] = 'string'
|
469
|
+
@document.keys.keys.include?('foo').should be_true
|
470
|
+
doc[:foo].should == 'string'
|
471
|
+
end
|
472
|
+
end
|
473
|
+
end
|
474
|
+
|
475
|
+
context "indifferent access" do
|
476
|
+
should "be enabled for keys" do
|
477
|
+
doc = @document.new(:name => 'string')
|
478
|
+
doc.attributes[:name].should == 'string'
|
479
|
+
doc.attributes['name'].should == 'string'
|
480
|
+
end
|
481
|
+
end
|
482
|
+
|
483
|
+
context "reading an attribute" do
|
484
|
+
should "work for defined keys" do
|
485
|
+
doc = @document.new(:name => 'string')
|
486
|
+
doc.name.should == 'string'
|
487
|
+
end
|
488
|
+
|
489
|
+
should "raise no method error for undefined keys" do
|
490
|
+
doc = @document.new
|
491
|
+
lambda { doc.fart }.should raise_error(NoMethodError)
|
492
|
+
end
|
493
|
+
|
494
|
+
should "be accessible for use in the model" do
|
495
|
+
@document.class_eval do
|
496
|
+
def name_and_age
|
497
|
+
"#{read_attribute(:name)} (#{read_attribute(:age)})"
|
498
|
+
end
|
499
|
+
end
|
500
|
+
|
501
|
+
doc = @document.new(:name => 'John', :age => 27)
|
502
|
+
doc.name_and_age.should == 'John (27)'
|
503
|
+
end
|
504
|
+
|
505
|
+
should "set instance variable" do
|
506
|
+
@document.key :foo, Array
|
507
|
+
doc = @document.new
|
508
|
+
doc.instance_variable_get("@foo").should be_nil
|
509
|
+
doc.foo
|
510
|
+
doc.instance_variable_get("@foo").should == []
|
511
|
+
end
|
512
|
+
|
513
|
+
should "not set instance variable if frozen" do
|
514
|
+
@document.key :foo, Array
|
515
|
+
doc = @document.new
|
516
|
+
doc.instance_variable_get("@foo").should be_nil
|
517
|
+
doc.freeze
|
518
|
+
doc.foo
|
519
|
+
doc.instance_variable_get("@foo").should be_nil
|
520
|
+
end
|
521
|
+
|
522
|
+
should "be overrideable by modules" do
|
523
|
+
@document = Class.new do
|
524
|
+
include MongoMapper::Document
|
525
|
+
key :other_child, String
|
526
|
+
end
|
527
|
+
|
528
|
+
child = @document.new
|
529
|
+
child.other_child.should be_nil
|
530
|
+
|
531
|
+
@document.send :include, KeyOverride
|
532
|
+
|
533
|
+
overriden_child = @document.new
|
534
|
+
overriden_child.other_child.should == 'special result'
|
535
|
+
end
|
536
|
+
end
|
537
|
+
|
538
|
+
context "reading an attribute before typcasting" do
|
539
|
+
should "work for defined keys" do
|
540
|
+
doc = @document.new(:name => 12)
|
541
|
+
doc.name_before_typecast.should == 12
|
542
|
+
end
|
543
|
+
|
544
|
+
should "raise no method error for undefined keys" do
|
545
|
+
doc = @document.new
|
546
|
+
lambda { doc.foo_before_typecast }.should raise_error(NoMethodError)
|
547
|
+
end
|
548
|
+
|
549
|
+
should "be accessible for use in a document" do
|
550
|
+
@document.class_eval do
|
551
|
+
def untypcasted_name
|
552
|
+
read_attribute_before_typecast(:name)
|
553
|
+
end
|
554
|
+
end
|
555
|
+
|
556
|
+
doc = @document.new(:name => 12)
|
557
|
+
doc.name.should == '12'
|
558
|
+
doc.untypcasted_name.should == 12
|
559
|
+
end
|
560
|
+
end
|
561
|
+
|
562
|
+
context "writing an attribute" do
|
563
|
+
should "work for defined keys" do
|
564
|
+
doc = @document.new
|
565
|
+
doc.name = 'John'
|
566
|
+
doc.name.should == 'John'
|
567
|
+
end
|
568
|
+
|
569
|
+
should "raise no method error for undefined keys" do
|
570
|
+
doc = @document.new
|
571
|
+
lambda { doc.fart = 'poof!' }.should raise_error(NoMethodError)
|
572
|
+
end
|
573
|
+
|
574
|
+
should "typecast value" do
|
575
|
+
doc = @document.new
|
576
|
+
doc.name = 1234
|
577
|
+
doc.name.should == '1234'
|
578
|
+
doc.age = '21'
|
579
|
+
doc.age.should == 21
|
580
|
+
end
|
581
|
+
|
582
|
+
should "be accessible for use in the model" do
|
583
|
+
@document.class_eval do
|
584
|
+
def name_and_age=(new_value)
|
585
|
+
new_value.match(/([^\(\s]+) \((.*)\)/)
|
586
|
+
write_attribute :name, $1
|
587
|
+
write_attribute :age, $2
|
588
|
+
end
|
589
|
+
end
|
590
|
+
|
591
|
+
doc = @document.new
|
592
|
+
doc.name_and_age = 'Frank (62)'
|
593
|
+
doc.name.should == 'Frank'
|
594
|
+
doc.age.should == 62
|
595
|
+
end
|
596
|
+
|
597
|
+
should "be overrideable by modules" do
|
598
|
+
@document = Class.new do
|
599
|
+
include MongoMapper::Document
|
600
|
+
key :other_child, String
|
601
|
+
end
|
602
|
+
|
603
|
+
child = @document.new(:other_child => 'foo')
|
604
|
+
child.other_child.should == 'foo'
|
605
|
+
|
606
|
+
@document.send :include, KeyOverride
|
607
|
+
|
608
|
+
overriden_child = @document.new(:other_child => 'foo')
|
609
|
+
overriden_child.other_child.should == 'foo modified'
|
610
|
+
end
|
611
|
+
end # writing an attribute
|
612
|
+
|
613
|
+
context "checking if an attributes value is present" do
|
614
|
+
should "work for defined keys" do
|
615
|
+
doc = @document.new
|
616
|
+
doc.name?.should be_false
|
617
|
+
doc.name = 'John'
|
618
|
+
doc.name?.should be_true
|
619
|
+
end
|
620
|
+
|
621
|
+
should "raise no method error for undefined keys" do
|
622
|
+
doc = @document.new
|
623
|
+
lambda { doc.fart? }.should raise_error(NoMethodError)
|
624
|
+
end
|
625
|
+
end
|
626
|
+
|
627
|
+
context "equality" do
|
628
|
+
should "be equal if id and class are the same" do
|
629
|
+
(@document.new('_id' => 1) == @document.new('_id' => 1)).should be(true)
|
630
|
+
end
|
631
|
+
|
632
|
+
should "not be equal if class same but id different" do
|
633
|
+
(@document.new('_id' => 1) == @document.new('_id' => 2)).should be(false)
|
634
|
+
end
|
635
|
+
|
636
|
+
should "not be equal if id same but class different" do
|
637
|
+
@another_document = Class.new do
|
638
|
+
include MongoMapper::Document
|
639
|
+
end
|
640
|
+
|
641
|
+
(@document.new('_id' => 1) == @another_document.new('_id' => 1)).should be(false)
|
642
|
+
end
|
643
|
+
end
|
644
|
+
end # instance of a embedded document
|
645
|
+
end
|