mongo_mapper 0.8.6 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (107) hide show
  1. data/UPGRADES +10 -0
  2. data/bin/mmconsole +0 -1
  3. data/examples/identity_map/automatic.rb +1 -7
  4. data/examples/plugins.rb +9 -9
  5. data/examples/safe.rb +43 -0
  6. data/lib/mongo_mapper.rb +46 -33
  7. data/lib/mongo_mapper/document.rb +33 -32
  8. data/lib/mongo_mapper/embedded_document.rb +22 -22
  9. data/lib/mongo_mapper/locale/en.yml +5 -0
  10. data/lib/mongo_mapper/middleware/identity_map.rb +16 -0
  11. data/lib/mongo_mapper/plugins.rb +16 -3
  12. data/lib/mongo_mapper/plugins/accessible.rb +2 -0
  13. data/lib/mongo_mapper/plugins/active_model.rb +18 -0
  14. data/lib/mongo_mapper/plugins/associations.rb +37 -42
  15. data/lib/mongo_mapper/plugins/associations/base.rb +14 -50
  16. data/lib/mongo_mapper/plugins/associations/belongs_to_association.rb +58 -0
  17. data/lib/mongo_mapper/plugins/associations/belongs_to_polymorphic_proxy.rb +6 -1
  18. data/lib/mongo_mapper/plugins/associations/belongs_to_proxy.rb +30 -2
  19. data/lib/mongo_mapper/plugins/associations/embedded_collection.rb +4 -0
  20. data/lib/mongo_mapper/plugins/associations/in_array_proxy.rb +12 -6
  21. data/lib/mongo_mapper/plugins/associations/many_association.rb +67 -0
  22. data/lib/mongo_mapper/plugins/associations/many_documents_proxy.rb +5 -5
  23. data/lib/mongo_mapper/plugins/associations/many_embedded_proxy.rb +1 -1
  24. data/lib/mongo_mapper/plugins/associations/one_association.rb +20 -0
  25. data/lib/mongo_mapper/plugins/associations/one_embedded_proxy.rb +5 -0
  26. data/lib/mongo_mapper/plugins/associations/one_proxy.rb +7 -7
  27. data/lib/mongo_mapper/plugins/associations/proxy.rb +2 -2
  28. data/lib/mongo_mapper/plugins/caching.rb +3 -1
  29. data/lib/mongo_mapper/plugins/callbacks.rb +12 -221
  30. data/lib/mongo_mapper/plugins/clone.rb +3 -1
  31. data/lib/mongo_mapper/plugins/dirty.rb +38 -91
  32. data/lib/mongo_mapper/plugins/document.rb +4 -2
  33. data/lib/mongo_mapper/plugins/dynamic_querying.rb +2 -0
  34. data/lib/mongo_mapper/plugins/embedded_callbacks.rb +43 -0
  35. data/lib/mongo_mapper/plugins/embedded_document.rb +16 -9
  36. data/lib/mongo_mapper/plugins/equality.rb +2 -0
  37. data/lib/mongo_mapper/plugins/identity_map.rb +4 -2
  38. data/lib/mongo_mapper/plugins/indexes.rb +2 -0
  39. data/lib/mongo_mapper/plugins/inspect.rb +3 -1
  40. data/lib/mongo_mapper/plugins/keys.rb +28 -22
  41. data/lib/mongo_mapper/plugins/keys/key.rb +12 -6
  42. data/lib/mongo_mapper/plugins/logger.rb +2 -0
  43. data/lib/mongo_mapper/plugins/modifiers.rb +3 -1
  44. data/lib/mongo_mapper/plugins/pagination.rb +2 -0
  45. data/lib/mongo_mapper/plugins/persistence.rb +2 -0
  46. data/lib/mongo_mapper/plugins/protected.rb +2 -0
  47. data/lib/mongo_mapper/plugins/querying.rb +5 -4
  48. data/lib/mongo_mapper/plugins/rails.rb +3 -5
  49. data/lib/mongo_mapper/plugins/safe.rb +2 -0
  50. data/lib/mongo_mapper/plugins/sci.rb +2 -0
  51. data/lib/mongo_mapper/plugins/scopes.rb +2 -0
  52. data/lib/mongo_mapper/plugins/serialization.rb +67 -46
  53. data/lib/mongo_mapper/plugins/timestamps.rb +3 -1
  54. data/lib/mongo_mapper/plugins/userstamps.rb +2 -0
  55. data/lib/mongo_mapper/plugins/validations.rb +40 -24
  56. data/lib/mongo_mapper/railtie.rb +49 -0
  57. data/lib/mongo_mapper/railtie/database.rake +60 -0
  58. data/lib/mongo_mapper/support/descendant_appends.rb +11 -11
  59. data/lib/mongo_mapper/translation.rb +10 -0
  60. data/lib/mongo_mapper/version.rb +1 -1
  61. data/lib/rails/generators/mongo_mapper/config/config_generator.rb +24 -0
  62. data/lib/rails/generators/mongo_mapper/config/templates/mongo.yml +18 -0
  63. data/lib/rails/generators/mongo_mapper/model/model_generator.rb +23 -0
  64. data/lib/rails/generators/mongo_mapper/model/templates/model.rb +11 -0
  65. data/test/functional/associations/test_belongs_to_polymorphic_proxy.rb +1 -0
  66. data/test/functional/associations/test_belongs_to_proxy.rb +131 -1
  67. data/test/functional/associations/test_in_array_proxy.rb +30 -0
  68. data/test/functional/associations/test_many_documents_proxy.rb +30 -2
  69. data/test/functional/associations/test_many_embedded_proxy.rb +33 -0
  70. data/test/functional/associations/test_many_polymorphic_proxy.rb +1 -0
  71. data/test/functional/associations/test_one_embedded_proxy.rb +21 -2
  72. data/test/functional/associations/test_one_proxy.rb +49 -9
  73. data/test/functional/test_associations.rb +2 -0
  74. data/test/functional/test_caching.rb +3 -2
  75. data/test/functional/test_callbacks.rb +25 -18
  76. data/test/functional/test_dirty.rb +123 -1
  77. data/test/functional/test_document.rb +26 -2
  78. data/test/functional/test_embedded_document.rb +68 -2
  79. data/test/functional/test_identity_map.rb +3 -4
  80. data/test/functional/test_querying.rb +11 -0
  81. data/test/functional/test_userstamps.rb +2 -2
  82. data/test/functional/test_validations.rb +31 -29
  83. data/test/models.rb +10 -0
  84. data/test/test_active_model_lint.rb +1 -1
  85. data/test/test_helper.rb +9 -10
  86. data/test/unit/associations/test_base.rb +24 -100
  87. data/test/unit/associations/test_belongs_to_association.rb +29 -0
  88. data/test/unit/associations/test_many_association.rb +63 -0
  89. data/test/unit/associations/test_one_association.rb +18 -0
  90. data/test/unit/serializers/test_json_serializer.rb +0 -1
  91. data/test/unit/test_descendant_appends.rb +8 -16
  92. data/test/unit/test_document.rb +4 -9
  93. data/test/unit/test_dynamic_finder.rb +1 -1
  94. data/test/unit/test_embedded_document.rb +51 -18
  95. data/test/unit/test_identity_map_middleware.rb +34 -0
  96. data/test/unit/test_inspect.rb +22 -0
  97. data/test/unit/test_key.rb +21 -1
  98. data/test/unit/test_keys.rb +0 -2
  99. data/test/unit/test_plugins.rb +106 -20
  100. data/test/unit/test_rails.rb +8 -8
  101. data/test/unit/test_serialization.rb +116 -1
  102. data/test/unit/test_translation.rb +27 -0
  103. data/test/unit/test_validations.rb +66 -81
  104. metadata +103 -43
  105. data/examples/identity_map/middleware.rb +0 -14
  106. data/lib/mongo_mapper/plugins/descendants.rb +0 -17
  107. data/rails/init.rb +0 -19
