reciprocating relationships

Let’s say I have a table of products and a table of accessories Each accessory can have many products that it’s compatible with while each product has many accessories available for it It In the problem I’m trying to solve accessories cannot belong to products and products cannot belong to accessories So, unless I was to create a third table with fields for product_id and accessory_id, how could I allow each to have many of the other Do I have to create the third table? Thanks in advance

This is a basic part of what is called a many-to-many relationship, which is what you describe here. These relationships always rely on a third table in any relational database-backed application (i.e. this is not specific to Rails).

There are two ways to model this relationship in Rails. The first (and oldest) is the has_and_belongs_to_many relationship, which uses a simple table consisting of two columns (accessory_id and product_id), and does a "dumb" join between the two models. Nothing (besides the simple fact of bi-directional relation) is stored in the middle table.

The second approach, which has been recommended in preference to the "dumb join" for many years, is the has_many through relationship. That also uses a third table, but that table backs a full ActiveRecord model. It would have an id, along with the product_id and accessory_id, and could include as many other columns as you may need in order to properly model the relationship between the other two objects. For example, you could "decorate" the join between the two main objects with more specific details about their relationship.

Say if you had a Person, Team, and Membership model, you could use the Membership to carry the date the person joined the team, or their role on the team. Your models would look like this:

class Person < ActiveModel
  has_many :memberships
  has_many :teams, through: :memberships
end

class Team < ActiveModel
  has_many :memberships
  has_many :people, through: :memberships
end

class Membership < ActiveModel
  belongs_to :person
  belongs_to :team
end

If you have any thoughts at all that you may want to have a richer connection between products and accessories, then by all means use the has_many through relationship.

Walter

Thanks Walter

You don't need to do that -- Rails cleans up those values for you when it saves the parent. So if you have

@user = User.new(user_params)
@widgets = @user.widgets.build(widget_params)

When you save @user, the @widgets will be saved as well, and the correct ID will be put into each of the @widgets.

Walter

