1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00

Fix update & destroy queries when default_scope is nillable with all_queries: true

This commit is contained in:
Nikita Vasilevsky 2021-09-16 14:12:11 -04:00
parent e21c38ed7f
commit 43d83b4423
No known key found for this signature in database
GPG key ID: 6A08045F084AAC9C
3 changed files with 61 additions and 6 deletions

View file

@ -431,9 +431,8 @@ module ActiveRecord
def _update_record(values, constraints) # :nodoc: def _update_record(values, constraints) # :nodoc:
constraints = constraints.map { |name, value| predicate_builder[name, value] } constraints = constraints.map { |name, value| predicate_builder[name, value] }
if default_scopes?(all_queries: true) default_constraint = build_default_constraint
constraints << default_scoped(all_queries: true).where_clause.ast constraints << default_constraint if default_constraint
end
if current_scope = self.global_current_scope if current_scope = self.global_current_scope
constraints << current_scope.where_clause.ast constraints << current_scope.where_clause.ast
@ -449,9 +448,8 @@ module ActiveRecord
def _delete_record(constraints) # :nodoc: def _delete_record(constraints) # :nodoc:
constraints = constraints.map { |name, value| predicate_builder[name, value] } constraints = constraints.map { |name, value| predicate_builder[name, value] }
if default_scopes?(all_queries: true) default_constraint = build_default_constraint
constraints << default_scoped(all_queries: true).where_clause.ast constraints << default_constraint if default_constraint
end
if current_scope = self.global_current_scope if current_scope = self.global_current_scope
constraints << current_scope.where_clause.ast constraints << current_scope.where_clause.ast
@ -479,6 +477,16 @@ module ActiveRecord
def discriminate_class_for_record(record) def discriminate_class_for_record(record)
self self
end end
# Called by +_update_record+ and +_delete_record+
# to build `where` clause from default scopes.
# Skips empty scopes.
def build_default_constraint
return unless default_scopes?(all_queries: true)
default_where_clause = default_scoped(all_queries: true).where_clause
default_where_clause.ast unless default_where_clause.empty?
end
end end
# Returns true if this object hasn't been saved yet -- that is, a record # Returns true if this object hasn't been saved yet -- that is, a record

View file

