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

Time:06-01

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"

CodePudding user response:

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

CodePudding user response:

The problem was due to the fact that User is a table in both the top-level namespace and inside the Beta namespace.

Therefore, it is necessary to inform rails which table to use to create the SQL for, by specifying the top-level class in the relation, like this:

class_name: '::User'

Final code:

class Beta::Group < ApplicationRecord

  has_many :beta_users, dependent: :destroy, class_name: 'Beta::User', foreign_key: 'beta_group_id'
  has_many :users, through: :beta_users, source: :user, class_name: '::User'

  validates :name, presence: true, uniqueness: true

  def beta_users_count
    beta_users.count
  end
end

which generates the correct SQL:

SELECT "users".* FROM "users" INNER JOIN "beta_users" ON "users"."id" = "beta_users"."user_id" WHERE "users"."viewer" = $1 AND "beta_users"."beta_group_id" = $2  [["viewer", false], ["beta_group_id", 1]]
  • Related