has_many :through with counter_cache - ruby-on-rails

It is my understanding that when defining a :counter_cache option it is to be specified on the model that includes the belongs_to declaration. So I am a little unsure of how to handle this when working with a has_may through association (as I believe that a belongs_to declaration is not used in this scenario):
class Physician < ActiveRecord::Base
has_many :appointments
has_many :patients, :through => :appointments
end
class Appointment < ActiveRecord::Base
belongs_to :physician, :counter_cache => appointment_count
end
class Patient < ActiveRecord::Base
end
I wish to use the :counter_cache option to make finding the number of Patients belonging to a Physician more efficient.
myPhysician.patients.count
FYI: Rails 3.1
Cheers

I'm not sure what kind of relationship you want. That example is similar to the one in the Rails Guide
class Physician < ActiveRecord::Base
has_many :appointments
has_many :patients, :through => :appointments
end
class Appointment < ActiveRecord::Base
belongs_to :physician
belongs_to :patient
end
class Patient < ActiveRecord::Base
has_many :appointments
has_many :physicians, :through => :appointments
end
A Physician has many Appointments, and has many Patients
An Appoinment belongs to (has one) Physician and one Patient
a Patient has many Appointments and many Physicians.
Regarding the :counter_cache option, according to the belongs_to doc:
If you want the number of Patients belonging to a Physician you would need:
class Appointment < ActiveRecord::Base
belongs_to :physician, :counter_cache => :patient_count
belongs_to :patient
end
And you need to write a migration to add the patient_count column to the Phyisicans table.
However, for has_many through relationships Rails 3.1 seems to automatically detect the counter_cache column, so you don't have to specify it (remove :counter_cache => :patient_count). If you do specify it your counter will go up by two (this is very weird).
By the way, there seems to be some problems with :counter_cache option in Rails 3.1, as reported here:
https://github.com/rails/rails/issues/3903
https://github.com/rails/rails/issues/3085
With all of that in mind, maybe your best bet is to write your own count mechanism using callbacks.
Hope it helps :)

I added a counter_cache to a has_many :through association on Rails 5.1, and the philosophy is the same as with has_many. Using the Physician, Appointment, Patient example:
add patients_count to the physicians table as an integer
add a counter cache to the the join model (appointment.rb): belongs_to :physician, counter_cache: :patients_count
Note: the answer above is correct, this answer just confirms that it works on Rails 5.1.

I ran into a similar problem, counting the number of records in a two-deep relationship. In your example, this would be the number of Patients for a Physician, as opposed to the number of Appointments. (e.g. don't count multiple appointments for one patient) I haven't tested the other solutions offered, but it appears they return the number of appointments.
I found no way to do this in Rails 4, primarily because there is no belongs_to through: option. After exhausting several fruitless approaches, I found gem counter_culture. This solved the problem easily, by defining a two-deep relationship to be counted:
class Patient < ActiveRecord::Base
belongs_to :appointment
counter_culture [:appointment, :physician]
end
Add a counter field to Physician with:
rails generate counter_culture Physician patients_count
And voila! You can now do easy activerecord queries like:
Physician.order(patients_count: 'DESC')

I can also confirm that the method outlined by Ivica Lakatos works with Rails 6 for has_many :through relationships using a join model.
add patients_count to the physicians table as an integer
add a counter cache to the the join model (appointment.rb):
belongs_to :physician, counter_cache: :patients_count

Related

Rails 5: "belongs_to through" kind of association

Happy New Year to everyone!
I have classic has_many through association:
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
However now I need to add has_many association from class Appointment to some class Example, which is in belongs_to association with another model and would be in belongs_to association with class Example.
If possible, how to to set this kind of assosiation? Thank you.
Update
I don't understand, why this question is downvoted.
Here is what I need in class Example :
class Example < ApplicationRecord
belongs_to :appointment
belongs_to :model_bbb
end
Update 2
Ok, I figured out I can use solution from this answer. Basically I can drop "Appointment" model and have class Example like this:
class Example < ApplicationRecord
belongs_to :physician
belongs_to :patient
belongs_to :model_bbb
end
Then in Physician and Patient I can do has_many :examples and another through relationship. I did wanted to do some strange belongs_to through thing as I could have relatively small table of class Appointment, but class Example table is expected to be quite big. So my initial thinking was not to create extra column which will be duplicated for so many times.
You could just add a has_many or has_many through to your Appointment model class.
Keep in mind that belongs_to association is always on the table that has the foreign key. So if you're right at your modeling you should have an appointment_id, for instance, on the examples table.
There's no problem using the associative table in relationship with another. Actually the idea of using an intermediate table is to be able to store other information on it (otherwhise you would be doing HABTM).

Belongs to many association?

I guess this is a pretty conceptual question. I'm looking through the possible associations available through Rails, but cannot seem to wrap my head around how to build a "belongs_to_many" and "has_many" association.
Specifically, I want readers to have many books, and each book to belong to many readers.
The closest I can find is the "has_many_and_belongs_to" association, but based on all of the examples I found, it is not exactly accurate.
Likewise, according to the documentation, the "belongs to" and "has many" association is meant as a one to many.
Is there an association available that matches a belongs to many style or some sort of model structure I could use?
Update: Jul 2022
has_and_belongs_to_many is not recommended anymore.
Please use has_many :through approach.
You need to use either
has_and_belongs_to_many
class Book < ActiveRecord::Base
has_and_belongs_to_many :readers
end
class Reader < ActiveRecord::Base
has_and_belongs_to_many :books
end
with this approach, you will need to create a join table named books_readers
rails g migration CreateJoinTableBooksReaders books readers
OR
has_many :through
class Book < ActiveRecord::Base
has_many :book_readers
has_many :readers, through: :book_readers
end
class Reader < ActiveRecord::Base
has_many :book_readers
has_many :books, through: :book_readers
end
class BookReader < ActiveRecord::Base
belongs_to :reader
belongs_to :book
end
with this approach, you will need to create a new model BookReader
rails g model BookReader book:references reader:references

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

Rails has_many dual relationship with other model

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

Resources