tpitale-mongo_mapper 0.6.9

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 (75) hide show
  1. data/.gitignore +10 -0
  2. data/LICENSE +20 -0
  3. data/README.rdoc +53 -0
  4. data/Rakefile +55 -0
  5. data/VERSION +1 -0
  6. data/bin/mmconsole +60 -0
  7. data/lib/mongo_mapper/associations/base.rb +110 -0
  8. data/lib/mongo_mapper/associations/belongs_to_polymorphic_proxy.rb +26 -0
  9. data/lib/mongo_mapper/associations/belongs_to_proxy.rb +21 -0
  10. data/lib/mongo_mapper/associations/collection.rb +19 -0
  11. data/lib/mongo_mapper/associations/many_documents_as_proxy.rb +26 -0
  12. data/lib/mongo_mapper/associations/many_documents_proxy.rb +115 -0
  13. data/lib/mongo_mapper/associations/many_embedded_polymorphic_proxy.rb +31 -0
  14. data/lib/mongo_mapper/associations/many_embedded_proxy.rb +54 -0
  15. data/lib/mongo_mapper/associations/many_polymorphic_proxy.rb +11 -0
  16. data/lib/mongo_mapper/associations/proxy.rb +113 -0
  17. data/lib/mongo_mapper/associations.rb +70 -0
  18. data/lib/mongo_mapper/callbacks.rb +109 -0
  19. data/lib/mongo_mapper/dirty.rb +136 -0
  20. data/lib/mongo_mapper/document.rb +472 -0
  21. data/lib/mongo_mapper/dynamic_finder.rb +74 -0
  22. data/lib/mongo_mapper/embedded_document.rb +384 -0
  23. data/lib/mongo_mapper/finder_options.rb +133 -0
  24. data/lib/mongo_mapper/key.rb +36 -0
  25. data/lib/mongo_mapper/observing.rb +50 -0
  26. data/lib/mongo_mapper/pagination.rb +55 -0
  27. data/lib/mongo_mapper/rails_compatibility/document.rb +15 -0
  28. data/lib/mongo_mapper/rails_compatibility/embedded_document.rb +27 -0
  29. data/lib/mongo_mapper/serialization.rb +54 -0
  30. data/lib/mongo_mapper/serializers/json_serializer.rb +92 -0
  31. data/lib/mongo_mapper/support.rb +206 -0
  32. data/lib/mongo_mapper/validations.rb +41 -0
  33. data/lib/mongo_mapper.rb +120 -0
  34. data/mongo_mapper.gemspec +173 -0
  35. data/specs.watchr +32 -0
  36. data/test/NOTE_ON_TESTING +1 -0
  37. data/test/functional/associations/test_belongs_to_polymorphic_proxy.rb +55 -0
  38. data/test/functional/associations/test_belongs_to_proxy.rb +48 -0
  39. data/test/functional/associations/test_many_documents_as_proxy.rb +246 -0
  40. data/test/functional/associations/test_many_documents_proxy.rb +387 -0
  41. data/test/functional/associations/test_many_embedded_polymorphic_proxy.rb +156 -0
  42. data/test/functional/associations/test_many_embedded_proxy.rb +192 -0
  43. data/test/functional/associations/test_many_polymorphic_proxy.rb +339 -0
  44. data/test/functional/test_associations.rb +44 -0
  45. data/test/functional/test_binary.rb +18 -0
  46. data/test/functional/test_callbacks.rb +85 -0
  47. data/test/functional/test_dirty.rb +159 -0
  48. data/test/functional/test_document.rb +1235 -0
  49. data/test/functional/test_embedded_document.rb +135 -0
  50. data/test/functional/test_logger.rb +20 -0
  51. data/test/functional/test_pagination.rb +95 -0
  52. data/test/functional/test_rails_compatibility.rb +25 -0
  53. data/test/functional/test_string_id_compatibility.rb +72 -0
  54. data/test/functional/test_validations.rb +378 -0
  55. data/test/models.rb +271 -0
  56. data/test/support/custom_matchers.rb +55 -0
  57. data/test/support/timing.rb +16 -0
  58. data/test/test_helper.rb +27 -0
  59. data/test/unit/associations/test_base.rb +166 -0
  60. data/test/unit/associations/test_proxy.rb +91 -0
  61. data/test/unit/serializers/test_json_serializer.rb +189 -0
  62. data/test/unit/test_document.rb +204 -0
  63. data/test/unit/test_dynamic_finder.rb +125 -0
  64. data/test/unit/test_embedded_document.rb +718 -0
  65. data/test/unit/test_finder_options.rb +296 -0
  66. data/test/unit/test_key.rb +172 -0
  67. data/test/unit/test_mongo_mapper.rb +65 -0
  68. data/test/unit/test_observing.rb +101 -0
  69. data/test/unit/test_pagination.rb +113 -0
  70. data/test/unit/test_rails_compatibility.rb +49 -0
  71. data/test/unit/test_serializations.rb +52 -0
  72. data/test/unit/test_support.rb +342 -0
  73. data/test/unit/test_time_zones.rb +40 -0
  74. data/test/unit/test_validations.rb +503 -0
  75. metadata +235 -0
