Guide to Data Relationships in Rails
In this guide you'll learn the basics of how data relationships work in Rails. Additionally, we'll walk through a system for determining how to decide on foreign key placement.
Guide Tasks
  • Read Tutorial
  • Watch Guide Video
Video locked
This video is viewable to users with a Bottega Bootcamp license

In this guide, we're going to start talking about data relationships. This is a very important topic that we're going to be addressing through the entire course because data relationships allow us to have a hierarchy and also a connection between different models and different tables in an application. If you are planning to become a professional Rails developer, you're going to be using data relationships constantly. So, in this guide, I want to introduce the concept to you and show you how we can use it in our application.

Update, if you are using a Rails Version after 5.1 you need to add this code to your topic model.

Instead of

belongs_to :topic

You need

belongs_to :topic, optional: true

Inside of your model
blog.rb

What Is a Data Relationship

First I want to show you an example of how data relationships work at a practical level. I have the DevCampsite open. I am on a section page. This section has the ability to have multiple posts. In Rails, the vernacular would be this section has many posts.

The section is called "Building your first Rails application". Below the header there are various posts that are associated with this section. Actually, all these posts belong to this section. If you where to go to the database and find "Creating a New Rails Application from Scratch", you would see that it has a section id that associates the post with this particular section.

Let's open our schema file. If you want to have a connection between any of these items, say blogs and portfolios, (there wouldn’t be, but let’s just suppose) you would have to build out a connection. The process would be to create an id for one of the tables that would associate it with the other table.

Creating a Data Relationship

For our application, we are going to create a new model from scratch, which is connected to blogs. In general, it's common for blogs to be associated with a specific category or a topic, so we will implement that. We're going to create a topic model and a topic table, then have blogs associated with that table.

To begin, open the terminal and switch to our data-feature branch with the code git checkout data-feature. You can verify you are on the correct branch by looking at the portfolio.rb file. You should see the data validations in your code that we created on this branch in the last guide.

Back in the terminal, let’s create a model with the command
rails g model Topic title:string

This will create a migration file and a model file.

large

Then, we'll migrate the database with the code rails db:migrate
We know that Topic is going to have a title. Go ahead and open the model file, topic.rb, and add the validation right away. Personally, I prefer to create these validations right away, otherwise I'll forget this step and can run into a data validation error.

class Topic < ApplicationRecord
  validates_presence_of :title
end

Determining the connection

Now that we have Topic, with a title, we need to create a connection between our blog table and topic table. The first step is to decide if a topic belongs to a blog or a blog belongs to a topic. If you haven't done a lot of database design before, this can seem a little confusing. So, let's draw this out with a base case. Let's say, we have the following:

Blog posts:
Baseball World Series
Super bowl
Spring Training

Topics Titles:
Baseball
Football

Keep in mind, this simply is a base case scenario, you can have whatever topics you want, and as many as you want, it is not limited. Since most people like sports analogies, I'm going to keep using them. Clearly we have two baseball blogs with a football blog in between. We know we have to add a data attribute to one of the tables. The question is whether the Baseball World Series blog is owned by baseball topic, or is baseball topic owned by the Baseball World Series blog. From this example, it's clear that it's the topic that owns the blog post. The reason is fairly practical.

Consider that we want to add a data attribute to the schema. If we add a blog id to the topics table, it means that the topic is owned by a blog post. If we do that, the sports scenario we have above would be impossible because the blog post would remain by itself, and the topic would need to have an id. Let’s clarify this by using ids. So each post get’s an id.

Blog posts:
1 Baseball World Series
2 Super bowl
3 Spring Training

Topic:
Baseball, blog_id: 1
Football

In the scenario, the topic baseball is owned by the Baseball World Series blog. But, what happens with Spring Training? This should be part of the baseball topic, but then, baseball already has a blog id of 1. So, this would not work.

Let's see what happens when the relationship is in the other direction. Now, the topics have an id of 1 and 2 respectively, and these ids will be associated with blog posts.
So, let's add the ids to blog posts. The Baseball World of Series post will have an id of 1, Super bowl will have an id of 2, and Spring Training will have an id of 1 as well.

Blog posts:
Baseball World Series, topic_id: 1
Super bowl, topic_id: 2
Spring Training, topic_id:1

Topics:
- 1 Baseball
- 2 Football

