Get association from custom name - ruby-on-rails

My Seminar model has a column named teacher_id. That part of the relationship is working. I can call seminar.teacher to retrieve the User who teaches that seminar.
I'd like to be able to invert that query. In other words, I need to call teacher.own_seminars to get a collection of all the seminars where that User is listed as the teacher_id. I know that I could call Seminar.where(:teacher => teacher), but that's clunky. And I think that the performance is probably worse that way.
Note: Some of the Users are students who are linked to Seminar through the seminar_user join table, but I don't think that affects this question.
This is the model setup that isn't quite working yet
class User < ApplicationRecord
has_many :own_seminars, :class_name => "Seminar", foreign_key: 'own_seminar_ids'
end
class Seminar < ApplicationRecord
belongs_to :teacher, class_name: "User", foreign_key: 'teacher_id'
end
Cheers!

In foreign_key option, you specify the column which is, well, the foreign key.
The way has_many works, is it tries to guess, which one of the fields in the referenced entity corresponds to the primary key of this entity. By default, it's user_id (derived from name User). But since your column is actually called teacher_id, you should use that instead.
has_many :own_seminars,
class_name: "Seminar",
foreign_key: 'teacher_id'

Related

Implementing a model with two separate attributes with different names but with same type

I have a Model named Lock which keeps track of two Students who have locked a deal with each other. My Lock model has attributes buyer_id and seller_id which both are the id's of users from my User model. Is there a way to have attributes buyer and seller (for Lock model) that directly access their appropriate User?
For example, I would like to make a call like Lock.last.buyer.email instead of having to go through the user_id like User.find(Lock.last.buyer_id).email.
Also, will this difference in syntax make for better code? Or am I merely making a syntactical improvement for my own readability? Either way, I would like to know if its possible :)
Try these
belongs_to :buyer, class_name: "User", foreign_key: :buyer_id
belongs_to :seller, class_name: "User", foreign_key: :seller_id
There is more info about belongs_to in the documentation.

Ruby on Rails model associations

New in rails here. I have trouble understanding this specific activerecord association. Can someone help me on this. The model looks like this:
class User < ActiveRecord::Base
has_many :client_occurrences,
foreign_key: "client_id",
class_name: "Occurrence"
has_many :requested_occurrences,
foreign_key: "requestor_id",
class_name: "Occurrence"
end
And the one it's associated to is:
class Occurrence < ActiveRecord::Base
belongs_to :template, autosave: true
belongs_to :requestor, class_name: "User"
belongs_to :client, class_name: "User"
end
I just can't seem to understand the associations being portrayed here. Everytime I see the user model, I immediately classify it as an issue because here's how I read the association in the user model:
User has many occurrences alias by client_occurrences and set
client_id as foreign_key
It's an issue for me since the foreign_key is not in the proper table (According to my understanding of the code). In addition, client_id and requestor_id are columns found in the Occurrence table.
Could anyone help?
I'm not sure where your issues are. I would say your reading is correct, namely:
User does have many Occurences (each Occurence points back to
the User)
They are aliased/referenced as client_occurrences from
the perspective of the User The foreign_key is indeed
client_id.
That is, the Occurence table uses client_id to point
to the User
From the point of view of Occurrence:
Each Occurrence belongs to a :client, which means the field name will be client_id (which matches the foreign_key clause in the User model)
The item being pointed to is really a User
One of the things that's confusing, I think, is that the order of the has_many clauses is different from the order of the corresponding belongs_to clauses.
These are the business rules I gather from that:
A User can be associated with an Occurrence as a client
A User can be associated with an Occurrence as a requestor
A User can be associated to many Occurrences
An Occurrence has one requestor User, and one client User
The foreign key is specified in the User model because it's associated to the same model multiple times, otherwise rails would default to using "user_id" as the foreign key in the Occurrence model.
Check this link out for the full details on what all the different ActiveRecord Associations do:
Rails Guides: ActiveRecord Associations

Rails one-to-one relationship