i was actually able to get @person.id after if @person.save Relationship.find_or_create_by(person_id: @person.id

So if I’m using the second approach the has_many_through then how do i create a new record?

Start by reading the rails guide on active record relations, that shows you
how to do that sort of thing.

Colin

This code @person.pictures << @picture causes an error

Could not find the association :people_pictures in model Person

class PeoplePictures < ApplicationRecord

belongs_to :person

belongs_to :picture

class Person < ApplicationRecord

has_many :pictures, through: :people_pictures

class Picture < ApplicationRecord

has_many :people, through: :people_pictures

So if I'm using the second approach the has_many_through then how do i create a new record?

Start by reading the rails guide on active record relations, that shows you how to do that sort of thing.

Colin

This code `@person.pictures << @picture` causes an error

Could not find the association :people_pictures in model Person

class PeoplePictures < ApplicationRecord
belongs_to :person
belongs_to :picture

class Person < ApplicationRecord
has_many :pictures, through: :people_pictures

class Picture < ApplicationRecord
  has_many :people, through: :people_pictures

If you have read the guide, as Colin recommended, really read every line of it, you would have seen this:

class Physician < ApplicationRecord
  has_many :appointments
  has_many :patients, through: :appointments
end

class Appointment < ApplicationRecord
  belongs_to :physician
  belongs_to :patient
end

class Patient < ApplicationRecord
  has_many :appointments
  has_many :physicians, through: :appointments
end

See how each has_many is described with two, separate has_many relationships? One goes to the "join object" and another to the "joined object". You are missing the association to the 'join object', which in your relationship would be:

has_many :people_pictures

Walter

Yikes Actually was falling asleep messin with it last night Sorry

Naming conventions for the join table are the same as for has_and_belongs_to_many ?

Let's say I have a table of products and a table of accessories Each accessory can have many products that it's compatible with while each product has many accessories available for it It In the problem I'm trying to solve accessories cannot belong to products and products cannot belong to accessories So, unless I was to create a third table with fields for product_id and accessory_id, how could I allow each to have many of the other Do I have to create the third table? Thanks in advance

Naming conventions for the join table are the same as for has_and_belongs_to_many ?

There are no requirements on this. The join table should be named for the join model, not the sides of the relationship. The reason that the HABTM naming convention exists at all is because there is no model in the middle, just the table. That means that the table name must be inferred from the sides of the relationship, since there would be no other way to configure that.

Often in this area, you will see a join table name that means something specific to the relationship. So between User and Group, you might see Membership, and that might mean that User has:

has_many :memberships
has_many :groups, through: :memberships

and Group has:

has_many :memberships
has_many :users, through: :memberships

But you may want to say @group.members because it expresses the semantics of the relationship better:

has_many :members, through: :memberships, class_name: 'User', foreign_key: :user_id

This sort of relationship is endlessly configurable and flexible, which is another reason why it is preferred over HABTM.

Walter

@person.pictures << @picture tries to, create a new picture, not add it to the person_picture association table

Where does @picture come from? Did you build it in memory, or load it from the database? Rather than adding an object you created to an association, try building the associated object from the beginning, like this:

@picture = @person.pictures.build(picture_attributes)

Now that picture is automatically in the @person.pictures association, and has whatever other attributes you assigned to it.

When you save the picture, the corresponding person_picture instance will be persisted as well. The picture has to be created and saved before the person_picture record can be, otherwise where would the person_picture.picture_id value come from?

Walter

to do that i added accepts_nested_attributes_for :picture it didn’t create the association

This is a basic part of what is called a many-to-many relationship, which is what you describe here. These relationships always rely on a third table in any relational database-backed application (i.e. this is not specific to Rails).

There are two ways to model this relationship in Rails. The first (and oldest) is the has_and_belongs_to_many relationship, which uses a simple table consisting of two columns (accessory_id and product_id), and does a “dumb” join between the two models. Nothing (besides the simple fact of bi-directional relation) is stored in the middle table.

The second approach, which has been recommended in preference to the “dumb join” for many years, is the has_many through relationship. That also uses a third table, but that table backs a full ActiveRecord model. It would have an id, along with the product_id and accessory_id, and could include as many other columns as you may need in order to properly model the relationship between the other two objects. For example, you could “decorate” the join between the two main objects with more specific details about their relationship.

Say if you had a Person, Team, and Membership model, you could use the Membership to carry the date the person joined the team, or their role on the team. Your models would look like this:

class Person < ActiveModel

has_many :memberships

has_many :teams, through: :memberships

end

class Team < ActiveModel

has_many :memberships

has_many :people, through: :memberships

end

class Membership < ActiveModel

belongs_to :person

belongs_to :team

end

If you have any thoughts at all that you may want to have a richer connection between products and accessories, then by all means use the has_many through relationship.

Walter

Let’s say I have a table of products and a table of accessories Each accessory can have many products that it’s compatible with while each product has many accessories available for it It In the problem I’m trying to solve accessories cannot belong to products and products cannot belong to accessories So, unless I was to create a third table with fields for product_id and accessory_id, how could I allow each to have many of the other Do I have to create the third table? Thanks in advance


You received this message because you are subscribed to the Google Groups “Ruby on Rails: Talk” group.

To unsubscribe from this group and stop receiving emails from it, send an email to rubyonrails-ta...@googlegroups.com.

To post to this group, send email to rubyonra...@googlegroups.com.

To view this discussion on the web visit https://groups.google.com/d/msgid/rubyonrails-talk/e161bc52-f089-493a-8d12-00f597726217%40googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

I was thinking of another use for has_many_through If I wanna put a new abstraction in between two existing relationships like adding a Blog model and letting users have many posts through blogs instead of users having many posts and all the layers under posts i can leave alone i don’t have to recode them

Can we revisit this one I’m doing a family tree and trying to manage spousal relationships so i have person has_many people through: :partnerships But now partnerships can’t have person_id twice so this is perplexing me

Google for

rails self referential has_many through

Colin

Surely if every person object(eg.father) has a unique Id. Then that Id can be included in their childrens identity and fathers Id can be put into his brothers identity and sister identity and parents identity.

Then it becames a searching for matching Id’s both depth wise and breadth wise.

Isn’t that what a family tree is all about creating relationships and you build it.

Cheers Dave