drogus-mongo_mapper 0.6.10

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