@@ -0,0 +1,10 @@
1
+ # encoding: UTF-8
2
+ module MongoMapper
3
+ module Translation
4
+ include ActiveModel::Translation
5
+
6
+ def i18n_scope
7
+ :mongo_mapper
8
+ end
9
+ end
10
+ end
@@ -1,4 +1,4 @@
1
1
  # encoding: UTF-8
2
2
  module MongoMapper
3
- Version = '0.8.6'
3
+ Version = '0.9.0'
4
4
  end
@@ -0,0 +1,24 @@
1
+ # encoding: utf-8
2
+
3
+ module MongoMapper
4
+ module Generators
5
+ class ConfigGenerator < Rails::Generators::Base
6
+ desc "creates the MongoMapper configuration at config/mongo.yml"
7
+
8
+ argument :database_name, :type => :string, :optional => true
9
+
10
+ def self.source_root
11
+ @source_root ||= File.expand_path("../templates", __FILE__)
12
+ end
13
+
14
+ def app_name
15
+ Rails::Application.subclasses.first.parent.to_s.underscore
16
+ end
17
+
18
+ def create_config_file
19
+ template 'mongo.yml', File.join('config', "mongo.yml")
20
+ end
21
+
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,18 @@
1
+ defaults: &defaults
2
+ host: 127.0.0.1
3
+ port: 27017
4
+
5
+ development:
6
+ <<: *defaults
7
+ database: <%= database_name || app_name %>_development
8
+
9
+ test:
10
+ <<: *defaults
11
+ database: <%= database_name || app_name %>_test
12
+
13
+ # set these environment variables on your prod server
14
+ production:
15
+ <<: *defaults
16
+ database: <%= database_name || app_name %>
17
+ username: <%%= ENV['MONGO_USERNAME'] %>
18
+ password: <%%= ENV['MONGO_PASSWORD'] %>
@@ -0,0 +1,23 @@
1
+ # encoding: utf-8
2
+
3
+ module MongoMapper
4
+ module Generators
5
+ class ModelGenerator < Rails::Generators::NamedBase
6
+ desc 'Creates a mongomapper model'
7
+ argument :name, :type => :string
8
+ argument :attributes, :type => :array, :default => [], :banner => "field:type field:type"
9
+ class_option :timestamps, :type => :boolean
10
+ check_class_collision
11
+
12
+ def self.source_root
13
+ @source_root ||= File.expand_path("../templates", __FILE__)
14
+ end
15
+
16
+ def create_model_file
17
+ template 'model.rb', File.join('app/models', "#{file_name}.rb")
18
+ end
19
+
20
+ hook_for :test_framework
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,11 @@
1
+ class <%= class_name %>
2
+ include MongoMapper::Document
3
+
4
+ <% attributes.each do |attribute| -%>
5
+ key :<%= attribute.name %>, <%= attribute.type.to_s.camelcase %>
6
+ <% end -%>
7
+ <% if options[:timestamps] %>
8
+ timestamps!
9
+ <% end -%>
10
+
11
+ end
@@ -54,6 +54,7 @@ class BelongsToPolymorphicProxyTest < Test::Unit::TestCase
54
54
  @status.target = project
