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

Fix scope_for_create to take only equality nodes

Related #39683.

If a scope has IN cluase, `scope_for_create` which is passed to
`assign_attributes` will include array values, and it will cause weird
behaviors.

That behavior was due to the `In` node was a subclass of the `Equality`
node (now the checking has migrated to `equality?` method).

i.e. It works unless IN queries, for example (BETWEEN):

```ruby
user = User.where(role: 0..1).find_or_initialize_by(name: "name")
```

I think that the `In` node was a subclass of the `Equality` node is
accidental, so the behavior is almost a bug to me.

This makes `scope_for_create` takes only equality nodes to fix the
issue.

Fixes #41295.
This commit is contained in:
Ryuta Kamizono 2021-02-03 10:52:53 +09:00
parent 5268006e70
commit 82a6bd7c89
3 changed files with 26 additions and 7 deletions

View file

@ -683,8 +683,7 @@ module ActiveRecord
end
def scope_for_create
hash = where_values_hash
hash.delete(klass.inheritance_column) if klass.finder_needs_type_condition?
hash = where_clause.to_h(klass.table_name, equality_only: true)
create_with_value.each { |k, v| hash[k.to_s] = v } unless create_with_value.empty?
hash
end

View file

@ -58,8 +58,8 @@ module ActiveRecord
end
end
def to_h(table_name = nil)
equalities(predicates).each_with_object({}) do |node, hash|
def to_h(table_name = nil, equality_only: false)
equalities(predicates, equality_only).each_with_object({}) do |node, hash|
next if table_name&.!= node.left.relation.name
name = node.left.name.to_s
value = extract_node_value(node.right)
@ -134,14 +134,14 @@ module ActiveRecord
attr_node
end
def equalities(predicates)
def equalities(predicates, equality_only)
equalities = []
predicates.each do |node|
if equality_node?(node)
if equality_only ? Arel::Nodes::Equality === node : equality_node?(node)
equalities << node
elsif node.is_a?(Arel::Nodes::And)
equalities.concat equalities(node.children)
equalities.concat equalities(node.children, equality_only)
end
end

View file

@ -212,6 +212,26 @@ class RelationScopingTest < ActiveRecord::TestCase
assert_includes Post.find(1).comments, new_comment
end
def test_scoped_create_with_where_with_array
new_comment = VerySpecialComment.where(label: [0, 1], post_id: 1).scoping do
VerySpecialComment.create body: "Wonderful world"
end
assert_equal 1, new_comment.post_id
assert_equal "default", new_comment.label
assert_includes Post.find(1).comments, new_comment
end
def test_scoped_create_with_where_with_range
new_comment = VerySpecialComment.where(label: 0..1, post_id: 1).scoping do
VerySpecialComment.create body: "Wonderful world"
end
assert_equal 1, new_comment.post_id
assert_equal "default", new_comment.label
assert_includes Post.find(1).comments, new_comment
end
def test_scoped_create_with_create_with
new_comment = VerySpecialComment.create_with(post_id: 1).scoping do
VerySpecialComment.create body: "Wonderful world"