20
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【Rails】多対多のモデル間で1つのデータに関連付けられる個数を制限する

20
Last updated at Posted at 2022-02-25

はじめに🧎🏻

現在個人開発しているサービスにて
多対多で関連付けたモデルに対して、紐付けられる数を制限する際に
まとまった情報がなく不便だったので備忘録も兼ねて記します。

環境💻

  • Ruby 3.0.1
  • Ruby on Rails 6.1.4.4
  • [gem] rails-i18n 6.0.0

想定読者👥

  • 多対多の関連付けをしているモデルに対して、1つのデータに関連付けられる個数に対するバリデーションをかけたい人。
  • そのバリデーションに引っかかった際のエラーメッセージの制御、および国際化をしたい人。

前提🎬

例として、映画と役者のモデルが多対多で関連付けられている場合を想定します。

  • 1つの映画Movieに対して役者の情報は複数持つ(出演者てきな)
  • 1人の役者Actorに対して映画の情報は複数持つ(出演作てきな)
  • 上記の紐付けを中間テーブルmovie_actorsで管理する
各モデルファイル
# movie.rb
class Movie < ApplicationRecord
  
  has_many :movie_actors
  has_many :actors, through: :movie_actors
  
end

# actor.rb
class Actor < ApplicationRecord
  
  has_many :movie_actors
  has_many :movies, through: :movie_actors
  
end

# movie_actor.rb
class MovieActor < ApplicationRecord
  belongs_to :movie
  belongs_to :actor

  # 組み合わせの一意制約
  validates :movie_id, uniqueness: { scope: :actor_id }  
end

バリデーションを定義👮🏻‍♂️

ここでさらに
「一つの映画に対して紐付けられる役者の情報を3人までに制限したい」
という仕様があるとします。今回の肝ですね。
これを実現するには

validates :中間テーブル名, length: { maximum: 上限数 }

これを制限をかける側のモデルファイルに記述するだけです。
今回の場合だとMovieモデルなので、

movie.rb
class Movie < ApplicationRecord
  
  validates :movie_actors, length: { maximum: 3 }
  
end

これだけでOK。
Railsのよしな力におんぶにだっこです。ヒモ男化が加速しますね〜。

ちなみに今回は想定した仕様上、
上記のようなバリデーションをかけましたが、たとえば

  • 1つの映画に対して役者の情報を2人以上、3人以下

というような制限や

  • 1人の役者に対して映画の情報は3つまで

のようなActor側の制限もできます。

エラーメッセージの制御と国際化🇯🇵

データの制約上は上記だけでOKですが、現状だとこのバリデーションに引っかかった際は
movie_actorsは3文字以内で入力してください
というエラーメッセージが表示されます。(日本語化している場合)
①属性名英語だし、②(文字じゃないのに)3文字って出てるしでなんとかしたい。

①属性名英語だし → movie_actorsを日本語化しましょう

model.ja.yml
ja:
  activerecord:
    attributes:
      movie:
        movie_actors: 出演者

②3文字って出てるし → エラーメッセージを制御しましょう

validatesメソッドのlengthヘルパーでは、messageオプションを指定することでエラーメッセージを制御することができます。

movie.rb
validates :movie_actors, length: { maximum: 3, message: 'は3人までじゃよ〜欲張りなさんな。' }

messageオプション以外でも色々できます

デフォルトのエラーメッセージは、実行されるバリデーションの種類によって異なります。デフォルトのメッセージは:wrong_length、:too_long、:too_shortオプションを使ってカスタマイズすることも、%{count}を長さ制限に対応する数値のプレースホルダにも使えます。:messageオプションを使ってエラーメッセージを指定することもできます。 - Railsガイド ActiveRecord バリデーション

これで、バリデーションに引っかかった際には
「出演者は3人までじゃよ〜欲張りなさんな。」
と、おそらく達観してそうなひとに優しく諭してもらえます。

ここからさらにmessageもi18nしたい人に向けて追加説明

①翻訳ファイルに定義

model.ja.yml
ja:
  activerecord:
    errors:
      messages:
        a_movie_has_upto_three_actors: は3人までじゃよ欲張りなさんな

②コードの書き換え

movie.rb
validates :movie_actors, length: { maximum: 3, message:
  I18n.t('activerecord.errors.messages.a_movie_has_upto_three_actors') }

※ここでI18n.t('...')の形でレシーバを明示しないと,Movieをレシーバに参照してしまいNoMethodErrorが発生するため注意!

おわりに🙇🏻‍♂️

model.ja.ymlでactiverecord→errors→messages配下に配置しているものの、呼び出し時の省略ができてないので、 呼び出し時に長くなるのを嫌う場合は別枠で切り出してもいいかも?
ただ単純に自分がなにかミスってる可能性もある。というか多分そうなので分かる方はぜひご教授ください。泣いて喜びます。 

その他にも間違っている点や補足、タイポなどありましたらお気軽にコメント、編集リクエストいただけますと幸いです!!

参考記事📄

20
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
20
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?