This allows for more that one blog post to have a particular topic. Hopefully this helps you to interpret which direction the relationship has to be. Whenever you have a system, where you have an item like topics, this topic should have the ability to have many items. So, you can have a million baseball blog posts and 500,000 football blog posts, and you wouldn't have to make any changes to the topic itself. You simply have to reference the connection inside of the database record to denote which topic owns that particular blog post.

Adding the Reference

The next step is to add a reference in the blog table to topics. To do that, use the command
rails g migration add_topic_reference_to_blogs topic:references

This will create the migration file.
Open the migration file, and you'll see that it indicates add reference to the blogs table for topic.

class AddTopicReferenceToBlogs < ActiveRecord::Migration[5.0]
  def change
    add_reference :blogs, :topic, foreign_key: true
  end
end

The last section foreign_key: true is a very efficient and helpful item. In the world of database relations, a foreign key signifies that the item this is not just a regular integer value, though it will be an integer, but that it is a value that references another table. In this case it signifies that blog posts are referencing a topic.

I could have chosen to do a different migration where I added a column to the table and spelled out each item explicitly to say that we were adding an integer data type and that it was a foreign key, however, using a column of type reference does that for us, and is also considered a better practice.

Now, let's migrate the database rails db:migrate.

We can test it out by looking in the schema.rb file.

 create_table "blogs", force: :cascade do |t|
    t.string   "title"
    t.text     "body"
    t.datetime "created_at",             null: false
    t.datetime "updated_at",             null: false
    t.string   "slug"
    t.integer  "status",     default: 0
    t.integer  "topic_id"
    t.index ["slug"], name: "index_blogs_on_slug", unique: true, using: :btree
    t.index ["topic_id"], name: "index_blogs_on_topic_id", using: :btree
  end

So this worked just like we wanted. There just above the slug is our topic_id, and it is of type integer. Remember when we use add_refernce Rails knows to use the integer data type. Now that we have this we can create ‘topics’ and associate topics with blogs. Before we do that we need to modify our models.

Let’s now open the topic.rb and blog.rb . Use view > layout > column:2 to view the files side by side. Our blog now needs to know that it is owned by a topic. In addition to the data in the database, in other words, in addition to having a foreign key in the blogs table for topic, we also need to express that to the model. The blog model has to know that it belongs to a topic. Additionally, the topic need to be aware that it has many blogs associated with it.

The syntax for this is very close to the way you would say it in English.

In blog.rb, we're going to declare belongs_to :topic

class Blog < ApplicationRecord
  enum status: { draft: 0, published: 1 }
  extend FriendlyId
  friendly_id :title, use: :slugged

  validates_presence_of :title, :body

  belongs_to :topic
end

While in topic.rb, we'll state a topichas_many :blogs

class Topic < ApplicationRecord
  validates_presence_of :title

  has_many :blogs
end

With that in place, let's jump to the rails console rails c to test this. I'm going to create a couple of topics.

Topic.create!(title: “Ruby programming”)
Topic.create!(title: “Software Engineering”).

Those are two good topics for developers. If you run Topic.all, you can see both of those items are now in the database. Now, we can connect our topics to our blogs. First let's create a new blog from scratch.
```Blog.create!(title: “Some cool ruby stuff”, body: ‘asdfasdf’, topic_id: Topic.first.id)

Here, the blog will have a regular title and a body. For the topic id , it will run a database query and find the first topic we created, which was ‘Ruby Programing’, then it will grab the topic_id for that topic and set that inside of the topic_id column for the blog. Hit return to run the code.

large

The new blog is created the terminal indicates it is * Blog id: 19*, it has a * topic_id: 1*. This is really useful, but Rails can actually take this to another level.

Let's examine the capabilities. We will use Topic.first and save it in a variable called 't'

t = Topic.first

This will bring back our ‘Ruby Programming’ topic

large

Now I can run t.blogs.

large

This runs a database query called ActiveRecord::Assocations::CollectionProxy which means it is a database relationship. It returned the blog that is associated with that topic. However that is just one side of the relationship.

We can also go in the other direction. If we run, Blog.last.topic, it'll bring back the associated topic.

large

So, it returns Ruby Programming.

That's pretty significant as we were able to create a database connection and have two items reference each other with just a few lines of code.

Finally, let's take a look at what we did in this guide by running a git status. We made a few changes to the blog and the schema and also created topics and then added a reference to them. Let’s add all of it to our GitHub branch with git add . Next use git commit-m ‘Integrated database relationship between topics and blog posts’. Follow that with git push origins data-feature

Now we can check off the ‘create data relationships’ item off in Pivotal Tracker.

Resources