Rails has_many dual relationship with other model - ruby-on-rails

I'm trying to get two has_many relationships from one model to another.
Specifically, I want:
class Driver < Active:Record::Base
has_many :reservations
has_many :requested_reservations
and
class Reservations < Active:Record::Base
belongs_to :driver
belongs_to :requester
The first one is a normal has_many/belongs_to relationship using driver_id on the reservations model.
But for the second one, I want to be able to call #driver.requested_reservations and #reservation.requester, and have it use the requester_id column in the Reservations class.
What do I need to put at the end of those has_many and belongs_to lines to get it to work properly?

I believe you can set the class and foreign key to get the desired results.
class Driver < Active:Record::Base
has_many :reservations
has_many :requested_reservations, class_name: 'Reservation', foreign_key: 'your_id'
...
end
class Reservations < Active:Record::Base
belongs_to :driver
belongs_to :requester, class_name: 'Driver', foreign_key: 'requester_id'
...
end

There are similar questions that have been asked before. See the following links for more information:
Rails multiple associations between two models
how to specify multiple relationships between models in rails using ActiveRecord associations

Related

Rails - Has many on model with multiple references

I have a model CompanyIntro which has two references to a Company:
class CompanyIntro < ApplicationRecord
belongs_to :company_one, class_name: "Company", foreign_key: "company_one_id"
belongs_to :company_two, class_name: "Company", foreign_key: "company_two_id"
...
I would like to do something like:
class Company < ApplicationRecord
has_many :company_intros, class_name: 'CompanyIntro', foreign_key: 'company_one_id'
has_many :company_intros, class_name: 'CompanyIntro', foreign_key: 'company_two_id'
...
But this is not valid
In my Company model, how to I create a has_many for both foreign keys? I am using Rails 6 which dos not allow custom sql for has_many (afaik). I also do not want to write a custom company_intros method on the Company model as I'm using another gem which looks for my has_many relationships.
You can't define has_many assocations where the foreign key is one of two columns. Its just not supported by ActiveRecord as the feature would add tons of complexity.
Using the same name for two assocations also just overwrites the previous assocation. If you want to have a single assocation here you need to add a join table.
class Company < ApplicationRecord
has_many :company_intro_participations
has_many :company_intros, through: :company_intro_participations
end
# for lack of a better name
class CompanyIntroParticipation < ApplicationRecord
belongs_to :company
belongs_to :company_intro
end
class CompanyIntro < ApplicationRecord
has_many :company_intro_participations
has_many :companies, through: :company_intro_participations
end
The alternative is creating a method which joins on company_one_id = companies.id OR company_two_id = companies.id but you will not be able to use that in the same way as an association when it comes to stuff like eager loading.

How to create a many to many relation in Rails

So currently i have in a app:
class Post < ApplicationRecord
belongs_to :category
end
and
class Category < ApplicationRecord
has_many :posts
end
which works fine as expected. However, i need to add multiple categories to a post . I thought about using has_many_and_belongs_to for each of them to acquire this but some trouble implementing this. It seems like a need to add an join table? If so, how would one look like with the setup shown above?
Any ideas?
I appreciate any input! Thanks in advance!
The table should be named categories_posts (categories comes first because of alphabetical sequence) and contain post_id and category_id integer columns (indexed, most probably). The it's as simple as:
class Post < ApplicationRecord
has_and_belongs_to_many :categories
end
class Category < ApplicationRecord
has_and_belongs_to_many :posts
end
You can create join table adding migration using:
rails g migration CreateJoinTableCategoryPost category post
Alternatively you can use has_many :through to have more control over a join table.
Advantages of using :through for many to many relationships
With has_many :through relationship you can have a model which will allow you to add validation, callbacks.
If you initially take some extra efforts to setup many to many relationship using through it can save a lot of time and headache in future
What if in future you want save the more information on join table like some custom sort, information about how the tables are associated which will not be allowed with has_and_belongs_to_many
Example
class Post < ApplicationRecord
has_many :categories, through: :post_categories
has_many :post_categories
end
class Category < ApplicationRecord
has_many :posts, through: :post_categories
has_many :post_categories
end
Adding relationship model with rails generator command
rails g model post_category category_id:integer post_id:integer custom:text
class PostCategory < ApplicationRecord
belongs_to :category
belongs_to :post
end

What is the correct naming method for Rails models that represent a join table?

I have two classes (organisation.rb and user.rb) which are linked through a join table via ActiveRecord with a many to many relationship. A user can belong to many organisations, and an organisation can have many users.
Initially, I represented this relationship in this manner and this worked fine:
class Organisation < ActiveRecord::Base
has_many :members, through: :memberships, source: :user
has_many :memberships
end
class User < ActiveRecord::Base
has_many :organisation_members, through: :memberships, source: :organisation
has_many :memberships
end
class Membership < ActiveRecord::Base
belongs_to :user
belongs_to :organisation
end
I asked a friend to review my pull request and he suggested that the Rails convention is to name the join table with a lexical ordered list of the tables to be joined. I searched and confirmed this:
Rails naming convention for join table
As such, I reverted my migrations and started anew, with the new relationships:
class Organisation < ActiveRecord::Base
has_many :members, through: :organisations_users, source: :user
has_many :organisations_users
end
class User < ActiveRecord::Base
has_many :organisation_memberships, through: :organisations_users, source: :organisation
has_many :organisations_user
end
class OrganisationsUser < ActiveRecord::Base
belongs_to :user
belongs_to :organisation
end
Having made these changes, a number of my tests failed due to not recognising the model 'organisation_user.rb'. I then tried 'organisations_users.rb', and also no luck. This was due to my ignorance of rails' pluralisation, and therefore naming the join table model correctly resulted in all tests passing. As described above, the correct naming is:
class OrganisationsUser
This results in a rather strange model name of a pluralised organisation followed by a singular user.
I understand that this is technically correct. The model name should singular, however it isn't particularly easy to understand when compared with the 'memberships' join table.
What is the correct way to handle this - should I revert back to memberships, find a way to override Rails' default pluralisation, or continue with the awkwardly named status quo?

