minimize

事業拡大のため、新しい仲間を募集しています。
→詳しくはこちら

new_post

では早速投稿の部分を見ていく。

new_post.png

まずはControllerから。

app/controllers/posts_controller.rb

def new
  @post = Post.new
  respond_to do |format|
    format.html # new.html.erb
    format.xml  { render :xml => @post }
  end
end

@post に空のPostを作っている。
続いてView。

app/views/posts/new.html.erb

<h1>New post</h1>

<% form_for(@post) do |f| %>
  <%= f.error_messages %>

  <p>
    <%= f.label :name %><br />
    <%= f.text_field :name %>
  </p>
  <p>
    <%= f.label :title %><br />
    <%= f.text_field :title %>
  </p>
  <p>
    <%= f.label :content %><br />
    <%= f.text_area :content %>
  </p>
  <p>
    <%= f.submit "Create" %>
  </p>
<% end %>

<%= link_to 'Back', posts_path %>

form_for(@post) というのが、フォームを形成する部分だろう。
ここから最後のendまでが1ブロックになっている。
これが Closure と呼ばれるRubyの得意分野。

Closure の中では、@post が f に代入される。
f.label :name の部分が、ラベルの定義。
画面上には "Name" と表示されるわけだが、これがどこで定義されてるのかも謎。

f.text_field は、INPUT TEXTタグの定義。
f.text_area は、TEXTAREAタグの定義。
f.submit は、INPUT SUBMITタグの定義。
まぁこの辺りは大体わかるだろう。

Closure

ここで少し考えてみる。f は @post だから、Postクラスのインスタンスである。
だから、f.label なんていう表現が出来ることに若干の違和感を感じる。
Postクラスがlabelメソッドを持っているとは到底思えない。

おそらくだが、これは Closure の機能。
form_for Closure の中でだけ、f.label という記述が有効になる。
f というインスタンスにフォーム関連のメソッドを拡張した。
これは mix-in などと呼ばれる機能。つまりDI(Dependency Injection) である。

new_post_result

最後に link_to で、トップページへのリンクを作成している。
では、何か一つ投稿してみよう。

new_post_1.png

new_post_success.png

無事、投稿に成功。
ついでにデータベースの中身も見てみる。

db_result_1.png

では次に Controller を見てみよう。
新規登録画面のソースを見ると、formタグは以下のように定義してある。

<form action="/posts" class="new_post" id="new_post" method="post">

つまり、投稿時のURLは "/posts" だ。
これだと Controller.index に処理が移りそうな気がするが
実際には Controller.create になる。

app/controllers/posts_controller.rb

# POST /posts
# POST /posts.xml
def create
  @post = Post.new(params[:post])
  respond_to do |format|
    if @post.save
      flash[:notice] = 'Post was successfully created.'
      format.html { redirect_to(@post) }
      format.xml  { render :xml => @post, :status => :created, :location => @post }
    else
      format.html { render :action => "new" }
      format.xml  { render :xml => @post.errors, :status => :unprocessable_entity }
    end
  end
end

コメントにもあるが、POSTだと create に処理が移る。
これはRailsの慣習らしいので、覚えておくとよい。

では、Controller を見ていく。
@post = Post.new(params[:post]) で、画面から入力された値を元に @post を生成する。
…と簡単に言っているが、実は色々なことをやっている。
少し見てみよう。

Post.new

まず、HTMLフォームは以下のように生成されている。

<form action="/posts" method="post">
  <input id="post_name" name="post[name]" size="30" type="text" />
  <input id="post_title" name="post[title]" size="30" type="text" />
  <textarea cols="40" id="post_content" name="post[content]" rows="20"></textarea>
  <input id="post_submit" name="commit" type="submit" value="Create" />
</form>

post[XXX]がポイント。
これで Post.new(params[:post]) とすることによって
以下のような Post インスタンスが生成される。

Post

name = '画面で入力されたname'
title = '画面で入力されたtitle'
content = '画面で入力されたcontent'

Ruby は動的言語なので、name や title といったフィールド(インスタンス変数)は
事前の定義が無くてもいきなり作成できる。その証拠に、Post Model を見てみよう。

app/models/post.rb

class Post < ActiveRecord::Base
end

フィールド定義は何もない。
定義が無いということは記述量が少なくて良い面でもあるが
一つの記述ミス(今回ならばHTML側)によって簡単にシステムが動かなくなってしまうという
危険性もある。

この危険を減らす最大のポイントは「自動生成」。
今回の例ならば、script/generate scaffold Post name:string とした段階で
新規入力画面のHTML(post[name])は自動生成されている。
これならば、入力ミスをすることは無い。

post.save

@post.save で、@post をDBにINSERTする。
ここで成功すれば真が返る。
とりあえず今回は成功したと仮定して処理を追っていく。

flash[:notice] = 'Post was successfully created.'
これは、投稿成功画面に表示されるメッセージ。
flash というのは特殊な変数なのだろう。

最後に画面遷移。redirect_to(@post) ということは、リダイレクトか。
実際、投稿後のURLを見ると以下のようになっている。

http://localhost:3000/posts/1

つまり、今投稿したメッセージの表示画面ということだ。
Controller は以下。

app/controllers/posts_controller.rb

# GET /posts/1
# GET /posts/1.xml
def show
  @post = Post.find(params[:id])
  respond_to do |format|
    format.html # show.html.erb
    format.xml  { render :xml => @post }
  end
end

これらのマッピング "/posts/1" => show はどこでも定義されていない為
Railsのデフォルトマッピングなのだろう。
おそらく親クラスである ApplicationController に書いてあるのだろうが、それはまた今度。

show がやっていることはただ一つ。引数で指定されたidから
投稿されたPostを引っぱってきて画面に表示するだけ。

Viewは以下のようになっている。
app/views/posts/show.html.erb

<p>
  <b>Name:</b>
  <%=h @post.name %>
</p>

<p>
  <b>Title:</b>
  <%=h @post.title %>
</p>

<p>
  <b>Content:</b>
  <%=h @post.content %>
</p>

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

h というのはおそらくメソッドなのだが、どこで定義されているかは不明。
とりあえず今は無視しておく。
さて、notice はどこに行ったのだろう。
ここで、親Viewの登場。

app/views/layouts/posts.html.erb

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
       "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
  <meta http-equiv="content-type" content="text/html;charset=UTF-8" />
  <title>Posts: <%= controller.action_name %></title>
  <%= stylesheet_link_tag 'scaffold' %>
</head>
<body>

<p style="color: green"><%= flash[:notice] %></p>

<%= yield  %>

</body>
</html>

ほら、あった。
ここの yield の部分に、show.html.erb の中身が展開される。
yield は、Closure を呼び出す文法。ここでも Closure が使われていたのだ。

ここでトップページに戻ってみる。

posts_index_2.png

今投稿した内容が表示されている。
ここで、先ほど放っておいた部分を見てみよう。

app/views/posts/index.html.erb

<% for post in @posts %>
  <tr>
    <td><%=h post.name %></td>
    <td><%=h post.title %></td>
    <td><%=h post.content %></td>
    <td><%= link_to 'Show', post %></td>
    <td><%= link_to 'Edit', edit_post_path(post) %></td>
    <td><%= link_to 'Destroy', post, :confirm => 'Are you sure?', :method => :delete %></td>
  </tr>
<% end %>

@posts には投稿のリストが格納されている。
特に目新しいことはやっていない。投稿の内容とリンクを表示しているだけ。
Destroy の部分が何やら変わっているが、これは後で見ることにしよう。