Search code examples
mysqlruby-on-railsruby-on-rails-5rails-upgrade

Rails upgrade includes method behavior change


I've been digging for a couple days and haven't found a good explanation for this behavior change. I'm in the process of upgrading a Rails application from 3.2 to 5.2 this is code in a passing test from my rails 3 app.

 ps = Project.includes(:rentals).where('rentals.id IN (?)', [1,2,3,4])

this spits out a big old left joins SQL query.
but if i do the same query in rails 5 I get a mysql error

ActiveRecord::StatementInvalid (Mysql2::Error: Unknown column 'rentals.id' in 'where clause': SELECT  `projects`.* FROM `projects` WHERE (rentals.id IN (1,2,3,4)) LIMIT 11 /*application:ConHQ*/)

my understanding of includes is that it should perform separate queries unless the included table is referenced in the where clause, in which case it should do a left join. but it seems like that is not happening here. using eager_load works in this case:

 ps = Project.eager_load(:rentals).where('rentals.id IN (?)', [1,2,3,4])

but i thought that includes should do the same.
I've also noticed that sometimes includes does perform the same query as eager_load

@project = Project.find(174)
@project.rentals.eager_load(:equipment_name).where('equipment_names.id IN (?)', [1,2,3,4])
@project.rentals.includes(:equipment_name).where('equipment_names.id IN (?)', [1,2,3,4])

In this case eager_load and includes both perform a left join. I haven't found any documentation that explains why includes acts differently in these situations. how exactly does includes choose the query that it performs?


Solution

  • includes just tells ActiveRecord that the data should be eager-loaded but it can achieve this however it likes. If you want to reference the other model in a query, you'll have to use references(:rentals), too.

    The ActiveRecord API is extremely powerful and versatile, but as is often the case with Ruby and its libraries, there are many ways to achieve something. In this case, you can also use merge to merge in another relation.

    rentals = Rental.where(id: [1,2,3,4])
    projects = Project.joins(:rentals).merge(rentals) # you can additionally add includes, too, if you want to access the rentals
    

    This has the added advantage that you don't need to care about the table name of the Rental model.