mongo_mapper-unstable 2009.10.16 → 2009.10.31

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 (51) hide show
  1. data/.gitignore +3 -1
  2. data/README.rdoc +3 -0
  3. data/Rakefile +31 -65
  4. data/VERSION +1 -1
  5. data/lib/mongo_mapper/associations/base.rb +31 -4
  6. data/lib/mongo_mapper/associations/many_documents_as_proxy.rb +0 -2
  7. data/lib/mongo_mapper/associations/many_documents_proxy.rb +21 -15
  8. data/lib/mongo_mapper/associations/many_embedded_polymorphic_proxy.rb +2 -2
  9. data/lib/mongo_mapper/associations/many_embedded_proxy.rb +21 -36
  10. data/lib/mongo_mapper/associations/many_polymorphic_proxy.rb +1 -1
  11. data/lib/mongo_mapper/associations/proxy.rb +1 -0
  12. data/lib/mongo_mapper/associations.rb +114 -17
  13. data/lib/mongo_mapper/callbacks.rb +18 -0
  14. data/lib/mongo_mapper/document.rb +230 -95
  15. data/lib/mongo_mapper/dynamic_finder.rb +1 -1
  16. data/lib/mongo_mapper/embedded_document.rb +7 -3
  17. data/lib/mongo_mapper/finder_options.rb +88 -56
  18. data/lib/mongo_mapper/pagination.rb +2 -0
  19. data/lib/mongo_mapper/serialization.rb +2 -3
  20. data/lib/mongo_mapper/serializers/json_serializer.rb +1 -1
  21. data/lib/mongo_mapper/support.rb +9 -0
  22. data/lib/mongo_mapper/validations.rb +14 -42
  23. data/lib/mongo_mapper.rb +15 -13
  24. data/mongo_mapper.gemspec +13 -13
  25. data/specs.watchr +2 -2
  26. data/test/functional/associations/test_belongs_to_polymorphic_proxy.rb +5 -5
  27. data/test/functional/associations/test_belongs_to_proxy.rb +28 -30
  28. data/test/functional/associations/test_many_documents_as_proxy.rb +4 -4
  29. data/test/functional/associations/test_many_embedded_polymorphic_proxy.rb +27 -3
  30. data/test/functional/associations/test_many_embedded_proxy.rb +58 -38
  31. data/test/functional/associations/test_many_polymorphic_proxy.rb +49 -7
  32. data/test/functional/associations/test_many_proxy.rb +65 -15
  33. data/test/functional/test_associations.rb +3 -3
  34. data/test/functional/test_binary.rb +1 -1
  35. data/test/functional/test_callbacks.rb +1 -1
  36. data/test/functional/test_dirty.rb +3 -3
  37. data/test/functional/test_document.rb +178 -57
  38. data/test/functional/test_embedded_document.rb +1 -1
  39. data/test/functional/test_pagination.rb +18 -18
  40. data/test/functional/test_rails_compatibility.rb +1 -1
  41. data/test/functional/test_validations.rb +80 -27
  42. data/test/models.rb +93 -17
  43. data/test/support/{test_timing.rb → timing.rb} +1 -1
  44. data/test/test_helper.rb +8 -11
  45. data/test/unit/test_association_base.rb +23 -1
  46. data/test/unit/test_document.rb +29 -12
  47. data/test/unit/test_embedded_document.rb +13 -4
  48. data/test/unit/test_finder_options.rb +74 -58
  49. data/test/unit/test_mongomapper.rb +2 -2
  50. data/test/unit/test_pagination.rb +4 -0
  51. metadata +7 -7
@@ -8,7 +8,7 @@ class ValidationsTest < Test::Unit::TestCase
8
8
  set_collection_name 'test'
9
9
  key :name, String, :required => true
10
10
  end
11
- @document.collection.clear
11
+ @document.collection.remove
12
12
  end
13
13
 
14
14
  should "not insert document" do
@@ -32,7 +32,7 @@ class ValidationsTest < Test::Unit::TestCase
32
32
  set_collection_name 'test'
33
33
  key :name, String, :required => true
34
34
  end
35
- @document.collection.clear
35
+ @document.collection.remove
36
36
  end
37
37
 
38
38
  should "raise error" do