I have the following:
class User < ActiveRecord::Base
has_one :car, :class_name => 'Car', :foreign_key => 'user_id'
class Car < ActiveRecord::Base
belongs_to :worker, :class_name => 'User', :foreign_key => 'user_id'
It is basically a one-to-one relationship between a user and a car.
What I want is for the User to be able to have one and only one car. That implies the fact that if he creates a car assigned to him, he won't be able to create the second.
How could this be done?
There are certainly a couple different ways of accomplishing this. I would suggest creating a composite key index on that table to ensure that the user_id is unique in the table. This will ensure that it will only be present once. In a migration, you could write something like this.
add_index(:cars, :worker_id, :unique => true)
The first argument is the table name (don't forget this is generally the pluralized version of the class name). The field name comes second. The unique true is what will prevent you from inserting an extra row.
Note: This is a database level constraint. If you hit this because validations didn't catch it, it will throw an error.
In addition to this solution, you will want to add a validation to the Car model itself.
validates_uniqueness_of :worker_id, message: "can not have more than one car"
You'll see this error come through with something like "Worker ID can not have more than one car". You will most likely want to customize the "Worker ID" section of this. Refer to this post for instructions on how to do that.
You certainly don't have to do the db constraint, but in case anyone else inserts into the DB, it's a good idea. Otherwise, you'll have "invalid" data as far as Rails is concerned.
Change the definition of the relationship slightly:
class User < ActiveRecord::Base
has_one :car
class Car < ActiveRecord::Base
belongs_to :worker, :class_name => 'User', :foreign_key => 'user_id'
And you'll establish what you're looking for. See: http://guides.rubyonrails.org/association_basics.html#the-has-one-association
why don't you just test before the user tries to add a car?
if worker.car
raise "sorry buddy, no car for you"
else
car = Car.create(user_id: worker.id)
end

Fake a composite primary key? Rails

I have a table with id|patient_id|client_id|active. A record is unique by patient_id, client_id meaning there should only be one enrollment per patient per client. Normally I would make that the primary key, but in rails I have id as my primary key.
What is the best way to enforce this? Validations?
Sounds like you have a model relationship of:
class Client < ActiveRecord::Base
has_many :patients, :through => :enrollments
has_many :enrollments
end
class ClientPatient < ActiveRecord::Base
belongs_to :client
belongs_to :patient
end
class Patient < ActiveRecord::Base
has_many :clients, :through => :enrollments
has_many :enrollments
end
To enforce your constraint I would do it in ActiveRecord, so that you get proper feedback when attempting to save a record that breaks the constraint. I would just modify your ClientPatient model like so:
class Enrollment < ActiveRecord::Base
belongs_to :client
belongs_to :patient
validates_uniqueness_of :patient_id, :scope => :client_id
end
Be careful though because, while this is great for small-scale applications it is still prone to possible race conditions as described here: http://apidock.com/rails/v3.0.5/ActiveRecord/Validations/ClassMethods/validates_uniqueness_of under "Concurrency and Integrity"
As they describe there, you should also add a unique index to the table in the database. This will provide two immediate benefits:
The validation check and any searches through this model based on these two id's will perform faster (since they're indexed)
The uniqueness constraint will be enforced DB-side, and on the rare occurrence of a race condition you won't get bad data saved to the database... although users will get a 500 Server Error if you don't catch the error.
In a migration file add the following:
add_index :enrollments, [:patient_id, :client_id], :unique => true
Hopefully this was helpful :)
Edit (fixed some naming issues and a couple obvious bugs):
It's then very easy to find the data you're looking for:
Client.find_by_name("Bob Smith").patients
Patient.find_by_name("Henry Person").clients
Validations would work (Back them up with a unique index!), but there's no way to get a true composite primary key in vanilla Rails. If you want a real composite primary key, you're going to need a gem/plugin - composite_primary_keys is the one I found, but I'm sure there are others.
Hope this helps!
Add a UNIQUE constraint to your table across the two columns. Here's a reference for MySQL http://dev.mysql.com/doc/refman/5.0/en/constraint-primary-key.html

How do I do multiple has_and_belongs_to_many associations between the same two classes?

I have the following setup:
class Publication < ActiveRecord::Base
has_and_belongs_to_many :authors, :class_name=>'Person', :join_table => 'authors_publications'
has_and_belongs_to_many :editors, :class_name=>'Person', :join_table => 'editors_publications'
end
class Person < ActiveRecord::Base
has_and_belongs_to_many :publications
end
With this setup I can do stuff like Publication.first.authors. But if I want to list all publications in which a person is involved Person.first.publications, an error about a missing join table people_publications it thrown. How could I fix that?
Should I maybe switch to separate models for authors and editors? It would however introduce some redundancy to the database, since a person can be an author of one publication and an editor of another.
The other end of your associations should probably be called something like authored_publications and edited_publications with an extra read-only publications accessor that returns the union of the two.
Otherwise, you'll run in to sticky situations if you try to do stuff like
person.publications << Publication.new
because you'll never know whether the person was an author or an editor. Not that this couldn't be solved differently by slightly changing your object model.
There's also hacks you can do in ActiveRecord to change the SQL queries or change the behavior of the association, but maybe just keep it simple?
I believe you should have another association on person model
class Person < ActiveRecord::Base
# I'm assuming you're using this names for your foreign keys
has_and_belongs_to_many :author_publications, :foreign_key => :author_id
has_and_belongs_to_many :editor_publications, :foreign_key => :editor_id
end

Resources