🚥

【Rails】下書き & 非公開機能

2023/08/27に公開
2

投稿機能に、下書き保存非公開機能をもたせます。

手順

  1. statusカラム追加
  2. postモデルにenum設定
  3. コントローラ設定
  4. 投稿view作成
  5. 公開/下書き/非公開のview管理

投稿や下書き保存、非公開への更新はトグルスイッチで実装。
新規投稿時に、下書きor公開、公開後に、非公開or公開 と表示させる。

下書き保存済みの投稿、非公開の投稿はタブで管理。

以下記事も参考になれば嬉しいです。
https://zenn.dev/ganmo3/articles/8b92ca2dc58c78
https://zenn.dev/ganmo3/articles/5130b908a62276

実装

1.statusカラム追加

db/migrate/xxxxx_create_posts.rb
class CreatePosts < ActiveRecord::Migration[6.1]
  def change
    create_table :posts do |t|
      t.references :user, null: false, foreign_key: true
      t.string :title, null: false
      t.string :link, null: false
      t.integer :status, default: 0, null: false

      t.timestamps
    end
  end
end

enum管理なのでinteger型にします。

Postテーブルは以下のようにしています!

2. postモデルへenum追記

:
enum status: { published: 0, draft: 1,  unpublished: 2 }
:

以下の通りです。
0: 公開中
1: 下書き
2: 非公開

非公開はprivateを使おうと思ったのですが、Rubyの予約語なので避けることをおすすめします。

3. コントローラ設定

長いので2つに分けます。

  • createセクション
app/controllers/posts_controller.rb
class Public::PostsController < ApplicationController
  def new
    @post = Post.new
  end

  def create
    @user = current_user
    @post = Post.new(post_params)
    @post.user_id = @user.id

    if params[:draft].present?
      @post.status = :draft
    else
      @post.status = :published
    end

    if @post.save
      if @post.draft?
        redirect_to dashboard_posts_path, notice: '下書きが保存されました。'
      else
        redirect_to post_path(@post), notice: '投稿が公開されました。'
      end
    else
      render :new
    end
  end
  :
  :

解説:

  • 投稿のステータス設定
if params[:draft].present?
  @post.status = :draft
else
  @post.status = :published
end

フォームから送信されたdraftパラメータの有無で、投稿のステータスを「下書き」か「公開」かを設定する。

  • updateセクション
app/controllers/posts_controller.rb
def edit
    @post = Post.find(params[:id])
    @user = current_user
  end

  def update
    @user = current_user
    @post = Post.find(params[:id])

    @post.assign_attributes(post_params)
   
    if params[:draft].present?
      @post.status = :draft
      notice_message = "下書きを保存しました。"
      redirect_path = dashboard_posts_path
    elsif params[:unpublished].present?
      @post.status = :unpublished
      notice_message = "非公開にしました。"
      redirect_path = dashboard_posts_path
    else
      @post.status = :published
      notice_message = "投稿を更新しました。"
      redirect_path = post_path(@post)
    end

    if @post.save
      redirect_to redirect_path, notice: notice_message
    else
      render :edit
    end
  end
 :
  private

  def post_params
    params.require(:post).permit(:title, :content, :status)
  end
end

解説:

  • 投稿のステータス設定
    if params[:draft].present?
      @post.status = :draft
      notice_message = "下書きを保存しました。"
      redirect_path = dashboard_posts_path
    elsif params[:unpublished].present?
      @post.status = :unpublished
      notice_message = "非公開にしました。"
      redirect_path = dashboard_posts_path
    else
      @post.status = :published
      notice_message = "投稿を更新しました。"
      redirect_path = post_path(@post)
    end

このコードは、投稿を更新する際に、フォームに含まれる特定のパラメータをチェックして投稿のステータスを設定している。このパラメータは、ユーザーがクリックしたボタンに合わせて設定される。

  • params[:draft].present? は、送信されたパラメータの中に draft というキーが存在するかをチェック。ユーザーが「下書き保存」ボタンをクリックした場合にtrueになる。
  • params[:unpublished].present? は、送信されたパラメータの中に unpublished というキーが存在するかをチェック。「非公開にする」ボタンをクリックした場合にtrueになる。
  • 上記のどちらの条件も満たさない場合は、投稿は公開になる。

4. 投稿View作成

ボタンにname属性を入れることで、公開/下書き/非公開の設定をしてあげます!
トグルスイッチの実装は以下で解説しています。
https://zenn.dev/ganmo3/articles/8b92ca2dc58c78