@@ -48,7 +48,7 @@ class ValidationsTest < Test::Unit::TestCase
48
48
  set_collection_name 'test'
49
49
  key :name, String, :required => true
50
50
  end
51
- @document.collection.clear
51
+ @document.collection.remove
52
52
  end
53
53
 
54
54
  should "raise error" do
@@ -68,7 +68,7 @@ class ValidationsTest < Test::Unit::TestCase
68
68
  set_collection_name 'test'
69
69
  key :name, String, :required => true
70
70
  end
71
- @document.collection.clear
71
+ @document.collection.remove
72
72
 
73
73
  @doc = @document.create(:name => 'John Nunemaker')
74
74
  end
@@ -97,7 +97,7 @@ class ValidationsTest < Test::Unit::TestCase
97
97
  errors.add(:action, 'is invalid') if action.blank?
98
98
  end
99
99
  end
100
- @document.collection.clear
100
+ @document.collection.remove
101
101
  end
102
102
 
103
103
  should "work with validate_on_create callback" do
@@ -140,7 +140,7 @@ class ValidationsTest < Test::Unit::TestCase
140
140
  key :name, String
141
141
  validates_uniqueness_of :name
142
142
  end
143
- @document.collection.clear
143
+ @document.collection.remove
144
144
  end
145
145
 
146
146
  should "not fail if object is new" do
@@ -170,8 +170,8 @@ class ValidationsTest < Test::Unit::TestCase
170
170
  doc.save.should be_true
171
171
 
172
172
  @document \
173
- .stubs(:find) \
174
- .with(:first, :conditions => {:name => 'joe'}, :limit => 1) \
173
+ .stubs(:first) \
174
+ .with(:name => 'joe') \
175
175
  .returns(doc)
176
176
 
177
177
  doc.name = "joe"
@@ -184,14 +184,67 @@ class ValidationsTest < Test::Unit::TestCase
184
184
  doc.save.should be_true
185
185
 
186
186
  @document \
187
- .stubs(:find) \
188
- .with(:first, :conditions => {:name => 'joe'}, :limit => 1) \
187
+ .stubs(:first) \
188
+ .with(:name => 'joe') \
189
189
  .returns(doc)
190
190
 
191
191
  doc2 = @document.new("name" => "joe")
192
192
  doc2.should have_error_on(:name)
193
193
  end
194
194
 
195
+ should "allow multiple blank entries if :allow_blank => true" do
196
+ document = Class.new do
197
+ include MongoMapper::Document
198
+ set_collection_name 'test'
199
+
200
+ key :name
201
+ validates_uniqueness_of :name, :allow_blank => :true
202
+ end
203
+
204
+ doc = document.new("name" => "")
205
+ doc.save.should be_true
206
+
207
+ document \
208
+ .stubs(:first) \
209
+ .with(:name => '') \
210
+ .returns(doc)
211
+
212
+ doc2 = document.new("name" => "")
213
+ doc2.should_not have_error_on(:name)
214
+ end
215
+
216
+ should "allow entries that differ only in case by default" do
217
+ document = Class.new do
218
+ include MongoMapper::Document
219
+ set_collection_name 'test'
220
+
221
+ key :name
222
+ validates_uniqueness_of :name
223
+ end
224
+
225
+ doc = document.new("name" => "BLAMMO")
226
+ doc.save.should be_true
227
+
228
+ doc2 = document.new("name" => "blammo")
229
+ doc2.should_not have_error_on(:name)
230
+ end
231
+
232
+ should "fail on entries that differ only in case if :case_sensitive => false" do
233
+ document = Class.new do
234
+ include MongoMapper::Document
235
+ set_collection_name 'test'
236
+
237
+ key :name
238
+ validates_uniqueness_of :name, :case_sensitive => false
239
+ end
240
+
241
+ doc = document.new("name" => "BLAMMO")
242
+ doc.save.should be_true
243
+
244
+ doc2 = document.new("name" => "blammo")
245
+ doc2.should have_error_on(:name)
246
+ end
247
+
195
248
  context "scoped by a single attribute" do
196
249
  setup do
197
250
  @document = Class.new do
@@ -202,36 +255,36 @@ class ValidationsTest < Test::Unit::TestCase
202
255
  key :scope, String
203
256
  validates_uniqueness_of :name, :scope => :scope
