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.
Files changed (61) hide show
  1. data/.gitignore +7 -0
  2. data/History +51 -0
  3. data/LICENSE +20 -0
  4. data/README.rdoc +39 -0
  5. data/Rakefile +71 -0
  6. data/VERSION +1 -0
  7. data/bin/mmconsole +56 -0
  8. data/lib/mongomapper.rb +96 -0
  9. data/lib/mongomapper/associations.rb +61 -0
  10. data/lib/mongomapper/associations/base.rb +71 -0
  11. data/lib/mongomapper/associations/belongs_to_polymorphic_proxy.rb +32 -0
  12. data/lib/mongomapper/associations/belongs_to_proxy.rb +22 -0
  13. data/lib/mongomapper/associations/many_documents_proxy.rb +85 -0
  14. data/lib/mongomapper/associations/many_embedded_polymorphic_proxy.rb +33 -0
  15. data/lib/mongomapper/associations/many_embedded_proxy.rb +17 -0
  16. data/lib/mongomapper/associations/many_polymorphic_proxy.rb +11 -0
  17. data/lib/mongomapper/associations/many_proxy.rb +6 -0
  18. data/lib/mongomapper/associations/proxy.rb +67 -0
  19. data/lib/mongomapper/callbacks.rb +106 -0
  20. data/lib/mongomapper/document.rb +278 -0
  21. data/lib/mongomapper/embedded_document.rb +237 -0
  22. data/lib/mongomapper/finder_options.rb +96 -0
  23. data/lib/mongomapper/key.rb +80 -0
  24. data/lib/mongomapper/observing.rb +50 -0
  25. data/lib/mongomapper/pagination.rb +52 -0
  26. data/lib/mongomapper/rails_compatibility/document.rb +15 -0
  27. data/lib/mongomapper/rails_compatibility/embedded_document.rb +25 -0
  28. data/lib/mongomapper/save_with_validation.rb +19 -0
  29. data/lib/mongomapper/serialization.rb +55 -0
  30. data/lib/mongomapper/serializers/json_serializer.rb +79 -0
  31. data/lib/mongomapper/validations.rb +47 -0
  32. data/mongomapper.gemspec +139 -0
  33. data/test/NOTE_ON_TESTING +1 -0
  34. data/test/functional/associations/test_belongs_to_polymorphic_proxy.rb +39 -0
  35. data/test/functional/associations/test_belongs_to_proxy.rb +35 -0
  36. data/test/functional/associations/test_many_embedded_polymorphic_proxy.rb +131 -0
  37. data/test/functional/associations/test_many_embedded_proxy.rb +106 -0
  38. data/test/functional/associations/test_many_polymorphic_proxy.rb +267 -0
  39. data/test/functional/associations/test_many_proxy.rb +236 -0
  40. data/test/functional/test_associations.rb +40 -0
  41. data/test/functional/test_callbacks.rb +85 -0
  42. data/test/functional/test_document.rb +691 -0
  43. data/test/functional/test_pagination.rb +81 -0
  44. data/test/functional/test_rails_compatibility.rb +31 -0
  45. data/test/functional/test_validations.rb +172 -0
  46. data/test/models.rb +108 -0
  47. data/test/test_helper.rb +67 -0
  48. data/test/unit/serializers/test_json_serializer.rb +103 -0
  49. data/test/unit/test_association_base.rb +136 -0
  50. data/test/unit/test_document.rb +125 -0
  51. data/test/unit/test_embedded_document.rb +370 -0
  52. data/test/unit/test_finder_options.rb +214 -0
  53. data/test/unit/test_key.rb +217 -0
  54. data/test/unit/test_mongo_id.rb +35 -0
  55. data/test/unit/test_mongomapper.rb +28 -0
  56. data/test/unit/test_observing.rb +101 -0
  57. data/test/unit/test_pagination.rb +113 -0
  58. data/test/unit/test_rails_compatibility.rb +34 -0
  59. data/test/unit/test_serializations.rb +52 -0
  60. data/test/unit/test_validations.rb +259 -0
  61. 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