めもめも。
Railsで依存関係があるモデルを実装するのに、この記事が大変参考になります。
素晴らしい記事です。ただ残念ながら2007年、当時はRails 2.*ですから、現在とは色々と違っています。
Rails 3.1.1で同じような事をやってみました。
ひとつのPostに複数のCommentが所属する、ブログ風のシステムです。
Postを作る
普通にPostを作ります。
Ruby on Railsを開始。
$ rails new blog $ cd blog
まずはscaffoldで、モデル、ビュー、コントローラーを、rakeでDBを作成。
$ rails g scaffold Post title:string body:text $ rake db:migrate
rootにアクセスしたとき、Postの一覧が表示されるようroutingを設定。
config/routes.rb
# You can have the root of your site routed with "root" # just remember to delete public/index.html. # root :to => 'welcome#index' root :to => 'posts#index'
そこのコメントにある通り、index.htmlを削除する。(削除しないとそれが表示されてしまうので。)
$ rm public/index.html
とりあえずここらで動かしてみる。Railsの簡易Webサーバーを起動する。(Apacheで見られるならそっちでもいい。)
$ rails s
http://192.168.1.1:3000/
等をブラウザーで開く。ついでなので幾つかデータを登録しておく。
終わったらサーバーを
CommentをPostの配下に作る
さあ本番だ。
Postを外部キーに持つCommentを作成
外部キーにPostを持つCommentを作成。
$ rails g scaffold Comment post:references body:text $ rake db:migrate
reference
ではなくreferences
ね。
config/routes.rb
冒頭のこれを、
resources :comments resources :posts
こうする。
resources :posts do resources :comments end
モデルも修正する。
app/models/post.rb
class Post < ActiveRecord::Base end
class Post < ActiveRecord::Base has_many :comments end
これでCommentはPost配下になった。(本当はCommentsのモデルでも指定しなくちゃいけないんだけど、scaffoldがちゃんとやってくれている。)
まだ実装がroutingに沿っていないので、修正が必要。
コントローラーを修正
CommentがPost配下であるからには、Commentの操作の際は必ずPostが存在する。
Commentのコントローラーで、所属するPostをインスタンス変数に呼び出しておく。冒頭でフィルターを追加。
app/controllers/comments_controller.rb
class CommentsController < ApplicationController before_filter :load_post def load_post @post = Post.find(params[:post_id]) end
これでPostのインスタンスを得るようになった。続いて、Commentのインスタンスは全てこの@postから得るようにする。
変更前 | 変更後 | 数 |
---|---|---|
Comment.find | @post.comments.find | 4 |
Comment.new | @post.comments.build | 2 |
redirect_to(@comment) | redirect_to([@post, @comment]) | 2 |
redirect_to(comments_url) | redirect_to(post_comments_url(@post)) | 1 |
例えばこれを、
def show @comment = Comments.find(params[:id])
こうする。
def show @comment = @post.comments.find(params[:id])
ビューを修正
コントローラーの次はビュー。
app/views/comments/_form.html.erb
<%= form_for(@comment) do |f| %>
<%= form_for([@post, @comment]) do |f| %>
Postはシステムが取得するので、ユーザーは入力しない。この部分は削除。
<div class="field"> <%= f.label :post %><br /> <%= f.text_field :post %> </div>
app/views/comments/new.html.erb
<%= link_to 'Back', comments_path %>
<%= link_to 'Back', post_comments_path %>
app/views/comments/edit.html.erb
<%= link_to 'Show', @comment %> | <%= link_to 'Back', comments_path %>
<%= link_to 'Show', [@post, @comment] %> | <%= link_to 'Back', post_comments_path %>
app/views/comments/index.html.erb
<td><%= link_to 'Show', comment %></td> <td><%= link_to 'Edit', edit_comment_path(comment) %></td> <td><%= link_to 'Destroy', comment, confirm: 'Are you sure?', method: :delete %></td>
<td><%= link_to 'Show', post_comment_path(@post, comment) %></td> <td><%= link_to 'Edit', edit_post_comment_path(@post, comment) %></td> <td><%= link_to 'Destroy', post_comment_path(@post, comment), confirm: 'Are you sure?', method: :delete %></td>
もいっちょ。
<%= link_to 'New Comment', new_comment_path %>
<%= link_to 'Back', post_path(@post) %> | <%= link_to 'New Comment', new_post_comment_path %>
app/views/comments/show.html.erb
<%= link_to 'Edit', edit_comment_path(@comment) %> | <%= link_to 'Back', comments_path %>
<%= link_to 'Edit', edit_post_comment_path %> | <%= link_to 'Back', post_comments_path %>
最後に、Postの画面からCommentの一覧へのリンクを張る。
app/views/posts/show.html.erb
<%= link_to 'Edit', edit_post_path(@post) %> | <%= link_to 'Back', posts_path %>
<%= link_to 'Comments', post_comments_path(@post) %> | <%= link_to 'Edit', edit_post_path(@post) %> | <%= link_to 'Back', posts_path %>
これで一通りの操作が行えるはず。/posts/1/comments/1
でコメントが表示されたりする事を確認。
Commentの一覧をPostの参照画面に結合
Postの表示画面にCommentの一覧を埋め込む。(元記事だとPostのviewにComment一覧を直接出力してるけど、こうやって分けた方が良さそう。)
新規作成画面も取り込むので、空のCommentインスタンスを用意しておく。(名前は変えた方が良さそうだなあ。)
app/controllers/posts_controller.rb
def show @post = Post.find(params[:id]) @comment = @post.comments.build
この空のCommentインスタンスも@post.commentsに含まれるみたいなので、@post.comments.sizeは最小でも1になる。
で、Postの参照画面に、Commentのビューを含める。
app/views/posts/show.html.erb
<%= link_to 'Comments', post_comments_path(@post) %> | <%= link_to 'Edit', edit_post_path(@post) %> | <%= link_to 'Back', posts_path %>
<%= render 'comments/index' %> <%= render 'comments/form' %> <%= link_to 'Edit', edit_post_path(@post) %> | <%= link_to 'Back', posts_path %>
これはpartialという機能。読み込むファイルはファイル名の先頭に”_”が付く。
Comment一覧へのリンクもいらないので削除。
一覧は新しくファイルを作る。
app/views/comments/_index.html.erb
<h2>Comments</h2> <ul> <% if @post.comments.size < 2 %> <li>No comments.</li> <% else %> <% @post.comments.each do |comment| %> <% unless comment.id.nil? %> <li><%= comment.body %> (<%= comment.created_at %> | <%= link_to 'Edit', edit_post_comment_path(@post, comment) %> | <%= link_to 'Destroy', post_comment_path(@post, comment), confirm: 'Are you sure?', method: :delete %>) </li> <% end %> <% end %> <% end %> </ul>
また書き込み後はComment一覧画面ではなく、そのPost参照画面に移動するようにする。
def create @comment = @post.comments.build(params[:comment]) respond_to do |format| if @comment.save format.html { redirect_to [@post, @comment], notice: 'Comment was successfully created.' }
def create @comment = @post.comments.build(params[:comment]) respond_to do |format| if @comment.save format.html { redirect_to @post, notice: 'Comment was successfully created.' }
これで一体化完了。
まあデザインはどうにかしてください。
おまけ、本文の改行を有効に
app/views/posts/show.html.erb
<%= @post.body %>
<%= simple_format @post.body %>
とっぴんぱらりのぷう。