【Rails】ブログに記事の下書き保存機能を実装する

はじめに

Railsアプリで作成したブログで記事を途中保存したいときがあります。保存はしたいのですが、書いている途中の記事を一般公開するわけにはいきません。記事を一般公開はせずに保存しておく下書き機能があると便利です。

本記事では、ブログの記事を下書き保存する機能を実装する方法について説明します。

下書き保存機能を実装

今回実装する下書き保存機能の概要は以下の通りです。

  • 記事投稿/編集画面で「下書き」または「公開中」を選択
  • 記事が「公開中」の場合、誰でも閲覧可能
  • 記事が「下書き」の場合、管理者のみ閲覧可能
    • 管理者以外が直接アクセスしたらログイン画面へ遷移
  • いつでも「下書き」⇔「公開中」を変更可能

モデルの実装

変数の追加

モデルに記事の状態を表す変数をEnum(列挙型)として追加します。Enum(列挙型)とは、モデルの数値カラムに対して文字列による名前定義をマッピングする機能です。詳しくは以下のページを参照してください。

モデルに以下を追記します。

  enum status: { draft: 0, published: 1 }
  validates :status, inclusion: { in: Article.statuses.keys }

statusをEnum(列挙型)として宣言することで、0/1という数値がそれぞれdraft/publishedという文字列にマッピングすることができます。

カラムの追加

データベーススキーマに記事の状態を保存しておくstatusカラムを追加します。以下のコマンドを実行して、マイグレーションファイルを作成します。

$ rails generate migration AddStatusToArticles status:integer

作成されたマイグレーションファイルYYYYMMDDHHMMSS_add_status_to_articles.rbを以下のように編集します。デフォルト値は0とし、NOT NULL制約を設定します。

class AddStatusToArticles < ActiveRecord::Migration[6.0]
  def change
    add_column :articles, :status, :integer, default: 0, null: false
  end
end

以下のコマンドを実行して、マイグレーションを実行します。

$ rails db:migrate

データベーススキーマに記事の状態を保存するstatusカラムが追加されました。

既存記事の更新

statusカラムを追加する前に作成した記事がある場合、statusカラムにはデフォルト値の0が設定されているので1に更新します。この手順はデプロイ後に本番データベースでも行う必要があります。

使用しているデータベースがSQLiteの場合、Railsコンソールから更新を行います。

$ rails console
> Article.update_all(status: "published")
> exit

使用しているデータベースがMySQLの場合、以下のクエリを実行します。

UPDATE articles SET status = 1;

クエリの実行にMySQL Workbenchを使っている場合、以下のエラーが出て更新できないことがあります。

Error Code: 1175. You are using safe update mode and you tried to update a table without a WHERE that uses a KEY column To disable safe mode, toggle the option in Preferences -> SQL Editor and reconnect.

MySQL Workbenchでは、セーフアップデートモードが有効になっていると全件更新(条件未指定)ができないようになっています。セーフアップデートモードを無効にするには以下の手順を行います。

メニューバーから [MySQLWorkbench] - [Preferences] をクリックします。設定ウインドウの左のメニューから「SQL Editor」をクリックし、下のほうにある「Safe Updates ...」というチェックボックスを外します。

サーバーに接続し直した後、再度クエリを実行します。

ビューの実装

フォームの実装

記事の投稿/編集フォームに記事の状態を選択するセレクトボックスを追加します。

<%= form.select :status, Article.statuses.map { |k, v| [t("article.status.#{k}"), k] }, selected: @article.status %>

Model.variablesでEnum(列挙型)の値をすべて取得することができます。mapメソッドでキーと値の組み合わせをマッピングし、それらをセレクトボックスの値として追加します。記事の編集時は現在の状態を初期値に設定する必要があるため、selected: @article.statusを記述します。

その他の実装

管理画面の記事一覧画面などで記事の状態を表示するには以下のように記述します。

<%= t("article.status.#{article.status}") %>

tメソッドはロケールファイルに記述されている多言語を呼び出すメソッドです。ロケールファイルの設定については次のセクションを参照してください。#{}を使った文字列と変数の結合を行うには、値をダブルクオーテーション("")でくくる必要があります(シングルクォーテーション('')は不可)。

ロケールファイルの設定

Railsアプリケーションを多言語対応するにはi18nというGemを使います。i18nはRails 2.2から標準同梱されているので、アプリケーション設定とロケールファイルを用意するだけで多言語対応ができます。

i18nのアプリケーション設定を行うには、config/application.rbに以下を追記します。

module ApplicationName
  class Application < Rails::Application
    # 以下を追記
    config.i18n.default_locale = :ja
    config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.{rb,yml}').to_s]
  end
end

アプリケーションのデフォルト言語を日本語(ja)に設定しています。これにより、ロケールファイルのja以下の設定がtメソッドで呼び出すことができるようになります。また、config/locales/ディレクトリ配下のすべてのファイルをロケールファイルとして設定しています。

新しくロケールファイルをconfig/locales/ディレクトリ配下に作成します。ファイル名は何でも構いません。新たにディレクトリを作成しても構いません。自分が管理しやすい構成で大丈夫です。

