Search code examples
ruby-on-railsrubyactiverecordruby-on-rails-5

Rails: has_many through not returning results


We recently upgraded rails to 5.1 following steps in the Rails guide, but see that the following is not working. I have the following model definitions

class User < ActiveRecord::Base
  has_many :members, :dependent => :destroy
  has_many :user_accounts, :dependent => :destroy
end

class Member < ActiveRecord::Base
  belongs_to :user
  has_many :user_accounts, :through => :user
end

class UserAccount < ActiveRecord::Base
  belongs_to :user
end

When I try to execute, for example

user = User.find 109
member = user.members[0]
member.user_accounts

this generates the following query

SELECT `user_accounts`.* FROM `user_accounts` INNER JOIN `users` ON `user_accounts`.`user_id` = `users`.`id` WHERE `user_accounts`.`users` = NULL

users is getting checked with NULL

it should be users.id which is 109.

One important thing to mention is that the same Member model has other relationship using through with other models which work. for example

member.stores
member.credit_cards

but member.user_accounts does not work.

Any help on how to address this would be great, Thanks.


Solution

  • I feel like I have to put this here:

    :through Specifies an association through which to perform the query. This can be any other type of association...
    https://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_many.


    I've set up models, just like you show, working fine:

    >> Member.first.user.user_accounts
    => #<ActiveRecord::Associations::CollectionProxy [#<UserAccount id: 1, user_id: 1>]>
    
    # through: user
    >> Member.first.user_accounts
    Member Load (0.8ms)  SELECT  `members`.* FROM `members` ORDER BY `members`.`id` ASC LIMIT 1
    UserAccount Load (0.9ms)  SELECT  `user_accounts`.* FROM `user_accounts` INNER JOIN `users` ON `user_accounts`.`user_id` = `users`.`id` WHERE `users`.`id` = 1 LIMIT 11
    => #<ActiveRecord::Associations::CollectionProxy [#<UserAccount id: 1, user_id: 1>]>
    

    Here's how I broke it:

    class AddUsersToUserAccounts < ActiveRecord::Migration[5.1]
      def change
        add_column :user_accounts, :users, :integer
      end
    end
    
    >> Member.first.user_accounts
    Member Load (0.8ms)  SELECT  `members`.* FROM `members` ORDER BY `members`.`id` ASC LIMIT 1
    UserAccount Load (0.7ms)  
      SELECT  `user_accounts`.* FROM `user_accounts` 
      INNER JOIN `users` ON `user_accounts`.`user_id` = `users`.`id` 
      WHERE `user_accounts`.`users` = NULL LIMIT 11
    #                        ^
    # using `users` column instead
    => #<ActiveRecord::Associations::CollectionProxy []>
    
    >> UserAccount.column_names
    => ["id", "user_id", "users"]
    >> Member.first.user_accounts.arel.ast.cores[0].wheres[0].children[0].to_sql
    => "`user_accounts`.`users` = ?"
    

    When users.id = 1 where clause is being built, arel takes a hash {"users"=>{"id"=>1}} and spits out some sql. If there is a users column present, it stumbles on this part and treats "users" key as a column instead of a table:

    when value.is_a?(Hash) && !table.has_column?(column_name)
    #                         ^^^^^^^^^ this one ^^^^^^^^^^^^
    

    https://github.com/rails/rails/blob/v5.1.7/activerecord/lib/active_record/relation/predicate_builder.rb#L91

    When I rollback and remove users column, it goes into the correct branch and builds a working sql:

    >> UserAccount.column_names
    => ["id", "user_id"]
    >> Member.first.user_accounts.arel.ast.cores[0].wheres[0].children[0].to_sql
    => "`users`.`id` = ?"