204
257
  end
205
- @document.collection.clear
258
+ @document.collection.remove
206
259
  end
207
-
260
+
208
261
  should "fail if the same name exists in the scope" do
209
262
  doc = @document.new("name" => "joe", "scope" => "one")
210
263
  doc.save.should be_true
211
264
 
212
265
  @document \
213
- .stubs(:find) \
214
- .with(:first, :conditions => {:name => 'joe', :scope => "one"}, :limit => 1) \
266
+ .stubs(:first) \
267
+ .with(:name => 'joe', :scope => "one") \
215
268
  .returns(doc)
216
269
 
217
270
  doc2 = @document.new("name" => "joe", "scope" => "one")
218
271
  doc2.should have_error_on(:name)
219
272
  end
220
-
273
+
221
274
  should "pass if the same name exists in a different scope" do
222
275
  doc = @document.new("name" => "joe", "scope" => "one")
223
276
  doc.save.should be_true
224
-
277
+
225
278
  @document \
226
- .stubs(:find) \
227
- .with(:first, :conditions => {:name => 'joe', :scope => "two"}, :limit => 1) \
279
+ .stubs(:first) \
280
+ .with(:name => 'joe', :scope => 'two') \
228
281
  .returns(nil)
229
282
 
230
283
  doc2 = @document.new("name" => "joe", "scope" => "two")
231
284
  doc2.should_not have_error_on(:name)
232
285
  end
233
286
  end
234
-
287
+
235
288
  context "scoped by a multiple attributes" do
236
289
  setup do
237
290
  @document = Class.new do
@@ -243,7 +296,7 @@ class ValidationsTest < Test::Unit::TestCase
243
296
  key :second_scope, String
244
297
  validates_uniqueness_of :name, :scope => [:first_scope, :second_scope]
245
298
  end
246
- @document.collection.clear
299
+ @document.collection.remove
247
300
  end
248
301
 
249
302
  should "fail if the same name exists in the scope" do
@@ -251,8 +304,8 @@ class ValidationsTest < Test::Unit::TestCase
251
304
  doc.save.should be_true
252
305
 
253
306
  @document \
254
- .stubs(:find) \
255
- .with(:first, :conditions => {:name => 'joe', :first_scope => "one", :second_scope => "two"}, :limit => 1) \
307
+ .stubs(:first) \
308
+ .with(:name => 'joe', :first_scope => 'one', :second_scope => 'two') \
256
309
  .returns(doc)
257
310
 
258
311
  doc2 = @document.new("name" => "joe", "first_scope" => "one", "second_scope" => "two")
@@ -264,8 +317,8 @@ class ValidationsTest < Test::Unit::TestCase
264
317
  doc.save.should be_true
265
318
 
266
319
  @document \
267
- .stubs(:find) \
268
- .with(:first, :conditions => {:name => 'joe', :first_scope => "one", :second_scope => "one"}, :limit => 1) \
320
+ .stubs(:first) \
321
+ .with(:name => 'joe', :first_scope => 'one', :second_scope => 'one') \
269
322
  .returns(nil)
270
323
 
271
324
  doc2 = @document.new("name" => "joe", "first_scope" => "one", "second_scope" => "one")
@@ -282,14 +335,14 @@ class ValidationsTest < Test::Unit::TestCase
282
335
 
283
336
  key :name, String, :unique => true
284
337
  end
285
- @document.collection.clear
338
+ @document.collection.remove
286
339
 
287
340
  doc = @document.create(:name => 'John')
288
341
  doc.should_not have_error_on(:name)
289
342
 
290
343
  @document \
291
- .stubs(:find) \
292
- .with(:first, :conditions => {:name => 'John'}, :limit => 1) \
344
+ .stubs(:first) \
345
+ .with(:name => 'John') \
293
346
  .returns(doc)
294
347
 
295
348
  second_john = @document.create(:name => 'John')
data/test/models.rb CHANGED
@@ -23,14 +23,13 @@ class WindowSize
23
23
  end
24
24
  end
25
25
 
26
-
27
26
  class Post
28
27
  include MongoMapper::Document
29
28
 
30
29
  key :title, String
31
30
  key :body, String
32
31
 
