【Rails】Deviseを使って同一モデルで属性分けして登録する


はじめに

RailsでDeviseを使ってユーザー登録をする際に、同一モデルにしつつパラメータで属性分けしたい時ってあると思うのですが、意外とこれだっていう記事が無く若干ハマったので記事にしてみます。
想定とするのは新規登録とログインのリンクとは別に他属性用の登録リンクがある感じです(プレミアム会員登録はこちら、みたいな)。
これをUserモデルは一つでビューやコントローラも使い回しつつ、Booleanなどのパラメータを持たせて属性分けしたいわけです。
間違ったことしてたらご指摘頂けると嬉しいです。

環境

console
Windows10
ruby 2.6.6
Rails 6.0.3.1
psql (PostgreSQL) 12.3

カラムを追加する

まずは既存のUserモデルを属性分けするためにカラムを追加します(今回はisPremium:booleanにします)。
このカラムの追加も初学者にとっては結構謎みが深く、最初はよくわからず既存のmigraitionファイルに書き足そうとしてましたが、どうやら最初にrails db:migrateした時に使ったmigrationファイルは以後読み込まれない?らしい。
なので新たに別のmigrationファイルを作ってそこにカラムを追加するメソッドを書く。

console
rails g migration AddCulumnToTable

AddCulmnToTableの部分は任意のクラス名なのですが、通常はAdd+追加するカラム名Toテーブル名など後からわかりやすいものが良いそう。

マイグレーションファイルを編集する

先ほど作成したマイグレーションファイルに以下を追加します。

2020~~_add_column_to_table.rb
  def change
    add_column :users, :isPremium, :boolean #追加
    change_column_null :users, :isPremium, false, false #追加
    change_column_default :users, :isPremium, from: nil, to: false #追加
  end

「add_column :users, :isPremium, :boolean」の部分でusersテーブルにboolean型のisPremiumというカラムを追加しています。
「change_column_null :users, :isPremium, false, false」でNullを禁止しています。ですが既存のuserはisPremiumカラムを持っていないので(null)それもfalseにします。
「change_column_default :users, :isPremium, from: nil, to: false」はデフォルト値を設定します。add_columnした際にはデフォルト値が"Null"になっているためfalseに変更しています。

データベースに反映

console
rails db:migrate

これで作成したマイグレーションファイルをもとにデータベースが更新されるはずです。

Strong Parametersの設定

Deviseではデフォルトでemailとpasswordを設定できるのですが、他のカラムを追加した際はStrong Parametersなるものを設定します(usernameや今回のisPremiumなど)。

application_controller.rb
    before_action :configure_permitted_parameters, if: :devise_controller?

    private
    def configure_permitted_parameters
        devise_parameter_sanitizer.permit(:sign_up, keys:[:username, :isPremium])
    end

これで新規登録時にusernameとisPremiumのカラムを登録することができます。

コントローラ周りの設定

これで一応UserモデルのisPremiumを手動で切り替えて属性分けすることはできるのですが(登録時にチェックボックスを設けるなど?)、今回はリンクによって自動で切り替えるような仕組みにしてみます。
なのでコントローラの作成とルーティングを行います(既に済んでいる方は飛ばして下さい)。

console
rails g devise:controllers users
config/routes.rb
devise_for :users, 
  controllers: {
     registrations: 'users/registrations'
  }

リンクを作成

あとは好きなところにリンクを貼ります(今回はhome/indexにします)。その際にパラメータを渡せるようにして、属性分けをしようと思います。

home/index.html.erb
      <%= link_to "ログイン", new_user_session_path %>
      <%= link_to "会員登録", new_user_registration_path(premium: "false") %>
      プレミアム会員登録は<%= link_to "こちら", new_user_registration_path(premium: "true") %>

会員登録もプレミアム会員登録も同じ「new_user_registration_path」ですが、premiumという変数を渡して差別化します。

コントローラで変数を受け取る

「new_user_registration_path」では先ほど作ったusersのRegistrationsControllerのnewアクションが呼ばれるはずなので書いていきます。

users/registrations_controller.rb
# GET /resource/sign_up
  def new
    @premium = params[:premium] #追加
    super
  end

newアクションのコメントアウト(#)を外して、先ほど渡したpremiumを@premiumに入れます。
(ここで私は@premium = params[:premium]をsuperの後ろに書いてしまい小一時間ハマりました。)

Viewに渡す

Viewでは特に表示させる必要が無いのでf.hidden_fieldなどを使います(その他のフォームは省略します)。

registrations/new.html.erb
<%= f.hidden_field :isPremium, :value => @premium %>

これをフォームの中の適当なところに書けばUserモデルのisPremiumカラムにリンクから渡ってきた@premiumが登録されます。

おわり

ひとまず作れはしましたが、なんだか遠回りをしている気がしてなりません。
あとはRailsへの理解が浅くregistrationsのnewアクションでsuperを先に呼んでしまい悩みました。
もっとスマートなやり方があったら教えて欲しいです!