【Rails】投稿機能に公開・非公開機能を追加した時の実装手順


# はじめに

制作しているポートフォリオに記事の公開・非公開機能を追加したので、実装手順を紹介します。
記事を投稿したけど、一旦非公開にしたい時があると思うので、実装してみました。

# 前提

- kaminariのgemの機能を使用し、非公開記事一覧取得してます
- deviseのgemの機能を使用し、他ユーザーを非公開記事からリダイレクトさせてます


# バージョン情報

- Ruby 2.6.3
- Rails 6.0.2.1


# 実装した手順



## postsテーブルにstatus用のカラムを追加

```
$ rails g migration Add_status_To_posts status:integur
```

```rb:db/migrate/20201111213454_add_status_to_posts.rb
class AddStatusToPosts < ActiveRecord::Migration[6.0]
  def change
    add_column :posts, :status, :integer, null: false, default: 0
  end
end
```

## モデルを定義

```rb:app/models/post.rb
class Post < ApplicationRecord

#・・・省略

  enum status: { public: 0, private: 1 }, _prefix: true

#・・・省略

end

```

上記のように```_prefix: true```を記述してない状態でブラウザを開いたら、下記のエラーが出た。
エラー文を確認すると、```public```というメソッドが重複しているとのこと。
重複してエラーが出ていなければ```_prefix: true```を記述しなくてもOK。


```log:log/development.log
ArgumentError (You tried to define an enum named "status" on the model "Post", but this will generate a class method "public", which is already defined by Active Record.):
  
app/models/post.rb:19:in `<class:Post>'
app/models/post.rb:1:in `<main>'
app/controllers/posts_controller.rb:85:in `set_post'
Started GET "/posts/3" for 127.0.0.1 at 2020-11-12 06:52:36 +0900
Cannot render console from 172.22.0.1! Allowed networks: 127.0.0.0/127.255.255.255, ::1
  [1m[35m (1.2ms)[0m  [1m[35mSET NAMES utf8mb4,  @@SESSION.sql_mode = CONCAT(CONCAT(@@sql_mode, ',STRICT_ALL_TABLES'), ',NO_AUTO_VALUE_ON_ZERO'),  @@SESSION.sql_auto_is_null = 0, @@SESSION.wait_timeout = 2147483[0m
Processing by PostsController#show as HTML
  Parameters: {"id"=>"3"}
Completed 500 Internal Server Error in 27ms (ActiveRecord: 0.0ms | Allocations: 5566)
```


## 投稿編集ページで投稿ステータスを選択できるようにする

```erb:app/views/posts/_form.html.erb
<%= form_with(model: post, local: true) do |form| %>

<!--・・・省略・・・-->

  <%= form.label(:public, for: nil, class:'post-status__label') do %>
      <%= form.radio_button :status, :public %>
      <%= I18n.t('activerecord.attributes.post.statuses.public') %>
  <% end %>
  <%= form.label(:private, for: nil, class:'post-status__label') do %>
      <%= form.radio_button :status, :private %>
      <%= I18n.t('activerecord.attributes.post.statuses.private') %>
  <% end %>

<!--・・・省略・・・-->

<% end %>
```

公開・非公開を選択するUIは下記のようにしました。
この記事でははその実装手順はメイントピックではないので、割愛。
しかし、この実装に時間かかってしまった。

![post_status.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261027/0407f7bd-aaa9-cac6-e4f5-5d3785bc7bf9.gif)


## 選択した投稿ステータスを保存できるようにする

```rb:app/controllers/posts_controller.rb
class PostsController < ApplicationController

# ・・・省略

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

    respond_to do |format|
      if @post.save
        format.html { redirect_to @post, notice: '新規投稿を行いました。' }
        format.json { render :show, status: :created, location: @post }
      else
        format.html { render :new }
        format.json { render json: @post.errors, status: :unprocessable_entity }
      end
    end
  end

# ・・・省略

  private

# ・・・省略

    def post_params
      params.require(:post).permit(
        :title,
        :content,
        :image,
        :status, # <= 追加：statusカラム
        {:cat_ids => []}
      )
    end

# ・・・省略

end


```

## 非公開記事一覧、詳細ページは、他のユーザーにアクセス時にはリダイレクトさせる

```rb:app/controllers/posts_controller.rb
class PostsController < ApplicationController
  before_action :set_post, only: [:show, :edit, :update, :destroy]

  # GET /posts/1
  # GET /posts/1.json
  def show

    if @post.status_private? && @post.user != current_user
      respond_to do |format|
        format.html { redirect_to posts_path, notice: 'このページにはアクセスできません' }
      end
    end

    # ・・・省略
  end

  # ・・・省略

  private
    def set_post
      @post = Post.find(params[:id])
    end

    # ・・・省略

end


```


## 記事一覧の取得方法


```
# 公開記事
$ Post.status_public.order(created_at: :desc).page(params[:page])

# 非公開記事
$ Post.status_private.order(created_at: :desc).page(params[:page])

# ランキング(Likeのトップ3)
$ Post.status_public.joins(:likes).group(:post_id).order('count(likes.post_id) desc').limit(3)


```

# 所要時間

| 作業内容                                            | 所要時間   | 
|:-----------|------------:|
| 見積                                            | 0.75H   | 
| 実装・検証：新規投稿                            | 7H     | 
| 実装・検証：ユーザー詳細ページ                  | 2.25H  | 
| 実装・検証：投稿詳細ページ(localhost/posts/:id) | 1H     | 
| 実装・検証：記事一覧・詳細ページ(Like Ranking)  | 0.5H   | 
| 実装・検証：記事一覧(localhost/posts)           | 0.125H | 
| 実装・検証：TOP(localhost)                      | 0.125H | 
| 合計                                            | 12H    | 

投稿編集ページを作るのに7Hかかってしまったので反省だが、なんとか作れたので良かったかな


# さいごに

記事の公開・非公開機能追加の際にこの記事が参考になれば幸いです。


# 参考

[【Rails】enumチュートリアル](https://pikawaka.com/rails/enum)
[Rails5 から enum 使う時は_prefix（接頭辞）_suffix（接尾辞）を使おう](https://qiita.com/emacs_hhkb/items/fce19f443e5770ad2e13)