view
    <div class="btn-group" role="group" id="buttonGroup">
      <% if @post.persisted? %>
        <% if @post.draft? %>
          <%= f.submit '下書き保存', id: 'draftButton', name: 'draft', class: "draft-btn" %>
          <%= f.submit '公開する', id: 'publishButton', name: 'published', class: "submit-btn" %>
        <% elsif @post.unpublished? %>
          <%= f.submit '非公開にする', id: 'unpublishButton', name: 'unpublished', class: "unpublish-btn" %>
          <%= f.submit '公開する', id: 'publishButton', name: 'published', class: "submit-btn" %>
        <% else %>
          <%= f.submit '非公開にする', id: 'unpublishButton', name: 'unpublished', class: "unpublish-btn" %>
          <%= f.submit '公開する', id: 'publishButton', name: 'published', class: "submit-btn" %>
        <% end %>
      <% else %>
          <%= f.submit '下書き保存', id: 'draftButton', name: 'draft', class: "draft-btn" %>
          <%= f.submit '公開する', id: 'postButton', name: 'post', class: "submit-btn" %>
      <% end %>
    </div>

解説:

  1. <% if @post.persisted? %>: 投稿がすでにデータベースに存在する(既存の投稿を編集している)場合を条件とする。もし新規投稿でない場合、この条件内のコードが実行。

  2. <% if @post.draft? %>: 投稿が下書き状態であるかどうかを判定。もし下書きの場合、下書き保存と公開するボタンが表示される。

  3. <% elsif @post.unpublished? %>: 投稿が非公開状態であるかどうかを判定。もし非公開の場合、非行化にすると公開するボタンが表示される。

  4. <% else %>: 公開中の投稿の場合、非公開にするボタンと公開するボタンが表示される。

  5. <% else %>: 新規投稿の場合、下書き保存と公開するボタンが表示される。

nameオプションとは

name オプションは、フォーム内のボタンや入力フィールドに名前をつけるためのもの。ユーザーがフォームを送信するとき、どの要素からのデータかをサーバーに伝えることができる。

今回のコードでは、例えば「下書き保存」ボタンの場合は name: 'draft' と指定している。サーバーはこの名前を見て、ユーザーが下書きボタンを押したことを把握できる。これにより、コントローラの下書き保存が実行される。

5. 公開/下書き/非公開のview管理

こんな感じで管理。

view
<div class="container">
  <div class="row">
    <div class="col-md-6 offset-md-3">
      
      <div class="tabs d-flex justify-content-between align-items-center">
        <ul class="nav nav-tabs">
          <li class="nav-item">
            <a class="nav-link active" data-toggle="tab" href="#published">公開中</a>
          </li>
          <li class="nav-item">
            <a class="nav-link" data-toggle="tab" href="#draft">下書き</a>
          </li>
          <li class="nav-item">
            <a class="nav-link" data-toggle="tab" href="#unpublished">非公開</a>
          </li>
        </ul>
        
        <%= link_to "新規投稿", new_post_path, class: "new-post-btn ml-auto" %>
      </div>
      
      <div class="tab-content">
        <div id="published" class="tab-pane fade show active">
          <% @published_posts.each do |post| %>
            <%= render "mypost", post: post %>
          <% end %>
        </div>
  
        <div id="draft" class="tab-pane fade">
          <% @draft_posts.each do |post| %>
            <%= render "mypost", post: post %>
          <% end %>
        </div>
  
        <div id="unpublished" class="tab-pane fade">
          <% @unpublished_posts.each do |post| %>
            <%= render "mypost", post: post %>
          <% end %>
        </div>
      </div>
    </div>
  </div>
</div>

公開ステータスの投稿のみを表示

以下のようにモデルやコントローラで公開ステータスのみのpostを表示させるようにします。

@posts = Post.published.order(created_at: :desc).page(params[:page]).per(12)

まとめ

以上、下書きと非公開の実装でした。nameオプションは結構便利!

意外に下書きと非公開どちらも解説している記事は少なかったので、参考になれば嬉しいです。
間違い等あったらコメントください💦

投稿一覧ページ等では、非公開の投稿が表示されないようにする(公開の投稿のみ取り出す)ことも忘れないようにしてくださいね。


最初は下書き機能だけにしていて、is_draftカラムでboolean型で管理していました。
でも非公開機能もやっぱり欲しい!ってなり、statusカラムに変えてenum管理に切り替えました。

まあまあな時間を使ってしまったので機能追加を見越した設計って大事だなという学び。

Discussion

jonsoku2jonsoku2

素晴らしいポートフォリオになりそうですね!
私から一点話したいことがあります。
このような記事は説明してる機能のデータベースの関係のダイアグラムとかを一緒に載せたらいいと思います。
見る人は文章だけしっかり理解できないしわからないのでおすすめです!

がんもがんも

コメント嬉しいです!ありがとうございます!体調を崩してしまい返信遅くなりすいません💦

このような記事は説明してる機能のデータベースの関係のダイアグラムとかを一緒に載せたらいいと思います。
確かに自分には関係性がわかっているけど、初見の方だとわかりませんもんね!早速次回の記事から一緒に書こうと思います♪
参考になるアドバイスありがとうございます!