Ruby on Rails Guides(v5.1.3) Getting Started with Railsを読む#2

これは、最新のRuby on Rails ガイド(v5.1.3) を読んだ際の学習記録です。

#1はこちら Ruby on Rails Guides(v5.1.3) Getting Started with Railsを読む#1 - Do Something

Adding a Second Model

Articleにつづいて、2つめのmodelを追加しよう。

Generating a Model

$ rails generate model Comment commenter:string body:text article:references
Running via Spring preloader in process 19903
      invoke  active_record
      create    db/migrate/20170817114832_create_comments.rb
      create    app/models/comment.rb
      invoke    test_unit
      create      test/models/comment_test.rb
      create      test/fixtures/comments.yml

生成された app/models/comment.rb を見てみよう。

class Comment < ApplicationRecord
  belongs_to :article
end

belongs_to :article という記述がある。これはアクティブレコードの関連付けを表している。db:migrateをコマンドを実行してみよう。

$ rails db:migrate

db/schema.rb を見てみよう。以下のように変更されている。

ActiveRecord::Schema.define(version: 20170817114832) do

  create_table "articles", force: :cascade do |t|
    t.string "title"
    t.text "text"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end

  create_table "comments", force: :cascade do |t|
    t.string "commenter"
    t.text "body"
    t.integer "article_id"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["article_id"], name: "index_comments_on_article_id"
  end

end

Associating Models

Active Record Associationを使用すると、2つのモデル間の関係を簡単に宣言できる。 コメントや記事の場合は、このように関係を書き出すことができます。

  • Each comment belongs to one article.
  • One article can have many comments.

app/models/article.rb へも関係を表すための記述を追加する必要がある。以下のようにhas_manyを追加しよう。

class Article < ApplicationRecord
  has_many :comments
  validates :title, presence: true,
                    length: { minimum: 5 }
end

Adding a Route for Comments

config/routes.rb を開き、以下のように編集する。

resources :articles do
  resources :comments
end

Generating a Controller

$ rails generate create controller Comments
      create  app/controllers/comments_controller.rb
      invoke  erb
      create    app/views/comments
      invoke  test_unit
      create    test/controllers/comments_controller_test.rb
      invoke  helper
      create    app/helpers/comments_helper.rb
      invoke    test_unit
      invoke  assets
      invoke    coffee
      create      app/assets/javascripts/comments.coffee
      invoke    scss
      create      app/assets/stylesheets/comments.scss

app/views/articles/show.html.erb

<p>
  <strong>Title:</strong>
  <%= @article.title %>
</p>
 
<p>
  <strong>Text:</strong>
  <%= @article.text %>
</p>
 
<h2>Add a comment:</h2>
<%= form_for([@article, @article.comments.build]) do |f| %>
  <p>
    <%= f.label :commenter %><br>
    <%= f.text_field :commenter %>
  </p>
  <p>
    <%= f.label :body %><br>
    <%= f.text_area :body %>
  </p>
  <p>
    <%= f.submit %>
  </p>
<% end %>
 
<%= link_to 'Edit', edit_article_path(@article) %> |
<%= link_to 'Back', articles_path %>

create in app/controllers/comments_controller.rb

class CommentsController < ApplicationController
  def create
    @article = Article.find(params[:article_id])
    @comment = @article.comments.create(comment_params)
    redirect_to article_path(@article)
  end
 
  private
    def comment_params
      params.require(:comment).permit(:commenter, :body)
    end
end

app/views/articles/show.html.erb

<p>
  <strong>Title:</strong>
  <%= @article.title %>
</p>
 
<p>
  <strong>Text:</strong>
  <%= @article.text %>
</p>
 
<h2>Comments</h2>
<% @article.comments.each do |comment| %>
  <p>
    <strong>Commenter:</strong>
    <%= comment.commenter %>
  </p>
 
  <p>
    <strong>Comment:</strong>
    <%= comment.body %>
  </p>
<% end %>
 
<h2>Add a comment:</h2>
<%= form_for([@article, @article.comments.build]) do |f| %>
  <p>
    <%= f.label :commenter %><br>
    <%= f.text_field :commenter %>
  </p>
  <p>
    <%= f.label :body %><br>
    <%= f.text_area :body %>
  </p>
  <p>
    <%= f.submit %>
  </p>