33
- has_many :comments, :as => :commentable, :class_name => 'PostComment'
32
+ many :comments, :as => :commentable, :class_name => 'PostComment'
34
33
 
35
34
  timestamps!
36
35
  end
@@ -68,29 +67,87 @@ class Message
68
67
  belongs_to :room
69
68
  end
70
69
 
71
- class Answer
72
- include MongoMapper::Document
73
-
74
- key :body, String
75
- end
76
-
77
70
  class Enter < Message; end
78
71
  class Exit < Message; end
79
72
  class Chat < Message; end
80
73
 
74
+ module AccountsExtensions
75
+ def inactive
76
+ all(:last_logged_in => nil)
77
+ end
78
+ end
79
+
80
+ class Account
81
+ include MongoMapper::Document
82
+
83
+ key :_type, String
84
+ key :room_id, String
85
+ key :last_logged_in, Time
86
+
87
+ belongs_to :room
88
+ end
89
+ class User < Account; end
90
+ class Bot < Account; end
91
+
81
92
  class Room
82
93
  include MongoMapper::Document
83
94
 
84
95
  key :name, String
85
- many :messages, :polymorphic => true
96
+ many :messages, :polymorphic => true do
97
+ def older
98
+ all(:position => {'$gt' => 5})
99
+ end
100
+ end
101
+ many :latest_messages, :class_name => 'Message', :order => 'position desc', :limit => 2
102
+
103
+ many :accounts, :polymorphic => true, :extend => AccountsExtensions
104
+ end
105
+
106
+ class Answer
107
+ include MongoMapper::Document
108
+
109
+ key :body, String
110
+ end
111
+
112
+ module PeopleExtensions
113
+ def find_by_name(name)
114
+ detect { |p| p.name == name }
115
+ end
116
+ end
117
+
118
+ module CollaboratorsExtensions
119
+ def top
120
+ first
121
+ end
86
122
  end
87
123
 
88
124
  class Project
89
125
  include MongoMapper::Document
90
126
 
91
127
  key :name, String
92
- many :statuses
93
- many :addresses
128
+
129
+ many :people, :extend => PeopleExtensions
130
+ many :collaborators, :extend => CollaboratorsExtensions
131
+
132
+ many :statuses, :order => 'position' do
133
+ def open
134
+ all(:name => %w(New Assigned))
135
+ end
136
+ end
137
+
138
+ many :addresses do
139
+ def find_all_by_state(state)
140
+ # can't use select here for some reason
141
+ find_all { |a| a.state == state }
142
+ end
143
+ end
144
+ end
145
+
146
+ class Collaborator
147
+ include MongoMapper::Document
148
+ key :project_id, String
149
+ key :name, String
150
+ belongs_to :project
94
151
  end
95
152
 
96
153
  class Status
@@ -99,7 +156,7 @@ class Status
99
156
  key :project_id, String
100
157
  key :target_id, String
101
158
  key :target_type, String
102
- key :name, String
159
+ key :name, String, :required => true
103
160
  key :position, Integer
104
161
 
105
162
  belongs_to :project
@@ -109,9 +166,13 @@ end
109
166
  class RealPerson
110
167
  include MongoMapper::Document
111
168
 
112
- many :pets
169
+ key :room_id, String
113
170
  key :name, String
114
-
171
+
172
+ belongs_to :room
173
+
174
+ many :pets
175
+
115
176
  def realname=(n)
116
177
  self.name = n
117
178
  end
@@ -138,6 +199,8 @@ class Media
138
199
 
139
200
  key :_type, String
140
201
  key :file, String
202
+
203
+ key :visible, Boolean
141
204
  end
142
205
 
143
206
  class Video < Media
@@ -155,8 +218,13 @@ end
155
218
 
156
219
  class Catalog
157
220
  include MongoMapper::Document
158
-
159
- many :medias, :polymorphic => true
221
+
222
+ many :medias, :polymorphic => true do
223
+ def visible
224
+ # for some reason we can't use select here
225
+ find_all { |m| m.visible? }
226
+ end
227
+ end
160
228
  end
161
229
 
162
230
  module TrModels
@@ -165,6 +233,7 @@ module TrModels
165
233
 
166
234
  key :_type, String
167
235
  key :license_plate, String
236
+ key :purchased_on, Date
168
237
  end
169
238
 
