Paul Harker
Paul Harker

Reputation: 166

merging two relations in Rails

This should be easy I think...

A Organisation has an Owner and Members - both are Users.

This is setup by the organisation using class_name: "User", like so:

class Organisation < ApplicationRecord
   belongs_to :owner, class_name: "User", foreign_key: "owner_id"
   has_many :organisations_users
   has_many :members, class_name: "User", through: :organisations_users, source: :user
end

This works but I need an all_members function (or scope) so I can get back both owner and members in one array (or ActiveRecord object). I thought this would be trivial but it's actually not.

I've tried:

def all_members
   members << owner
end

this of course is not what I want at all... this adds the owner to the staff every time I call it.

def all_members
   [owner, members]
end

this sort of works but returns a nested array which is hard to access properly.

  scope :all_members, joins(:members).merge(:owner)

this doesn't work at all. Probably nonsense.

  def all_members
     members_array = members.dup
     members_array << owner
  end

This still permanently alters the members to include the owner?!!

Help! (Thanks)

Upvotes: 0

Views: 196

Answers (2)

Simone Carletti
Simone Carletti

Reputation: 176412

If getting an Array back is sufficient, then you can simply use:

def all_members
  [owner] + members
end

If you need a relation, then the simplest (but not most efficient) approach would be:

def all_members
  User.where(id: [owner] + members.ids)
end

It's not the most efficient as the SQL that gets generated may include a fairly large IN statement that cannot be efficiently cached by the database parser. Still, if the numbers are in the range of a few dozens, it would be enough even that relation trick.

Upvotes: 1

engineersmnky
engineersmnky

Reputation: 29328

If the order is not necessarily important then we can get this into a single query like so:

If Rails 5

def all_members
  # not sure if you could just do 
  # owner.or(members) since they are both User 
  # but it seems unlikely due to the join table 
  User.where(id: self.owner_id).or(
    User.where(id: organisation_users.select(:user_id))
  )
end 

All versions of Rails (less than and including Rails 5) we can use Arel to build the more complex query

def all_members
  user_table = User.arel_table   
  User.where(
    users_table[:id].eq(self.owner_id).or(
       users_table[:id].in(
         organisation_users.select(:user_id).arel
       )
    )
  ) 
end 

these will both result in the following SQL

SELECT users.* 
FROM users 
WHERE 
  (users.id = [YOUR_OWNER_ID] OR 
   users.id IN (
     SELECT organisations_users.user_id 
     FROM organisations_users
     WHERE organisations_users.organisation_id = [YOUR_ORGANISATION_ID]
  ))

the end result will be an ActiveRecord::Relation of User objects.

Upvotes: 1

Related Questions