Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 971
Chris Muench Avatar asked Mar 07 '11 22:03

Chris Muench


People also ask

Are composite keys good?

Composite keys in SQL prove to be useful in those cases where you have a requirement of keys that can uniquely identify records for better search purposes, but you do not possess any single unique column. In such cases, you must combine multiple columns to create a unique key.

What is composite key made of?

In database design, a composite key is a candidate key that consists of two or more attributes (table columns) that together uniquely identify an entity occurrence (table row). A compound key is a composite key for which each attribute that makes up the key is a foreign key in its own right.

How do Composite keys work?

A composite key is made by the combination of two or more columns in a table that can be used to uniquely identify each row in the table when the columns are combined uniqueness of a row is guaranteed, but when it is taken individually it does not guarantee uniqueness, or it can also be understood as a primary key made ...


2 Answers

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
like image 194
nzifnab Avatar answered Sep 19 '22 01:09

nzifnab


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!

like image 29
Xavier Holt Avatar answered Sep 18 '22 01:09

Xavier Holt