170
239
  class Car < TrModels::Transport
@@ -189,7 +258,14 @@ module TrModels
189
258
  class Fleet
190
259
  include MongoMapper::Document
191
260
 
192
- many :transports, :polymorphic => true, :class_name => "TrModels::Transport"
261
+ module TransportsExtension
262
+ def to_be_replaced
263
+ # for some reason we can't use select
264
+ find_all { |t| t.purchased_on < 2.years.ago.to_date }
265
+ end
266
+ end
267
+
268
+ many :transports, :polymorphic => true, :class_name => "TrModels::Transport", :extend => TransportsExtension
193
269
  key :name, String
194
270
  end
195
271
  end
@@ -5,7 +5,7 @@ class Test::Unit::TestCase
5
5
  end_time = Time.now
6
6
 
7
7
  duration = end_time - begin_time
8
- threshold = 0.3
8
+ threshold = 0.5
9
9
 
10
10
  if duration > threshold
11
11
  puts "\nSLOW TEST: #{duration} - #{self.name}"
data/test/test_helper.rb CHANGED
@@ -1,4 +1,4 @@
1
- require File.join(File.expand_path(File.dirname(__FILE__) + '/../lib/mongo_mapper'))
1
+ require File.expand_path(File.dirname(__FILE__) + '/../lib/mongo_mapper')
2
2
 
3
3
  gem 'jnunemaker-matchy', '0.4.0'
4
4
  gem 'shoulda', '2.10.2'
@@ -12,19 +12,16 @@ require 'mocha'
12
12
  require 'pp'
13
13
 
14
14
  require 'support/custom_matchers'
15
- require 'support/test_timing'
15
+ require 'support/timing'
16
16
 
17
17
  class Test::Unit::TestCase
18
18
  include CustomMatchers
19
-
20
- def clear_all_collections
21
- MongoMapper::Document.descendants.map { |d| d.collection.clear }
22
- end
23
19
  end
24
20
 
25
- DefaultDatabase = 'test' unless defined?(DefaultDatabase)
26
- AlternateDatabase = 'test2' unless defined?(AlternateDatabase)
21
+ test_dir = File.expand_path(File.dirname(__FILE__) + '/../tmp')
22
+ FileUtils.mkdir_p(test_dir) unless File.exist?(test_dir)
27
23
 
28
- logger = Logger.new(File.expand_path(File.dirname(__FILE__) + '/../tmp/test.log'))
29
- MongoMapper.connection = Mongo::Connection.new('127.0.0.1', 27017, :logger => logger)
30
- MongoMapper.database = DefaultDatabase
24
+ MongoMapper.connection = Mongo::Connection.new('127.0.0.1', 27017, {
25
+ :logger => Logger.new(test_dir + '/test.log')
26
+ })
27
+ MongoMapper.database = 'test'
@@ -33,9 +33,14 @@ class AssociationBaseTest < Test::Unit::TestCase
33
33
  end
34
34
 
35
35
  context "klass" do
36
- should "be class_name constantized" do
36
+ should "default to class_name constantized" do
37
37
  Base.new(:belongs_to, :foo_monster).klass.should == FooMonster
38
38
  end
39
+
40
+ should "be the specified class" do
41
+ anonnymous_class = Class.new
42
+ Base.new(:belongs_to, :foo_monster, :class => anonnymous_class).klass.should == anonnymous_class
43
+ end
39
44
  end
40
45
 
41
46
  context "many?" do
@@ -48,6 +53,23 @@ class AssociationBaseTest < Test::Unit::TestCase
48
53
  end
49
54
  end
50
55
 
56
+ context "finder_options" do
57
+ should "default to empty hash" do
58
+ base = Base.new(:many, :foos)
59
+ base.finder_options.should == {}
60
+ end
61
+
62
+ should "work with order" do
63
+ base = Base.new(:many, :foos, :order => 'position')
64
+ base.finder_options.should == {:order => 'position'}
65
+ end
66
+
67
+ should "correctly parse from options" do
68
+ base = Base.new(:many, :foos, :order => 'position', :somekey => 'somevalue')
69
+ base.finder_options.should == {:order => 'position', :somekey => 'somevalue'}
70
+ end
71
+ end
72
+
51
73
  context "belongs_to?" do
52
74
  should "be true if belongs_to" do
