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
Related
I have the following models
class Widget
has_one :widget_sprocket
has_one :sprocket, through: :widget_sprockets
end
class Sprocket
has_many :widget_sprockets
has_many :widgets, though: :widget_sprockets
end
class WidgetSprocket
belongs_to :widget
belongs_to :sprocket
end
This works fine in the console, but I'm struggling with view updates for Widget. has_many :through gives Sprocket widget_ids, which I believe can be treated like a local attribute for most purposes, but the Rails docs evidently expect a different table configuration for has_one :through and therefore doesn't define a sprocket_id on Widget. As a result code like this throws an unknown attribute error
<%= f.collection_select(:sprocket_id, Sprocket.all, :id, :sprocket_type) %>
Of course I could use has_many :through for both models, but I consider it a last resort.
I think you're falling for a classic trap and overcomplicating this. If you want a one to many assocation between Sprocket and Widget you should just be using belongs_to and adding a sprocket_id foreign key column to the widgets table:
class AddSprocketToWidgets < ActiveRecord::Migration[6.1]
def change
add_reference :widgets, :sprocket, null: false, foreign_key: true
end
end
class Widget
belongs_to :sprocket
end
This guarentees on the database level that a Widget can only have one Sprocket because the column can only hold one single value. Your join table gives no such guarentee. You're really just selecting the first matching row off the join table and its actually a many to many relation. Unless thats acceptable or you prevent it with unique indexes thats an invitation for some nasty bugs.
While there are scenarios where you actually need an intermediadary table that describes a one to many relation - YAGNI.
has_many :through gives Sprocket widget_ids, which I believe can be treated like a local attribute for most purposes
Its not an attribute in any way or form. Its a method which will actually do a SELECT id FROM other_tablequery unless the assocation is preloaded.
but the Rails docs evidently expect a different table configuration for has_one :through and therefore doesn't define a sprocket_id on Widget.
Classic noob trap caused by the confusing semantics of the method names. has_one means there is a foreign key column on the other models table. Its like has_many but with a LIMIT 1 tacked onto the end of the query. To get the id you would actually call other.id.
In the case of belongs_to its not the relations macro that creates the attribute. Its having an actual sprocket_id column on the widgets table.
If you actually wanted to go though creating an intermediary table you can't just assign an id. You would have to use nested attributes and fields_for to create or update a WidgetSprocket instance. Again YAGNI.
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'
I have next structure of models in my app:
class Company
has_many :employees
end
class Employee
belongs_to :company
end
Is there a way to make it possible for employees to have unique ids (default primary keys) depending on belongs_to Company association?
These should return different Employee models:
/companies/1/employees/1
/companies/2/employees/1
Thanks!
Try the sequenced gem, it does exactly what you're asking. There is one consideration to keep in mind though.
Your requirement deprives Employee's id field of uniqueness which it needs to be a primary key. Therefore you'd either need to have a composite key in Employee, namely [:company_id, :employee_id] or use the Employee's acts_as_sequenced field not as the primary key but more like a slug.
Just in case you care to explore the composite key approach, there is composite_primary_key gem which aims to support ActiveRecord associations on top of composite keys. I haven't tried it myself though.
According to its docs, your associations could look something like this:
class Company < ActiveRecord::Base
has_many :employees, :foreign_key => [:company_id, :employee_id]
end
class Employee < ActiveRecord::Base
self.primary_keys = :user_id, :employee_id
belongs_to :company
end
But its quite likely this is an overkill approach for your goal.
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
I have this:
class User < ActiveRecord::Base
has_many :serials
has_many :sites, :through => :series
end
class Serial < ActiveRecord::Base
belongs_to :user
belongs_to :site
has_many :episodes
end
class Site < ActiveRecord::Base
has_many :serials
has_many :users, :through => :serials
end
class Episode < ActiveRecord::Base
belongs_to :serial
end
I would like to do some operations on User.serials.episodes but I know this would mean all sorts of clever tricks. I could in theory just put all the episode data into serial (denormalize) and then group_by Site when needed.
If I have a lot of episodes that I need to query on would this be a bad idea?
thanks
I wouldn't bother denormalizing.
If you need to look at counts, you can check out counter_cache on the relationship to save querying for that.
Do you have proper indexes on your foreign keys? If so, pulling the data from one extra join shouldn't be that big of a deal, but you might need to drop down to SQL to get all the results in one query without iterating over .serials:
User.serials.collect { |s| s.episodes }.uniq # ack! this could be bad
It really depends on the scale you are needing out of this application. If the app isn't going to need to serve tons and tons of people then go for it. If you are getting a lot of benefit from the active record associations then go ahead and use them. As your application scales you may find yourself replacing specific instances of the association use with a more direct approach to handle your traffic load though.