作成したロケールファイルに以下を記述します。

ja:
  article:
    status:
      draft: '下書き'
      published: '公開中'

ロケールファイルはビューの書き方とモデルの書き方があります。今回の設定対象はArticle.statusの値なのでモデルの書き方かと思いますが、モデルの書き方では日本語化できません。モデルの書き方はモデル名とカラム名の日本語化はできますが、その設定値までは日本語化できないので注意してください。

# モデルの書き方
ja:
  activerecord:
    models:
      article: '記事'
    attributes:
      article:
        title: '題名'
        content: '本文'
        status: '状態'
          draft: '下書き'       # この書き方は不可
          published: '公開中'   # この書き方は不可

ロケールファイルの詳しい書き方については以下の記事を参照してください。

コントローラーの実装

ストロングパラメーターの変更

ストロングパラメーターにstatusを追加します。

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

記事の取得処理の変更

記事一覧画面などで取得している記事の取得処理を「公開中」記事に限定するには、以下のように変更します。nokogiriで使用するオプションや並べ替えのオプションなどはpublishedの後に記述していきます。

  def index
    @articles = Article.published
  end

サイドバーにある最新記事の取得処理や人気記事の取得処理なども同じように変更します。また、RSSフィード生成時の記事の取得処理(rss_controller.rb)やサイトマップの登録処理(config/sitemap.rb)も忘れずに変更してください。

アクセス権限の変更

記事一覧画面に「下書き」記事は表示されなくなりましたが、「下書き」記事のURLに直接アクセスされると誰でも見れてしまいます。「下書き」記事のアクセス権限を管理者に限定するには以下を追記します。

  def show
    @article = Article.find(params[:id])
    # 以下を追記
    require_login if @article.draft?
  end

取得した記事が「下書き」の場合require_login(ログイン済みかを確認するメソッド)を呼び出します。require_loginメソッドの内容は以下の通りです。logged_inメソッドはセッションのヘルパーメソッドです。

  private
    def require_login
      redirect_to login_path if !logged_in?
    end

まとめ

記事中にも書きましたが、今回追加したstatusカラムはデフォルト値が0のため、本番環境にデプロイすると元々あった記事がすべて「下書き」になってしまいます。デプロイ後に最後の仕上げとして本番データベースの更新を忘れないようにしてください。

本記事を参考にして、ブログに記事の下書き保存機能を実装していただければと思います。

関連記事

【Rails】Railsアップグレードまとめ
# はじめに Ruby on Railsに限らず、何らかのフレームワークを使ってWebシステムを構築している場合、フレームワークのアップグレード作業は避けて通れません。 一般的にフレームワークはバージョン毎にEOL (End of Life [...]
2022年10月1日 14:32
【Rails】ユーザー登録時に行うメールアドレス認証機能の実装方法
# はじめに ユーザー登録/解除やログイン/ログアウトといった認証機能の導入に「devise」というGemを使っている人は多いと思います。「devise」では以下のように記述するだけで、ユーザー登録時に確認メールを送付しメールアドレス認証を行う機 [...]
2022年9月24日 14:24
【Rails】モデルに列挙型(enum)を定義し、使いこなす方法
# はじめに Railsはモデルでカラム名と同名の列挙型(enum)を定義することで、カラムと列挙型の変数を紐付けることができます。カラムと列挙型の変数を紐付けると、カラムに対して様々な便利な使い方ができるようになります。 本記事では、モデ [...]
2022年9月3日 10:29
【Rails】RailsでCORSとPreflight requestの設定を行う方法
# はじめに RailsアプリをAPIサーバーとして構築するには、CORS (Cross-Origin Resource Sharing)と Preflight requestの設定を行う必要があります。APIサーバーは外部からの要求に対して処理 [...]
2022年8月27日 10:44
【Ruby】Bundlerを使ってRubyGemsを作成/公開する方法
# はじめに Bundlerを使ってRubyGemsを作成および公開する方法について説明します。Bundlerを使わずにRubyGemsを作成/公開する方法については以下の記事を参照してください。 <iframe class="hatena [...]
2022年7月12日 23:18
【Ruby】RubyGemsを作成/公開する方法
# はじめに RubyGemsを作成および公開する方法について説明します。Bundlerを使ってRubyGemsを作成する方法については以下の記事を参照してください。 <iframe class="hatenablogcard" style [...]
2022年7月11日 21:52
【Rails】M1チップ搭載MacでRuby on Railsの開発環境構築
# はじめに M1チップ搭載MacにRuby on Railsの開発環境を構築する手順を記載します。 - MacBook Air (M1, 2020) - macOS Monterey 12.3.1 # Homebrew ## [...]
2022年5月5日 11:56
【Rails】Rakeタスクの基本情報と作成・実行方法
# はじめに Railsには標準でRakeというGemが同梱されています。RakeはRubyで実装されたMake(UNIX系のOSで使用できるコマンド)のようなビルド作業を自動化するツールです。Ruby Make、略してRakeというわけですね。 [...]
2022年3月7日 22:12