Userモデルに子モデルを効率よく紐付ける手順


はじめに

やりたいこと

  • deviseを用いてUser登録
  • 子モデルProfileにプロフィール画像等のデータを扱わせる
  • userレコードの生成と同時にprofileレコードも生成する
  • profileレコードにデフォルトでデータを入れる

やる理由

  • できるだけdeviseを触りたくない
  • profileレコードを同時生成してnew&createアクションを省略
  • デフォルトでデータを入れることで空カラムによる厄介事を回避

やってみて

思ったより簡単にできました。特にimage(プロフィール画像)を生成時にアタッチしておくのは今後も使えそうなので備忘しておきます。

参考にした記事

url4u.jp > Profile モデルを定義して Devise の User に紐付ける

手順

前提として

  1. deviseのインストール
  2. 各モデル(User、Profile)の作成&マイグレーション
  3. deviseのストロングパラメーターの設定

は完了しているものとして進めます。

アソシエーションを記述

まずは各モデルにアソシエーションを記述します。

models/user.rb
class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable

  has_one :profile, dependent: :destroy
end

dependent: :destroyを付与してuserレコードの削除に伴ってprofileが削除されるようにしました。

models/profile.rb
class Profile < ApplicationRecord
  belongs_to :user
  has_one_attached :image
end

profileモデルにはActive Storageを使ってプロフィール画像を持たせるので、そちらのアソシエーションも記述しました。詳細は後述します。

これで各モデル側の準備はできたので、次はdeviseコントローラーを編集してuserレコード生成時にprofileレコードが自動的に生成される仕組みを作ります。

devise controller 生成

devise関連のコントローラーを生成します。

ターミナル
% rails g devise:controllers users

次に、こちらのコマンドによって生成されたregistrations_controller.rbを編集します。
…の前に、忘れずにルーティングを記述しておきます。

routes.rb
Rails.application.routes.draw do
  devise_for :users, controllers: {
    registrations: 'users/registrations'
  }
end

このルーティングを書いておかないとregistrations_controllerに編集した内容が反映されません。

registration_controllerの編集

そして本題の「registrations_controller.rbの編集」ですが、デフォルトの記述がありますので、必要な部分のコメントアウトを外して編集していきます。

controllers/users/registrations_controller.rb

class Users::RegistrationsController < Devise::RegistrationsController

  # before_action :configure_sign_up_params, only: [:create]
  # before_action :configure_account_update_params, only: [:update]

  〜中略〜

  # POST /resource
  def create
    super

    resource.build_profile
    resource.save
  end

  # GET /resource/edit

  〜中略〜
end

createメソッドの中にbuild_profileとsaveを記述することで、userレコードのcreate時にprofileレコードが保存されるようになりました。

superとは

親クラスで定義されている同名のメソッドを呼ぶことが出来るメソッド
=> 参照 : Rubyの継承とオーバーライドについてまとめてみた
こちらの記事がわかりやすく、勉強させていただきました

resourceとは

今回編集したUsers::RegistrationsControllerはDevise::ResistrationsControllerの子クラスになります。
resouseは親クラスDevise::ResistrationsControllerで定義されている変数?なので、そちらを見に行かないと実態はわかりません。
=> 参照 : [Rails][devise] registrations_controllerのcreateを読み解く

応用① 特定のカラムにデータを入れて生成する

例えば「プロフィールの自己紹介文をデフォルトで入れておきたい!」みたいなときに使えます。

controllers/users/registrations_controller.rb
class Users::RegistrationsController < Devise::RegistrationsController

  〜中略〜

  def create
    super

    resource.build_profile
    resource.profile.intro = 'プロフィールを編集しよう!'
    resource.save
  end

  〜中略〜
end

こちらの記述でintroカラムに初期データを入れることができました。
NOT NULL制約を設定している場合もこれで回避できます(たぶん)

応用② 画像をアタッチして生成する

僕が一番やってよかったと思ったのがデフォルト画像を設定したことです。
今つくっているアプリはいろいろなページでアバターを表示させているのですが、当初はimage=nilをすべて条件分岐で対応しようと思っていました(無知ってこわい…)

ダミー画像を初期値で入れておくことで、無駄な記述が減るし、サインアップ直後のユーザーさんがイメージしやすい見栄えになったし、良いこと尽くしでした。

注)imageはActive Storageで紐付けしています。
Active Storage の設定方法はこちら
=> 【Ruby/Rails】Active Storage 初期設定マニュアル

controllers/users/registrations_controller.rb
class Users::RegistrationsController < Devise::RegistrationsController

  〜中略〜

  def create
    super

    resource.build_profile
    resource.profile.image.attach(io: File.open('ディレクトリ名/ファイル名'), filename: 'ファイル名')    resource.save
  end

  〜中略〜
end

こちらの記述でActive Storageにデータが保存されます。

ちなみに僕の場合はこんな感じ

controllers/users/registrations_controller.rb
class Users::RegistrationsController < Devise::RegistrationsController

  〜 中略 〜

  def create
    super

    resource.build_profile
    resource.profile.image.attach(io: File.open('public/images/human_icon.png'), filename: 'human_icon.png')
  end

  〜 中略 〜
end

human_icon.png

彼を初期値に設定しました。かわいい。

おわりに

初めてQiitaで記事を書きました。
これから備忘録を中心に、Qiitaに投稿したいと思います。

初学者故、間違っている点/非効率な点が多々あると思いますので、お気づきになったらご指摘いただけると嬉しいです。

もし「これは使える」「わかりやすい」と思った方がいらっしゃいましたら、ぜひLGTMしてください!飛び跳ねてよろこびます。

またプログラミング初学者仲間のみなさん、初学者同士ぜひ絡んでください!!
最後までお読みいただいてありがとうございました!

✔︎