Search code examples
ruby-on-railsmany-to-many

Rails - loading related data on other side of many-to-many relationship


I have Beta::Groups and Beta::Users.

Users can belong to more than one Group, but each Beta::User is also a regular User.

class Beta::User < ApplicationRecord
  belongs_to :beta_group, class_name: "Beta::Group"
  belongs_to :user

  validates :user_id, uniqueness: { scope: :beta_group_id }
end

class Beta::Group < ApplicationRecord

  has_many :beta_users, dependent: :destroy, class_name: "Beta::User", foreign_key: 'beta_group_id'

  validates :name, presence: true, uniqueness: true

  def beta_users_count
    beta_users.count
  end
end

Effectively, Beta::User is just a join table between Beta:Group and User

When I load a Beta::Group, how can I retrieve the data for all the user's in that group?

    @beta_group = Beta::Group.find(beta_user_params[:beta_group_id])
    @beta_users = @beta_group.beta_users.all

The last line only retrieves the beta_users data obviously (just the beta_group_id and user_id). I don't want to have to then iterate through all the user_id's to get the actual User's full data.

So how I can set this up so that I can do something like @beta_group.users.all to retrieve all the data for each user that is a Beta:User?

EDIT - What I have tried

I tried adding this to the Beta::Group model:

has_many :users, through: :beta_users, source: :user

But when I call beta_group.users the query that runs is as follows, returning an empty array:

SELECT "beta_users".* FROM "beta_users" INNER JOIN "beta_users" "beta_users_users" ON "beta_users"."id" = "beta_users_users"."user_id" WHERE "beta_users_users"."beta_group_id" = $1  [["beta_group_id", 1]]

Notice that it is not joining correctly. It should be trying to join on "beta_users"."user_id" to "users.id"


Solution

  • I believe you can add a has_many through association:

    class Beta::Group < ApplicationRecord
    
      has_many :beta_users, dependent: :destroy, class_name: "Beta::User", foreign_key: 'beta_group_id'
      
      # here:
      has_many :users, through: :beta_users, source: :user
    
      validates :name, presence: true, uniqueness: true
    
      def beta_users_count
        beta_users.count
      end
    end
    

    Then you should be able to call the association:

    @beta_group = Beta::Group.find(beta_user_params[:beta_group_id])
    @beta_users = @beta_group.users
    

    EDIT - I believe you have an issue with scopes and the class_name because you have a User inside the Beta module.

    Try adding the class_name: "::User" to both associations:

    class Beta::User < ApplicationRecord
      belongs_to :beta_group, class_name: "Beta::Group"
      belongs_to :user, class_name: "::User" # without class_name it'll try a self association
    end
    
    class Beta::Group < ApplicationRecord
      has_many :beta_users, dependent: :destroy, class_name: "Beta::User", foreign_key: 'beta_group_id'
      has_many :users, class_name: "::User", through: :beta_users # without a class_name it'll try to join the Beta::User
    end