Creating a many-to-many relation in Rails

So I'm Rails n00b and I want to create a "favorites" relationship such that a User can have many favorite Item. I'm not entirely sure how to do this, this is how I'm going to try but I'm not sure if this is a good practice at all:
class User < ActiveRecord::Base
has_many :favorites
//other code
end
class Favorite < ActiveRecord::Base
belong_to :user
has_one :item
end
class Item < ActiveRecord::Base
belongs_to :item
end
Is this a good way to do it? Should I be using has_and_belongs_to_many ?
I'm specially concerned in the following scenario: Say a user has 100 favorite items.
When I do a User.find(id) will I also be retrieving the 100 favorites and the 100 Items?
In case it's important: ruby version 1.9.3, rails version 3.2.11
Can you try has_many => :through?
class User < ActiveRecord::Base
has_many :favorites
has_many :items, :through => :favorites
//other code
end
In your case has_many :through is definitely the way to go. I would recommend reading: http://guides.rubyonrails.org/association_basics.html
Of particular interest with regard to your question:
2.8 Choosing Between has_many :through and has_and_belongs_to_many
Rails offers two different ways to declare a many-to-many relationship between models. The simpler way is to use has_and_belongs_to_many, which allows you to make the association directly:
class Assembly < ActiveRecord::Base
has_and_belongs_to_many :parts
end
class Part < ActiveRecord::Base
has_and_belongs_to_many :assemblies
end
The second way to declare a many-to-many relationship is to use has_many :through. This makes the association indirectly, through a join model:
class Assembly < ActiveRecord::Base
has_many :manifests
has_many :parts, :through => :manifests
end
class Manifest < ActiveRecord::Base
belongs_to :assembly
belongs_to :part
end
class Part < ActiveRecord::Base
has_many :manifests
has_many :assemblies, :through => :manifests
end
The simplest rule of thumb is that you should set up a has_many :through relationship if you need to work with the relationship model as an independent entity. If you don’t need to do anything with the relationship model, it may be simpler to set up a has_and_belongs_to_many relationship (though you’ll need to remember to create the joining table in the database).
You should use has_many :through if you need validations, callbacks, or extra attributes on the join model.
It is better than using has_and_belongs_to_many.
When I do a User.find(id) will I also be retrieving the 100 favorites
and the 100 Items?
No. You'll just get the user object.
Update:
Calling User.include(:favourites, :items).find(id) will get you joined tables in case you want to make many calls to items table from user object.

Proper Rails Association to use

I am trying to create an association between two tables. A student table and a computer table.
A computer can only ever be assigned to one student (at any one time) but a student can be assigned to multiple computers.
This is what I currently have in mind. Setting up a has-many through relationship and modifying it a bit.
class Student < ActiveRecord::Base
has_many :assignemnts
has_many :computers, :through => :assignments
end
class Computer < ActiveRecord::Base
has_one :assignment
has_one :student, :through => :assignments
end
class Assignment < ActiveRecord::Base
belongs_to :student
belongs_to :computer
end
Does this seem like the best way to handle this problem? Or something better sound out quickly to the experts here. Thanks!
You need first to decide if a simple one-to many relationship is enough for you.
If yes, it gets a lot easier, because you can get rid of the Assignment-class and table.
Your database-table "computers" then needs a student_id column, with a non-unique index
Your models should look like this:
class Computer < ActiveRecord::Base
belongs_to :student
end
class Student < ActiveRecord::Base
has_many :computers, :dependent => :nullify
end
"dependent nullify" because you don't want to delete a computer when a student is deleted, but instead mark it as free.
Each of your computers can only be assigned to a single student, but you can reassign it to a different student, for example in the next year.
Actually your approach is fine, as one offered by #alexkv. It is more discussion, than question.
Another thing if you want to use mapping table for some other purposes, like storing additional fields - then your approach is the best thing. In has_many :through table for the join model has a primary key and can contain attributes just like any other model.
From api.rubyonrails.org:
Choosing which way to build a many-to-many relationship is not always
simple. If you need to work with the relationship model as its own
entity, use has_many :through. Use has_and_belongs_to_many when
working with legacy schemas or when you never work directly with the
relationship itself.
I can advise you read this, to understand what approach better to choose in your situation:
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html
http://blog.hasmanythrough.com/2006/4/20/many-to-many-dance-off
You can also use has_and_belongs_to_many method. In your case it will be:
class Student < ActiveRecord::Base
has_many :assignemnts
has_and_belongs_to_many :computers, :join_table => 'assignments',
end
class Computer < ActiveRecord::Base
has_one :assignment
has_and_belongs_to_many :student, :join_table => 'assignments',
end
or you can rename assignments table to computers_students and remove join_table
class Student < ActiveRecord::Base
has_many :assignemnts
has_and_belongs_to_many :computers
end
class Computer < ActiveRecord::Base
has_one :assignment
has_and_belongs_to_many :student
end

Resources