Creating an has_many relationship through a recursive has_many relation

Viewed 121

Situation:

class Company < ActiveRecord::Base
  has_many :groups #<-- recursive sub-groups
  has_many :profiles, through: :groups #<-- This only returns the :profiles of the first groups layer
end

class Group < ActiveRecord::Base
  belongs_to :company, optional: true
  belongs_to :group, optional: true
  validate :company_xor_group
  has_many :groups
  has_many :profiles
end

class Profile < ActiveRecord::Base
  belongs_to :group
end

Example data:

  company = Company.new
  group = Group.new.profiles.build(name: 'A')
  sub_group = Group.new.profiles.build([{name: 'B'},{name: 'C'}])
  group.groups << sub_group
  company.groups << group

So a company has a group, which contains a profile and a sub-group, which contains 2 profiles.

So I want to get all profiles of a the company. Currently, the has_many :profiles, through: :groups relation, only returns the profiles of the first child group:

  company.profiles.length # 1

I'm looking for a solution where an ActiveRecord AssociationProxy is returned. I've tried the following:

# Company Model
def all_profiles
  groups.map(&:all_profiles)
end

# Group Model
def all_profiles
  profiles + groups.map(&:all_profiles)
end

But this returns an array, instead of an AssociationProxy.

1 Answers

As @spickermann responded, a huge help with recursive tables is Awesome Nested Set.

The answer to my question "how can I get it to return an ActiveRecord AssociationProxy?" was given by @arieljuod:

# Company Model
def all_profiles
  Profile.where(group_id: groups.map(&:all_groups).flatten.map(&:id))
end

# Group Model
def all_groups
  [self].push(groups.map(&:all_groups)).flatten.compact
end

In my solution this is still quite efficient, because the recursive groups are preloaded. So the all_groups method doesn't trigger any new queries.

Related