RailsでModelをDBから分割・整理する
それなりの規模のRailsプロジェクトではよくFat Model問題とかFat Controller問題が課題になってくる。
で、Fat Model問題については、Concernsなどに振る舞いを切り出したり、そもそも「それって1つのテーブルに収める必要ある?」というものを分割するなどの対策がある。
後者については、http://kenn.hatenablog.com/entry/2014/03/05/081525 この記事がわかりやすいサンプルとしてUserモデルからProfileモデルをテーブルごと切り出す例を挙げている。
今僕が関わっているプロジェクトでも上記例のように姓名などはProfileに切り出してあり、その他も住所関連の情報をAddressに、「退会理由」みたいなめったにつかわない情報をExtraInfoに、といったように意味合いと使用頻度を考慮していくつか分割する設計になっているものがあるんだけど、このような立ち位置のモデルが増えてくると、app/modelsの直下にフラットにモデルを配置していることがキツくなってくる。
ということで、userに関するモデルはネームスペース切ってまとめたので、そのときの手順をProfileを例にして以下にメモ。
テーブル名を変更
'profiles'というテーブル名だったけど、MySQLなどのRDBMSではuserとの関連が見えたほうがわかりやすいので、'user_profiles'という名前に変えた。これによって'admin'の'profiles'が欲しくなっても怖くないぜ、という効果もあるかもないかも。
usersディレクトリをmodels下に掘る
app/models/users/profile.rb
という按配になる。
userディレクトリにしたいキモチが少しあるけど、userモデルがあるので同名のmoduleは作れないので、usersで良しとする。
ProfileモデルをUsers module配下に
こんな感じですな。
module Users class Profile < ActiveRecord::Base self.table_name = 'user_profiles' ~snip~
self.table_nameの指定が必要になるけど、僕はあまり気にしない。気になる人は、module名とテーブル名のprefixのルールを厳格に管理できるのならActiveRecord::Baseを継承したクラスを作ってごにょごにょしてもいいかもしれないし、僕もそのうちそうするかもしれない。
Userモデルの関連を修正
app/models/user.rb
class User < ActiveRecord::Base ~snip~ has_one :profile, dependent: :destroy, foreign_key: 'id', class_name: 'Users::Profile' ~snip~
こんな感じで、class_nameを指定する必要がでてくるけど、大した問題じゃあない。
テスト(Spec)ファイルを移動・修正
app側と同じく、spec/models/usersディレクトリを掘って、そこにprofile_spec.rbを移動。
修正は冒頭のクラス指定箇所くらい。
RSpec.describe Users::Profile, type: :model do
Factoryファイルを移動・修正
Rspecの場合。
spec/factories/usersディレクトリを切って、そこに移動する。
で、class指定をする。
FactoryGirl.define do factory :user_profile, class: Users::Profile do ~snip~
localeファイルの移動・修正
僕は日本語対応しかしないプロジェクトでも表記ゆれ防止とかテスタビリティのこととか考えるとI18nを利用したほうがいいと思っているので、この作業も必須になる。
これも同じようにconfig/locales/models/usersディレクトリを切ってprofile/ja.ymlを配置する(localファイルは好きな場所に置けるのでシステム的な意味はない。)
で、ネームスペースをどう表現するか、なんだけど以下のようになる。
Before
ja: activerecord: models: profile: プロフィール
After
ja: activerecord: models: users/profile: プロフィール
ということで/でネームスペース(module名)とモデル名を区切ればいい。
使う時も以下のような感じで問題ない。
I18n.t('activerecord.attributes.users/profile.last_name')
こんな感じにしておけば、Userに関連するモデルがどこにあるのかわかりやすくなるし、使う分にはuser.proflieと言った具合にこれまでと何の変わりもないし、自分的には良いこと尽くめだと思うんですけどどうですかね。