Rails Foreign Key Constraints - The Ugly Way
Render unto Caesar…
Let the database do it’s job. For a while, Rails development focused on pushing logic into models that would be better suited being put in as a table constraint. Articles like this one are great at outlining the proper approach. The problem with this is that it’s not built into the Rails tools, leaving you writing ugly code in your migrations. Not very ruby-esque.
Foreign Keys
Databases have a built in way for managing data integrity between 2 tables. The Foreign Key constraint. This ensures the value of a foreign key exists in the referenced table.
Since Rails was created, it relied on SQL, and foreign keys, but never had a way of confirming the id
being referenced existed, shy of adding a model validation.
Exiting news! Rails 4.2 shipped with foreign key support. You can create migrations that delegate the responsibility directly to the database if your database supports it.
There are other articles that cover some of the basics. I wanted to cover something I ran into recently.
Learn stuff
We’re going to model an app that lets you teach and sign up for courses.
1
2
3
User -> Can be both a teacher and a student
Course -> Belongs to a user by a teacher_id
Enrollment -> Links a user by a student_id, and a course
Some rails
1
2
3
4
5
rails new learn_stuff -d postgresql && cd learn_stuff
rails g model user name
rails g model course topic teacher:belongs_to
rails g model enrollment student:belongs_to course:belongs_to
rake db:create
This sets us up, but when we run rake db:migrate
fireworks start.
1
PG::UndefinedTable: ERROR: relation "teachers" does not exist
This makes sense. We don’t have a teachers
table. We need to pass the course table some more options.
Here’s our migration as it stands.
1
2
3
4
5
6
7
8
9
10
class CreateCourses < ActiveRecord::Migration
def change
create_table :courses do |t|
t.string :topic
t.belongs_to :teacher, index: true, foreign_key: true
t.timestamps null: false
end
end
end
The Rails source shows that we can’t switch the name of table. What would be great is to be able to say something like: “OK RAILS, I’M GOING TO GIVE YOU A COLUMN NAME BUT IT’S NOT THE TABLE NAME FOR THE CONSTRAINT. I’LL GIVE YOU MORE INFO LATER. KTHXBAI”
We have to first create the column, THEN add the constraint.
1
2
3
4
5
6
7
8
9
10
11
class CreateCourses < ActiveRecord::Migration
def change
create_table :courses do |t|
t.string :topic
t.belongs_to :teacher, index: true
t.timestamps null: false
end
add_foreign_key :courses, :users, column: :teacher_id
end
end
The rest of the source code, including the associations for all of this stuff can be found here
Meh
I wish this was a bit smarter, off to work on a pull request.
comments powered by Disqus