Skip to content
Alex Naser edited this page Oct 8, 2015 · 1 revision

objectives

  • learn relational database basics
  • learn ActiveRecord + ORM basics
    • associations
    • instance methods
  • create Post records from tux
  • incorporate Post records into index.erb

tutorial

In our last step, we solved the problem of hardcoding html every time a post is added. But now we'd have to hardcode Ruby code every time! So let's talk about persistence, our key to a dynamic, user-driven application.

intro to relational databases

First, we need a place to put these post objects and we need a structure for organizing them. For applications like Finstagram, a relational database is our answer.

RDBs use tables where:

  • rows represent objects
  • columns represent fields

Let's arrange our three sample posts in a database table. Here's our first post in a Ruby hash:

@post_shark = {
  username: "sharky_j",
  avatar_url: "http://naserca.com/images/sharky_j.jpg",
  photo_url: "http://naserca.com/images/shark.jpg",
  humanized_time_ago: humanized_time_ago(15),
  like_count: 0,
  comment_count: 1,
  comments: [{
    username: "sharky_j",
    text: "Out for the long weekend... too embarrassed to show y'all the beach bod!"
  }]
}

Ignoring the :comments for now, let's see how that maps to a table:

username avatar_url photo_url time_ago_in_minutes like_count comment_count
"sharky_j" "http://naserca.com/images/sharky_j.jpg" "http://naserca.com/images/shark.jpg" 15 0 1

A few notes:

  • The column (or field) names at the top are there simply for reference. That row obvsiouly doesn't represent a post.
  • Notice we've changed humanized_time_ago back to the raw time_ago_in_minutes. We can't run a Ruby method in our database. We'll elaborate soon about this.

And here's all three posts together:

username avatar_url photo_url time_ago_in_minutes like_count comment_count
"sharky_j" "http://naserca.com/images/sharky_j.jpg" "http://naserca.com/images/shark.jpg" 15 0 1
"kirk_whalum" "http://naserca.com/images/kirk_whalum.jpg" "http://naserca.com/images/whale.jpg" 65 0 1
"marlin_peppa" "http://naserca.com/images/marlin_peppa.jpg" "http://naserca.com/images/marlin.jpg" 190 0 1

Looks great. But now we need to actually create this table.

database setup

Take a look in db/migrate/. You'll see I've left you a gift of a file: 0_create_base_tables.rb. We won't go over the specifics of this, but you can get the gist: we're creating four database tables with various columns.

To run this file, in your console, run:

bundle exec rake db:migrate

Let's make sure those tables were actually created by opening a database visualizer.

  1. Open Database
  2. Select path/to/finstagram/db/db.sqlite3
  3. Browse Data
  4. Cool!

We can also check out our database structure in db/schema.rb. Notice in our posts table, we only have a few columns: user_id, photo_url, created_at, updated_at. Those last two are auto-created by ActiveRecord, so really we only will be managing two: user_id and photo_url.

Where's our other information going? Some of it will be held in other tables. For example, the users table will hold our username and avatar_url columns because that information really "belongs" to the user, not the post. The post will refer to that user via—you guessed it—user_id!

ActiveRecord

We haven't talked about how we actually get our data in to the database. We're going to use the go-to ORM for Ruby: ActiveRecord. Basically, ActiveRecord gives us the ability to easily manage our database tables using Ruby.

To start with ActiveRecord, we'll need a Ruby class that inherits from ActiveRecord::Base. Let's create a file in app/models called post.rb:

class Post < ActiveRecord::Base
  
end

Since we know we need to throw some information in our user objects (thus users table) as well, let's create that model class at app/models/user.rb:

class User < ActiveRecord::Base
  
end

You're right: these are our templates for creating Post and User objects. Now instead of dumb Hash objects representing our posts, we have much more powerful object types. (We won't harness all that power right away, but patience!)

Let's take it for a spin. In your console, fire up tux:

bundle exec tux

Now let's initialize an instance of our User class:

user = User.new({ username: "sharky_j", avatar_url: "http://naserca.com/images/sharky_j.jpg" })

Already we can see some superpowers over our lowly hashes. Notice we didn't have to list all of our user properties. Our class (or template for an object) knows what a user needs and acts accordingly.

We can also call pre-baked methods on our user:

user.username # => "sharky_j"
user.avatar_url # => "http://naserca.com/images/sharky_j.jpg"
user.valid? # => true

Take a look at that last one. That lets us know the user is valid and ready to be saved into the database. Prove it!

user.save # => true

Tada! Check out sqlite viewer to see the new record.

Back in tux, let's create a Post record in the same way as our User:

post = Post.new({ photo_url: "http://naserca.com/images/shark.jpg" })

In addition to the Hash we pass in on initialization, we can also set properties on ActiveRecord objects like this:

post.user_id = user.id # => 1

This is where we really start to see the magic of relational databases. In our @post_shark hash, we had to hold all the information about our user (avatar_url and username). But what would happen for other posts from that same user? We'd have to duplicate that same information for every post! And then what happens if that user changes their username? Yeah. Bad.

For this reason, we hold all that information in the users table and simple refer to its row in our posts table. Hence post.user_id = user.id!

Okay let's save it:

post.save # => true

Using the posts in app/actions.rb as a reference, go ahead and add the other two posts and users via tux. I'll wait here.

Psst...

user = User.new({ username: "kirk_whalum", avatar_url: "http://naserca.com/images/kirk_whalum.jpg" })
user.save

post = Post.new({ photo_url: "http://naserca.com/images/whale.jpg", user_id: user.id })
post.save

user = User.new({ username: "marlin_peppa", avatar_url: "http://naserca.com/images/marlin_peppa.jpg" })
user.save

post = Post.new({ photo_url: "http://naserca.com/images/marlin.jpg", user_id: user.id })
post.save

Check sqlite viewer to make sure you've saved all three posts and all three users. And strap yourself in for pulling these in to our index.erb.