はじめに🧎🏻
現在個人開発しているサービスにて
多対多で関連付けたモデルに対して、紐付けられる数を制限する際に
まとまった情報がなく不便だったので備忘録も兼ねて記します。
環境💻
- 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モデルなので、
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を日本語化しましょう
ja:
activerecord:
attributes:
movie:
movie_actors: 出演者
②3文字って出てるし → エラーメッセージを制御しましょう
validatesメソッドのlengthヘルパーでは、messageオプションを指定することでエラーメッセージを制御することができます。
validates :movie_actors, length: { maximum: 3, message: 'は3人までじゃよ〜欲張りなさんな。' }
↓messageオプション以外でも色々できます
デフォルトのエラーメッセージは、実行されるバリデーションの種類によって異なります。デフォルトのメッセージは:wrong_length、:too_long、:too_shortオプションを使ってカスタマイズすることも、%{count}を長さ制限に対応する数値のプレースホルダにも使えます。:messageオプションを使ってエラーメッセージを指定することもできます。 - Railsガイド ActiveRecord バリデーション
これで、バリデーションに引っかかった際には
「出演者は3人までじゃよ〜欲張りなさんな。」
と、おそらく達観してそうなひとに優しく諭してもらえます。
ここからさらにmessageもi18nしたい人に向けて追加説明
①翻訳ファイルに定義
ja:
activerecord:
errors:
messages:
a_movie_has_upto_three_actors: は3人までじゃよ〜欲張りなさんな。
②コードの書き換え
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配下に配置しているものの、呼び出し時の省略ができてないので、 呼び出し時に長くなるのを嫌う場合は別枠で切り出してもいいかも?
ただ単純に自分がなにかミスってる可能性もある。というか多分そうなので分かる方はぜひご教授ください。泣いて喜びます。
その他にも間違っている点や補足、タイポなどありましたらお気軽にコメント、編集リクエストいただけますと幸いです!!