-
Notifications
You must be signed in to change notification settings - Fork 1.6k
step 5: databases
- learn relational database basics
- learn ActiveRecord + ORM basics
- associations
- instance methods
- create
Post
records from tux - incorporate
Post
records intoindex.erb
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.
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 rawtime_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.
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.
- Open Database
- Select
path/to/finstagram/db/db.sqlite3
- Browse Data
- 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
!
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
.