👫

Railsで開発中のアプリに「性別」のモデルおよびテーブルを作成したい

2022/03/06に公開

現在私が個人で開発しているアプリケーションにて、「ユーザー」一人ひとりに「性別」の情報を与える必要がありました。

そこで「性別」テーブルの作り方などをネットでたくさん調べたのですが、思っていたよリも情報が少なく...。それならいっそ!ということで、色々と試行錯誤した私のやり方を備忘録として残しておこうと思います。

英語による「性別」の名称は「sex」か「gender」か?

今回は「sex」で統一したいと思います。

テーブルを設計する

まずは「性別」のテーブルを設計します。
テーブルの内容を考えるにあたっては Qiita の以下の記事を参考にさせていただきました。

https://qiita.com/aoshirobo/items/32deb45cb8c8b87d65a4

sexesテーブルの設計

キー data name データ名 データ型(Rails) NULL
PK id ID integer FALSE
name 「性別」の名前 string FALSE
code 「性別」のコード string FALSE
alias 「性別」の別名 string FALSE
created_at 作成時間 datetime FALSE
updated_at 更新時間 datetime FALSE

実際のsexesテーブルの中身

id name code alias created_at cupdated_at
1 not known 0 unanswered 2022-XX-XX XX:XX 2022-XX-XX XX:XX
2 male 1 men 2022-XX-XX XX:XX 2022-XX-XX XX:XX
3 female 3 women 2022-XX-XX XX:XX 2022-XX-XX XX:XX
4 not applicable 9 others 2022-XX-XX XX:XX 2022-XX-XX XX:XX

ビューファイル上(HTMLなど)で性別ごとの class を作成するのに備えて「alias」という名前のカラムを個人的に追加しています。「name」カラムのデータだと「not known」や「not applicable」など class の生成を阻む半角スペースが含まれてしまっていますし、「code」のデータでは数字だけなのでそれが何の性別を示しているのか(そもそも何の数字なのか?)がパッと見でわかりません。「name」のデータも「code」のデータも、どちらもそのままでは各性別を区別するための class を作成する上で扱いづらいわけです。そういった事情から、それがどの性別なのか直感的にわかる1単語だけの名前を、それぞれの性別データに別名として付け直しています。

モデルの作成

続いて必要なモデルを作成していきます。
以下、ターミナルに打ち込むコマンドです。
※「性別」を紐付ける大元の「User」モデルはすでに作成済みという前提で進めます。

「Sex」モデルの作成

rails g model Sex --no-fixture

※Fixtureファイルを作成したくなかったので、コマンドの末尾にオプションとして「--no-fixture」を付けています。

現在私が作成しているアプリでは User だけでなく「投稿」などにも別途性別の情報を付けられるようにしたかったため、「users」テーブルと「sexes」テーブルの紐付けには中間テーブルを用いることにします。
「UserSex」モデルを作成します。

「UserSex」モデルの作成

rails g model UserSex --no-fixture

それでは作成したモデルファイルを編集していきます。
「uses」テーブル ⇄ 「user_sexes」テーブル ⇄ 「sexes」テーブルのアソシエーションを作成します。

今回、「ユーザー」と「性別」の関係性は、

ユーザー視点: 一人のユーザーには必ず一つの性別が紐付いている(1対1。has_one。)

性別視点: 一つの性別は多数のユーザーに紐付く(1対多。has_many。)

となっているという体で各モデルの紐付けを行なっていきます。

「User」モデルの編集内容

api/app/models/user.rb
class User < ApplicationRecord

  # 「ユーザー」に紐付いている「性別」(1対1)
  has_one :user_sex,
    dependent: :destroy
  has_one :sex,
    through: :user_sex

end

「UserSex」モデルの編集内容

api/app/models/user_sex.rb
class UserSex < ApplicationRecord

  # 一つの「user_sex」は、ある一人の「ユーザー」と
  belongs_to :user

  # ある一つの「性別」との関係性を示している
  belongs_to :sex

  # validates ########################

  validates :user_id,
    presence: true

  validates :sex_id,
    presence: true

end

「Sex」モデルの編集内容

api/app/models/sex.rb
class Sex < ApplicationRecord

  # 「性別」が紐付いている「ユーザー」(1対多)
  has_many :user_sexes,
    dependent: :destroy,
    foreign_key: 'sex_id'
  has_many :users,
    through: :user_sexes

