How to Manage Many-to-Many Relationships - ruby-on-rails

I have two models Group and Person that I want to have a many-to-many relationship, but I'm unclear on how to manage the relationship itself. I want to be able to create groups and persons separately -- NOT necessarily via a nested model -- and then link persons to groups from the group view/model itself.
Does anyone have any suggestions on how to do so?
I thought of creating a many-to-many relationship via a join model and then accepting nested attributes for the join model in the Group model -- so I believe I will be able to add and remove relationships via the Group view/model. Does this approach make sense?

I would create a PersonGroup model that looks like this:
class PersonGroup < ActiveRecord::Base
has_many :people
has_many :groups
end
And you might also do rails generate migration create_person_group and put this in the up method of the generated migration file:
create_table :person_group do |t|
t.integer :person_id, :null => false
t.integer :group_id, :null => false
t.timestamps
end
add_index :person_group, [:person_id, :group_id], :unique => true
Then in Person:
class Person < ActiveRecord::Base
has_many :person_groups
has_many :groups, :through => :person_groups
end
And in Group:
class Group < ActiveRecord::Base
has_many :person_groups
has_many :people, :through => :person_groups
end

Create a junction table. Junction tables are used when you want to store many-to-many relationships. I don't develop in ROR so I don't know the specifics for ActiveRecord but I am sure that this can help you think about the problem as well.
group_id INTEGER,
person_id INTEGER

Related

Referencing a column on a table to a column on another table Ruby on Rails