55
55
  @status.save.should be_true
56
56
  project.destroy
57
+ @status.reload
57
58
  end
58
59
 
59
60
  should "return nil instead of raising error" do
@@ -5,7 +5,7 @@ class BelongsToProxyTest < Test::Unit::TestCase
5
5
  def setup
6
6
  @post_class = Doc()
7
7
  @comment_class = Doc do
8
- key :post_id, String
8
+ key :post_id, ObjectId
9
9
  end
10
10
 
11
11
  @comment_class.belongs_to :post, :class => @post_class
@@ -15,6 +15,10 @@ class BelongsToProxyTest < Test::Unit::TestCase
15
15
  @comment_class.new.post.nil?.should be_true
16
16
  end
17
17
 
18
+ should "return nil instead of a proxy" do
19
+ nil.should === @comment_class.new.post
20
+ end
21
+
18
22
  should "have boolean presence method" do
19
23
  comment = @comment_class.new(:name => 'Foo!')
20
24
  comment.post?.should be_false
@@ -23,6 +27,19 @@ class BelongsToProxyTest < Test::Unit::TestCase
23
27
  comment.post?.should be_true
24
28
  end
25
29
 
30
+ should "allow overriding association methods" do
31
+ @comment_class.class_eval do
32
+ def post?
33
+ super
34
+ end
35
+ end
36
+
37
+ instance = @comment_class.new
38
+ instance.post?.should be_false
39
+ instance.post = @post_class.new
40
+ instance.post?.should be_true
41
+ end
42
+
26
43
  should "be able to replace the association" do
