diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 907decff1f..747204a2f1 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -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 diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb index 2243d654e2..640271493e 100644 --- a/activerecord/lib/active_record/relation/predicate_builder.rb +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -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? diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 39c0ffac96..3edd75094e 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -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 @@ -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. diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index 6d22c0be28..d8d99637c2 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -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)))