Commands in Getting Started with Rails

Notes from Getting Started with Rails.

$ ruby -v
$ rails new blog
$ cd blog
$ rails s
$ rails g controller welcome index

Edit config/routes.rb by adding resources and root to

...
resources :posts

root to: "welcome#index"
...
# Check RESTful actions.
$ rake routes

# create posts controller
$ rails g controller posts

http://localhost:3000/posts/new not working. Add new to posts_controller.rg

def new
end

Create app/views/posts/new.html.erb

<h1>New Post</h1>
    
<%= form_for :post, url: posts_path do |f| %>
    <p>
      <%= f.label :title %><br>
      <%= f.text_field :title %>
    </p>

    <p>
      <%= f.label :text %><br>
      <%= f.text_area :text %>
    </p>

    <p>
      <%= f.submit %>
    </p>
<% end %>

<%= link_to 'Back', posts_path %>

Add create to posts_controller.rb

def create
end

Create Post model

$ rails g model Post title:string text:text

This will create app/models/post.rb and db/migrate/20120419084633createposts.rb.

Check db/migrate/20120419084633createposts.rb and run migration.

$ rake db:migrate

Edit posts_controller.rb for create.

def create
  @post = Post.new(post_params)
 
  @post.save
  redirect_to @post
end
 
private
  def post_params
    params.require(:post).permit(:title, :text)
  end

Edit posts_controller.rb for show. Add it before private.

def show
  @post = Post.find(params[:id])
end

Create app/views/posts/show.html.erb.

<p>
  <strong>Title:</strong>
  <%= @post.title %>
</p>
 
<p>
  <strong>Text:</strong>
  <%= @post.text %>
</p>

Can edit posts_controller.rb

def create
  @post = Post.new(params[:post].permit(:title, :text))
 
  @post.save
  redirect_to @post
end

Edit posts_controller.rb index.

def index
  @posts = Post.all
end

Edit app/views/posts/index.html.erb:

<h1>Listing posts</h1>
 
<table>
  <tr>
    <th>Title</th>
    <th>Text</th>
  </tr>
 
  <% @posts.each do |post| %>
    <tr>
      <td><%= post.title %></td>
      <td><%= post.text %></td>
    </tr>
  <% end %>
</table>

Links

Edit app/views/welcome/index.html.erb.

<h1>Hello, Rails!</h1>
<%= link_to "My Blog", controller: "posts" %>
...
...
<%= link_to 'New post', new_post_path %>

Add in app/views/posts/new.html.erb.

...
<%= link_to 'Back', posts_path %>

Add in app/views/posts/show.html.erb.

...
<%= link_to 'Back', posts_path %>

Validation

In app/models/post.rb.

...
validates :title, presence: true,
                    length: { minimum: 5 }
...

Change new in app/controllers/posts_controller.rb.

def new
  @post = Post.new
end
 
def create
  @post = Post.new(params[:post].permit(:title, :text))
 
  if @post.save
    redirect_to @post
  else
    render 'new'
  end
end

Added @post = Post.new in posts_controller is that otherwise @post would be nil in our view, and calling @post.errors.any? would throw an error.

Use render instead of redirect_to when save returns false. The render method is used so that the @post object is passed back.

Add error message to app/views/posts/new.html.erb after form tag.

<% if @post.errors.any? %>
  <div id="errorExplanation">
    <h2><%= pluralize(@post.errors.count, "error") %> prohibited
      this post from being saved:</h2>
    <ul>
    <% @post.errors.full_messages.each do |msg| %>
      <li><%= msg %></li>
    <% end %>
    </ul>
  </div>
  <% end %>

Updating posts

In posts_controller.

def edit
  @post = Post.find(params[:id])
end

Create a file called app/views/posts/edit.html.erb.

<h1>Editing post</h1>
 
<%= form_for :post, url: post_path(@post.id) },
method: :patch do |f| %>
  <% if @post.errors.any? %>
  <div id="errorExplanation">
    <h2><%= pluralize(@post.errors.count, "error") %> prohibited
      this post from being saved:</h2>
    <ul>
    <% @post.errors.full_messages.each do |msg| %>
      <li><%= msg %></li>
    <% end %>
    </ul>
  </div>
  <% end %>
  <p>
    <%= f.label :title %><br>
    <%= f.text_field :title %>
  </p>
 
  <p>
    <%= f.label :text %><br>
    <%= f.text_area :text %>
  </p>
 
  <p>
    <%= f.submit %>
  </p>
<% end %>
 