53
75
  Base.new(:belongs_to, :foo).belongs_to?.should be_true
@@ -8,7 +8,7 @@ class DocumentTest < Test::Unit::TestCase
8
8
  include MongoMapper::Document
9
9
  set_collection_name 'test'
10
10
  end
11
- @document.collection.clear
11
+ @document.collection.remove
12
12
  end
13
13
 
14
14
  should "have logger method" do
@@ -36,8 +36,9 @@ class DocumentTest < Test::Unit::TestCase
36
36
  end
37
37
 
38
38
  should "allow setting a different database without affecting the default" do
39
- @document.database AlternateDatabase
40
- @document.database.name.should == AlternateDatabase
39
+ @document.set_database_name 'test2'
40
+ @document.database_name.should == 'test2'
41
+ @document.database.name.should == 'test2'
41
42
 
42
43
  another_document = Class.new do
43
44
  include MongoMapper::Document
@@ -47,13 +48,24 @@ class DocumentTest < Test::Unit::TestCase
47
48
  end
48
49
 
49
50
  should "default collection name to class name tableized" do
50
- class Item
51
+ class ::Item
51
52
  include MongoMapper::Document
52
53
  end
53
54
 
54
55
  Item.collection.should be_instance_of(Mongo::Collection)
55
56
  Item.collection.name.should == 'items'
56
57
  end
58
+
59
+ should "default collection name of namespaced class to tableized with dot separation" do
60
+ module ::BloggyPoo
61
+ class Post
62
+ include MongoMapper::Document
63
+ end
64
+ end
65
+
66
+ BloggyPoo::Post.collection.should be_instance_of(Mongo::Collection)
67
+ BloggyPoo::Post.collection.name.should == 'bloggy_poo.posts'
68
+ end
57
69
 
58
70
  should "allow setting the collection name" do
59
71
  @document.set_collection_name('foobar')
@@ -91,7 +103,7 @@ class DocumentTest < Test::Unit::TestCase
91
103
  key :name, String
92
104
  key :age, Integer
93
105
  end
94
- @document.collection.clear
106
+ @document.collection.remove
95
107
  end
96
108
 
97
109
  should "have access to logger" do
@@ -102,7 +114,7 @@ class DocumentTest < Test::Unit::TestCase
102
114
 
103
115
  should "have access to the class's collection" do
104
116
  doc = @document.new
105
- doc.collection.should == @document.collection
117
+ doc.collection.name.should == @document.collection.name
106
118
  end
107
119
 
108
120
  should "use default values if defined for keys" do
@@ -124,13 +136,18 @@ class DocumentTest < Test::Unit::TestCase
124
136
  @document.new._root_document.should be_nil
125
137
  end
126
138
 
127
- should "set self to the root document on embedded documents" do
128
- document = Class.new(RealPerson) do
129
- many :pets
139
+ should "set self to the root document on embedded documents" do
140
+ klass = Class.new do
141
+ include MongoMapper::Document
130
142
  end
131
-
132
- doc = document.new 'pets' => [{}]
133
- doc.pets.first._root_document.should == doc
143
+
144
+ pets = Class.new do
145
+ include MongoMapper::EmbeddedDocument
146
+ end
147
+ klass.many :pets, :class => pets
148
+
149
+ doc = klass.new(:pets => [{}])
150
+ doc.pets.first._root_document.should == doc
134
151
  end
135
152
  end
136
153
 
@@ -465,11 +465,20 @@ class EmbeddedDocumentTest < Test::Unit::TestCase
465
465
  clone.age.should == 27
466
466
  end
467
467
  end
468
-
468
+
469
469
  context "key shorcut access" do
470
- should "be able to read key with []" do
471
- doc = @document.new(:name => 'string')
472
- doc[:name].should == 'string'
470
+ context "[]" do
471
+ should "work when key found" do
472
+ doc = @document.new(:name => 'string')
473
+ doc[:name].should == 'string'
474
+ end
475
+
476
+ should "raise exception when key not found" do
477
+ doc = @document.new(:name => 'string')
478
+ lambda {
479
+ doc[:not_here]
480
+ }.should raise_error(MongoMapper::KeyNotFound)
481
+ end
473
482
  end
474
483
 
475
484
  context "[]=" do