【インターン】4日目:業務内容ではないが、コソーリ日本語訳しつつ勉強なうパート2

Rubyの公式チュートリアルをやってみています。(日本語版は説明部分が結構省かれている感じがしたので、原文読んでみてます)

昨日の続きから

6.4 The Model

6.5 Adding Some Validation

app/models/post.rbのPostクラスの中に

  validates_presence_of :name, :title
  validates_length_of :title, :minimum => 5

を追加します。

These changes will ensure that all posts have a name and a title, and that the title is at least five characters long. Rails can validate a variety of conditions in a model, including the presence or uniqueness of columns, their format, and the existence of associated objects.

「これらを追加したことで、投稿されるものには、nameとtitleが記述されているもの+5文字以上である条件が追加されました。」

6.6 Using the Console

To see your validations in action, you can use the console. The console is a command-line tool that lets you execute Ruby code in the context of your application:

「6.5で書き加えた、バリデーションの動きを見るために、コンソールを使います。コンソール画面でコマンドを打つことで、アプリケーション上でrubyのコードを実行させることができます。」
というわけで、このコマンド実行(windowsだと、先頭にRuby を付けて)

script/console

しばらく待っていると、

>>

というのが表示されます。
それが表示されたら、下の実行結果のように

C:\Documents and Settings\sea_mountain\blog>ruby script/console
Loading development environment (Rails 2.3.8)
>> p = Post.create(:content=>"A new post")
=> #<Post id: nil, name: nil, title: nil, content: "A new post", created_at: nil
, updated_at: nil>
>> p.save
=> false
>> p.errors
=> #<ActiveRecord::Errors:0x4899d84 @errors=#<OrderedHash {"name"=>[#<ActiveReco
rd::Error:0x48973cc @type=:blank, @message=:blank, @attribute=:name, @options={:
default=>nil}, @base=#<Post id: nil, name: nil, title: nil, content: "A new post
", created_at: nil, updated_at: nil>>], "title"=>[#<ActiveRecord::Error:0x48972f
0 @type=:blank, @message=:blank, @attribute=:title, @options={:default=>nil}, @b
ase=#<Post id: nil, name: nil, title: nil, content: "A new post", created_at: ni
l, updated_at: nil>>, #<ActiveRecord::Error:0x489705c @type=:too_short, @message
=:too_short, @attribute=:title, @options={:count=>5, :default=>nil}, @base=#<Pos
t id: nil, name: nil, title: nil, content: "A new post", created_at: nil, update
d_at: nil>>]}>, @base=#<Post id: nil, name: nil, title: nil, content: "A new pos
t", created_at: nil, updated_at: nil>>
>>

実行結果の>>のあとに書いてあるコマンドを順番に打ってはエンターしてみてください。上と似たような結果が出るはずです。よく見ると、エラーがでて失敗しています。先程書いたバリデーション(5文字以上かつnameとtitleがないのは禁止)が効いているようです。

Unlike the development web server, the console does not automatically load your code afresh for each line. If you make changes to your models while the console is open, type reload! at the console prompt to load them.

「開発webサーバと違って、コンソールで上のように実行しているときは、自動で変更を読み込んでくれないので、modelに変更を加えた場合は、コンソール画面で、reroad!とコマンドを打ってください。」

6.7 Listing All Posts

ruby script/serverコマンド後に、
http://localhost:3000/posts.xml にアクセスすると、今までの投稿がxml形式で見れるよーとか
xmlで見れるようになっている大本のファイルの説明などがありました。

6.8 Customizing the Layout

「viewはHTMLをどのように見せるかという部分であるが、railsはlayoutの概念も持っている。script/generate scaffoldのコマンドで、自動でデフォルトのレイアウトが作成される。それが、app/views/layouts/posts.html.erbである。」
ということで、
app/views/layouts/posts.html.erbのbodyタグに、style="background: #EEEEEE;"を追加したりして、 http://localhost:3000/posts にアクセスすると、背景色が変わるよーというおはなしです。index本体を変更していないのに、色が変わっている!とか全てに同じレイアウトの適用が簡単にできるのか!という話だと思いました。

6.9 Creating New Posts

どういう仕組でpostが行われているのかという話や、HTMLが自動で作られている流れれなどの説明が書かれていたようです。

6.10 Showing an Individual Post

このアドレス http://localhost:3000/posts/1 にアクセスすると、1番目のpostだけ見れます。これは、showアクション(posts_controller.rbの中にある)を呼び出して、id=1のものを表示するとrailsに理解されるようです。

6.11 Editing Posts

destroyメソッドの話

8.1 Generating a Model

Models in Rails use a singular name, and their corresponding database tables use a plural name. For the model to hold comments, the convention is to use the name Comment. Even if you don’t want to use the entire apparatus set up by scaffolding, most Rails developers still use generators to make things like models and controllers. To create the new model, run this command in your terminal:

railsのモデルは、1つの名前を扱い、関連するDBテーブルは複数の名前があります(?)。"comments"を持つためのmodelには"Comment"と名づけられます。scaffolding全般を使いたくない場合でも、開発者はmodelとcontorollersを作るためにgeneratorを使います。新しいモデルを作るためには、以下のコマンドを実行します」

