mongo_mapper-unstable 2009.10.16 → 2009.10.31

Sign up to get free protection for your applications and to get access to all the features.
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