We're going to use rails to create a simple blog app. As you go through these instructions:
- Carefully read each instruction.
- Be mindful of spelling.
- Commit your changes as you go.
Do not copy and paste the commands. It is ok to copy and paste the html.erb code only. Typing the rest in will help you to remember it and think about what it does.
You'll be creating your rails app in another directory - NOT inside this repo.
- As you build your app, keep
homework.md
open. - Each time you run a new
rake ...
orrails ...
command, jot it down in homework.md. - Describe what each command does. If it has arguments, what do they mean?
- When you're done (even if you don't finish) submit a Pull Request. Your PR should have just 1 changed file:
homework.md
.
Note: Rails has a very large spell-book; it comes with a lot of built-in commands and helpers which can be tough to remember. You may want to write these down in your notes as well.
First go to your wdi development directory, then:
rails new blog -d postgresql
This might take a minute...
rails new example
tells rails to create an app called "example"
-d postgresql
tells rails we want to use Postgres instead of sqlite.
cd blog
rake db:create
git init
git add .
git commit -m "ran rails new blog"
We did
git init
because rails doesn't assume we're going to use git.Note that rails added a
.gitignore
file for you!
rake db:create
tells Rails to create the database our app will use. Otherwise we'll get an error: ActiveRecord::NoDatabaseError
Run rails s
to start the rails server, then visit localhost:3000. You should see a default page.
We're going to use "Posts" as the resource for our app. Let's start with a controller.
rails generate controller posts index
Rails will generate a whole bunch of files here.
```sh create app/controllers/posts_controller.rb route get 'posts/index' invoke erb create app/views/posts create app/views/posts/index.html.erb invoke test_unit create test/controllers/posts_controller_test.rb invoke helper create app/helpers/posts_helper.rb invoke test_unit invoke assets invoke coffee create app/assets/javascripts/posts.coffee invoke scss create app/assets/stylesheets/posts.scss ```In particular you'll notice that it created:
- a new
app/controllers/posts_controller.rb
- a view
app/views/posts/index.html.erb
.
Open up app/controllers/posts_controller.rb
you should see an index method (empty).
If we hadn't specifed
index
at the end of the command rails would not have added the index method, nor created the index view and route.
Rails also made a change to our routes. If you run rake routes
you can see that rails is now mapping /posts/index
to the PostsController's #index
method. It uses the verb GET.
Let's change the routes so that PostsController#index serves the root route (`localhost:3000/) of our app.
- Open
config/routes.rb
- Change the line that says
get 'posts/index'
to say:root 'posts#index'
. - Run
rake routes
and check that the URI/
orlocalhost:3000/
is now mapped toposts#index
. - Refresh the page at
localhost:3000
. You should see a very boring "Posts#index" header that matches the html in/app/views/posts/index.html.erb
The
#
means method in Ruby. Withposts#index
we told rails that the root route should use the index method in the PostsController.
We've created a controller and a view and their associated files. Commit your work.
We need to store our blog posts somewhere, right? We're going to give this model 2 attributes: title, and body.
rails generate model post title:string body:text
What's the difference between
string
andtext
? Just length.
Creating a model also creates a database migration, or code that will add the correct table to the database. We need to run the migration to alter the database. The model, migration and schema.rb change should all be in one git commit because they are all part of creating that model/table.
rake db:migrate
Feel free to look at your migration first before migrating. Check
db/migrate/*
In the future you may even need to edit it before you migrate and commit.
At this point you can use your model from the rails console
$ rails c
.
Post.create('title: first post', body: 'yay!')
Click here to see the files changed in this round.
app/models/post.rb db/migrate/ db/schema.rb test/fixtures/posts.yml test/models/post_test.rb
git status
git add .
git commit -m "Added Post model"
In this step we'll display a post on the page using the controller to get the data from the model.
We don't have any way to write blog posts yet so let's add a little data to our database using the database seed file.
Add these lines to the bottom of your db/seeds.rb
file:
Post.create(title: 'My first Post', body: 'At long last I have created a blog in rails!')
Post.create(title: 'MVC', body: "Model + View + Controller == <3")
puts "Seed complete!"
Now we need to run the seed file. Run:
rake db:seed
Open up app/controllers/posts_controller.rb
:
Edit the index method to look like:
def index
@posts = Post.all
puts "PostsController#index found #{@posts.length} posts!"
render :index
end
Now if you visit localhost:3000
again and then look in the terminal where rails server
is running, you should be able to find the text:
PostsController#index found 2 posts!
.
You might have to scroll up a bit; look for
Post Load (0.2ms) SELECT "posts".* FROM "posts"
in BOLD.
Rails is calling the code in PostsController#index whenever someone requests the root route!
Sadly, your page doesn't display anything new; we'll fix that next!
At this point you have your controller using the model to get the posts from the database. Now we need to display those on the page.
Open the view in app/views/posts/index.html.erb
; replace it's contents with:
<h1>My Blog Posts</h1>
<% @posts.each do |post| %>
<h2><%= post.title %></h2>
<p><%= post.body %></h2>
<% end %>
Then refresh your page. You should see your posts on the page instead.
Quickly, commit all the changes! Take a look at the work you did in this section. See if you can see how things are wired up.
- When a request comes in, which piece of code is first involved?
- What do you think
Post.all
does?
How did we know we could use **@posts** in the view?
Rails takes the instance variables we set in our **controller actions** and makes them available in the **view** that was **render**ed by the action.
`render :index` in the controller is actually optional!
If you don't include a render command Rails will automagically guess that we want to render the `views/posts/index.html.erb` file by looking at the method name (`index`) and the controller name `PostsController`.
WTH is a `<%=`?!
Rails, or rather **erb** uses these for templating similar to the `{{ }}` in handlebars. You can stick Ruby code inside of them! Sometimes we call `<% ... %>`, _clown-hats_. And, `<%= ... %>` is _crying-clown-hats_. The important thing to know is that in _crying-clown-hats_ the output of the ruby inside is rendered into the HTML of the page.
You have a working app now! Congratulations!
We currently have one route in our app that serves all of our blog posts. Buuuuut we don't have any way to write blog posts without editing our seed file.
$ rake routes
Prefix Verb URI Pattern Controller#Action
root GET / posts#index
Let's add a page where we can type our new blog posts.
For this we're going to need to add two routes bringing us to a total of 3.
Verb | URI | Controller#Action |
---|---|---|
GET | / | posts#index |
GET | /posts/new | posts#new |
POST | /posts | posts#create |
We might also consider having:
Verb | URI | Controller#Action |
---|---|---|
GET | /posts | posts#index |
It's a little duplicative but its also a little more RESTful and we can basically do it for free.
- Open up
config/routes.rb
. - Add the following routes:
# /posts/new get 'posts/new', to: 'posts#new', as: 'new_post' # /posts get 'posts', to: 'posts#index' post 'posts', to: 'posts#create'
- Now if you run
rake routes
again you should see the new routes.
to: 'posts#create'
means map this route to the PostsController create method.
as: 'new_post'
assigns a name to this route. Rails gives us methods likenew_post_url
based on these names that we can use later to refer to the URL. It's common to usenew_RESOURCE
as the name here.Why didn't we specify an `as: 'posts'` for the others?
If you run rake routes you can see that Rails automagically assigned the correct name `posts`.Couldn't I use `get 'posts' => 'posts#index'`?
Yes, Rails often has more than one syntax for many of it's methods.
If you were to try to visit GET /posts/new
or POST /posts
right now you'd see an error. That's because we're missing the views for these routes.
You're also missing the methods in the controller, let's deal with that now. Let's go into our app/controllers/posts_controller.rb
and add the two new methods we need.
Wait, you mean 3 new methods, right?
Nope, we mapped both the `GET /` and `GET /posts` routes to the same controller method: `posts#index`.In app/controllers/posts_controller.rb
add the following method:
def new
# assign an empty, un-saved Post instance to use in our view
@post = Post.new
render :new # this is optional
end
Create a new file for the view app/views/posts/new.html.erb
.
Add the following (this time it's ok to copy-paste):
<h1>Write a new blog post</h1>
<%= form_for(@post) do |f| %>
<div class="field">
<%= f.label :title %><br>
<%= f.text_field :title %>
</div>
<div class="field">
<%= f.label :body %><br>
<%= f.text_area :body, rows: 15, cols: 100 %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
<%= link_to 'Back', posts_path %>
You should now be able to visit http://localhost:3000/posts/new. Submitting the form though will result in an error.
- What method (in the controller) gets called when you submit the form?
`form_for` ?
Rails uses form_for to generate a form for a model instance. It has some knowledge about the model used and will take care of setting the 'name' and 'id' attributes in the html. If the instance of the model has some data in it, it can pre-populate the fields. Rails will also add a few hidden fields that it uses for security which you can see if you inspect it in the browser.Let's handle the submitted form. We know that the form will POST to /posts
and that POST /posts
is handled by PostsController#create
; we configured that in our router. (Run rake routes
if you're uncertain.)
We need to take the data from the form (it'll be in params
similar to express) and then save it into the database. Then we'll just redirect our user to the index page so they can view their post.
Open the controller up again and let's add the create method.
def create
post_params = params.require(:post).permit(:title, :body)
post = Post.new(post_params)
if post.save
redirect_to posts_path # go back to /posts index page
else
render :new # in case of error, re-render the '/posts/new' page
end
end
The above code does everything you need. It takes the form data from the params, it creates a new Post instance and saves it to the database and finally it redirects back to the /posts/
route.
Why can't we just do `params.title` and `params.body`?
Rails encourages the use of what's called "strong parameters" for security reasons. Since we might have attributes on our models that we don't want to let a user change we specify which attributes are permitted thusly: `params.require(:post).permit(:title, :body)`You've added the ability to create new posts and display them on your blog. Commit your changes!
Submit homework.md
as a pull-request.
Rails comes with a lot of built-in commands and helpers which can be hard to remember. You may want to write these down in your notes as well.
-
On your own, edit the index view adding a link
<%= link_to _________ %>
to link to the new page. Try usingrake routes
. -
Figure out how to delete a post. (You'll need a
PostsController#destroy
) and a link like<%= link_to 'Destroy', post, method: :delete, data: { confirm: 'Are you sure?' } %>
-
Open up the layout for your app in
app/views/layouts/application.html.erb
, add bootstrap and customize to your <3's content.