script/generate model Comment commenter:string body:text
    post:references

もちろんwindowsだと、先頭にruby をつけてコマンドを実行しないといけません。
あとはrake db:migrateをするだけでokですよーという感じの内容。
成功すると以下の結果が返ってきます。

C:\Documents and Settings\sea_mountain\blog>rake db:migrate
(in C:/Documents and Settings/sea_mountain/blog)
==  CreateComments: migrating =================================================
-- create_table(:comments)
   -> 0.1710s
==  CreateComments: migrated (0.1710s) ========================================

7.1 Using Partials to Eliminate View Duplication

このところで作成されているはずの、_form.html.erbがなぜかなかった・・・。
ので9.まで進んだところで、手動で作成してしまいました・・・。

8.3 Adding a Route

config/routes.rb に変更を加えるようです。
一番上が以下のようになっていますが、

ActionController::Routing::Routes.draw do |map|
  map.resources :posts

以下のように付け加えます。

ctionController::Routing::Routes.draw do |map|
  map.resources :posts, :has_many => :comments

8.4 Generating a Controller

上のほうで作成していっていた、commentモデルにあったcontrollerを作成します。
「generatorは以下です」

script/generate controller Comments index show new edit

windowsはrubyを先頭につ(ry

実行結果はこんな感じです。

C:\Documents and Settings\sea_mountain\blog>ruby script/generate controller Comm
ents index show new edit
      exists  app/controllers/
      exists  app/helpers/
      create  app/views/comments
      exists  test/functional/
      exists  test/unit/helpers/
      create  app/controllers/comments_controller.rb
      create  test/functional/comments_controller_test.rb
      create  app/helpers/comments_helper.rb
      create  test/unit/helpers/comments_helper_test.rb
      create  app/views/comments/index.html.erb
      create  app/views/comments/show.html.erb
      create  app/views/comments/new.html.erb
      create  app/views/comments/edit.html.erb
The controller will be generated with empty methods and views for each action that you specified in the call to script/generate controller:

「さっきの、script/generate controllerコマンドで、指定していた分の、空のメソッドとviewが作成されました」

class CommentsController < ApplicationController
  def index
  end

  def show
  end

  def new
  end

  def edit
  end

end

こんな感じです。
でさらにこれを以下のように書き換えます。

class CommentsController < ApplicationController
  def index
    @post = Post.find(params[:post_id])
    @comments = @post.comments
  end

  def show
    @post = Post.find(params[:post_id])
    @comment = @post.comments.find(params[:id])
  end

  def new
    @post = Post.find(params[:post_id])
    @comment = @post.comments.build
  end

  def create
    @post = Post.find(params[:post_id])
    @comment = @post.comments.build(params[:comment])
    if @comment.save
      redirect_to post_comment_url(@post, @comment)
    else
      render :action => "new"
    end
  end

  def edit
    @post = Post.find(params[:post_id])
    @comment = @post.comments.find(params[:id])
  end

  def update
    @post = Post.find(params[:post_id])
    @comment = Comment.find(params[:id])
    if @comment.update_attributes(params[:comment])
      redirect_to post_comment_url(@post, @comment)
    else
      render :action => "edit"
    end
  end

  def destroy
    @post = Post.find(params[:post_id])
    @comment = Comment.find(params[:id])
    @comment.destroy

    respond_to do |format|
      format.html { redirect_to post_comments_path(@post) }
      format.xml  { head :ok }
    end
  end

end
You’ll see a bit more complexity here than you did in the controller for posts. That’s a side-effect of the nesting that you’ve set up; each request for a comment has to keep track of the post to which the comment is attached.

「postsのcontorollerのときに書いたものよりも、もっと複雑ですよね。これは、コメントが、自分がどのpostにつけられたpostであるかということを覚えておくために必要な要素です。」

8.5 Building Views

Because you skipped scaffolding, you’ll need to build views for comments “by hand.” Invoking script/generate controller will give you skeleton views, but they’ll be devoid of actual content. Here’s a first pass at fleshing out the comment views.

「scaffoldingをしないようにするときは、commentsのviewを自分で作らないといけません。script/generate controllerは中身がないviewを作ってくれますが、それでは内容が足りないviewです。」
というわけで、
以下をコピペで書き足していきます。

***views/comments/index.html.erb view

<h1>Comments for <%= @post.title %></h1>

<table>
  <tr>
    <th>Commenter</th>
    <th>Body</th>
  </tr>

<% for comment in @comments %>
  <tr>
    <td><%=h comment.commenter %></td>
    <td><%=h comment.body %></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>
  </tr>
<% end %>
</table>

<br />

<%= link_to 'New comment', new_post_comment_path(@post) %>
<%= link_to 'Back to Post', @post %>
views/comments/new.html.erb view
<h1>New comment</h1>

<% form_for([@post, @comment]) do |f| %>
  <%= f.error_messages %>

  <p>
    <%= f.label :commenter %><br />
    <%= f.text_field :commenter %>
  </p>
  <p>
    <%= f.label :body %><br />
    <%= f.text_area :body %>
  </p>
  <p>
    <%= f.submit "Create" %>
  </p>
<% end %>

<%= link_to 'Back', post_comments_path(@post) %>

views/comments/show.html.erb view

<h1>Comment on <%= @post.title %></h1>

<p>
  <b>Commenter:</b>
  <%=h @comment.commenter %>
</p>

<p>
  <b>Comment:</b>
  <%=h @comment.body %>
</p>

<%= link_to 'Edit', edit_post_comment_path(@post, @comment) %> |
<%= link_to 'Back', post_comments_path(@post) %>

views/comments/edit.html.erb view

<h1>Editing comment</h1>

<% form_for([@post, @comment]) do |f| %>
  <%= f.error_messages %>

  <p>
    <%= f.label :commenter %><br />
    <%= f.text_field :commenter %>
  </p>
  <p>
    <%= f.label :body %><br />
    <%= f.text_area :body %>
  </p>
  <p>
    <%= f.submit "Update" %>
  </p>
<% end %>

<%= link_to 'Show', post_comment_path(@post, @comment) %> |
<%= link_to 'Back', post_comments_path(@post) %>
Again, the added complexity here (compared to the views you saw for managing posts) comes from the necessity of juggling a post and its comments at the same time.

「また、viewのpostsらへんと比べると複雑な内容が追加されましたが、これはpostとcommentを同時に扱うときに必要になります。」

8.6 Hooking Comments to Posts

As a next step, I’ll modify the views/posts/show.html.erb view to show the comments on that post, and to allow managing those comments

「次は、コメント機能を利用することができるように、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>

<h2>Comments</h2>
<% @post.comments.each do |c| %>
  <p>
    <b>Commenter:</b>
    <%=h c.commenter %>
  </p>

  <p>
    <b>Comment:</b>
    <%=h c.body %>
  </p>
<% end %>

<%= link_to 'Edit Post', edit_post_path(@post) %> |
<%= link_to 'Back to Posts', posts_path %> |
<%= link_to 'Manage Comments', post_comments_path(@post) %>

9 Building a Multi-Model Form

とりあえず実行

C:\Documents and Settings\sea_mountain\blog>ruby script/generate model tag name:
string post:references
      exists  app/models/
      exists  test/unit/
      exists  test/fixtures/
      create  app/models/tag.rb
      create  test/unit/tag_test.rb
      create  test/fixtures/tags.yml
      exists  db/migrate
      create  db/migrate/20100820114601_create_tags.rb

さらに実行

C:\Documents and Settings\sea_mountain\blog>rake db:migrate
(in C:/Documents and Settings/sea_mountain/blog)
==  CreateTags: migrating =====================================================
-- create_table(:tags)
   -> 0.0940s
==  CreateTags: migrated (0.0940s) ============================================


/app/models/post.rbを変更します。
元々は

class Post < ActiveRecord::Base
  validates_presence_of :name, :title
  validates_length_of :title, :minimum => 5
  has_many :comments
end

変更後

class Post < ActiveRecord::Base
  validates_presence_of :name, :title
  validates_length_of :title, :minimum => 5
  has_many :comments
  has_many :tags

  accepts_nested_attributes_for :tags, :allow_destroy => :true  ,
    :reject_if => proc { |attrs| attrs.all? { |k, v| v.blank? } }
end
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 prevents saving new tags that do not have any attributes filled in.

「」


タグを持たせるためにviews/posts/_form.html.erbにも変更が必要なようです。
が・・・なぜかないので、手動で作って以下を書き込みます・・・。

<% @post.tags.build if @post.tags.empty? %>
<% form_for(@post) do |post_form| %>
  <%= post_form.error_messages %>

  <p>
    <%= post_form.label :name %><br />
    <%= post_form.text_field :name %>
  </p>
  <p>
    <%= post_form.label :title, "title" %><br />
    <%= post_form.text_field :title %>
  </p>
  <p>
    <%= post_form.label :content %><br />
    <%= post_form.text_area :content %>
  </p>
  <h2>Tags</h2>
  <% post_form.fields_for :tags do |tag_form| %>
    <p>
      <%= tag_form.label :name, 'Tag:' %>
      <%= tag_form.text_field :name %>
    </p>
    <% unless tag_form.object.nil? || tag_form.object.new_record? %>
      <p>
        <%= tag_form.label :_delete, 'Remove:' %>
        <%= tag_form.check_box :_delete %>
      </p>
    <% end %>
  <% end %>

  <p>
    <%= post_form.submit "Save" %>
  </p>
<% end %>

がしかし。1つ1つのpostのページでエラーが出ている・・・。 comments.post_idとかないよーといわれているので、どこか抜かしたか、失敗したようである。


うーん…よく分からない現象ですが、これ以上残りにくい感じだったので、諦めました…
会社のPCでしかとかノートじゃないとかすごくやりにくいなーと実感です…。
月曜に時間があったりしたらちょっといじります。