DEV Community

Savannah Younts
Savannah Younts

Posted on

Multiple ActiveRecord Associations

Hey so I know with ActiveRecord I can have associations like belongs_to, has_many and has_many through, but how would it work if I have a model that has two associations with the same second model?

For example, if I have three class models Foo, Bar, & Baz. If

class Foo
has_many :baz
has_many :bar
has_many :bar, through: :baz
end

Basically, I want my foo concept to be able to create a bar but also to have bars through baz. Can I use it the way I have it set up there (with the other associations set up to match) and if I can, am I able to call .bars on my foo object to get back ALL the bars from both associations?

Top comments (4)

Collapse
 
rhymes profile image
rhymes

Hi Sav, can I ask why? What are you trying to accomplish modeling this association? What's the case for Foo to have many Bar by itself (one to many) and many Bar through Baz (many to many) at the same time?

I'm asking because maybe there's a simpler way to model these relationships.

If not I think you can tell AR to map one of those relationships with another alias, using class_name: guides.rubyonrails.org/association...

See this answer as well stackoverflow.com/a/3098487/4186181

Collapse
 
savyounts profile image
Savannah Younts

I'm definitely down to use some other sort of association if you think there is a better way.

So my goal is to have a travel app where I have a User who can have many Trips and many Destinations through Trips. I also want them to be able to create their own Destinations. The idea of this is so that other Users could add these Destinations to their Trips and leave comments on them and only the User who created the Destination can edit that Destination. So maybe I could just do the has_many through association, but when a User creates a Destination, it also has a hidden field with something like a created_by_id that stores that User's id to be able to reference back to it? Open to any other ideas as well.

Collapse
 
rhymes profile image
rhymes • Edited

Bear with me :-)

If I'm not mistaken:

  • a user logs in and creates a trip
  • each trip belongs to only one user
  • each trip has multiple destinations

A first version could be:

class User
  has_many :trips
end

class Trip
  belongs_to :user
  has_many :destinations, inverse_of: :trip
end

class Destination
  belongs_to :trip
end

This way you can ask, given user Alice

  • which trips did alice created?
  • for each given trip, which destinations she added?

The problem with this one is that a destination belongs to a single trip, so if two people want to add "Bora Bora" to their trip, they can't, unless you duplicate the record.

To allow two trips to have Bora Bora you need to have a many-to-many relationship between them (a trip has multiple destinations, a destination can appear in multiple trips).

So our models would become:

class User
  has_many :trips
end

class Trip
  belongs_to :user

  has_many :trip_destinations
  has_many :destinations, through: :trip_destinations
end

class TripDestination # come up with a better name :D
  belongs_to :trip
  belongs_to :destination
end

class Destination
  has_many :trip_destinations
  has_many :trips, through: :trip_destinations
end

This way, you can know the following:

  • which trips Alice has created
  • which destinations belong to each trip
  • which trips include each destination

In your database you will have a list of possible destinations and each user can add them to their trips.

Next, we need to let these users create their own destinations, instead of just using the pre-populated ones.

The only difference between a predefined destination and a user created is the notion of creator. Other than having a flag predefined or custom or something like that you need to attach the id of the creator to a destination.

Something like:

class User
  has_many :trips

  has_many :custom_destinations, class_name: "Destination", foreign_key: "creator_id"
end

class Trip
  belongs_to :user

  has_many :trip_destinations
  has_many :destinations, through: :trip_destinations
end

class TripDestination # come up with a better name :D
  belongs_to :trip
  belongs_to :destination
end

class Destination
  belongs_to :creator, class_name: "User", foreign_key: "creator_id"

  has_many :trip_destinations
  has_many :trips, through: :trip_destinations
end

The reason why I'm calling the foreign key creator_id and not user_id is for future proofing. Now you're mapping a relationship of creator/creation between a user and a destination, you might want to add another type of relationship between the two entities in the future, and add another key like moderator_id or whatever. They all point to the User table, they just point to separate concepts mapped to different foreign keys.

I haven't tested it but it should work. You need to create the various foreign keys in the migration:

A tip: add the creation timestamp to the join table, so you'll know for free when the user has added the destination to their trip.

I hope I didn't forget something, let me know in case :-)

ps. my favorite gem to model permission (you're going to need something like it to describe the various actions a user can do, on destinations and comments) is Pundit

Thread Thread
 
savyounts profile image
Savannah Younts

Awesome! I think that is exactly what I’m looking for! I’ll try it out and let you know how it goes. And thanks for the tip on Pundit! Excited to see how that works :)