27
44
  post = @post_class.new(:name => 'mongomapper')
28
45
  comment = @comment_class.new(:name => 'Foo!', :post => post)
@@ -32,6 +49,43 @@ class BelongsToProxyTest < Test::Unit::TestCase
32
49
  comment.post.should == post
33
50
  comment.post.nil?.should be_false
34
51
  end
52
+
53
+ should "not reload the association when replacing" do
54
+ post = @post_class.new(:name => 'mongomapper')
55
+ comment = @comment_class.new(:name => 'Foo!', :post => post)
56
+ comment.post.proxy_target.object_id.should == post.object_id
57
+ end
58
+
59
+ should "properly assign the associated object when assigning the association with create" do
60
+ child_class = Doc('Child')
61
+ parent_class = Doc('Parent')
62
+
63
+ parent_class.one :child, :class => child_class
64
+ child_class.belongs_to :parent, :class => parent_class
65
+
66
+ child = child_class.create(:parent => parent_class.create)
67
+ child.parent.child.should == child
68
+ end
69
+
70
+ should "generate a new proxy when replacing the association" do
71
+ post1 = @post_class.create(:name => 'post1')
72
+ post2 = @post_class.create(:name => 'post2')
73
+
74
+ comment = @comment_class.new(:name => 'Foo!', :post => post1)
75
+ comment.save.should be_true
76
+
77
+
78
+ comment = comment.reload
79
+ comment.post.should == post1
80
+ comment.post.nil?.should be_false
81
+
82
+ original_post = comment.post
83
+ original_post.name.should == 'post1'
84
+
85
+ comment.post = post2
86
+ comment.post.name.should == 'post2'
87
+ original_post.name.should == 'post1'
88
+ end
35
89
 
36
90
  should "unset the association" do
37
91
  post = @post_class.new(:name => 'mongomapper')
@@ -90,4 +144,80 @@ class BelongsToProxyTest < Test::Unit::TestCase
90
144
  end
91
145
  end
92
146
  end
147
+
148
+ should "be able to build" do
149
+ @comment_class.belongs_to :post, :class => @post_class
150
+
151
+ comment = @comment_class.create
152
+ post = comment.build_post(:title => 'Hello, world!')
153
+ comment.post.should be_instance_of(@post_class)
154
+ comment.post.should be_new
155
+ comment.post.title.should == 'Hello, world!'
156
+ comment.post.should == post
157
+ comment.post_id.should == post.id
158
+ end
159
+
160
+ should "be able to create" do
161
+ @comment_class.belongs_to :post, :class => @post_class
162
+
163
+ comment = @comment_class.create
164
+ post = comment.create_post(:title => 'Hello, world!')
165
+ comment.post.should be_instance_of(@post_class)
166
+ comment.post.should_not be_new
167
+ comment.post.title.should == 'Hello, world!'
168
+ comment.post.should == post
169
+ comment.post_id.should == post.id
170
+ end
171
+
172
+ context "#create!" do
173
+ setup do
174
+ @post_class.key :title, String, :required => true
175
+ @comment_class.belongs_to :post, :class => @post_class
176
+ end
177
+
178
+ should "raise exception if invalid" do
179
+ comment = @comment_class.create
180
+ assert_raises(MongoMapper::DocumentNotValid) do
181
+ comment.create_post!
182
+ end
183
+ end
184
+
185
+ should "work if valid" do
186
+ comment = @comment_class.create
187
+ post = comment.create_post!(:title => 'Hello, world!')
188
+ comment.post.should be_instance_of(@post_class)
189
+ comment.post.should_not be_new
190
+ comment.post.title.should == 'Hello, world!'
191
+ comment.post.should == post
192
+ comment.post_id.should == post.id
193
+ end
194
+ end
195
+
196
+ context 'autosave' do
197
+ should 'not be true by default' do
198
+ @comment_class.associations[:post].options[:autosave].should_not be_true
199
+ end
200
+
201
+ should 'save parent changes when true' do
202
+ @comment_class.associations[:post].options[:autosave] = true
203
+
204
+ comment = @comment_class.create
205
+ post = comment.create_post(:title => 'Hello, world!')
206
+
207
+ comment.post.attributes = {:title => 'Hi, world.'}
208
+ comment.save
209
+
210
+ post.reload.title.should == 'Hi, world.'
211
+ end
212
+
213
+ should 'not save parent changes when false' do
214
+ comment = @comment_class.create
215
+ post = comment.create_post(:title => 'Hello, world!')
216
+
217
+ comment.post.attributes = {:title => 'Hi, world.'}
218
+ comment.save
219
+
220
+ post.reload.title.should == 'Hello, world!'
221
+ end
222
+ end
93
223
  end
