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