Visible module の実装: 小林研 Rails Tips (73)

このページの内容は以下のリポジトリに1日遅れで反映されます(記事執筆前に前日分をコミットしています)。

https://github.com/hkob/hkob_blog

はじめに

Rails Tips の 73 回目です。昨日値限定制約を追加して、status 属性のテストを記述しました。続きで archived? メソッドを追加する部分から concern による Visible module を導入していきましょう。

Rails をはじめよう - Railsガイド

Visible module の実装

まず、status が archived だった時に true を返す archived? メソッドを article_spec および comment_spec の両方でテストします。ここにある「配列メソッド呼び出し」は以下で説明しています。

  context "複数の Article オブジェクトについて" do
    let!(:targets) { articles(*%i[article1 can_delete]) }
    subject { targets }

    it_behaves_like "配列メソッド呼び出し" do
      let(:test_set) do
        {
          archived?: [nil, [false, false]],
        }
      end
    end
  end
  context "複数の Comment オブジェクトについて" do
    let!(:targets) { comments(*%i[comment1 can_delete]) }
    subject { targets }

    it_behaves_like "配列メソッド呼び出し" do
      let(:test_set) do
        {
          archived?: [nil, [false, true]],
        }
      end
    end
  end

app/models/concerns/visible.rb を作成し、以下の内容を記述します。

module Visible
  def archived?
    status == "archived"
  end
end

article.rb の 2行目に include Visible を入れたことで、archive? メソッドが Mix-in されました。これでテストも通過します。

class Article < ApplicationRecord
  include Visible
  複数の Article オブジェクトについて
    behaves like 配列メソッド呼び出し
        amst: archived?
      上述のメソッドの結果が正しいこと

同様に comment.rb にも追加してみます。こちらもテストが通過しました。

class Comment < ApplicationRecord
  include Visible
  複数の Comment オブジェクトについて
    behaves like 配列メソッド呼び出し
        amst: archived?
      上述のメソッドの結果が正しいこと

これでモデルのテストは成功していましたが、これにより request_spec が通らなくなっていました。articles_controller の article_params に status が入っていないためです。

  def article_params
    params.require(:article).permit(:title, :body, :status)
  end

同様に articles/comments_controller の comment_params にも status を追加します。

  def comment_params
    params.require(:comment).permit(:commenter, :body, :article_id, :status)
  end

後は、定数定義と validates を引っ越したいです。validates はクラスメソッドに相当する処理にあたるので、extend と included を活用します。これにより、article.rb と comment.rb の定数定義、validates は消します。

module Visible
  extend ActiveSupport::Concern

  VALID_STATUSES = %w[public private archived]

  included do
    validates :status, inclusion: { in: VALID_STATUSES }
  end

  def archived?
    status == "archived"
  end
end

articles/index.html.haml は以下のようにして archived? のものを表示しないようにしました。

    - @articles.each do |article|
      - next if article.archived?

articles/comments/_comment.html.haml の一行目には以下のように archived? の時に処理をやめて return するようにしました。

- return if comment.archived?

articles/_form.html.haml に status を設定する form を設定します。

= form_with model: @article do |f|
  %table
    %thead
      %tr
        %th 項目
        %th 内容
    %tbody
      - %i[title body status].each do |attr|
        %tr
          %td= form_label_and_error f, @article, attr
          %td
            - case attr
            - when :title
              = f.text_field attr
            - when :status
              = f.select attr, Article::VALID_STATUSES
            - else
              = f.text_area attr, rows: 10, cols: 80
      %tr
        %td{colspan: 2}= f.submit

articles/comments/_form.html.haml も同様です。

= form_with model: [@article, @comment] do |form|
  %table
    %thead
      %tr
        %th 項目
        %th 内容
    %tbody
      - %i[commenter body status].each do |attr|
        %tr
          %td= form_label_and_error form, @comment, attr
          %td
            - case attr
            - when :commenter
              = form.text_field attr
            - when :status
              = form.select attr, Comment::VALID_STATUSES
            - else
              = form.text_area attr, rows: 10, cols: 80
      %tr
        %td{colspan: 2}
          = form.hidden_field :article_id
          = form.submit

おわりに

archived に対する処理を一通り追加してみました。ガイドの内容ももうすぐ終了です。