@@ -141,6 +141,12 @@ class InArrayProxyTest < Test::Unit::TestCase
141
141
  should "work with conditions" do
142
142
  @user.lists.all(:name => 'Foo 1').should == [@list1]
143
143
  end
144
+
145
+ should "not hit the database if ids key is empty" do
146
+ @user.list_ids = []
147
+ @user.lists.expects(:query).never
148
+ @user.lists.all.should == []
149
+ end
144
150
  end
145
151
 
146
152
  context "first" do
@@ -151,6 +157,12 @@ class InArrayProxyTest < Test::Unit::TestCase
151
157
  should "work with conditions" do
152
158
  @user.lists.first(:position => 2).should == @list2
153
159
  end
160
+
161
+ should "not hit the database if ids key is empty" do
162
+ @user.list_ids = []
163
+ @user.lists.expects(:query).never
164
+ @user.lists.first.should be_nil
165
+ end
154
166
  end
155
167
 
156
168
  context "last" do
@@ -161,6 +173,12 @@ class InArrayProxyTest < Test::Unit::TestCase
161
173
  should "work with conditions" do
162
174
  @user.lists.last(:position => 2, :order => 'position').should == @list2
163
175
  end
176
+
177
+ should "not hit the database if ids key is empty" do
178
+ @user.list_ids = []
179
+ @user.lists.expects(:query).never
180
+ @user.lists.last.should be_nil
181
+ end
164
182
  end
165
183
 
166
184
  context "with one id" do
@@ -209,6 +227,12 @@ class InArrayProxyTest < Test::Unit::TestCase
209
227
  should "return the subject" do
210
228
  @lists.collect(&:name).should == ['Foo 1']
211
229
  end
230
+
231
+ should "not hit the database if ids key is empty" do
232
+ @user.list_ids = []
233
+ @user.lists.expects(:query).never
234
+ @user.lists.paginate(:page => 1).should == []
235
+ end
212
236
  end
213
237
 
214
238
  context "dynamic finders" do
@@ -266,6 +290,12 @@ class InArrayProxyTest < Test::Unit::TestCase
266
290
  @user.lists.count(:name => 'Foo 1').should == 1
267
291
  @user2.lists.count(:name => 'Foo 1').should == 0
268
292
  end
293
+
294
+ should "not hit the database if ids key is empty" do
295
+ @user.list_ids = []
296
+ @user.lists.expects(:query).never
297
+ @user.lists.count(:name => 'Foo 1').should == 0
298
+ end
269
299
  end
270
300
 
271
301
  context "Removing documents" do
@@ -22,6 +22,19 @@ class ManyDocumentsProxyTest < Test::Unit::TestCase
22
22
  project.statuses.should == []
23
23
  end
24
24
 
25
+ should "allow overriding association methods" do
26
+ @owner_class.class_eval do
27
+ def pets
28
+ super
29
+ end
30
+ end
31
+
32
+ instance = @owner_class.new
33
+ instance.pets.should == []
34
+ instance.pets.build
35
+ instance.pets.should_not be_empty
36
+ end
37
+
25
38
  should "allow assignment of many associated documents using a hash" do
