Search code examples
ruby-on-rails-5arel

undefined method `and' for #<Arel::Attributes::Attribute


I'm having an issue getting a query to work.

I'm essentially trying to write something like the following SQL, with the literal 5 replaced with a variable:

SELECT * 
FROM "my_table"
WHERE 5 BETWEEN "my_table"."minimum" AND "my_table"."maximum"

This is what I have at the moment:

    MyModel.where(
      Arel::Nodes::Between.new(
        my_variable, (MyModel.arel_table[:minimum]).and(MyModel.arel_table[:maximum])
      )
    )

Please ignore the way I am using arel_table, the actual query has multiple joins and is more complex, but this is the most minimum reproducible example I have to demonstrate the problem.

The error, as in the subject of the question is as follows:

undefined method `and' for #<Arel::Attributes::Attribute:0x00007f55e15514f8>

Solution

  • and method is for Arel::Nodes::Node i.e. MyModel.arel_attribute[:name].eq(Arel::Nodes::Quoted.new('engineersmnky')) This is an Arel::Nodes::Equality and you can chain with and.

    That being said you can construct an Arel::Nodes::And for yourself via

    Arel::Nodes::And.new([left,right])
    

    Then we can pass this to the Between class like so

    Arel::Nodes::Between.new(
      Arel::Nodes::Quoted.new(my_variable),
      Arel::Nodes::And.new([
        MyModel.arel_table[:minimum], 
        MyModel.arel_table[:maximum]])
    )
    

    The Arel::Nodes::Quoted (also: Arel::Nodes.build_quoted(arg)) is not needed in your case since your my_variable is an Integer which can be visited and will be treated as an Arel::Nodes::SqlLiteral but I find it best to let arel decide how to handle the quoting in case your my_variable ends up being some other un-visitable Object

    There are other ways to create a Between and other ways to create an And depending on what objects you are dealing with.

    between is a Arel::Predication and these predications are available to Arel::Nodes::Attribute objects e.g.

     MyModel.arel_table[:minimum].between([1,6])
    

    and as mentioned is available to Arel::Nodes::Node and instances of this class provides a convenience method (create_and) for creating an And so we could do the following:

    Arel::Nodes::Node.new.create_and([
         MyModel.arel_table[:minimum],
         MyModel.arel_table[:maximum]])
    

    There are a number of other ways to hack this functionality together by using other Arel classes but this should get you headed in the right direction.