mongo_mapper 0.8.6 → 0.9.0

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 (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()