@ -94,6 +94,12 @@ class DefaultScopingTest < ActiveRecord::TestCase
assert_match(/mentor_id/, create_sql) assert_match(/mentor_id/, create_sql)
end end
def test_nilable_default_scope_with_all_queries_runs_on_create
create_sql = capture_sql { DeveloperWithDefaultNilableMentorScopeAllQueries.create!(name: "Nikita") }.first
assert_no_match(/AND$/, create_sql)
end
def test_default_scope_runs_on_select def test_default_scope_runs_on_select
Mentor.create! Mentor.create!
DeveloperwithDefaultMentorScopeNot.create!(name: "Eileen") DeveloperwithDefaultMentorScopeNot.create!(name: "Eileen")
@ -110,6 +116,13 @@ class DefaultScopingTest < ActiveRecord::TestCase
assert_match(/mentor_id/, select_sql) assert_match(/mentor_id/, select_sql)
end end
def test_nilable_default_scope_with_all_queries_runs_on_select
DeveloperWithDefaultNilableMentorScopeAllQueries.create!(name: "Nikita")
select_sql = capture_sql { DeveloperWithDefaultNilableMentorScopeAllQueries.find_by(name: "Nikita") }.first
assert_no_match(/AND$/, select_sql)
end
def test_default_scope_doesnt_run_on_update def test_default_scope_doesnt_run_on_update
Mentor.create! Mentor.create!
dev = DeveloperwithDefaultMentorScopeNot.create!(name: "Eileen") dev = DeveloperwithDefaultMentorScopeNot.create!(name: "Eileen")
@ -126,6 +139,13 @@ class DefaultScopingTest < ActiveRecord::TestCase
assert_match(/mentor_id/, update_sql) assert_match(/mentor_id/, update_sql)
end end
def test_nilable_default_scope_with_all_queries_runs_on_update
dev = DeveloperWithDefaultNilableMentorScopeAllQueries.create!(name: "Nikita")
update_sql = capture_sql { dev.update!(name: "Not Nikita") }.first
assert_no_match(/AND$/, update_sql)
end
def test_default_scope_doesnt_run_on_update_columns def test_default_scope_doesnt_run_on_update_columns
Mentor.create! Mentor.create!
dev = DeveloperwithDefaultMentorScopeNot.create!(name: "Eileen") dev = DeveloperwithDefaultMentorScopeNot.create!(name: "Eileen")
@ -142,6 +162,13 @@ class DefaultScopingTest < ActiveRecord::TestCase
assert_match(/mentor_id/, update_sql) assert_match(/mentor_id/, update_sql)
end end
def test_nilable_default_scope_with_all_queries_runs_on_update_columns
dev = DeveloperWithDefaultNilableMentorScopeAllQueries.create!(name: "Nikita")
update_sql = capture_sql { dev.update_columns(name: "Not Nikita") }.first
assert_no_match(/AND$/, update_sql)
end
def test_default_scope_doesnt_run_on_destroy def test_default_scope_doesnt_run_on_destroy
Mentor.create! Mentor.create!
dev = DeveloperwithDefaultMentorScopeNot.create!(name: "Eileen") dev = DeveloperwithDefaultMentorScopeNot.create!(name: "Eileen")
@ -158,6 +185,13 @@ class DefaultScopingTest < ActiveRecord::TestCase
assert_match(/mentor_id/, destroy_sql) assert_match(/mentor_id/, destroy_sql)
end end
def test_nilable_default_scope_with_all_queries_runs_on_destroy
dev = DeveloperWithDefaultNilableMentorScopeAllQueries.create!(name: "Nikita")
destroy_sql = capture_sql { dev.destroy }.first
assert_no_match(/AND$/, destroy_sql)
end
def test_default_scope_doesnt_run_on_reload def test_default_scope_doesnt_run_on_reload
Mentor.create! Mentor.create!
dev = DeveloperwithDefaultMentorScopeNot.create!(name: "Eileen") dev = DeveloperwithDefaultMentorScopeNot.create!(name: "Eileen")
@ -174,6 +208,13 @@ class DefaultScopingTest < ActiveRecord::TestCase
assert_match(/mentor_id/, reload_sql) assert_match(/mentor_id/, reload_sql)
end end
def test_nilable_default_scope_with_all_queries_runs_on_destroy
dev = DeveloperWithDefaultNilableMentorScopeAllQueries.create!(name: "Nikita")
reload_sql = capture_sql { dev.reload }.first
assert_no_match(/AND$/, reload_sql)
end
def test_default_scope_with_all_queries_doesnt_run_on_destroy_when_unscoped def test_default_scope_with_all_queries_doesnt_run_on_destroy_when_unscoped
dev = DeveloperWithDefaultMentorScopeAllQueries.create!(name: "Eileen", mentor_id: 2) dev = DeveloperWithDefaultMentorScopeAllQueries.create!(name: "Eileen", mentor_id: 2)
reload_sql = capture_sql { dev.reload({ unscoped: true }) }.first reload_sql = capture_sql { dev.reload({ unscoped: true }) }.first

View file

@ -162,6 +162,12 @@ class DeveloperWithDefaultMentorScopeAllQueries < ActiveRecord::Base
default_scope -> { where(mentor_id: 1) }, all_queries: true default_scope -> { where(mentor_id: 1) }, all_queries: true
end end
class DeveloperWithDefaultNilableMentorScopeAllQueries < ActiveRecord::Base
self.table_name = "developers"
firm_id = nil # Could be something like Current.mentor_id
default_scope -> { where(firm_id: firm_id) if firm_id }, all_queries: true
end
class DeveloperWithIncludes < ActiveRecord::Base class DeveloperWithIncludes < ActiveRecord::Base
self.table_name = "developers" self.table_name = "developers"
has_many :audit_logs, foreign_key: :developer_id has_many :audit_logs, foreign_key: :developer_id