<%= link_to 'Back', posts_path %>

Define update in app/controllers/posts_controller.rb.

def update
  @post = Post.find(params[:id])
 
  if @post.update(params[:post].permit(:title, :text))
    redirect_to @post
  else
    render 'edit'
  end
end

Update app/views/posts/index.html.erb.

<table>
  <tr>
    <th>Title</th>
    <th>Text</th>
    <th></th>
    <th></th>
  </tr>
 
<% @posts.each do |post| %>
  <tr>
    <td><%= post.title %></td>
    <td><%= post.text %></td>
    <td><%= link_to 'Show', post_path(post) %></td>
    <td><%= link_to 'Edit', edit_post_path(post) %></td>
  </tr>
<% end %>
</table>

And in app/views/posts/show.html.erb.

...
<%= link_to 'Back', posts_path %>
| <%= link_to 'Edit', edit_post_path(@post) %>

Typo in the website

# views/posts/edit.html.erb
<%= form_for :post, url: post_path(@post.id) },
# should be 
<%= form_for :post, url: post_path(@post.id),

# views/posts/index.html.erb
<td><%= link_to 'Show', post_path %></td>
# should be 
<td><%= link_to 'Show', post_path(post) %></td>

Using partials

Create a new file app/views/posts/_form.html.erb.

<%= form_for @post do |f| %>
  <% if @post.errors.any? %>
  <div id="errorExplanation">
    <h2><%= pluralize(@post.errors.count, "error") %> prohibited
      this post from being saved:</h2>
    <ul>
    <% @post.errors.full_messages.each do |msg| %>
      <li><%= msg %></li>
    <% end %>
    </ul>
  </div>
  <% end %>
  <p>
    <%= f.label :title %><br>
    <%= f.text_field :title %>
  </p>
 
  <p>
    <%= f.label :text %><br>
    <%= f.text_area :text %>
  </p>
 
  <p>
    <%= f.submit %>
  </p>
<% end %>

Update the app/views/posts/new.html.erb

<h1>New post</h1>
 
<%= render 'form' %>
 
<%= link_to 'Back', posts_path %>

And update app/views/posts/edit.html.erb view:

<h1>Edit post</h1>
 
<%= render 'form' %>
 
<%= link_to 'Back', posts_path %>

Add destroy to posts_controller.rb

def destroy
  @post = Post.find(params[:id])
  @post.destroy
 
  redirect_to posts_path
end

Add destroy link to app/views/posts/index.html.erb.

<h1>Listing Posts</h1>
<table>
  <tr>
    <th>Title</th>
    <th>Text</th>
    <th></th>
    <th></th>
    <th></th>
  </tr>
 
<% @posts.each do |post| %>
  <tr>
    <td><%= post.title %></td>
    <td><%= post.text %></td>
    <td><%= link_to 'Show', post_path %></td>
    <td><%= link_to 'Edit', edit_post_path(post) %></td>
    <td><%= link_to 'Destroy', post_path(post), method: :delete, data: { confirm: 'Are you sure?' } %></td>
  </tr>
<% end %>
</table>

Typo

# Don'd add any extra space in 

    <td><%= link_to 'Destroy', post_path(post),
                    method: :delete, data: { confirm: 'Are you sure?' } %></td>

# Should be

    <td><%= link_to 'Destroy', post_path(post),method: :delete, data: { confirm: 'Are you sure?' } %></td>

Second model

$ rails generate model Comment commenter:string body:text post:references

This command creates four files. db/migrate/20100207235629createcomments.rb, app/models/comment.rb, test/models/comment_test.rb, test/fixtures/comments.yml

Run migration.

$ rake db:migrate

Edit app/models/post.rb

class Post < ActiveRecord::Base
    has_many :comments
...

Adding a Route for Comments

config/routes.rb

resources :posts do
  resources :comments
end

This creates comments as a nested resource within posts.

Generating a comments controller

$ rails g controller Comments

This command creates the following files.

app/controllers/commentscontroller.rb
app/views/comments/
test/controllers/comments
controllertest.rb
app/helpers/comments
helper.rb
test/helpers/commentshelpertest.rb
app/assets/javascripts/comment.js.coffee
app/assets/stylesheets/comment.css.scss

Edit app/views/posts/show.html.erb for comment.

<p>
  <strong>Title:</strong>
  <%= @post.title %>
</p>
 
<p>
  <strong>Text:</strong>
  <%= @post.text %>
</p>
 