<% end %>
 
<%= link_to 'Edit', edit_article_path(@article) %> |
<%= link_to 'Back', articles_path %>

Refactoring

app/views/articles/show.html.erb をpartialsファイルを作成することで簡潔にする。

Rendering Partial Collections

app/views/comments/_comment.html.erb を作成

<p>
  <strong>Commenter:</strong>
  <%= comment.commenter %>
</p>
 
<p>
  <strong>Comment:</strong>
  <%= comment.body %>
</p>

app/views/articles/show.html.erb

<p>
  <strong>Title:</strong>
  <%= @article.title %>
</p>
 
<p>
  <strong>Text:</strong>
  <%= @article.text %>
</p>
 
<h2>Comments</h2>
<%= render @article.comments %>
 
<h2>Add a comment:</h2>
<%= form_for([@article, @article.comments.build]) do |f| %>
  <p>
    <%= f.label :commenter %><br>
    <%= f.text_field :commenter %>
  </p>
  <p>
    <%= f.label :body %><br>
    <%= f.text_area :body %>
  </p>
  <p>
    <%= f.submit %>
  </p>
<% end %>
 
<%= link_to 'Edit', edit_article_path(@article) %> |
<%= link_to 'Back', articles_path %>

これでコメント毎にapp / views / comments / _comment.html.erb をレンダーされる。

Rendering a Partial Form

app/views/comments/_form.html.erb を作成する。

<%= form_for([@article, @article.comments.build]) do |f| %>
  <p>
    <%= f.label :commenter %><br>
    <%= f.text_field :commenter %>
  </p>
  <p>
    <%= f.label :body %><br>
    <%= f.text_area :body %>
  </p>
  <p>
    <%= f.submit %>
  </p>
<% end %>

app/views/articles/show.html.erb

<p>
  <strong>Title:</strong>
  <%= @article.title %>
</p>
 
<p>
  <strong>Text:</strong>
  <%= @article.text %>
</p>
 
<h2>Comments</h2>
<%= render @article.comments %>
 
<h2>Add a comment:</h2>
<%= render 'comments/form' %>
 
<%= link_to 'Edit', edit_article_path(@article) %> |
<%= link_to 'Back', articles_path %>

Deleting Comments

スパムコメントの削除機能。

app/views/comments/_comment.html.erb

<p>
  <strong>Commenter:</strong>
  <%= comment.commenter %>
</p>
 
<p>
  <strong>Comment:</strong>
  <%= comment.body %>
</p>
 
<p>
  <%= link_to 'Destroy Comment', [comment.article, comment],
               method: :delete,
               data: { confirm: 'Are you sure?' } %>
</p>

app/controllers/comments_controller.rb へ destroyアクションを追加する。

class CommentsController < ApplicationController
  def create
    @article = Article.find(params[:article_id])
    @comment = @article.comments.create(comment_params)
    redirect_to article_path(@article)
  end
 
  def destroy
    @article = Article.find(params[:article_id])
    @comment = @article.comments.find(params[:id])
    @comment.destroy
    redirect_to article_path(@article)
  end
 
  private
    def comment_params
      params.require(:comment).permit(:commenter, :body)
    end
end

Deleting Associated Objects

記事が削除されるとき、関連しているコメントも削除する必要がある。 以下のように dependentオプションを利用する。

app/models/article.rb

class Article < ApplicationRecord
  has_many :comments, dependent: :destroy
  validates :title, presence: true,
                    length: { minimum: 5 }
end

Security

Basic Authentication

Rails はHTTP認証システム備えている。 Railsのhttp_basic_authenticate_withメソッドを使用する。

app/controllers/articles_controller.rb

http_basic_authenticate_with name: "dhh", password: "secret", except: [:index, :show]

これで、index, show 以外のアクションを実行する際に、basic認証が表示されるようになる。 destroyだけに認証をかける場合は以下のようにする。

http_basic_authenticate_with name: "dhh", password: "secret", only: :destroy

Getting Started with Rails は以上。