end

ポイントは「User」モデルの「has_one」の設定ですね。
...ネット上にある中間テーブル・アソシエーション作成の記事だと参考になるものがなく(複数タグ付けの記事ばかり)、この書き方にたどり着くまで苦労しました。ものすごく初歩的なミスなのですが、私は「has_one」で関連付けるモデルの名前を複数形にしていたことで長いこと詰まってしまいました。ここは単数形でないとダメです。モデル名の末尾に「s」が付いているとエラーになってしまいます。

また余談ですが、本記事の内容は「投稿」に「カテゴリー」を一つだけ付けられるようにしたいなどのケースにも応用できると思います。

それではマイグレーションファイルも編集していきます。

テーブルの作成

「sexe」テーブル作成用マイグレーションファイルの編集内容

api/db/migrate/2022XXXXXXXXXX_create_sexes.rb
class CreateSexes < ActiveRecord::Migration[6.1]
  def change
    create_table :sexes do |t|

      t.string :name, null: false
      t.string :code, null: false
      t.string :alias, null: false

      t.timestamps
    end
  end
end

「user_sexes」テーブル作成用マイグレーションファイルの編集内容

api/db/migrate/2022XXXXXXXXXX_create_user_sex_maps.rb
class CreateUserSexes < ActiveRecord::Migration[6.1]
  def change
    create_table :user_sexes do |t|

      t.references :sex, null: false, foreign_key: true
      t.references :user, null: false, foreign_key: true

      t.timestamps
    end
  end
end

今回は「users」テーブルと「sexes」テーブルの関連付けに中間テーブル「user_sexes」を用いるので、「users」テーブルに「sex」カラムを追加したりはしません。同じ理由で「sexes」テーブルに「user」カラムを作成することもしません。

ここまでファイルの編集が済んだらターミナルに以下のコマンドを打ち込み、マイグレーションファイルの内容をデータベースに反映させます。

マイグレーションファイルの内容をデータベースに反映

rails db:migrate

「sexes」テーブルと中間テーブルの「user_sexes」テーブルが作成されました。

「sexes」テーブルの中身は決まっているので、seedファイルを作成してあらかじめテーブルにデータを入れておきます。ターミナルからseedファイルを作成しましょう。

seedファイルによる初期データの作成

「sexes」テーブル用のseedファイルを作成

touch api/db/seeds/development/sexes.rb

※seedファイルの作成場所はご自身のアプリケーションの作業環境で適宜読み替えてください。

作成したseedファイル「sexes.rb」の編集

api/db/seeds/development/sexes.rb
# 「性別」の初期設定データ
Sex.create!(
  [
    # 不明
    {
      name: "not known",
      code: 0,
      alias: "unanswered"
    },
    # 男性
    {
      name: "male",
      code: 1,
      alias: "men"
    },
    # 女性
    {
      name: "female",
      code: 2,
      alias: "women"
    },
    # その他
    {
      name: "not applicable",
      code: 9,
      alias: "others"
    }
  ]
)

puts "sexes = #{Sex.count}"

作成したseedファイルの内容を「sexes」テーブルに反映させます。
ターミナルで以下のコマンドを実行しましょう。

seedファイル「sexes.rb」を「sexes」テーブルに反映

rails db:seed

すでに何かしらのseedファイルを作成・反映させていた場合は次のコマンドを実行してすべてのseedファイルを反映し直します。

seedファイルの反映をリセット&再度反映

rails db:reset

ここまでの作業で「性別」のモデルとテーブルの作成が完了しました!
あとはコントローラーファイルの設定です。参考までに私のindexアクションの内容だけ掲載しておきます。

コントローラーの編集

コントローラーファイル「users_controller.rb」の編集

api/app/controllers/api/v1/users_controller.rb
module Api
  module V1
    class UsersController < ApplicationController

      # 「ユーザー」の一覧の取得
      def index
        @user = User.all.includes(:sex)
        render json:
          @question.as_json(
            include: %i[sex]
          )
      end

    end
  end
end

説明は以上となります。
わかりにくいところや間違っている箇所などがありましたらごめんなさい。
おかしな部分があればコメントにてご指摘いただけるとありがたいです!

Discussion