Railsで簡単掲示板その3
今回は前回までに作った掲示板ののブラッシュアップを行っていきたいと思います。
モデルの検証(バリデーション)
掲示板でいろいろと遊んでいると気づけるのですが、トピックを作成する際にタイトルを空欄のままにすることができます。また、以前と同じタイトルのトピックを作成することができます。
やはり、タイトルからどのトピックなのかすぐに分かるようにするためにも、タイトルには重複がない方が良いですし、タイトルが空というのはもってのほかです。
そういうわけで、トピックのタイトルに以上のような制約条件をつけたいと思います。
Rails にはデータが制約条件を満たしているのかを確認するための機構として、バリデーションというものがあります。ここでは、このバリデーションを使って重複の検知などを行ってみましょう。
バリデーションはモデルクラスに書き込んでいきます。 app/models/topic.rb
を開いて以下のように中身を書き換えましょう。
class Topic < ActiveRecord::Base
attr_accessible :title
has_many :posts
validates_presence_of :title
validates_uniqueness_of :title
end
以上のような記述 validates_presence_of
で要素が空でないこと、 validates_uniqueness_of
で要素が一意であることを検証することができます。
実際に重複したタイトルをつけられないことや、タイトルを空にできないことを確認しておきましょう。
ただし、完全に同時に同じタイトルのトピックを作ると、同じタイトルのトピックを作成できる可能性があります。このような可能性を完全に排除したい場合はデータベースレベルで制約を書くようにする必要があります。
ポストにバリデーションの追加
トピックのタイトルと同様にポストの方にもバリデーションを書いておきましょう。
ポストの制約としては投稿者 (contributor) が空でないこと、 内容 (content) が空でないことぐらいで十分でしょうか?
これを実現するために app/models/post.rb
を以下のように編集すれば良いでしょう。
class Post < ActiveRecord::Base
attr_accessible :content, :contributor, :post_number
belongs_to :topic
validates_presence_of :contributor
validates_presence_of :content
end
これで、うまく動くことを確認しておいてください。
投稿失敗時の処理の改良
現在、投稿失敗時に表示される URL を確認すると、 /topics/{id}/posts
となっていることが確認できます。しかし実際にレンダリングされている画面は /topics/{id}/posts/new
のようです。確かにこれでも良いかもしれませんが、投稿時は /topics/{id}
の画面を表示しているので、失敗時にもこちらの画面を表示してくれる方が親切な気がします。そこで、これを達成するための改良を行いたいと思います。
現状の app/controllers/posts_controller.rb
のコードをのぞいてみると
if @post.save
format.html { redirect_to [@topic, @post], notice: 'Post was successfully created.' }
format.json { render json: @post, status: :created, location: [@post, @post] }
else
format.html { render action: "new" }
format.json { render json: @post.errors, status: :unprocessable_entity }
end
の部分から投稿失敗時に posts の new のテンプレートを使ってレンダリングを行っていることが分かります。
そこで以下のようにコードを書きかえます。
def create
@topic = Topic.find(params[:topic_id])
@post = @topic.posts.build(params[:post])
max_num = @topic.posts.maximum(:post_number)
max_num = 0 if max_num.blank?
@post.post_number = max_num + 1
respond_to do |format|
if @post.save
format.html { redirect_to [@topic, @post], notice: 'Post was successfully created.' }
format.json { render json: @post, status: :created, location: [@post, @post] }
else
@topic = Topic.find(params[:topic_id])
@posts = @topic.posts
format.html { render "topics/show" }
format.json { render json: @post.errors, status: :unprocessable_entity }
end
end
end
ここで、@topic を代入し直していることに気をつけてください。
上で使われている @topic を失敗時のレンダリングにそのまま利用しようとするとルーティングエラーが発生します。これは @topic.posts.build
を利用しているのが原因です。
いらないリンクを削除する
現状では個々の post の表示ができるようになっていますが、この機能はいらないですし、編集機能も使わない気がします。そこで、これらのリンクはすべて削除してしまいましょう。
以下のように app/views/topics/show.html.erb
を編集します。折角のテコ入れなのでテーブルの使用をやめるなどの変更も行いました。
<h1><%= @topic.title %></h1>
<div id="post-list">
<% @posts.each do |post| %>
<div class="post">
<div class="post-header">
<%= post.post_number %>:<%= post.contributor %></div>
</div>
<div class="post-body">
<%= post.content %>
</div>
<div class="post-footer">
<%= link_to '削除', [@topic, post], method: :delete, data: { confirm: '本当によろしいですか?' } %>
</div>
</div>
<% end %>
</div>
<p id="notice"><%= notice %></p>
<%= form_for([@topic, @post]) do |f| %>
<% if @post.errors.any? %>
<div id="error_explanation">
<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 %>
<div class="field">
<%= f.label :contributor, '投稿者名' %><br />
<%= f.text_field :contributor, :size => 20 %>
</div>
<div class="field">
<%= f.label :content, '投稿内容' %><br />
<%= f.text_area :content, :size => '60x5' %>
</div>
<div class="actions">
<%= f.submit '投稿' %>
</div>
<% end %>
<%= link_to 'トピック一覧に戻る', topics_path %>
Post のいらないアクションを削除する
いろいろと改良を重ねていくうちに使わなくても良いようなビュー、アクションが増えてきました。そこで、いらないものを消す作業を行いたいと思います。
いらないと思われるのは、 app/views/posts
以下のビューすべてと、 app/controllers/posts_controller.rb
の index
, show
, new
, edit
, update
メソッドでしょうか。
以上のファイル(ディレクトリ)やメソッドを削除してしまいましょう。 posts_controller.rb
は以下のようにだいぶシンプルになるはずです。
class PostsController < ApplicationController
# POST /posts
# POST /posts.json
def create
@topic = Topic.find(params[:topic_id])
@post = @topic.posts.build(params[:post])
max_num = @topic.posts.maximum(:post_number)
max_num = 0 if max_num.blank?
@post.post_number = max_num + 1
respond_to do |format|
if @post.save
format.html { redirect_to [@topic, @post], notice: 'Post was successfully created.' }
format.json { render json: @post, status: :created, location: [@post, @post] }
else
@topic = Topic.find(params[:topic_id])
@posts = @topic.posts
format.html { render "topics/show" }
format.json { render json: @post.errors, status: :unprocessable_entity }
end
end
end
# DELETE /posts/1
# DELETE /posts/1.json
def destroy
@topic = Topic.find(params[:topic_id])
@post = Post.find(params[:id])
@post.destroy
respond_to do |format|
format.html { redirect_to topic_posts_url(@topic) }
format.json { head :no_content }
end
end
end
次に、消したメソッドに合わせてルーティングのほうも編集しておきましょう。
以下のように config/routes.rb
の resources 部分を編集してください。
resources :topics do
resources :posts, :only => [:create, :destroy]
end
これで、 posts_controller
の create
と destroy
だけにルーティングされるようになりました。
このままだと、posts_controller
での処理後に存在しないアクションに飛ばされてしまうので、 app/controllers/posts_controllers.rb
を編集して、 topics/{id}
にリダイレクトすることにしましょう。
class PostsController < ApplicationController
# POST /posts
# POST /posts.json
def create
@topic = Topic.find(params[:topic_id])
@post = @topic.posts.build(params[:post])
max_num = @topic.posts.maximum(:post_number)
max_num = 0 if max_num.blank?
@post.post_number = max_num + 1
respond_to do |format|
if @post.save
format.html { redirect_to @topic, notice: '投稿されました。' }
format.json { render json: @post, status: :created, location: @topic }
else
@topic = Topic.find(params[:topic_id])
@posts = @topic.posts
format.html { render "topics/show" }
format.json { render json: @post.errors, status: :unprocessable_entity }
end
end
end
# DELETE /posts/1
# DELETE /posts/1.json
def destroy
@topic = Topic.find(params[:topic_id])
@post = Post.find(params[:id])
@post.destroy
respond_to do |format|
format.html { redirect_to @topic }
format.json { head :no_content }
end
end
end
以上のようにすれば、期待する場所にリダイレクトされます。ブラウザで動作を確認しておきましょう。
トピック一覧画面の整理
トピック一覧画面を編集して綺麗にまとめておきましょう。
以下のように app/views/topics/index.html.erb
を書き換えましょう。
<h1>トピック一覧</h1> <ul> <% @topics.each do |topic| %> <li> <%= link_to topic.title, topic %>: <%= link_to '削除', topic, method: :delete, data: { confirm: '本当によろしですか?' } %> </li> <% end %> </ul> <br /> <%= link_to '新しいトピック', new_topic_path %>
この編集によって edit, update アクションがいらなくなったので、それに関する編集を行います。
まず、 app/views/topics/edit.html.erb
を削除します。次に topics_controller.rb
から edit, update アクションを削除します。
最後に、config/routes.rb
を以下のように書き換えれば edit アクションに関する設定は完了です。
resources :topics, :except => [:edit, :update] do
resources :posts, :only => [:create, :destroy]
end
まとめ
これで掲示板作成は完了です。お疲れ様でした。
まだまだ直したいというところもあると思うので、そこは各自取り組んでもらえればと思います。
特にやることが思いつかないなぁという方は、トピックの作成時に最初の投稿も一緒に行うように改良を加えてもらえれば面白いのかなと思っていますが、ちょっと難しいでしょうか?
もちろん、見た目にこだわって CSS を使い始めるのも良いかと思います。
Rails でのスタイルシートの埋め込み方はいろいろなページで言及しているのでそちらを参考にやってみるとよいと思います。
ここから一工夫して自分独自の味付けをしてもらう、それが今回の宿題です。