Skip to content

17b Categories Posts Migration

Dave Strus edited this page Jul 16, 2015 · 1 revision

SOLUTION

All we need is a foreign key column—category_id—on the posts table. You could simply add that column as an integer and be done with it.

We'll explore a cooler, and equally easy, approach.

When creating a table, you can use a column type of references to define a foreign key column.

create_table :posts do |t|
  t.references :category
end

You can also add an index simultaneously (always a good idea for foreign key columns).

create_table :posts do |t|
  t.references :category, index: true
end

Now to put this knowledge to good use. Generate a migration as always.

$ bin/rails g migration add_category_id_to_posts
      invoke  active_record
      create    db/migrate/20141105034340_add_category_id_to_posts.rb

And edit your migration as follows.

class AddCategoryIdToPosts < ActiveRecord::Migration
  def change
    add_reference :posts, :category, index: true
  end
end

Now migrate!

$ bin/rake db:migrate
== 20141105034340 AddCategoryIdToPosts: migrating =============================
-- add_reference(:posts, :category, {:index=>true})
   -> 0.0073s
== 20141105034340 AddCategoryIdToPosts: migrated (0.0074s) ====================

Let's check out the updated posts table in the database. Remember how to do that?

$ psql -d bluit_development
psql (9.3.2)
Type "help" for help.

bluit_development=# \d posts
                                      Table "public.posts"
   Column    |            Type             |                     Modifiers
-------------+-----------------------------+----------------------------------------------------
 id          | integer                     | not null default nextval('posts_id_seq'::regclass)
 title       | character varying(255)      |
 link        | character varying(2000)     |
 body        | text                        |
 created_at  | timestamp without time zone |
 updated_at  | timestamp without time zone |
 post_type   | integer                     | default 0
 category_id | integer                     |
Indexes:
    "posts_pkey" PRIMARY KEY, btree (id)
    "index_posts_on_category_id" btree (category_id)

(You can quit psql with the command \q.)

Now that our database is prepared, we can add the relationships to our models.

app/models/post.rb

class Post < ActiveRecord::Base
  belongs_to :category

app/models/category.rb

class Category < ActiveRecord::Base
  has_many :posts

Try out those relationships in the console (which you'll need to restart).

Loading development environment (Rails 4.1.6)
[1] pry(main)> post = Post.first
  Post Load (0.6ms)  SELECT  "posts".* FROM "posts"   ORDER BY "posts"."id" ASC LIMIT 1
=> #<Post id: 1, title: "TIL there's an immersive coding experience in Indy...", link: "https://elevenfifty.com/", body: "You should totally check this out. They have a T. ...", created_at: "2014-10-29 18:40:38", updated_at: "2014-11-05 04:03:21", post_type: 0, category_id: 1>
[2] pry(main)> category = Category.last
  Category Load (0.3ms)  SELECT  "categories".* FROM "categories"   ORDER BY "categories"."id" DESC LIMIT 1
=> #<Category id: 3, name: "todayilearned", title: "Today I Learned", description: "You learn something new every day; what did you le...", sidebar: "Submissions must be verifiable. Please link direct...", submission_text: "Please make sure your headline starts with 'TIL' a...", created_at: "2014-11-05 02:41:55", updated_at: "2014-11-05 02:41:55">
[3] pry(main)> post.category = category
=> #<Category id: 3, name: "todayilearned", title: "Today I Learned", description: "You learn something new every day; what did you le...", sidebar: "Submissions must be verifiable. Please link direct...", submission_text: "Please make sure your headline starts with 'TIL' a...", created_at: "2014-11-05 02:41:55", updated_at: "2014-11-05 02:41:55">
[4] pry(main)> post.save
   (0.1ms)  BEGIN
  SQL (0.4ms)  UPDATE "posts" SET "category_id" = $1, "updated_at" = $2 WHERE "posts"."id" = 1  [["category_id", 3], ["updated_at", "2014-11-05 04:08:08.596470"]]
   (6.4ms)  COMMIT
=> true
[5] pry(main)> post.reload
  Post Load (0.3ms)  SELECT  "posts".* FROM "posts"  WHERE "posts"."id" = $1 LIMIT 1  [["id", 1]]
=> #<Post id: 1, title: "TIL there's an immersive coding experience in Indy...", link: "https://elevenfifty.com/", body: "You should totally check this out. They have a T. ...", created_at: "2014-10-29 18:40:38", updated_at: "2014-11-05 04:08:08", post_type: 0, category_id: 3>
[6] pry(main)> post.category
  Category Load (0.3ms)  SELECT  "categories".* FROM "categories"  WHERE "categories"."id" = $1 LIMIT 1  [["id", 3]]
=> #<Category id: 3, name: "todayilearned", title: "Today I Learned", description: "You learn something new every day; what did you le...", sidebar: "Submissions must be verifiable. Please link direct...", submission_text: "Please make sure your headline starts with 'TIL' a...", created_at: "2014-11-05 02:41:55", updated_at: "2014-11-05 02:41:55">
[7] pry(main)> category.posts
  Post Load (0.2ms)  SELECT "posts".* FROM "posts"  WHERE "posts"."category_id" = $1  [["category_id", 3]]
=> [#<Post id: 1, title: "TIL there's an immersive coding experience in Indy...", link: "https://elevenfifty.com/", body: "You should totally check this out. They have a T. ...", created_at: "2014-10-29 18:40:38", updated_at: "2014-11-05 04:08:08", post_type: 0, category_id: 3>]

Are both sides of the relationship working? Let's take a moment to require that all posts have a category assigned to them. Add the following to the Post model:

app/models/post.rb

  validates :category_id, presence: true

You probably have some old post records that don't yet have categories assigned them. You can quickly edit them to assign categories, or do it in bulk via the console.

[1] pry(main)> Post.where(category: nil).each {|p| p.category = Category.first; p.save}

Commit!

$ git add .
$ git commit -m "Add many-to-1 relationship between Post and Category."