Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@eprothro
Last active August 29, 2015 14:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save eprothro/242c03f784b96fb1f674 to your computer and use it in GitHub Desktop.
Save eprothro/242c03f784b96fb1f674 to your computer and use it in GitHub Desktop.

ActiveRecord::QueryMethods#Unscoping

Issue 1

unscoping doesn't work with Arel Nodes as an argument if a symbol is used for the attribute identifier.

Works with Arel Nodes if a string is used for the attribute identifier

s = Model.arel_table['id'].lteq(5) # <= attribute identified with string, not symbol

s.class
=> Arel::Nodes::LessThanOrEqual

(r = Model.where(s)).to_sql
=> "SELECT \"models\".* FROM \"models\" WHERE (\"models\".\"id\" <= 5)"

(r2 = r.unscope(where: s)).to_sql
=> "SELECT \"models\".* FROM \"models\""

Note: there's nothing magical about unscoping with the Arel Node as the arg, the more basic string or symbol work as well

(r2 = r.unscope(where: 'id')).to_sql
=> "SELECT \"models\".* FROM \"models\""

r2 = r.unscope(where: :id)
=> "SELECT \"models\".* FROM \"models\""

Doesn't work with Arel Nodes if a symbol is used for the attribute identifier

s = Model.arel_table[:id].lteq(5)  # <= attribute identified with symbol, not string

(r = Model.where(s)).to_sql
=> "SELECT \"models\".* FROM \"models\" WHERE (\"models\".\"id\" <= 5)"

(r2 = r.unscope(where: s)).to_sql
=> "SELECT \"models\".* FROM \"models\" WHERE (\"models\".\"id\" <= 5)"

Note: there's nothing magical about unscoping with the Arel Node as the arg, the more basic string or symbol don't work either

(r2 = r.unscope(where: 'id')).to_sql
=> "SELECT \"models\".* FROM \"models\" WHERE (\"models\".\"id\" <= 5)"

r2 = r.unscope(where: :id)
=> "SELECT \"models\".* FROM \"models\" WHERE (\"models\".\"id\" <= 5)"

Issue 1 cause: comparison of attribute name, for rejection, always uses string

# active_record/relation/query_methods.rb#906

    def where_unscoping(target_value)
      target_value = target_value.to_s #<= SOURCE, exhibit A

      where_values.reject! do |rel|
        case rel
        when Arel::Nodes::Between, Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual, Arel::Nodes::LessThanOrEqual, Arel::Nodes::GreaterThanOrEqual
          subrelation = (rel.left.kind_of?(Arel::Attributes::Attribute) ? rel.left : rel.right)
          subrelation.name == target_value #<= SOURCE, exhibit B, will never match a symbol name introduced by an Arel::Node
        end
      end

      bind_values.reject! { |col,_| col.name == target_value }
    end

Issue 2

Unscoping doesn't work with certain Arel Node types (LessThan shown here)

s = Model.arel_table['id'].lt(5)

(r = Model.where(s)).to_sql
=> "SELECT \"models\".* FROM \"models\" WHERE (\"models\".\"id\" < 5)"

(r2 = r.unscope(where: s)).to_sql
=> "SELECT \"models\".* FROM \"models\" WHERE (\"models\".\"id\" < 5)"

Issue 2 source: only certain Arel Node types are compared for unscoping

# active_record/relation/query_methods.rb#906

    def where_unscoping(target_value)
      target_value = target_value.to_s

      where_values.reject! do |rel|
        case rel
        when Arel::Nodes::Between, Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual, Arel::Nodes::LessThanOrEqual, Arel::Nodes::GreaterThanOrEqual 
          # ^^^ SOURCE: e.g. Arel::Nodes::LessThan is not present in this list
          subrelation = (rel.left.kind_of?(Arel::Attributes::Attribute) ? rel.left : rel.right)
          subrelation.name == target_value
        end
      end

      bind_values.reject! { |col,_| col.name == target_value }
    end

Issue 3

unscoping doesn't work when array or string where args are used

(r = Model.where('id < ?', 5)).to_sql
=> "SELECT \"models\".* FROM \"models\" WHERE (id < 5)"

(r2 = r.unscope(where: s)).to_sql
=> "SELECT \"models\".* FROM \"models\" WHERE (id < 5)"
(r = Model.where('id < 5')).to_sql
=> "SELECT \"models\".* FROM \"models\" WHERE (id < 5)"

(r2 = r.unscope(where: s)).to_sql
=> "SELECT \"models\".* FROM \"models\" WHERE (id < 5)"

Issue 3 source

Only Arel::Node classes are compared for the rejection

# active_record/relation/query_methods.rb#906

    def where_unscoping(target_value)
      target_value = target_value.to_s

      where_values.reject! do |rel|
        case rel
        when Arel::Nodes::Between, Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual, Arel::Nodes::LessThanOrEqual, Arel::Nodes::GreaterThanOrEqual 
          # ^^^ SOURCE: e.g. only arel classes are compared for rejection
          subrelation = (rel.left.kind_of?(Arel::Attributes::Attribute) ? rel.left : rel.right)
          subrelation.name == target_value
        end
      end

      bind_values.reject! { |col,_| col.name == target_value }
    end
@eprothro
Copy link
Author

wip:

    def where_unscoping(target_value)
      target_value = target_value.to_s

      where_values.reject! do |rel|
        case rel
        when Arel::Nodes::Between, Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual, Arel::Nodes::LessThanOrEqual, Arel::Nodes::GreaterThanOrEqual
          subrelation = (rel.left.kind_of?(Arel::Attributes::Attribute) ? rel.left : rel.right)
          subrelation.name.to_s == target_value
        when /([^\s<>=]+)[\s<>=]/
          $1 == target_value
        else
          rel == target_value
        end
      end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment