Rails Study(III)Creating a sample
7. Adding a Second Model
7.1 Generating a Model
>rails generate model Comment commenter:string body:text post:references
class Comment < ActiveRecord::Base
belongs_to :post
end
The model belongs_to
migrate database to mysql
>rake db:migrate
7.2. Associating Models
edit post.rb and add the other side of the association:
has_many :comments
If you have an instance variable @post containing a post, you can retrieve all the comments belonging to that post
as the array @post.comments
7.3 Adding a Route for Comments
>vi routes.rb
Railsexample::Application.routes.draw do
root :to => "home#index"
resources :posts do
resources :comments
end
end
7.4 Generating a Controller
>rails generate controller Comments
Link the comments pages to posts pages.
<h2>Add a comment:</h2>
<%= form_for([@post, @post.comments.build]) do |f| %>
<div class="field">
<%= f.label :commenter %><br />
<%= f.text_field :commenter %>
</div>
<div class="field">
<%= f.label :body %><br />
<%= f.text_area :body %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
Link the controller
def create
@post = Post.find(params[:post_id])
@comment = @post.comments.create(params[:comment])
redirect_to post_path(@post)
end
show comments on the post page
<h2>Comments</h2>
<% @post.comments.each do |comment| %>
<p>
<b>Commenter:</b>
<%= comment.commenter %>
</p>
<p>
<b>Comment:</b>
<%= comment.body %>
</p>
<% end %>
It is done. Go to the URL http://localhost:3000/posts/2 to add comments.
8. Refactoring
8.1 Rendering Partial Collections
create the file app/views/comments/_comment.html.erb
<p>
<b>Commenter:</b>
<%= comment.commenter %>
</p>
<p>
<b>Comment:</b>
<%= comment.body %>
</p>
change the file show.html.erb to >
<h2>Comments</h2>
<%= render :partial => "comments/comment",
:collection => @post.comments %>
8.2. Rendering a Partial Form
create a file app/views/comments/_form.html.erb
<%= form_for([@post, @post.comments.build]) do |f| %>
<div class="field">
<%= f.label :commenter %><br />
<%= f.text_field :commenter %>
</div>
<div class="field">
<%= f.label :body %><br />
<%= f.text_area :body %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
edit the page show.html.erb>
<h2>Add a comment:</h2>
<%= render "comments/form" %>
9. Deleting Comments
in file /app/views/comments/_comment.html.erb
<p>
<%= link_to 'Destroy Comment', [comment.post, comment],
:confirm => 'Are you sure?',
:method => :delete %>
</p>
add delete method in controller
def destroy
@post = Post.find(params[:post_id])
@comment = @post.comments.find(params[:id])
@comment.destroy
redirect_to post_path(@post)
end
9.1 Deleting Associated Objects
change in app/models/post.rb
has_many :comments, :dependent => :destroy
10. Security
Rails provides a very simple HTTP authentication system that will work nicely in this situation.
edit our controller app/controllers/application_controller.rb
protect_from_forgery
private
def authenticate
authenticate_or_request_with_http_basic do |user_name, password|
user_name == 'admin' && password == 'password'
end
end
I put put this method inside of ApplicationController so that it is available to all of our controllers.
In post controller
before_filter :authenticate, :except => [:index, :show]
In comments controller
before_filter :authenticate, :only => :destroy
11. Building a Multi-Model Form
Create a new model to hold the tags
>rails generate model tag name:string post:references
>rake db:migrate
edit the post.rb file to create the other side of the association. And to tell rails that you intend to edit tags via posts.
accepts_nested_attributes_for
has_many :tags
accepts_nested_attributes_for :tags, :allow_destroy => :true,
:reject_if => proc { |attrs| attrs.all? { |k, v| v.blank? } }
The :allow_destroy option on the nested attribute declaration tells Rails to display a "remove" checkbox on the view that you'll
build shortly.
The :reject_if option prvents saving new tags that do not have any attributes filled in.
modify views/posts/_form.html.erb to render a partial to make a tag:
<% @post.tags.build %>
<%= form_for(@post) do |post_form| %>
...snip...
<%= render :partial => 'tags/form',
:locals => {:form => post_form} %>
<div class="field">
<%= post_form.label :content %><br />
<%= post_form.text_area :content %>
</div>
create folder app/views/tags and a new file _form.html.erb
<%= form.fields_for :tags do |tag_form| %>
<div class="field">
<%= tag_form.label :name, 'Tag:' %>
<%= tag_form.text_field :name %>
</div>
<% unless tag_form.object.nil? || tag_form.object.new_record? %>
<div class="field">
<%= tag_form.label :_destroy, 'Remove:' %>
<%= tag_form.check_box :_destroy %>
</div>
<% end %>
<% end %>
edit app/views/posts/show.html.erb template to show our tags.
<p>
<b>Tags:</b>
<%= @post.tags.map { |t| t.name }.join(", ") %>
</p>
12 View Helper
Put all the reusable code for views.
edit app/helpers/posts_helper.rb
module PostsHelper
def join_tags(post)
post.tags.map { |t| t.name }.join(", ")
end
end
change show.html.erb from
<p>
<b>Tags:</b>
<%= @post.tags.map { |t| t.name }.join(", ") %>
</p>
to
<p>
<b>Tags:</b>
<%= join_tags(@post) %>
</p>
references:
http://guides.rubyonrails.org/getting_started.html
7. Adding a Second Model
7.1 Generating a Model
>rails generate model Comment commenter:string body:text post:references
class Comment < ActiveRecord::Base
belongs_to :post
end
The model belongs_to
migrate database to mysql
>rake db:migrate
7.2. Associating Models
edit post.rb and add the other side of the association:
has_many :comments
If you have an instance variable @post containing a post, you can retrieve all the comments belonging to that post
as the array @post.comments
7.3 Adding a Route for Comments
>vi routes.rb
Railsexample::Application.routes.draw do
root :to => "home#index"
resources :posts do
resources :comments
end
end
7.4 Generating a Controller
>rails generate controller Comments
Link the comments pages to posts pages.
<h2>Add a comment:</h2>
<%= form_for([@post, @post.comments.build]) do |f| %>
<div class="field">
<%= f.label :commenter %><br />
<%= f.text_field :commenter %>
</div>
<div class="field">
<%= f.label :body %><br />
<%= f.text_area :body %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
Link the controller
def create
@post = Post.find(params[:post_id])
@comment = @post.comments.create(params[:comment])
redirect_to post_path(@post)
end
show comments on the post page
<h2>Comments</h2>
<% @post.comments.each do |comment| %>
<p>
<b>Commenter:</b>
<%= comment.commenter %>
</p>
<p>
<b>Comment:</b>
<%= comment.body %>
</p>
<% end %>
It is done. Go to the URL http://localhost:3000/posts/2 to add comments.
8. Refactoring
8.1 Rendering Partial Collections
create the file app/views/comments/_comment.html.erb
<p>
<b>Commenter:</b>
<%= comment.commenter %>
</p>
<p>
<b>Comment:</b>
<%= comment.body %>
</p>
change the file show.html.erb to >
<h2>Comments</h2>
<%= render :partial => "comments/comment",
:collection => @post.comments %>
8.2. Rendering a Partial Form
create a file app/views/comments/_form.html.erb
<%= form_for([@post, @post.comments.build]) do |f| %>
<div class="field">
<%= f.label :commenter %><br />
<%= f.text_field :commenter %>
</div>
<div class="field">
<%= f.label :body %><br />
<%= f.text_area :body %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
edit the page show.html.erb>
<h2>Add a comment:</h2>
<%= render "comments/form" %>
9. Deleting Comments
in file /app/views/comments/_comment.html.erb
<p>
<%= link_to 'Destroy Comment', [comment.post, comment],
:confirm => 'Are you sure?',
:method => :delete %>
</p>
add delete method in controller
def destroy
@post = Post.find(params[:post_id])
@comment = @post.comments.find(params[:id])
@comment.destroy
redirect_to post_path(@post)
end
9.1 Deleting Associated Objects
change in app/models/post.rb
has_many :comments, :dependent => :destroy
10. Security
Rails provides a very simple HTTP authentication system that will work nicely in this situation.
edit our controller app/controllers/application_controller.rb
protect_from_forgery
private
def authenticate
authenticate_or_request_with_http_basic do |user_name, password|
user_name == 'admin' && password == 'password'
end
end
I put put this method inside of ApplicationController so that it is available to all of our controllers.
In post controller
before_filter :authenticate, :except => [:index, :show]
In comments controller
before_filter :authenticate, :only => :destroy
11. Building a Multi-Model Form
Create a new model to hold the tags
>rails generate model tag name:string post:references
>rake db:migrate
edit the post.rb file to create the other side of the association. And to tell rails that you intend to edit tags via posts.
accepts_nested_attributes_for
has_many :tags
accepts_nested_attributes_for :tags, :allow_destroy => :true,
:reject_if => proc { |attrs| attrs.all? { |k, v| v.blank? } }
The :allow_destroy option on the nested attribute declaration tells Rails to display a "remove" checkbox on the view that you'll
build shortly.
The :reject_if option prvents saving new tags that do not have any attributes filled in.
modify views/posts/_form.html.erb to render a partial to make a tag:
<% @post.tags.build %>
<%= form_for(@post) do |post_form| %>
...snip...
<%= render :partial => 'tags/form',
:locals => {:form => post_form} %>
<div class="field">
<%= post_form.label :content %><br />
<%= post_form.text_area :content %>
</div>
create folder app/views/tags and a new file _form.html.erb
<%= form.fields_for :tags do |tag_form| %>
<div class="field">
<%= tag_form.label :name, 'Tag:' %>
<%= tag_form.text_field :name %>
</div>
<% unless tag_form.object.nil? || tag_form.object.new_record? %>
<div class="field">
<%= tag_form.label :_destroy, 'Remove:' %>
<%= tag_form.check_box :_destroy %>
</div>
<% end %>
<% end %>
edit app/views/posts/show.html.erb template to show our tags.
<p>
<b>Tags:</b>
<%= @post.tags.map { |t| t.name }.join(", ") %>
</p>
12 View Helper
Put all the reusable code for views.
edit app/helpers/posts_helper.rb
module PostsHelper
def join_tags(post)
post.tags.map { |t| t.name }.join(", ")
end
end
change show.html.erb from
<p>
<b>Tags:</b>
<%= @post.tags.map { |t| t.name }.join(", ") %>
</p>
to
<p>
<b>Tags:</b>
<%= join_tags(@post) %>
</p>
references:
http://guides.rubyonrails.org/getting_started.html