Skip to content

Commit

Permalink
Allow unscope to be aware of table name qualified values
Browse files Browse the repository at this point in the history
It is possible to unscope only the column in the specified table.

```ruby
posts = Post.joins(:comments).group(:"posts.hidden")
posts = posts.where("posts.hidden": false, "comments.hidden": false)

posts.count
# => { false => 10 }

# unscope both hidden columns
posts.unscope(where: :hidden).count
# => { false => 11, true => 1 }

# unscope only comments.hidden column
posts.unscope(where: :"comments.hidden").count
# => { false => 11 }
```

Co-authored-by: Slava Korolev <korolvs@gmail.com>
  • Loading branch information
kamipo and slavadev committed May 7, 2020
1 parent d22216f commit 5136277
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 1 deletion.
22 changes: 22 additions & 0 deletions activerecord/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,25 @@
* Allow `unscope` to be aware of table name qualified values.

It is possible to unscope only the column in the specified table.

```ruby
posts = Post.joins(:comments).group(:"posts.hidden")
posts = posts.where("posts.hidden": false, "comments.hidden": false)

posts.count
# => { false => 10 }

# unscope both hidden columns
posts.unscope(where: :hidden).count
# => { false => 11, true => 1 }

# unscope only comments.hidden column
posts.unscope(where: :"comments.hidden").count
# => { false => 11 }
```

*Ryuta Kamizono*, *Slava Korolev*

* Fix `rewhere` to truly overwrite collided where clause by new where clause.

```ruby
Expand Down
4 changes: 4 additions & 0 deletions activerecord/lib/active_record/relation/predicate_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ def build_bind_attribute(column_name, value)
Arel::Nodes::BindParam.new(attr)
end

def resolve_arel_attribute(table_name, column_name)
table.associated_table(table_name).arel_attribute(column_name)
end

protected
def expand_from_hash(attributes)
return ["1=0"] if attributes.empty?
Expand Down
26 changes: 25 additions & 1 deletion activerecord/lib/active_record/relation/query_methods.rb
Original file line number Diff line number Diff line change
Expand Up @@ -483,7 +483,7 @@ def unscope!(*args) # :nodoc:
raise ArgumentError, "Hash arguments in .unscope(*args) must have :where as the key."
end

target_values = Array(target_value)
target_values = resolve_arel_attributes(Array.wrap(target_value))
self.where_clause = where_clause.except(*target_values)
end
else
Expand Down Expand Up @@ -1399,6 +1399,30 @@ def order_column(field)
end
end

def resolve_arel_attributes(attrs)
attrs.flat_map do |attr|
case attr
when Arel::Attributes::Attribute
attr
when Hash
attr.flat_map do |table, columns|
table = table.to_s
Array(columns).map do |column|
predicate_builder.resolve_arel_attribute(table, column)
end
end
else
attr = attr.to_s
if attr.include?(".")
table, column = attr.split(".", 2)
predicate_builder.resolve_arel_attribute(table, column)
else
attr
end
end
end
end

# Checks to make sure that the arguments are not blank. Note that if some
# blank-like object were initially passed into the query method, then this
# method will not raise an error.
Expand Down
30 changes: 30 additions & 0 deletions activerecord/test/cases/relations_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2050,6 +2050,36 @@ def test_unscope_specific_where_value
assert_equal 1, posts.unscope(where: :body).count
end

def test_unscope_with_aliased_column
posts = Post.where(author: authors(:mary), text: "hullo").order(:id)
assert_equal [posts(:misc_by_mary)], posts

posts = posts.unscope(where: :"posts.text")
assert_equal posts(:eager_other, :misc_by_mary, :other_by_mary), posts
end

def test_unscope_with_table_name_qualified_column
comments = Comment.joins(:post).where("posts.id": posts(:thinking))
assert_equal [comments(:does_it_hurt)], comments

comments = comments.where(id: comments(:greetings))
assert_empty comments

comments = comments.unscope(where: :"posts.id")
assert_equal [comments(:greetings)], comments
end

def test_unscope_with_table_name_qualified_hash
comments = Comment.joins(:post).where("posts.id": posts(:thinking))
assert_equal [comments(:does_it_hurt)], comments

comments = comments.where(id: comments(:greetings))
assert_empty comments

comments = comments.unscope(where: { posts: :id })
assert_equal [comments(:greetings)], comments
end

def test_unscope_with_arel_sql
posts = Post.where(Arel.sql("'Welcome to the weblog'").eq(Post.arel_attribute(:title)))

Expand Down

0 comments on commit 5136277

Please sign in to comment.