mongoid 9.0.10 → 9.0.11
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.
- checksums.yaml +4 -4
- data/README.md +3 -3
- data/Rakefile +31 -0
- data/lib/mongoid/association/nested/many.rb +7 -1
- data/lib/mongoid/association/referenced/auto_save.rb +48 -6
- data/lib/mongoid/config/defaults.rb +9 -5
- data/lib/mongoid/config.rb +25 -0
- data/lib/mongoid/version.rb +1 -1
- data/lib/mongoid/warnings.rb +4 -0
- data/spec/integration/app_spec.rb +194 -36
- data/spec/mongoid/association/auto_save_spec.rb +98 -0
- data/spec/mongoid/attributes/nested_spec.rb +48 -22
- data/spec/mongoid/errors/document_not_found_spec.rb +10 -3
- metadata +16 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b6976a4808058209fda8554306590eeb5fd44759d8c228d02bca544acbe5c200
|
|
4
|
+
data.tar.gz: e0dcd20719682c2a425d9ed17c1459376b6956311f9ec439c74843a4cefb8502
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 64ee6ee13a326ed9fae0e6b6dc08fa28ba0608a75342993b873023e4f87076160f6a8997e3edd53a273da4716c92d5aabc6b49f0931a489d2074d92f8de912d7
|
|
7
|
+
data.tar.gz: 491ecfb05581a08609bc41aedd39600f143a15f681405400c813402be95e11ed24d4be7363ac8079da9bc022d7d5b22ad3c53e534d41ee66a10977751a44b954
|
data/README.md
CHANGED
data/Rakefile
CHANGED
|
@@ -68,6 +68,37 @@ RSpec::Core::RakeTask.new('spec:progress') do |spec|
|
|
|
68
68
|
spec.pattern = "spec/**/*_spec.rb"
|
|
69
69
|
end
|
|
70
70
|
|
|
71
|
+
RUBOCOPABLE = %w[ examples gemfiles perf lib spec mongoid.gemspec Gemfile Rakefile upload-api-docs ].freeze
|
|
72
|
+
|
|
73
|
+
desc 'Run rubocop'
|
|
74
|
+
task rubocop: %w[ rubocop:run ]
|
|
75
|
+
|
|
76
|
+
namespace :rubocop do
|
|
77
|
+
desc 'Run rubocop on the codebase'
|
|
78
|
+
task :run do
|
|
79
|
+
Bundler.with_unbundled_env do
|
|
80
|
+
sh 'bundle', 'exec', 'rubocop', *RUBOCOPABLE, verbose: false
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
desc 'Add a git pre-commit hook that runs rubocop'
|
|
85
|
+
task :install_hook do
|
|
86
|
+
hook_path = File.join('.git', 'hooks', 'pre-commit')
|
|
87
|
+
hook_script = <<~HOOK
|
|
88
|
+
#!/usr/bin/env bash
|
|
89
|
+
set -e
|
|
90
|
+
|
|
91
|
+
echo "Running rubocop..."
|
|
92
|
+
rake rubocop
|
|
93
|
+
HOOK
|
|
94
|
+
|
|
95
|
+
File.write(hook_path, hook_script)
|
|
96
|
+
FileUtils.chmod('+x', hook_path)
|
|
97
|
+
|
|
98
|
+
puts "Git pre-commit hook installed at #{hook_path}."
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
71
102
|
desc 'Build and validate the evergreen config'
|
|
72
103
|
task eg: %w[ eg:build eg:validate ]
|
|
73
104
|
|
|
@@ -184,11 +184,17 @@ module Mongoid
|
|
|
184
184
|
else
|
|
185
185
|
update_document(doc, attrs)
|
|
186
186
|
end
|
|
187
|
-
|
|
187
|
+
elsif association.embedded?
|
|
188
|
+
raise Errors::DocumentNotFound.new(association.klass, id)
|
|
189
|
+
elsif association.is_a?(Association::Referenced::HasAndBelongsToMany) || Mongoid.allow_reparenting_via_nested_attributes?
|
|
190
|
+
Mongoid::Warnings.warn_reparenting_via_nested_attributes if Mongoid.allow_reparenting_via_nested_attributes?
|
|
191
|
+
|
|
188
192
|
# push existing document to association
|
|
189
193
|
doc = association.klass.unscoped.find(converted)
|
|
190
194
|
update_document(doc, attrs)
|
|
191
195
|
existing.push(doc) unless destroyable?(attrs)
|
|
196
|
+
else
|
|
197
|
+
raise Errors::DocumentNotFound.new(association.klass, { _id: id, association.foreign_key => parent.id })
|
|
192
198
|
end
|
|
193
199
|
|
|
194
200
|
parent.children_may_have_changed!
|
|
@@ -32,13 +32,24 @@ module Mongoid
|
|
|
32
32
|
Threaded.exit_autosave(self)
|
|
33
33
|
end
|
|
34
34
|
|
|
35
|
-
# Check if there
|
|
35
|
+
# Check if there are changes for auto-saving. Returns true if the
|
|
36
|
+
# document is new, changed, or marked for destruction, or if any
|
|
37
|
+
# in-memory referenced child with autosave: true recursively
|
|
38
|
+
# satisfies the same condition.
|
|
36
39
|
#
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
#
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
# The seen set prevents infinite recursion when autosave associations
|
|
41
|
+
# form a cycle (e.g. a belongs_to with autosave: true whose target
|
|
42
|
+
# has a has_many with autosave: true pointing back).
|
|
43
|
+
#
|
|
44
|
+
# @param [ Document ] doc The document to check.
|
|
45
|
+
# @param [ Set ] seen Documents already visited (cycle guard).
|
|
46
|
+
#
|
|
47
|
+
# @return [ true | false ] Whether the document needs autosaving.
|
|
48
|
+
def changed_for_autosave?(doc, seen = Set.new)
|
|
49
|
+
return false unless seen.add?(doc)
|
|
50
|
+
|
|
51
|
+
doc.new_record? || doc.changed? || doc.marked_for_destruction? ||
|
|
52
|
+
autosave_children_changed?(doc, seen)
|
|
42
53
|
end
|
|
43
54
|
|
|
44
55
|
# Define the autosave method on an association's owning class for
|
|
@@ -60,6 +71,8 @@ module Mongoid
|
|
|
60
71
|
__autosaving__ do
|
|
61
72
|
if assoc_value = ivar(association.name)
|
|
62
73
|
Array(assoc_value).each do |doc|
|
|
74
|
+
next unless changed_for_autosave?(doc)
|
|
75
|
+
|
|
63
76
|
pc = doc.persistence_context? ? doc.persistence_context : persistence_context.for_child(doc)
|
|
64
77
|
doc.with(pc) do |d|
|
|
65
78
|
d.save
|
|
@@ -72,6 +85,35 @@ module Mongoid
|
|
|
72
85
|
klass.after_persist_parent save_method, unless: :autosaved?
|
|
73
86
|
end
|
|
74
87
|
end
|
|
88
|
+
|
|
89
|
+
private
|
|
90
|
+
|
|
91
|
+
# Returns true if any in-memory referenced child with autosave: true
|
|
92
|
+
# needs saving.
|
|
93
|
+
#
|
|
94
|
+
# @param [ Document ] doc The document whose children to check.
|
|
95
|
+
# @param [ Set ] seen Cycle guard passed through from changed_for_autosave?.
|
|
96
|
+
#
|
|
97
|
+
# @return [ true | false ]
|
|
98
|
+
def autosave_children_changed?(doc, seen)
|
|
99
|
+
if Mongoid.autosave_saves_unchanged_documents?
|
|
100
|
+
Mongoid::Warnings.warn_autosave_saves_unchanged_documents
|
|
101
|
+
return true
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
doc.class.relations.values.select { |a| a.autosave? && !a.embedded? }.any? do |assoc|
|
|
105
|
+
(assoc_value = doc.ivar(assoc.name)) &&
|
|
106
|
+
in_memory_docs(assoc_value).any? { |child| changed_for_autosave?(child, seen) }
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Returns the in-memory documents for an association value without
|
|
111
|
+
# triggering a database load of any unloaded documents. Association
|
|
112
|
+
# proxies expose in_memory for this purpose; a plain document (which
|
|
113
|
+
# belongs_to can store directly in the ivar) is itself in-memory.
|
|
114
|
+
def in_memory_docs(assoc_value)
|
|
115
|
+
assoc_value.respond_to?(:in_memory) ? assoc_value.in_memory : [ assoc_value ]
|
|
116
|
+
end
|
|
75
117
|
end
|
|
76
118
|
end
|
|
77
119
|
end
|
|
@@ -18,19 +18,23 @@ module Mongoid
|
|
|
18
18
|
case version.to_s
|
|
19
19
|
when /^[0-7]\./
|
|
20
20
|
raise ArgumentError, "Version no longer supported: #{version}"
|
|
21
|
-
|
|
21
|
+
|
|
22
|
+
when '8.0'
|
|
22
23
|
self.legacy_readonly = true
|
|
23
24
|
|
|
24
|
-
load_defaults
|
|
25
|
-
|
|
25
|
+
load_defaults '8.1'
|
|
26
|
+
|
|
27
|
+
when '8.1'
|
|
26
28
|
self.immutable_ids = false
|
|
27
29
|
self.legacy_persistence_context_behavior = true
|
|
28
30
|
self.around_callbacks_for_embeds = true
|
|
29
31
|
self.prevent_multiple_calls_of_embedded_callbacks = false
|
|
30
32
|
|
|
31
|
-
load_defaults
|
|
32
|
-
|
|
33
|
+
load_defaults '9.0'
|
|
34
|
+
|
|
35
|
+
when '9.0'
|
|
33
36
|
# All flag defaults currently reflect 9.0 behavior.
|
|
37
|
+
|
|
34
38
|
else
|
|
35
39
|
raise ArgumentError, "Unknown version: #{version}"
|
|
36
40
|
end
|
data/lib/mongoid/config.rb
CHANGED
|
@@ -110,6 +110,31 @@ module Mongoid
|
|
|
110
110
|
# to `:global_thread_pool`.
|
|
111
111
|
option :global_executor_concurrency, default: nil
|
|
112
112
|
|
|
113
|
+
# When this flag is true, it will be possible to change the parent of a
|
|
114
|
+
# record in a "has_many" association by passing the child record's id in the
|
|
115
|
+
# nested attributes for another parent record.
|
|
116
|
+
#
|
|
117
|
+
# When this flag is false, attempting to change the parent of a record in a
|
|
118
|
+
# "has-many" association via nested attributes will raise an error.
|
|
119
|
+
#
|
|
120
|
+
# The default is `true`. Note that allowing reparenting via nested attributes
|
|
121
|
+
# is a potential security risk, since it could allow a malicious user to move
|
|
122
|
+
# records that they do not own to a parent record that they do own.
|
|
123
|
+
#
|
|
124
|
+
# This option will default to `false` in Mongoid 9.1, and will be removed
|
|
125
|
+
# in Mongoid 10.
|
|
126
|
+
option :allow_reparenting_via_nested_attributes, default: true
|
|
127
|
+
|
|
128
|
+
# When this flag is true, any documents in associations with `autosave: true`
|
|
129
|
+
# will be saved even if they have not been changed. When this flag is false,
|
|
130
|
+
# only autosaved documents that have been changed will be saved. The default
|
|
131
|
+
# is `true`.
|
|
132
|
+
#
|
|
133
|
+
# This option will default to `false` in Mongoid 9.1, and will be removed
|
|
134
|
+
# in Mongoid 10, with the only behavior at that point being as if this
|
|
135
|
+
# option were set to `false`.
|
|
136
|
+
option :autosave_saves_unchanged_documents, default: true
|
|
137
|
+
|
|
113
138
|
# When this flag is false, a document will become read-only only once the
|
|
114
139
|
# #readonly! method is called, and an error will be raised on attempting
|
|
115
140
|
# to save or update such documents, instead of just on delete. When this
|
data/lib/mongoid/version.rb
CHANGED
data/lib/mongoid/warnings.rb
CHANGED
|
@@ -33,5 +33,9 @@ module Mongoid
|
|
|
33
33
|
warning :symbol_type_deprecated, 'The BSON Symbol type is deprecated by MongoDB. Please use String or StringifiedSymbol field types instead of the Symbol field type.'
|
|
34
34
|
warning :legacy_readonly, 'The readonly! method will only mark the document readonly when the legacy_readonly feature flag is switched off.'
|
|
35
35
|
warning :mutable_ids, 'Ignoring updates to immutable attribute `_id`. Please set Mongoid::Config.immutable_ids to true and update your code so that `_id` is never updated.'
|
|
36
|
+
warning :reparenting_via_nested_attributes, 'Reparenting documents via nested attributes is insecure and is deprecated. Set Mongoid.allow_reparenting_via_nested_attributes to false and update your code to avoid reparenting documents via nested attributes.'
|
|
37
|
+
warning :autosave_saves_unchanged_documents, "Autosave associations are currently configured to save documents even if they haven't changed. " \
|
|
38
|
+
'This legacy behavior is deprecated. Set Mongoid.autosave_saves_unchanged_documents to false to ' \
|
|
39
|
+
'skip saving unchanged documents in autosave associations.'
|
|
36
40
|
end
|
|
37
41
|
end
|
|
@@ -37,37 +37,34 @@ describe 'Mongoid application tests' do
|
|
|
37
37
|
FileUtils.mkdir_p(TMP_BASE)
|
|
38
38
|
end
|
|
39
39
|
|
|
40
|
-
context '
|
|
40
|
+
context 'generated application' do
|
|
41
41
|
context 'sinatra' do
|
|
42
42
|
it 'runs' do
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
clone_application(
|
|
46
|
-
'https://github.com/mongoid/mongoid-demo',
|
|
47
|
-
subdir: 'sinatra-minimal',
|
|
48
|
-
) do
|
|
49
|
-
|
|
43
|
+
create_sinatra_app('mongoid-sinatra-test') do
|
|
50
44
|
# JRuby needs a long timeout
|
|
51
45
|
start_app(%w(bundle exec ruby app.rb), 4567, 40) do |port|
|
|
52
46
|
uri = URI.parse('http://localhost:4567/posts')
|
|
53
47
|
resp = JSON.parse(uri.open.read)
|
|
54
48
|
|
|
55
49
|
resp.should == []
|
|
56
|
-
|
|
57
50
|
end
|
|
58
51
|
end
|
|
59
52
|
end
|
|
60
53
|
end
|
|
61
54
|
|
|
62
55
|
context 'rails-api' do
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
'
|
|
68
|
-
|
|
69
|
-
|
|
56
|
+
before(:all) do
|
|
57
|
+
# Rails 6.0/6.1 have Logger issues on Ruby 3.1+
|
|
58
|
+
# Rails < 7.1 have concurrent-ruby issues on Ruby 4.0+
|
|
59
|
+
if RUBY_VERSION >= '4.0' && SpecConfig.instance.rails_version < '7.1'
|
|
60
|
+
skip 'Rails < 7.1 is not compatible with Ruby 4.0+. Set RAILS=7.1 or higher.'
|
|
61
|
+
elsif RUBY_VERSION >= '3.1' && SpecConfig.instance.rails_version < '6.1'
|
|
62
|
+
skip 'Rails < 6.1 is not compatible with Ruby 3.1+. Set RAILS=6.1 or higher.'
|
|
63
|
+
end
|
|
64
|
+
end
|
|
70
65
|
|
|
66
|
+
it 'runs' do
|
|
67
|
+
create_rails_api_app('mongoid-rails-api-test') do
|
|
71
68
|
# JRuby needs a long timeout
|
|
72
69
|
start_app(%w(bundle exec rails s), 3000, 50) do |port|
|
|
73
70
|
uri = URI.parse('http://localhost:3000/posts')
|
|
@@ -114,7 +111,7 @@ describe 'Mongoid application tests' do
|
|
|
114
111
|
|
|
115
112
|
Dir.chdir(TMP_BASE) do
|
|
116
113
|
FileUtils.rm_rf(name)
|
|
117
|
-
check_call(insert_rails_gem_version(%W(rails new #{name} --skip-spring --skip-active-record)), env:
|
|
114
|
+
check_call(insert_rails_gem_version(%W(rails new #{name} --skip-spring --skip-active-record)), env: rails_env)
|
|
118
115
|
|
|
119
116
|
Dir.chdir(name) do
|
|
120
117
|
adjust_rails_defaults
|
|
@@ -126,10 +123,149 @@ describe 'Mongoid application tests' do
|
|
|
126
123
|
end
|
|
127
124
|
end
|
|
128
125
|
|
|
126
|
+
def create_sinatra_app(name)
|
|
127
|
+
Dir.chdir(TMP_BASE) do
|
|
128
|
+
FileUtils.rm_rf(name)
|
|
129
|
+
FileUtils.mkdir_p(name)
|
|
130
|
+
|
|
131
|
+
Dir.chdir(name) do
|
|
132
|
+
# Create minimal Sinatra app with Post model
|
|
133
|
+
File.open('app.rb', 'w') do |f|
|
|
134
|
+
f.write(<<~RUBY)
|
|
135
|
+
require 'sinatra'
|
|
136
|
+
require 'mongoid'
|
|
137
|
+
require 'json'
|
|
138
|
+
|
|
139
|
+
Mongoid.load!('config/mongoid.yml')
|
|
140
|
+
|
|
141
|
+
class Post
|
|
142
|
+
include Mongoid::Document
|
|
143
|
+
field :title, type: String
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
get '/posts' do
|
|
147
|
+
content_type :json
|
|
148
|
+
Post.all.to_json
|
|
149
|
+
end
|
|
150
|
+
RUBY
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Create Gemfile
|
|
154
|
+
File.open('Gemfile', 'w') do |f|
|
|
155
|
+
f.write(<<~RUBY)
|
|
156
|
+
source 'https://rubygems.org'
|
|
157
|
+
gem 'sinatra'
|
|
158
|
+
gem 'rackup'
|
|
159
|
+
gem 'mongoid', path: '#{File.expand_path(BASE)}'
|
|
160
|
+
gem 'puma'
|
|
161
|
+
RUBY
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
FileUtils.mkdir_p('config')
|
|
165
|
+
write_mongoid_yml
|
|
166
|
+
check_call(%w(bundle install), env: clean_env)
|
|
167
|
+
|
|
168
|
+
yield
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def create_rails_api_app(name)
|
|
174
|
+
install_rails
|
|
175
|
+
|
|
176
|
+
Dir.chdir(TMP_BASE) do
|
|
177
|
+
FileUtils.rm_rf(name)
|
|
178
|
+
check_call(insert_rails_gem_version(%W(rails new #{name} --api --skip-spring --skip-active-record)), env: rails_env)
|
|
179
|
+
|
|
180
|
+
Dir.chdir(name) do
|
|
181
|
+
adjust_rails_defaults
|
|
182
|
+
adjust_app_gemfile
|
|
183
|
+
|
|
184
|
+
# Create Post model
|
|
185
|
+
File.open('app/models/post.rb', 'w') do |f|
|
|
186
|
+
f.write(<<~RUBY)
|
|
187
|
+
class Post
|
|
188
|
+
include Mongoid::Document
|
|
189
|
+
field :title, type: String
|
|
190
|
+
end
|
|
191
|
+
RUBY
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# Create PostsController
|
|
195
|
+
File.open('app/controllers/posts_controller.rb', 'w') do |f|
|
|
196
|
+
f.write(<<~RUBY)
|
|
197
|
+
class PostsController < ApplicationController
|
|
198
|
+
def index
|
|
199
|
+
render json: Post.all
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
RUBY
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# Add route
|
|
206
|
+
routes_content = File.read('config/routes.rb')
|
|
207
|
+
routes_content.sub!(/Rails\.application\.routes\.draw do\n/,
|
|
208
|
+
"Rails.application.routes.draw do\n resources :posts, only: [:index]\n")
|
|
209
|
+
File.open('config/routes.rb', 'w') { |f| f.write(routes_content) }
|
|
210
|
+
|
|
211
|
+
write_mongoid_yml
|
|
212
|
+
check_call(%w(bundle install), env: clean_env)
|
|
213
|
+
|
|
214
|
+
yield
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def create_rails_rake_test_app(name)
|
|
220
|
+
install_rails
|
|
221
|
+
|
|
222
|
+
Dir.chdir(TMP_BASE) do
|
|
223
|
+
FileUtils.rm_rf(name)
|
|
224
|
+
check_call(insert_rails_gem_version(%W(rails new #{name} --api --skip-spring --skip-active-record)), env: rails_env)
|
|
225
|
+
|
|
226
|
+
Dir.chdir(name) do
|
|
227
|
+
adjust_rails_defaults
|
|
228
|
+
adjust_app_gemfile
|
|
229
|
+
|
|
230
|
+
# Create Post model with index
|
|
231
|
+
File.open('app/models/post.rb', 'w') do |f|
|
|
232
|
+
f.write(<<~RUBY)
|
|
233
|
+
class Post
|
|
234
|
+
include Mongoid::Document
|
|
235
|
+
include Mongoid::Timestamps
|
|
236
|
+
field :subject, type: String
|
|
237
|
+
field :message, type: String
|
|
238
|
+
|
|
239
|
+
index subject: 1
|
|
240
|
+
end
|
|
241
|
+
RUBY
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
# Create Comment model
|
|
245
|
+
File.open('app/models/comment.rb', 'w') do |f|
|
|
246
|
+
f.write(<<~RUBY)
|
|
247
|
+
class Comment
|
|
248
|
+
include Mongoid::Document
|
|
249
|
+
include Mongoid::Timestamps
|
|
250
|
+
belongs_to :post
|
|
251
|
+
end
|
|
252
|
+
RUBY
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
write_mongoid_yml
|
|
256
|
+
check_call(%w(bundle install), env: clean_env)
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
|
|
129
261
|
context 'new application - rails' do
|
|
130
262
|
before(:all) do
|
|
131
|
-
|
|
132
|
-
|
|
263
|
+
# Rails 6.0/6.1 have Logger issues on Ruby 3.1+
|
|
264
|
+
# Rails < 7.1 have concurrent-ruby issues on Ruby 4.0+
|
|
265
|
+
if RUBY_VERSION >= '4.0' && SpecConfig.instance.rails_version < '7.1'
|
|
266
|
+
skip 'Rails < 7.1 is not compatible with Ruby 4.0+. Set RAILS=7.1 or higher.'
|
|
267
|
+
elsif RUBY_VERSION >= '3.1' && SpecConfig.instance.rails_version < '6.1'
|
|
268
|
+
skip 'Rails < 6.1 is not compatible with Ruby 3.1+. Set RAILS=6.1 or higher.'
|
|
133
269
|
end
|
|
134
270
|
end
|
|
135
271
|
|
|
@@ -186,17 +322,31 @@ describe 'Mongoid application tests' do
|
|
|
186
322
|
if (rails_version = SpecConfig.instance.rails_version) == 'master'
|
|
187
323
|
else
|
|
188
324
|
check_call(%w(gem list))
|
|
325
|
+
|
|
326
|
+
# Rails 6.0 and 6.1 need logger gem on Ruby 2.7+ due to stdlib changes
|
|
327
|
+
if rails_version.to_f < 7.0 && RUBY_VERSION >= '2.7'
|
|
328
|
+
check_call(%w(gem install logger --no-document))
|
|
329
|
+
end
|
|
330
|
+
|
|
189
331
|
check_call(%w(gem install rails --no-document --force -v) + ["~> #{rails_version}.0"])
|
|
190
332
|
end
|
|
191
333
|
end
|
|
192
334
|
|
|
193
|
-
context '
|
|
335
|
+
context 'generated test applications' do
|
|
336
|
+
before(:all) do
|
|
337
|
+
# Rails 6.0/6.1 have Logger issues on Ruby 3.1+
|
|
338
|
+
# Rails < 7.1 have concurrent-ruby issues on Ruby 4.0+
|
|
339
|
+
if RUBY_VERSION >= '4.0' && SpecConfig.instance.rails_version < '7.1'
|
|
340
|
+
skip 'Rails < 7.1 is not compatible with Ruby 4.0+. Set RAILS=7.1 or higher.'
|
|
341
|
+
elsif RUBY_VERSION >= '3.1' && SpecConfig.instance.rails_version < '6.1'
|
|
342
|
+
skip 'Rails < 6.1 is not compatible with Ruby 3.1+. Set RAILS=6.1 or higher.'
|
|
343
|
+
end
|
|
344
|
+
end
|
|
345
|
+
|
|
194
346
|
let(:client) { Mongoid.default_client }
|
|
195
347
|
|
|
196
348
|
describe 'create_indexes rake task' do
|
|
197
349
|
|
|
198
|
-
APP_PATH = File.join(File.dirname(__FILE__), '../../test-apps/rails-api')
|
|
199
|
-
|
|
200
350
|
%w(development production).each do |rails_env|
|
|
201
351
|
context "in #{rails_env}" do
|
|
202
352
|
|
|
@@ -207,20 +357,11 @@ describe 'Mongoid application tests' do
|
|
|
207
357
|
clean_env.merge(RAILS_ENV: rails_env, AUTOLOADER: autoloader)
|
|
208
358
|
end
|
|
209
359
|
|
|
360
|
+
let(:app_name) { "mongoid-rake-test-#{rails_env}-#{autoloader}" }
|
|
361
|
+
let(:app_path) { File.join(TMP_BASE, app_name) }
|
|
362
|
+
|
|
210
363
|
before do
|
|
211
|
-
|
|
212
|
-
remove_bundler_req
|
|
213
|
-
|
|
214
|
-
if BSON::Environment.jruby?
|
|
215
|
-
# Remove existing Gemfile.lock - see
|
|
216
|
-
# https://github.com/rubygems/rubygems/issues/3231
|
|
217
|
-
require 'fileutils'
|
|
218
|
-
FileUtils.rm_f('Gemfile.lock')
|
|
219
|
-
end
|
|
220
|
-
|
|
221
|
-
check_call(%w(bundle install), env: env)
|
|
222
|
-
write_mongoid_yml
|
|
223
|
-
end
|
|
364
|
+
create_rails_rake_test_app(app_name)
|
|
224
365
|
|
|
225
366
|
client['posts'].drop
|
|
226
367
|
client['posts'].create
|
|
@@ -233,7 +374,7 @@ describe 'Mongoid application tests' do
|
|
|
233
374
|
index.should be nil
|
|
234
375
|
|
|
235
376
|
check_call(%w(bundle exec rake db:mongoid:create_indexes -t),
|
|
236
|
-
cwd:
|
|
377
|
+
cwd: app_path, env: env)
|
|
237
378
|
|
|
238
379
|
index = client['posts'].indexes.detect do |index|
|
|
239
380
|
index['key'] == {'subject' => 1}
|
|
@@ -317,6 +458,12 @@ describe 'Mongoid application tests' do
|
|
|
317
458
|
line =~ /mongoid/
|
|
318
459
|
end
|
|
319
460
|
gemfile_lines << "gem 'mongoid', path: '#{File.expand_path(BASE)}'\n"
|
|
461
|
+
|
|
462
|
+
# Rails 6.0 and 6.1 need logger gem on Ruby 2.7+ due to stdlib changes
|
|
463
|
+
if rails_version && rails_version.to_f < 7.0 && RUBY_VERSION >= '2.7'
|
|
464
|
+
gemfile_lines << "gem 'logger'\n"
|
|
465
|
+
end
|
|
466
|
+
|
|
320
467
|
if rails_version
|
|
321
468
|
gemfile_lines.delete_if do |line|
|
|
322
469
|
line =~ /gem ['"]rails['"]/
|
|
@@ -376,6 +523,17 @@ describe 'Mongoid application tests' do
|
|
|
376
523
|
@clean_env ||= Hash[ENV.keys.grep(/BUNDLE|RUBYOPT/).map { |k| [k, nil ] }]
|
|
377
524
|
end
|
|
378
525
|
|
|
526
|
+
def rails_env
|
|
527
|
+
# For Rails 6.0/6.1 on Ruby 2.7+, we need to require logger
|
|
528
|
+
# to fix "uninitialized constant ActiveSupport::LoggerThreadSafeLevel::Logger"
|
|
529
|
+
env = clean_env.dup
|
|
530
|
+
rails_version = SpecConfig.instance.rails_version
|
|
531
|
+
if rails_version && rails_version.to_f < 7.0 && RUBY_VERSION >= '2.7'
|
|
532
|
+
env['RUBYOPT'] = '-rlogger'
|
|
533
|
+
end
|
|
534
|
+
env
|
|
535
|
+
end
|
|
536
|
+
|
|
379
537
|
def wait_for_port(port, timeout, process)
|
|
380
538
|
deadline = Mongoid::Utils.monotonic_time + timeout
|
|
381
539
|
loop do
|
|
@@ -5,6 +5,53 @@ require "spec_helper"
|
|
|
5
5
|
require_relative './referenced/has_many_models'
|
|
6
6
|
require_relative './referenced/has_one_models'
|
|
7
7
|
|
|
8
|
+
# Models for the MONGOID-5751 regression test: after_save callbacks must not
|
|
9
|
+
# fire for pre-existing, unchanged documents when autosave: true cascades a
|
|
10
|
+
# save from a parent to its children.
|
|
11
|
+
module AutoSaveMONGOID5751
|
|
12
|
+
class Table
|
|
13
|
+
include Mongoid::Document
|
|
14
|
+
|
|
15
|
+
has_many :rows, autosave: true, class_name: 'AutoSaveMONGOID5751::Row',
|
|
16
|
+
inverse_of: :table
|
|
17
|
+
|
|
18
|
+
class << self
|
|
19
|
+
attr_accessor :after_save_count
|
|
20
|
+
end
|
|
21
|
+
self.after_save_count = 0
|
|
22
|
+
|
|
23
|
+
after_save { self.class.after_save_count += 1 }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
class Row
|
|
27
|
+
include Mongoid::Document
|
|
28
|
+
|
|
29
|
+
belongs_to :table, class_name: 'AutoSaveMONGOID5751::Table', inverse_of: :rows
|
|
30
|
+
has_many :cells, autosave: true, class_name: 'AutoSaveMONGOID5751::Cell',
|
|
31
|
+
inverse_of: :row
|
|
32
|
+
|
|
33
|
+
class << self
|
|
34
|
+
attr_accessor :after_save_count
|
|
35
|
+
end
|
|
36
|
+
self.after_save_count = 0
|
|
37
|
+
|
|
38
|
+
after_save { self.class.after_save_count += 1 }
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
class Cell
|
|
42
|
+
include Mongoid::Document
|
|
43
|
+
|
|
44
|
+
belongs_to :row, class_name: 'AutoSaveMONGOID5751::Row', inverse_of: :cells
|
|
45
|
+
|
|
46
|
+
class << self
|
|
47
|
+
attr_accessor :after_save_count
|
|
48
|
+
end
|
|
49
|
+
self.after_save_count = 0
|
|
50
|
+
|
|
51
|
+
after_save { self.class.after_save_count += 1 }
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
8
55
|
describe Mongoid::Association::Referenced::AutoSave do
|
|
9
56
|
|
|
10
57
|
describe ".auto_save" do
|
|
@@ -399,6 +446,57 @@ describe Mongoid::Association::Referenced::AutoSave do
|
|
|
399
446
|
expect(harvest.reload.season).to eq('Fall')
|
|
400
447
|
end
|
|
401
448
|
end
|
|
449
|
+
|
|
450
|
+
# Regression test for MONGOID-5751: after_save must not fire for
|
|
451
|
+
# pre-existing, unchanged documents that are merely loaded into memory
|
|
452
|
+
# as a side-effect of the autosave traversal.
|
|
453
|
+
context 'when a parent with existing children has a new child added' do
|
|
454
|
+
before do
|
|
455
|
+
# Persist a table with 3 pre-existing rows, each with 3 cells.
|
|
456
|
+
table = AutoSaveMONGOID5751::Table.create!
|
|
457
|
+
3.times do
|
|
458
|
+
row = table.rows.create!
|
|
459
|
+
3.times { row.cells.create! }
|
|
460
|
+
end
|
|
461
|
+
|
|
462
|
+
# Reset counters so only the saves triggered by the call below are
|
|
463
|
+
# measured.
|
|
464
|
+
AutoSaveMONGOID5751::Table.after_save_count = 0
|
|
465
|
+
AutoSaveMONGOID5751::Row.after_save_count = 0
|
|
466
|
+
AutoSaveMONGOID5751::Cell.after_save_count = 0
|
|
467
|
+
|
|
468
|
+
# Reload the table fresh from the database, then append exactly one
|
|
469
|
+
# new row (with one new cell) and persist.
|
|
470
|
+
reloaded = AutoSaveMONGOID5751::Table.find(table.id)
|
|
471
|
+
new_row = reloaded.rows.build
|
|
472
|
+
new_row.cells.build
|
|
473
|
+
reloaded.save!
|
|
474
|
+
end
|
|
475
|
+
|
|
476
|
+
it 'fires after_save once for the parent table' do
|
|
477
|
+
expect(AutoSaveMONGOID5751::Table.after_save_count).to eq(1)
|
|
478
|
+
end
|
|
479
|
+
|
|
480
|
+
it 'fires after_save only for the newly added cell' do
|
|
481
|
+
expect(AutoSaveMONGOID5751::Cell.after_save_count).to eq(1)
|
|
482
|
+
end
|
|
483
|
+
|
|
484
|
+
context 'when autosave_saves_unchanged_documents is true' do
|
|
485
|
+
config_override :autosave_saves_unchanged_documents, true
|
|
486
|
+
|
|
487
|
+
it 'fires after_save for all new and pre-existing rows' do
|
|
488
|
+
expect(AutoSaveMONGOID5751::Row.after_save_count).to eq(4)
|
|
489
|
+
end
|
|
490
|
+
end
|
|
491
|
+
|
|
492
|
+
context 'when autosave_saves_unchanged_documents is false' do
|
|
493
|
+
config_override :autosave_saves_unchanged_documents, false
|
|
494
|
+
|
|
495
|
+
it 'fires after_save only for the newly added row, not for pre-existing rows' do
|
|
496
|
+
expect(AutoSaveMONGOID5751::Row.after_save_count).to eq(1)
|
|
497
|
+
end
|
|
498
|
+
end
|
|
499
|
+
end
|
|
402
500
|
end
|
|
403
501
|
end
|
|
404
502
|
end
|
|
@@ -164,24 +164,47 @@ describe Mongoid::Attributes::Nested do
|
|
|
164
164
|
end
|
|
165
165
|
end
|
|
166
166
|
|
|
167
|
-
context
|
|
168
|
-
|
|
167
|
+
context 'when the relation is a has-many' do
|
|
169
168
|
before do
|
|
170
169
|
Person.send(:undef_method, :posts_attributes=)
|
|
171
170
|
Person.accepts_nested_attributes_for :posts
|
|
172
171
|
end
|
|
173
172
|
|
|
174
|
-
|
|
175
|
-
|
|
173
|
+
context 'when adding a new document to a relation' do
|
|
174
|
+
let(:person) do
|
|
175
|
+
Person.new(posts_attributes: { '1' => { title: 'First' } })
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
it 'sets the nested attributes' do
|
|
179
|
+
expect(person.posts.first.title).to eq('First')
|
|
180
|
+
end
|
|
176
181
|
end
|
|
177
182
|
|
|
178
|
-
|
|
179
|
-
|
|
183
|
+
context 'when adding an existing document to a relation' do
|
|
184
|
+
let(:person1) { Person.create! }
|
|
185
|
+
let(:post) { person1.posts.create!(title: 'Sample Post') }
|
|
186
|
+
|
|
187
|
+
let(:person2) { Person.create!(posts_attributes: { '0' => { id: post.id, title: 'Reparented!' } }) }
|
|
188
|
+
|
|
189
|
+
context 'when allow_reparenting_via_nested_attributes is false' do
|
|
190
|
+
config_override :allow_reparenting_via_nested_attributes, false
|
|
191
|
+
|
|
192
|
+
it 'raises a document not found error' do
|
|
193
|
+
expect { person2 }.to raise_error(Mongoid::Errors::DocumentNotFound)
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
context 'when allow_reparenting_via_nested_attributes is true' do
|
|
198
|
+
config_override :allow_reparenting_via_nested_attributes, true
|
|
199
|
+
|
|
200
|
+
it 'sets the nested attributes' do
|
|
201
|
+
expect(person2.posts.map(&:title)).to eq([ 'Reparented!' ])
|
|
202
|
+
end
|
|
203
|
+
end
|
|
180
204
|
end
|
|
181
205
|
end
|
|
182
206
|
|
|
183
|
-
context
|
|
184
|
-
|
|
207
|
+
context 'when the relation is a has-and-belongs-to-many' do
|
|
185
208
|
before do
|
|
186
209
|
Person.send(:undef_method, :preferences_attributes=)
|
|
187
210
|
Person.accepts_nested_attributes_for :preferences
|
|
@@ -1355,10 +1378,11 @@ describe Mongoid::Attributes::Nested do
|
|
|
1355
1378
|
context "when the ids do not match" do
|
|
1356
1379
|
|
|
1357
1380
|
it "raises an error" do
|
|
1358
|
-
expect
|
|
1381
|
+
expect do
|
|
1359
1382
|
person.addresses_attributes =
|
|
1360
|
-
{
|
|
1361
|
-
|
|
1383
|
+
{ 'foo' => { 'id' => 'test', 'street' => 'Test' } }
|
|
1384
|
+
end.to raise_error(Mongoid::Errors::DocumentNotFound,
|
|
1385
|
+
/Document\(s\) not found for class Address/)
|
|
1362
1386
|
end
|
|
1363
1387
|
end
|
|
1364
1388
|
end
|
|
@@ -3012,12 +3036,12 @@ describe Mongoid::Attributes::Nested do
|
|
|
3012
3036
|
end
|
|
3013
3037
|
|
|
3014
3038
|
it "raises a document not found error" do
|
|
3015
|
-
expect
|
|
3039
|
+
expect do
|
|
3016
3040
|
person.posts_attributes =
|
|
3017
|
-
{
|
|
3018
|
-
{
|
|
3019
|
-
|
|
3020
|
-
|
|
3041
|
+
{ '0' =>
|
|
3042
|
+
{ 'id' => BSON::ObjectId.new.to_s, 'title' => 'Rogue' } }
|
|
3043
|
+
end.to raise_error(Mongoid::Errors::DocumentNotFound,
|
|
3044
|
+
/Document\(s\) not found for class Post/)
|
|
3021
3045
|
end
|
|
3022
3046
|
end
|
|
3023
3047
|
end
|
|
@@ -3048,10 +3072,11 @@ describe Mongoid::Attributes::Nested do
|
|
|
3048
3072
|
context "when the ids do not match" do
|
|
3049
3073
|
|
|
3050
3074
|
it "raises an error" do
|
|
3051
|
-
expect
|
|
3075
|
+
expect do
|
|
3052
3076
|
person.posts_attributes =
|
|
3053
|
-
{
|
|
3054
|
-
|
|
3077
|
+
{ 'foo' => { 'id' => 'test', 'title' => 'Test' } }
|
|
3078
|
+
end.to raise_error(Mongoid::Errors::DocumentNotFound,
|
|
3079
|
+
/Document\(s\) not found for class Post/)
|
|
3055
3080
|
end
|
|
3056
3081
|
end
|
|
3057
3082
|
end
|
|
@@ -3765,10 +3790,11 @@ describe Mongoid::Attributes::Nested do
|
|
|
3765
3790
|
context "when the ids do not match" do
|
|
3766
3791
|
|
|
3767
3792
|
it "raises an error" do
|
|
3768
|
-
expect
|
|
3793
|
+
expect do
|
|
3769
3794
|
person.preferences_attributes =
|
|
3770
|
-
{
|
|
3771
|
-
|
|
3795
|
+
{ 'foo' => { 'id' => 'test', 'name' => 'Test' } }
|
|
3796
|
+
end.to raise_error(Mongoid::Errors::DocumentNotFound,
|
|
3797
|
+
/Document\(s\) not found for class Preference/)
|
|
3772
3798
|
end
|
|
3773
3799
|
end
|
|
3774
3800
|
end
|
|
@@ -64,9 +64,16 @@ describe Mongoid::Errors::DocumentNotFound do
|
|
|
64
64
|
end
|
|
65
65
|
|
|
66
66
|
it "contains the problem in the message" do
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
67
|
+
# Ruby 3.4+ changed Hash#inspect format from {:name=>"syd"} to {name: "syd"}
|
|
68
|
+
if RUBY_VERSION >= '3.4'
|
|
69
|
+
expect(error.message).to include(
|
|
70
|
+
"Document not found for class Person with attributes {name: \"syd\"}."
|
|
71
|
+
)
|
|
72
|
+
else
|
|
73
|
+
expect(error.message).to include(
|
|
74
|
+
"Document not found for class Person with attributes {:name=>\"syd\"}."
|
|
75
|
+
)
|
|
76
|
+
end
|
|
70
77
|
end
|
|
71
78
|
|
|
72
79
|
it "contains the summary in the message" do
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: mongoid
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 9.0.
|
|
4
|
+
version: 9.0.11
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- The MongoDB Ruby Team
|
|
@@ -75,6 +75,20 @@ dependencies:
|
|
|
75
75
|
- - "<"
|
|
76
76
|
- !ruby/object:Gem::Version
|
|
77
77
|
version: '2.0'
|
|
78
|
+
- !ruby/object:Gem::Dependency
|
|
79
|
+
name: ostruct
|
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
|
81
|
+
requirements:
|
|
82
|
+
- - ">="
|
|
83
|
+
- !ruby/object:Gem::Version
|
|
84
|
+
version: '0'
|
|
85
|
+
type: :runtime
|
|
86
|
+
prerelease: false
|
|
87
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
88
|
+
requirements:
|
|
89
|
+
- - ">="
|
|
90
|
+
- !ruby/object:Gem::Version
|
|
91
|
+
version: '0'
|
|
78
92
|
- !ruby/object:Gem::Dependency
|
|
79
93
|
name: bson
|
|
80
94
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -1231,7 +1245,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
1231
1245
|
- !ruby/object:Gem::Version
|
|
1232
1246
|
version: 1.3.6
|
|
1233
1247
|
requirements: []
|
|
1234
|
-
rubygems_version: 4.0.
|
|
1248
|
+
rubygems_version: 4.0.10
|
|
1235
1249
|
specification_version: 4
|
|
1236
1250
|
summary: Elegant Persistence in Ruby for MongoDB.
|
|
1237
1251
|
test_files:
|