I was reading another question on here regarding referencing columns from two separate tables but was a little confused if it addressed my issue. What's going on is I have two tables, Destination and Booking. The Destination table has a column for location_id, and the Booking has a column for location, and I am trying to reference location in Booking table from location_id column in Destination table.
Here is my table for Booking(migration)
class CreateBookings < ActiveRecord::Migration[6.1]
def change
create_table :bookings do |t|
t.string :name
t.string :start_date
t.string :end_date
t.string :email
t.integer :location
t.timestamps
end
end
end
and here is my table(Migration) for Destination
class CreateDestinations < ActiveRecord::Migration[6.1]
def change
create_table :destinations do |t|
t.string :address
t.string :city
t.string :state
t.string :zip
t.integer :location_id
t.timestamps
end
end
end
My Models are setup currently as
class Booking < ApplicationRecord
# belongs_to :reservation, optional: true
has_many :destinations, :class_name => 'Destination', :foreign_key=> 'location_id'
validates :name, :start_date, :end_date, :email, presence: true
end
and
class Destination < ApplicationRecord
has_many :bookings, :class_name => 'Booking', :foreign_key=> 'location'
end
Am I currently referencing the columns correctly, or is there something else I should be doing?
How you should write your migrations depends on the association between your models. Foreign keys go onto tables that have a belongs_to association.
Can a single Booking have multiple Destinations? If the answer is no, you need to change the association in your Booking model to belongs_to :destination and then put a :destination_id on your bookings table (you can give it a custom name like :location_id if you want but the convention is to use the model name).
If a single Booking can have multiple Destinations, and surely a single Destination can have multiple Bookings, then you have a many-to-many relationship. In that case you will not put foreign keys on the destinations table, nor the bookings table. Instead you will need a join table between them and that's where the foreign keys go.
Rails gives 2 different ways to declare many-to-many relationships. See https://guides.rubyonrails.org/association_basics.html#choosing-between-has-many-through-and-has-and-belongs-to-many.
If you want to use has_and_belongs_to_many, your models would look like this:
class Booking < ApplicationRecord
has_and_belongs_to_many :destinations
end
class Destination < ApplicationRecord
has_and_belongs_to_many :bookings
end
And the migration would look like this:
class CreateBookingsAndDestinations < ActiveRecord::Migration[6.0]
def change
create_table :bookings do |t|
# ...
end
create_table :destinations do |t|
# ...
end
create_table :bookings_destinations, id: false do |t|
t.belongs_to :booking
t.belongs_to :destination
end
end
end
Caveat: Based on your question I'm assuming you want a booking to have a destination. If you want a destination to many bookings and vise-versa, Sean's answer is great.
I think you're misunderstanding how foreign keys / associations work in databases.
It sounds like you want a column in the bookings table to "reference" a value column in the destinations table (or maybe the opposite), as in:
bookings.location -> destinations.location_id or maybe destinations.location_id -> bookings.location.
That's not typically what we mean by "reference" in a relational database. Instead, when you say that a table (for example, a 'comments' table) references another table (for example, a comments table references a user table), what we typically mean is that we're storing the primary key column of the referenced table (e.g. the user's id) in a column in the first table (e.g. comments.user_id --> users.id).
From an english language standpoint I expect that you want a booking to refer to a destination, so I'm going to assuming we want a the booking table to reference/refer to the destinations table, like this:
booking.location -> destinations.id
In Ruby on Rails, the convention is to name a column that stores an association with the same as the table it references, plus _id, like so the convention would be this:
booking.destination_id -> destinations.id
A common way to create this in a migration would be with:
add_reference :bookings, :destination
When adding a reference in a database you almost always want to index by that value (so that you can do Bookings.where(destination_id: #destination.id) and not kill your database). I am also a strong advocate for letting your database enforce referential integrity for you, so (if your database supports it) i'd recommend the following:
add_reference :destinations, :booking, index: true, foreign_key: true
This would prevent someone from deleting a destination that has a booking associated with it.

Storing only the id of two related tables

Like ownership.
Suppose I already have two models:
person name:string
dog name:string
Now I need to have the third table - ownership.
In a normal relational database, I only need to store the two ids from those tables, but rails automatically generates them. So how can I reference them?
rails generate model Ownership XXXXXXXX
You can set up a relationship model, like this:
rails generate model ownerships person:references dog:references
rails db:migrate
app/models/ownership.rb:
class Ownership < ApplicationRecord
belongs_to :person
belongs_to :dog
end
app/models/person.rb:
class Person < ApplicationRecord
has_many :ownerships
has_many :dogs, through: :ownerships
end
Now you can do:
john = Person.create(name: 'John')
doggy = Dog.create(name: 'Doggy')
john.dogs << doggy
And you just added a dog to John's ownerships. You can find them like this:
puts john.dogs.first.name
# => "Doggy"
If you look at your generated schema.rb:
create_table "ownerships", force: :cascade do |t|
t.integer "person_id" # Here are your two ids
t.integer "dog_id" #
# ...
I would recommend you going through Rails Tutorial by Michael Hartl. In the last section he implements a relationship model with very thorough explanation.
What you want is a has_ :through relationship or a has_and_belongs_to_many relationship. Check the docs for more info (you can find detailed explanation on how to set up the DB and relationship.)It is a standard issue in rails:
http://guides.rubyonrails.org/association_basics.html

Rails: self join scheme with has_and_belongs_to_many?

I would like to create a structure of Users having many friends, also of class User:
class User < ActiveRecord::Base
has_and_belongs_to_many :friends, class_name: "User"
end
I do not need any details of their relationship thus I do not use :through with kind of class Friendship. But now I cannot find any way how to create corresponding database (neither with migration file nor using rails g model User username:string ... command). Any ideas?
Here are some resources which may be helpful:
RailsCasts episode #163 Self-Referential Association regarding self-referential many-to-many relationships
RailsCasts episode #47 Two Many-to-Many. This might be more relevant to what you're attempting to accomplish
A gist someone created for self-referential relationships using HABTM
I'll summarize the information found in those links:
Given that you're describing a self-referential many-to-many relationship, you will of course end up with a join table. Normally, the join table should be deliberately named in such a way that Rails will automatically figure out which models the table is joining, however the "self-referential" part makes this a tad awkward, but not difficult. You'll merely have to specify the name of the join table, as well as the joining columns.
You'll need to create this table using a migration that will probably look something like this:
class CreateFriendships < ActiveRecord::Migration
def self.up
create_table :friendships, id: false do |t|
t.integer :user_id
t.integer :friend_user_id
end
add_index(:friendships, [:user_id, :friend_user_id], :unique => true)
add_index(:friendships, [:friend_user_id, :user_id], :unique => true)
end
def self.down
remove_index(:friendships, [:friend_user_id, :user_id])
remove_index(:friendships, [:user_id, :friend_user_id])
drop_table :friendships
end
end
I'm not certain whether there is a shortcut way of creating this table, but bare minimum you can simply do rails g migration create_friendships, and fill in the self.up and self.down methods.
And then finally in your user model, you simply add the name of the join table, like so:
class User < ActiveRecord::Base
has_and_belongs_to_many :friends,
class_name: "User",
join_table: :friendships,
foreign_key: :user_id,
association_foreign_key: :friend_user_id
end
As you can see, while you do have a join table in the database, there is no related join model.
Please let me know whether this works for you.

Creating assoc record in HABTM through

Models:
cities.rb:
has_many :cities_users
has_many :users, :through => :cities_users
I have a HABTM (through) between cities and users. I want to view all cities associated with a user. Here's what I have and what the error is:
users.rb
has_many :cities_users
has_many :cities, :through => :cities_users
Controller:
#user = User.find(current_user.id)
#users_cities = #user.cities
I have written a migration that creates the JOIN table:
create_table "cities_users", :id => false, :force => true do |t|
t.integer "user_id"
t.integer "city_id"
end
This is my error (relating to second line of controller code):
uninitialized constant User::CitiesUser
I'm having similar problems creating a city that is associated with a user too.
Many thanks.
You should create new model if you want to use has_many :through association.
Please, consider using has_and_belongs_to_many for direct many-to-many connection with no intervening model.
For any details you can read http://guides.rubyonrails.org/association_basics.html#choosing-between-has-many-through-and-has-and-belongs-to-many.

Rails Many to Many SQLite3 error

I created a many-to-many relationship in rails, here's my models and migrations
class Channel < ActiveRecord::Base
has_and_belongs_to_many :packages
validates_presence_of :name
end
class Package < ActiveRecord::Base
has_and_belongs_to_many :channels
validates_presence_of :name
end
class CreateChannelsPackages < ActiveRecord::Migration
def change
create_table :channels_packages, :id => false do |t|
t.references :channel
t.references :package
t.timestamps
end
add_index :channels_packages, :channel_id
add_index :channels_packages, :package_id
end
end
Then i have a multiple select, but when i try to save i get this error
SQLite3::ConstraintException: constraint failed: INSERT INTO "channels_packages" ("package_id", "channel_id") VALUES (1, 1)
I tried to remove the indexes from the migration but it didn't solve it, did somebody else have this problem?
Btw i'm using Rails 3.2.6 and sqlite3 1.3.6
I think gabrielhilal's answer is not quite correct: use of extra attributes in the join table is deprecated, thus you need to remove the timestamp in your migration, then it should work just fine with the has_and_belongs_to_many wich itself is not deprecated.
If you do need additional attributes in your join table, though, has_many :through is the way to go.
There is also another question with good answers on this topic:
Rails migration for has_and_belongs_to_many join table
I don't know if it is the reason of your problem, but the has_and_belongs_to_many association is deprecated.
According to the Rails Guide:
The use of extra attributes on the join table in a has_and_belongs_to_many association is deprecated. If you require this sort of complex behavior on the table that joins two models in a many-to-many relationship, you should use a has_many :through association instead of has_and_belongs_to_many.
I know that you are not adding any extra attribute to the join table, but try changing your migration to the below, which I think is the default:
class CreateChannelPackageJoinTable < ActiveRecord::Migration
def change
create_table :channels_packages, :id => false do |t|
t.integer :channel_id
t.integer :package_id
t.timestamps
end
end
end

Resources