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/commentscontrollertest.rb
app/helpers/commentshelper.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