<h2>Add a comment:</h2>
<%= form_for([@post, @post.comments.build]) do |f| %>
  <p>
    <%= f.label :commenter %><br />
    <%= f.text_field :commenter %>
  </p>
  <p>
    <%= f.label :body %><br />
    <%= f.text_area :body %>
  </p>
  <p>
    <%= f.submit %>
  </p>
<% end %>
 
<%= link_to 'Edit Post', edit_post_path(@post) %> |
<%= link_to 'Back to Posts', posts_path %>

Edit app/controllers/comments_controller.rb:

class CommentsController < ApplicationController
  def create
    @post = Post.find(params[:post_id])
    @comment = @post.comments.create(params[:comment].permit(:commenter, :body))
    redirect_to post_path(@post)
  end
end

Edit app/views/posts/show.html.erb to show comments.

<p>
  <strong>Title:</strong>
  <%= @post.title %>
</p>
 
<p>
  <strong>Text:</strong>
  <%= @post.text %>
</p>
 
<h2>Comments</h2>
<% @post.comments.each do |comment| %>
  <p>
    <strong>Commenter:</strong>
    <%= comment.commenter %>
  </p>
 
  <p>
    <strong>Comment:</strong>
    <%= comment.body %>
  </p>
<% end %>
 
<h2>Add a comment:</h2>
<%= form_for([@post, @post.comments.build]) do |f| %>
  <p>
    <%= f.label :commenter %><br />
    <%= f.text_field :commenter %>
  </p>
  <p>
    <%= f.label :body %><br />
    <%= f.text_area :body %>
  </p>
  <p>
    <%= f.submit %>
  </p>
<% end %>
 
<%= link_to 'Edit Post', edit_post_path(@post) %> |
<%= link_to 'Back to Posts', posts_path %>

Refactoring

Create app/views/comments/_comment.html.erb and put the following into it:

<p>
  <strong>Commenter:</strong>
  <%= comment.commenter %>
</p>
 
<p>
  <strong>Comment:</strong>
  <%= comment.body %>
</p>

Create app/views/comments/_form.html.erb containing:

<%= form_for([@post, @post.comments.build]) do |f| %>
  <p>
    <%= f.label :commenter %><br />
    <%= f.text_field :commenter %>
  </p>
  <p>
    <%= f.label :body %><br />
    <%= f.text_area :body %>
  </p>
  <p>
    <%= f.submit %>
  </p>
<% end %>

And edit app/views/posts/show.html.erb look like the following:

<p>
  <strong>Title:</strong>
  <%= @post.title %>
</p>
 
<p>
  <strong>Text:</strong>
  <%= @post.text %>
</p>
 
<h2>Comments</h2>
<%= render @post.comments %>
 
<h2>Add a comment:</h2>
<%= render "comments/form" %>
 
<%= link_to 'Edit Post', edit_post_path(@post) %> |
<%= link_to 'Back to Posts', posts_path %>

Deleting comments

Add to app/views/comments/_comment.html.erb partial:

<p>
  <strong>Commenter:</strong>
  <%= comment.commenter %>
</p>
 
<p>
  <strong>Comment:</strong>
  <%= comment.body %>
</p>
 
<p>
  <%= link_to 'Destroy Comment', [comment.post, comment],
               method: :delete,
               data: { confirm: 'Are you sure?' } %>
</p>

Edit app/controllers/comments_controller.rb:

class CommentsController < ApplicationController
 
  def create
    @post = Post.find(params[:post_id])
    @comment = @post.comments.create(params[:comment])
    redirect_to post_path(@post)
  end
 
  def destroy
    @post = Post.find(params[:post_id])
    @comment = @post.comments.find(params[:id])
    @comment.destroy
    redirect_to post_path(@post)
  end
 
end

Deleting posts delete comments

Edit app/models/post.rb, as follows:

class Post < ActiveRecord::Base
  has_many :comments, dependent: :destroy
  validates :title, presence: true,
                    length: { minimum: 5 }
  [...]
end

Security

Edit app/controllers/posts_controller.rb:

class PostsController < ApplicationController
 
  http_basic_authenticate_with name: "dhh", password: "secret", except: [:index, :show]
 
  def index
    @posts = Post.all
  end
 
  # snipped for brevity

Edit app/controllers/comments_controller.rb) we write:

class CommentsController < ApplicationController
 
  http_basic_authenticate_with name: "dhh", password: "secret", only: :destroy
 
  def create
    @post = Post.find(params[:post_id])
    ...
  end
  # snipped for brevity
Written on September 21, 2013