Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions activerecord/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
* Fix `unscoped(where: [columns])` removing the wrong bind values

When the `where` is called on a relation after a `or`, unscoping the column of that later `where` removed
bind values used by the `or` instead. (possibly other cases too)

```
Post.where(id: 1).or(Post.where(id: 2)).where(foo: 3).unscope(where: :foo).to_sql
# Currently:
# SELECT "posts".* FROM "posts" WHERE ("posts"."id" = 2 OR "posts"."id" = 3)
# With fix:
# SELECT "posts".* FROM "posts" WHERE ("posts"."id" = 1 OR "posts"."id" = 2)
```

*Maxime Handfield Lapointe*

* Values constructed using multi-parameter assignment will now use the
post-type-cast value for rendering in single-field form inputs.

Expand Down
3 changes: 2 additions & 1 deletion activerecord/lib/active_record/relation/where_clause.rb
Original file line number Diff line number Diff line change
Expand Up @@ -136,10 +136,11 @@ def except_predicates_and_binds(columns)
binds_index = 0

predicates = self.predicates.reject do |node|
binds_contains = node.grep(Arel::Nodes::BindParam).size if node.is_a?(Arel::Nodes::Node)

except = \
case node
when Arel::Nodes::Between, Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual, Arel::Nodes::LessThan, Arel::Nodes::LessThanOrEqual, Arel::Nodes::GreaterThan, Arel::Nodes::GreaterThanOrEqual
binds_contains = node.grep(Arel::Nodes::BindParam).size
subrelation = (node.left.kind_of?(Arel::Attributes::Attribute) ? node.left : node.right)
columns.include?(subrelation.name.to_s)
end
Expand Down
18 changes: 17 additions & 1 deletion activerecord/test/cases/relation/where_clause_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ class WhereClauseTest < ActiveRecord::TestCase
assert_equal expected, original.invert
end

test "accept removes binary predicates referencing a given column" do
test "except removes binary predicates referencing a given column" do
where_clause = WhereClause.new([
table["id"].in([1, 2, 3]),
table["name"].eq(bind_param),
Expand All @@ -111,6 +111,22 @@ class WhereClauseTest < ActiveRecord::TestCase
assert_equal expected, where_clause.except("id", "name")
end

test "except jumps over unhandled binds (like with OR) correctly" do
wcs = (0..9).map do |i|
WhereClause.new([table["id#{i}"].eq(bind_param)], [bind_attribute("id#{i}", i)])
end

wc = wcs[0] + wcs[1] + wcs[2].or(wcs[3]) + wcs[4] + wcs[5] + wcs[6].or(wcs[7]) + wcs[8] + wcs[9]

expected = wcs[0] + wcs[2].or(wcs[3]) + wcs[5] + wcs[6].or(wcs[7]) + wcs[9]
actual = wc.except("id1", "id2", "id4", "id7", "id8")

# Easier to read than the inspect of where_clause
assert_equal expected.ast.to_sql, actual.ast.to_sql
assert_equal expected.binds.map(&:value), actual.binds.map(&:value)
assert_equal expected, actual
end

test "ast groups its predicates with AND" do
predicates = [
table["id"].in([1, 2, 3]),
Expand Down