26
39
  person_attributes = {
27
40
  'name' => 'Mr. Pet Lover',
@@ -248,7 +261,7 @@ class ManyDocumentsProxyTest < Test::Unit::TestCase
248
261
  should "work on association" do
249
262
  project = Project.create
250
263
  3.times { |i| project.statuses.create(:name => i.to_s) }
251
-
264
+
252
265
  JSON.parse(project.statuses.to_json).collect{|status| status["name"] }.sort.should == ["0","1","2"]
253
266
  end
254
267
  end
@@ -257,7 +270,7 @@ class ManyDocumentsProxyTest < Test::Unit::TestCase
257
270
  should "work on association" do
258
271
  project = Project.create
259
272
  3.times { |i| project.statuses.create(:name => i.to_s) }
260
-
273
+
261
274
  project.statuses.as_json.collect{|status| status["name"] }.sort.should == ["0","1","2"]
262
275
  end
263
276
  end
@@ -612,4 +625,19 @@ class ManyDocumentsProxyTest < Test::Unit::TestCase
612
625
  end
613
626
  end
614
627
  end
628
+
629
+ context "namespaced foreign keys" do
630
+ setup do
631
+ News::Paper.many :articles, :class_name => 'News::Article'
632
+ News::Article.belongs_to :paper, :class_name => 'News::Paper'
633
+
634
+ @paper = News::Paper.create
635
+ end
636
+
637
+ should "properly infer the foreign key" do
638
+ article = @paper.articles.create
639
+ article.should respond_to(:paper_id)
640
+ article.paper_id.should == @paper.id
641
+ end
642
+ end
615
643
  end
@@ -83,8 +83,10 @@ class ManyEmbeddedProxyTest < Test::Unit::TestCase
83
83
 
84
84
  owner = @owner_class.new(person_attributes)
85
85
  owner.name.should == 'Mr. Pet Lover'
86
+ owner.pets[0].id.class.should == BSON::ObjectId
86
87
  owner.pets[0].name.should == 'Jimmy'
87
88
  owner.pets[0].species.should == 'Cocker Spainel'
89
+ owner.pets[1].id.class.should == BSON::ObjectId
88
90
  owner.pets[1].name.should == 'Sasha'
89
91
  owner.pets[1].species.should == 'Siberian Husky'
90
92
 
@@ -92,12 +94,43 @@ class ManyEmbeddedProxyTest < Test::Unit::TestCase
92
94
  owner.reload
93
95
 
94
96
  owner.name.should == 'Mr. Pet Lover'
97
+ owner.pets[0].id.class.should == BSON::ObjectId
95
98
  owner.pets[0].name.should == 'Jimmy'
96
99
  owner.pets[0].species.should == 'Cocker Spainel'
100
+ owner.pets[1].id.class.should == BSON::ObjectId
97
101
  owner.pets[1].name.should == 'Sasha'
98
102
  owner.pets[1].species.should == 'Siberian Husky'
99
103
  end
100
104
 
105
+ context "passing documents between versions of code" do
106
+ setup do
107
+ @old_klass = Doc do
108
+ set_collection_name 'generic_parents'
109
+ key :name, String
110
+ end
111
+
112
+ @updated_klass = Doc do
113
+ set_collection_name 'generic_parents'
114
+ key :name, String
115
+ end
116
+ @updated_klass.many :pets, :class => @pet_class
117
+ end
118
+
119
+ should "not break many embedded proxy" do
120
+ @old_klass.collection.drop
121
+ created_by_new_code = @updated_klass.create!
122
+ created_by_new_code.pets.should == []
123
+
124
+ @old_klass.first # ensure_key_exists calls @old_klass.key(:embedded_docs) (not @old_klass.many(:embedded_docs))
125
+ @old_klass.create!(:name => 'created in old code') # creates doc with {embedded_docs : null}
126
+
127
+ lambda {
128
+ loaded_in_new_code = @updated_klass.find_by_name('created in old code')
129
+ loaded_in_new_code.pets.should == []
130
+ }.should_not raise_error
131
+ end
132
+ end
133
+
101
134
  context "embedding many embedded documents" do
102
135
  setup do
103
136
  @klass = Doc()