@@ -0,0 +1,296 @@
1
+ require 'test_helper'
2
+ require 'models'
3
+
4
+ class FinderOptionsTest < Test::Unit::TestCase
5
+ include MongoMapper
6
+
7
+ should "raise error if provided something other than a hash" do
8
+ lambda { FinderOptions.new(Room) }.should raise_error(ArgumentError)
9
+ lambda { FinderOptions.new(Room, 1) }.should raise_error(ArgumentError)
10
+ end
11
+
12
+ should "symbolize the keys of the hash provided" do
13
+ FinderOptions.new(Room, 'offset' => 1).options.keys.map do |key|
14
+ key.should be_instance_of(Symbol)
15
+ end
16
+ end
17
+
18
+ context "Converting conditions to criteria" do
19
+ should "not add _type to query if model does not have superclass that is single collection inherited" do
20
+ FinderOptions.new(Message, :foo => 'bar').criteria.should == {
21
+ :foo => 'bar'
22
+ }
23
+ end
24
+
25
+ should "not add _type to nested conditions" do
26
+ FinderOptions.new(Enter, :foo => 'bar', :age => {'$gt' => 21}).criteria.should == {
27
+ :foo => 'bar',
28
+ :age => {'$gt' => 21},
29
+ :_type => 'Enter'
30
+ }
31
+ end
32
+
33
+ %w{gt lt gte lte ne in nin mod size where exists}.each do |operator|
34
+ should "convert #{operator} conditions" do
35
+ FinderOptions.new(Room, :age.send(operator) => 21).criteria.should == {
36
+ :age => {"$#{operator}" => 21}
37
+ }
38
+ end
39
+ end
40
+
41
+ should "automatically add _type to query if model is single collection inherited" do
42
+ FinderOptions.new(Enter, :foo => 'bar').criteria.should == {
43
+ :foo => 'bar',
44
+ :_type => 'Enter'
45
+ }
46
+ end
47
+
48
+ should "work with simple criteria" do
49
+ FinderOptions.new(Room, :foo => 'bar').criteria.should == {
50
+ :foo => 'bar'
51
+ }
52
+
53
+ FinderOptions.new(Room, :foo => 'bar', :baz => 'wick').criteria.should == {
54
+ :foo => 'bar',
55
+ :baz => 'wick'
56
+ }
57
+ end
58
+
59
+ should "convert id to _id" do
60
+ id = Mongo::ObjectID.new
61
+ FinderOptions.new(Room, :id => id).criteria.should == {:_id => id}
62
+ end
63
+
64
+ should "make sure that _id's are object ids" do
65
+ id = Mongo::ObjectID.new
66
+ FinderOptions.new(Room, :_id => id.to_s).criteria.should == {:_id => id}
67
+ end
68
+
69
+ should "work fine with _id's that are object ids" do
70
+ id = Mongo::ObjectID.new
71
+ FinderOptions.new(Room, :_id => id).criteria.should == {:_id => id}
72
+ end
73
+
74
+ should "make sure other object id typed keys get converted" do
75
+ id = Mongo::ObjectID.new
76
+ FinderOptions.new(Message, :room_id => id.to_s).criteria.should == {:room_id => id}
77
+ end
78
+
79
+ should "work fine with object ids for object id typed keys" do
80
+ id = Mongo::ObjectID.new
81
+ FinderOptions.new(Message, :room_id => id).criteria.should == {:room_id => id}
82
+ end
83
+
84
+ should "use $in for arrays" do
85
+ FinderOptions.new(Room, :foo => [1,2,3]).criteria.should == {
86
+ :foo => {'$in' => [1,2,3]}
87
+ }
88
+ end
89
+
90
+ should "not use $in for arrays if already using array operator" do
91
+ FinderOptions.new(Room, :foo => {'$all' => [1,2,3]}).criteria.should == {
92
+ :foo => {'$all' => [1,2,3]}
93
+ }
94
+
95
+ FinderOptions.new(Room, :foo => {'$any' => [1,2,3]}).criteria.should == {
96
+ :foo => {'$any' => [1,2,3]}
97
+ }
98
+ end
99
+
100
+ should "work arbitrarily deep" do
101
+ FinderOptions.new(Room, :foo => {:bar => [1,2,3]}).criteria.should == {
102
+ :foo => {:bar => {'$in' => [1,2,3]}}
103
+ }
104
+
105
+ FinderOptions.new(Room, :foo => {:bar => {'$any' => [1,2,3]}}).criteria.should == {
106
+ :foo => {:bar => {'$any' => [1,2,3]}}
107
+ }
108
+ end
109
+ end
110
+
111
+ context "ordering" do
112
+ should "single field with ascending direction" do
113
+ sort = [['foo', 1]]
114
+ FinderOptions.new(Room, :order => 'foo asc').options[:sort].should == sort
115
+ FinderOptions.new(Room, :order => 'foo ASC').options[:sort].should == sort
116
+ end
117
+
118
+ should "single field with descending direction" do
119
+ sort = [['foo', -1]]
120
+ FinderOptions.new(Room, :order => 'foo desc').options[:sort].should == sort
121
+ FinderOptions.new(Room, :order => 'foo DESC').options[:sort].should == sort
122
+ end
123
+
124
+ should "convert field without direction to ascending" do
125
+ sort = [['foo', 1]]
126
+ FinderOptions.new(Room, :order => 'foo').options[:sort].should == sort
127
+ end
128
+
129
+ should "convert multiple fields with directions" do
130
+ sort = [['foo', -1], ['bar', 1], ['baz', -1]]
131
+ FinderOptions.new(Room, :order => 'foo desc, bar asc, baz desc').options[:sort].should == sort
132
+ end
133
+
134
+ should "convert multiple fields with some missing directions" do
135
+ sort = [['foo', -1], ['bar', 1], ['baz', 1]]
136
+ FinderOptions.new(Room, :order => 'foo desc, bar, baz').options[:sort].should == sort
137
+ end
138
+
139
+ should "just use sort if sort and order are present" do
140
+ sort = [['$natural', 1]]
141
+ FinderOptions.new(Room, :sort => sort, :order => 'foo asc').options[:sort].should == sort
142
+ end
143
+
144
+ should "convert natural in order to proper" do
145
+ sort = [['$natural', 1]]
146
+ FinderOptions.new(Room, :order => '$natural asc').options[:sort].should == sort
147
+ sort = [['$natural', -1]]
148
+ FinderOptions.new(Room, :order => '$natural desc').options[:sort].should == sort
149
+ end
150
+
151
+ should "work for natural order ascending" do
152
+ FinderOptions.new(Room, :sort => {'$natural' => 1}).options[:sort]['$natural'].should == 1
153
+ end
154
+
155
+ should "work for natural order descending" do
156
+ FinderOptions.new(Room, :sort => {'$natural' => -1}).options[:sort]['$natural'].should == -1
157
+ end
158
+ end
159
+
160
+ context "skip" do
161
+ should "default to 0" do
162
+ FinderOptions.new(Room, {}).options[:skip].should == 0
163
+ end
164
+
165
+ should "use skip provided" do
166
+ FinderOptions.new(Room, :skip => 2).options[:skip].should == 2
167
+ end
168
+
169
+ should "covert string to integer" do
170
+ FinderOptions.new(Room, :skip => '2').options[:skip].should == 2
171
+ end
172
+
173
+ should "convert offset to skip" do
174
+ FinderOptions.new(Room, :offset => 1).options[:skip].should == 1
175
+ end
176
+ end
177
+
178
+ context "limit" do
179
+ should "default to 0" do
180
+ FinderOptions.new(Room, {}).options[:limit].should == 0
181
+ end
182
+
183
+ should "use limit provided" do
184
+ FinderOptions.new(Room, :limit => 2).options[:limit].should == 2
185
+ end
186
+
187
+ should "covert string to integer" do
188
+ FinderOptions.new(Room, :limit => '2').options[:limit].should == 2
189
+ end
190
+ end
191
+
192
+ context "fields" do
193
+ should "default to nil" do
194
+ FinderOptions.new(Room, {}).options[:fields].should be(nil)
195
+ end
196
+
197
+ should "be converted to nil if empty string" do
198
+ FinderOptions.new(Room, :fields => '').options[:fields].should be(nil)
199
+ end
200
+
201
+ should "be converted to nil if []" do
202
+ FinderOptions.new(Room, :fields => []).options[:fields].should be(nil)
203
+ end
204
+
205
+ should "should work with array" do
206
+ FinderOptions.new(Room, {:fields => %w(a b)}).options[:fields].should == %w(a b)
207
+ end
208
+
209
+ should "convert comma separated list to array" do
210
+ FinderOptions.new(Room, {:fields => 'a, b'}).options[:fields].should == %w(a b)
211
+ end
212
+
213
+ should "also work as select" do
214
+ FinderOptions.new(Room, :select => %w(a b)).options[:fields].should == %w(a b)
215
+ end
216
+ end
217
+
218
+ context "Condition auto-detection" do
219
+ should "know :conditions are criteria" do
220
+ finder = FinderOptions.new(Room, :conditions => {:foo => 'bar'})
221
+ finder.criteria.should == {:foo => 'bar'}
222
+ finder.options.keys.should_not include(:conditions)
223
+ end
224
+
225
+ should "know fields is an option" do
226
+ finder = FinderOptions.new(Room, :fields => ['foo'])
227
+ finder.options[:fields].should == ['foo']
228
+ finder.criteria.keys.should_not include(:fields)
229
+ end
230
+
231
+ # select gets converted to fields so just checking keys
232
+ should "know select is an option" do
233
+ finder = FinderOptions.new(Room, :select => 'foo')
234
+ finder.options.keys.should include(:sort)
235
+ finder.criteria.keys.should_not include(:select)
236
+ finder.criteria.keys.should_not include(:fields)
237
+ end
238
+
239
+ should "know skip is an option" do
240
+ finder = FinderOptions.new(Room, :skip => 10)
241
+ finder.options[:skip].should == 10
242
+ finder.criteria.keys.should_not include(:skip)
243
+ end
244
+
245
+ # offset gets converted to skip so just checking keys
246
+ should "know offset is an option" do
247
+ finder = FinderOptions.new(Room, :offset => 10)
248
+ finder.options.keys.should include(:skip)
249
+ finder.criteria.keys.should_not include(:skip)
250
+ finder.criteria.keys.should_not include(:offset)
251
+ end
252
+
253
+ should "know limit is an option" do
254
+ finder = FinderOptions.new(Room, :limit => 10)
255
+ finder.options[:limit].should == 10
256
+ finder.criteria.keys.should_not include(:limit)
257
+ end
258
+
259
+ should "know sort is an option" do
260
+ finder = FinderOptions.new(Room, :sort => [['foo', 1]])
261
+ finder.options[:sort].should == [['foo', 1]]
262
+ finder.criteria.keys.should_not include(:sort)
263
+ end
264
+
265
+ # order gets converted to sort so just checking keys
266
+ should "know order is an option" do
267
+ finder = FinderOptions.new(Room, :order => 'foo')
268
+ finder.options.keys.should include(:sort)
269
+ finder.criteria.keys.should_not include(:sort)
270
+ end
271
+
272
+ should "work with full range of things" do
273
+ finder_options = FinderOptions.new(Room, {
274
+ :foo => 'bar',
275
+ :baz => true,
276
+ :sort => [['foo', 1]],
277
+ :fields => ['foo', 'baz'],
278
+ :limit => 10,
279
+ :skip => 10,
280
+ })
281
+
282
+ finder_options.criteria.should == {
283
+ :foo => 'bar',
284
+ :baz => true,
285
+ }
286
+
287
+ finder_options.options.should == {
288
+ :sort => [['foo', 1]],
289
+ :fields => ['foo', 'baz'],
290
+ :limit => 10,
291
+ :skip => 10,
292
+ }
293
+ end
294
+ end
295
+
296
+ end # FinderOptionsTest
@@ -0,0 +1,172 @@
1
+ require 'test_helper'
2
+
3
+ class Address
4
+ include MongoMapper::EmbeddedDocument
5
+
6
+ key :address, String
7
+ key :city, String
8
+ key :state, String
9
+ key :zip, Integer
10
+ end
11
+
12
+ class FooType < Struct.new(:bar)
13
+ def self.to_mongo(value)
14
+ 'to_mongo'
15
+ end
16
+
17
+ def self.from_mongo(value)
18
+ 'from_mongo'
19
+ end
20
+ end
21
+
22
+ class KeyTest < Test::Unit::TestCase
23
+ include MongoMapper
24
+
25
+ context "Initializing a new key" do
26
+ should "allow setting the name" do
27
+ Key.new(:foo, String).name.should == 'foo'
28
+ end
29
+
30
+ should "allow setting the type" do
31
+ Key.new(:foo, Integer).type.should be(Integer)
32
+ end
33
+
34
+ should "allow setting options" do
35
+ Key.new(:foo, Integer, :required => true).options[:required].should be(true)
36
+ end
37
+
38
+ should "default options to {}" do
39
+ Key.new(:foo, Integer, nil).options.should == {}
40
+ end
41
+
42
+ should "symbolize option keys" do
43
+ Key.new(:foo, Integer, 'required' => true).options[:required].should be(true)
44
+ end
45
+
46
+ should "work with just name" do
47
+ key = Key.new(:foo)
48
+ key.name.should == 'foo'
49
+ end
50
+
51
+ should "work with name and type" do
52
+ key = Key.new(:foo, String)
53
+ key.name.should == 'foo'
54
+ key.type.should == String
55
+ end
56
+
57
+ should "work with name, type, and options" do
58
+ key = Key.new(:foo, String, :required => true)
59
+ key.name.should == 'foo'
60
+ key.type.should == String
61
+ key.options[:required].should be_true
62
+ end
63
+
64
+ should "work with name and options" do
65
+ key = Key.new(:foo, :required => true)
66
+ key.name.should == 'foo'
67
+ key.options[:required].should be_true
68
+ end
69
+ end
70
+
71
+ context "A key" do
72
+ should "be equal to another key with same name and type" do
73
+ Key.new(:name, String).should == Key.new(:name, String)
74
+ end
75
+
76
+ should "not be equal to another key with different name" do
77
+ Key.new(:name, String).should_not == Key.new(:foo, String)
78
+ end
79
+
80
+ should "not be equal to another key with different type" do
81
+ Key.new(:name, String).should_not == Key.new(:name, Integer)
82
+ end
83
+
84
+ should "know if it is a embedded_document" do
85
+ klass = Class.new do
86
+ include MongoMapper::EmbeddedDocument
87
+ end
88
+ Key.new(:name, klass).embeddable?.should be_true
89
+ end
90
+
91
+ should "know if it is not a embedded_document" do
92
+ Key.new(:name, String).embeddable?.should be_false
93
+ end
94
+
95
+ should "know if it is a number" do
96
+ Key.new(:age, Integer).number?.should be_true
97
+ Key.new(:age, Float).number?.should be_true
98
+ end
99
+
100
+ should "know if it is not a number" do
101
+ Key.new(:age, String).number?.should be_false
102
+ end
103
+ end
104
+
105
+ context "setting a value with a custom type" do
106
+ should "correctly typecast" do
107
+ key = Key.new(:foo, FooType)
108
+ key.set("something").should == 'to_mongo'
109
+ end
110
+
111
+ should "correctly typecast if object of that type is given" do
112
+ key = Key.new(:foo, FooType)
113
+ key.set(FooType.new('something')).should == 'to_mongo'
114
+ end
115
+ end
116
+
117
+ context "getting a value with a custom type" do
118
+ should "use #from_mongo to convert back to custom type" do
119
+ key = Key.new(:foo, FooType)
120
+ key.get('something').should == 'from_mongo'
121
+ end
122
+ end
123
+
124
+ context "getting a value" do
125
+ should "work with a type" do
126
+ key = Key.new(:foo, String)
127
+ key.get('bar').should == 'bar'
128
+ end
129
+
130
+ should "work without type" do
131
+ key = Key.new(:foo)
132
+ key.get([1, '2']).should == [1, '2']
133
+ key.get(false).should == false
134
+ key.get({}).should == {}
135
+ end
136
+
137
+ context "for a embedded_document" do
138
+ should "default to nil" do
139
+ key = Key.new(:foo, Address)
140
+ key.get(nil).should be_nil
141
+ end
142
+
143
+ should "return instance if instance" do
144
+ address = Address.new(:city => 'South Bend', :state => 'IN', :zip => 46544)
145
+ key = Key.new(:foo, Address)
146
+ key.get(address).should == address
147
+ end
148
+ end
149
+ end
150
+
151
+ context "getting a value with a default set" do
152
+ setup do
153
+ @key = Key.new(:foo, String, :default => 'baz')
154
+ end
155
+
156
+ should "return default value if value nil" do
157
+ @key.get(nil).should == 'baz'
158
+ end
159
+
160
+ should "return value if not blank" do
161
+ @key.get('foobar').should == 'foobar'
162
+ end
163
+
164
+ should "work with Boolean type and false value" do
165
+ Key.new(:active, Boolean, :default => false).get(nil).should be_false
166
+ end
167
+
168
+ should "work with Boolean type and true value" do
169
+ Key.new(:active, Boolean, :default => true).get(nil).should be_true
170
+ end
171
+ end
172
+ end # KeyTest
@@ -0,0 +1,65 @@
1
+ require 'test_helper'
2
+
3
+ class Address; end
4
+
5
+ class MongoMapperTest < Test::Unit::TestCase
6
+ should "be able to write and read connection" do
7
+ conn = Mongo::Connection.new
8
+ MongoMapper.connection = conn
9
+ MongoMapper.connection.should == conn
10
+ end
11
+
12
+ should "default connection to new mongo ruby driver" do
13
+ MongoMapper.connection = nil
14
+ MongoMapper.connection.should be_instance_of(Mongo::Connection)
15
+ end
16
+
17
+ should "be able to write and read default database" do
18
+ MongoMapper.database = 'test'
19
+ MongoMapper.database.should be_instance_of(Mongo::DB)
20
+ MongoMapper.database.name.should == 'test'
21
+ end
22
+
23
+ should "have document not found error" do
24
+ lambda {
25
+ MongoMapper::DocumentNotFound
26
+ }.should_not raise_error
27
+ end
28
+
29
+ context "use_time_zone?" do
30
+ should "be true if Time.zone set" do
31
+ Time.zone = 'Hawaii'
32
+ MongoMapper.use_time_zone?.should be_true
33
+ Time.zone = nil
34
+ end
35
+
36
+ should "be false if Time.zone not set" do
37
+ MongoMapper.use_time_zone?.should be_false
38
+ end
39
+ end
40
+
41
+ context "time_class" do
42
+ should "be Time.zone if using time zones" do
43
+ Time.zone = 'Hawaii'
44
+ MongoMapper.time_class.should == Time.zone
45
+ Time.zone = nil
46
+ end
47
+
48
+ should "be Time if not using time zones" do
49
+ MongoMapper.time_class.should == Time
50
+ end
51
+ end
52
+
53
+ context "normalize_object_id" do
54
+ should "turn string into object id" do
55
+ id = Mongo::ObjectID.new
56
+ MongoMapper.normalize_object_id(id.to_s).should == id
57
+ end
58
+
59
+ should "leave object id alone" do
60
+ id = Mongo::ObjectID.new
61
+ MongoMapper.normalize_object_id(id).should == id
62
+ end
63
+ end
64
+
65
+ end
@@ -0,0 +1,101 @@
1
+ require 'test_helper'
2
+
3
+ class Comment
4
+ include MongoMapper::Document
5
+
6
+ key :name, String
7
+ key :body, String
8
+
9
+ attr_accessor :callers
10
+ before_validation :record_callers
11
+
12
+ def after_validation
13
+ record_callers
14
+ end
15
+
16
+ def record_callers
17
+ callers << self.class if callers
18
+ end
19
+ end
20
+
21
+ class Article
22
+ include MongoMapper::Document
23
+
24
+ key :title, String
25
+ key :body, String
26
+ end
27
+
28
+ class CommentObserver < MongoMapper::Observer
29
+ attr_accessor :callers
30
+
31
+ def after_validation(model)
32
+ callers << self.class if callers
33
+ end
34
+ end
35
+
36
+ class AuditObserver < MongoMapper::Observer
37
+ observe Article, Comment
38
+ attr_reader :document
39
+
40
+ def after_validation(document)
41
+ @document = document
42
+ end
43
+ end
44
+
45
+ class GlobalObserver < MongoMapper::Observer
46
+ observe Article, Comment
47
+ attr_reader :document
48
+
49
+ def before_save(document)
50
+ @document = document
51
+ end
52
+ end
53
+
54
+ class NonAutomaticObserver < MongoMapper::Observer
55
+ observe Comment
56
+ attr_reader :comment
57
+
58
+ def after_validation(comment)
59
+ @comment = comment
60
+ end
61
+ end
62
+
63
+ class ObserverTest < Test::Unit::TestCase
64
+ should "fire model callbacks before observer" do
65
+ callers = []
66
+ comment = Comment.new
67
+ comment.callers = callers
68
+
69
+ CommentObserver.instance.callers = callers
70
+
71
+ comment.valid?
72
+ callers.should == [Comment, Comment, CommentObserver]
73
+ end
74
+
75
+ should "automatically observe model based on name when possible" do
76
+ CommentObserver.observed_class.should == Comment
77
+ end
78
+
79
+ should "be able to observe other models using observe" do
80
+ obs = NonAutomaticObserver.instance
81
+ comment = Comment.new(:name => 'John Nunemaker', :body => 'is awesome')
82
+ comment.valid?
83
+ obs.comment.name.should == 'John Nunemaker'
84
+ obs.comment.body.should == 'is awesome'
85
+ end
86
+
87
+ should "be able to observe multiple models" do
88
+ obs = AuditObserver.instance
89
+ comment = Comment.new(:name => 'Steve Smith', :body => 'is awesome')
90
+ comment.valid?
91
+
92
+ obs.document.name.should == 'Steve Smith'
93
+ obs.document.body.should == 'is awesome'
94
+
95
+ article = Article.new(:title => 'Ordered List Is Awesome', :body => 'Learn to accept it!')
96
+ article.valid?
97
+
98
+ obs.document.title.should == 'Ordered List Is Awesome'
99
+ obs.document.body.should == 'Learn to accept it!'
100
+ end
101
+ end