Rails4でエンタープライズなActiveRecordモデルを作るための6のステップ
1. 階層を分けてモデルを作る
まずはrails gコマンドでモデルを作ります。たいていの場合はお互いに関連のある複数のモデルを作ることになりますが、それらモデルは同じ階層の下に置くようにします。
例えば
$ rails g model Blog::User name:string profile:text
$ rails g model Blog::Post user:references permalink:string title:string content:text
というようにモデルを作成します。
2. データベース制約を追加する
Railsが生成するmigrationにはデータベース制約が設定されていないので、そのままではエンタープライズ用途には使えません。
基本的にNOT NULL制約とUNIQUE制約は必ず設定しますし、外部キーがあればForeignerを使って外部キー制約も必ず設定します。
class CreateBlogPosts < ActiveRecord::Migration
  def change
    create_table :blog_posts do |t|
      t.references :user, index: true, null: false
      t.string :permalink, null: false, limit: 64
      t.string :title, null: false, limit: 128
      t.text :content, null: false
      t.timestamps
    end
    add_index :blog_posts, :permalink, unique: true
    add_foreign_key :blog_posts, :blog_users, column: :user_id
  end
end3. モデルにvalidationを追加する
生成されたモデルにはvalidationが設定されていないので追加します。
class Blog::Post < ActiveRecord::Base
  belongs_to :user
  validates :permalink, presence: true, uniqueness: true, length: {maximum: 64}
  validates :title, presence: true, length: {maximum: 128}
  validates :content, presence: true
  # 他のよく使うバリデーション例
  # validates :x, uniquness: {scope: :title}
  # validates :x, numericality: {only_integer: true, greater_than_or_equal_to: 0, less_than_or_equal_to: 100}
  # validates :x, inclusion: [:a, :b]
  # validates :x, inclusion: {in: [:a, :b], allow_blank: true}
end4. FactoryGirlを定義する
FactoryGirlを導入しているとFactoryGirl用のファイルも生成してくれますが、こちらもそのままでは不十分なのでNOT NULL制約とUNIQUE制約への対応を行います。
FactoryGirl.define do
  factory :blog_post, :class => 'Blog::Post' do
    user
    sequence(:permalink) { |n| "permalink_#{n}" }
    title "title"
    content "content"
  end
end5. モデルテスト用のマッチャを追加する
続いてモデルのテストを行いますが、その前に必要なマッチャを追加します。
まずはshoulda-matchersを追加しますが、これだけでは不十分なのでいくつかカスタムマッチャを追加します。
今回使うカスタムマッチャはRails4のモデル用カスタムマッチャの以下のカスタムマッチャを追加します。
- have_not_null_constraint_onマッチャ
- データベースのNOT NULL制約をテストします
- have_unique_constraint_onマッチャ
- データベースのUNIQUE制約をテストします
- have_foreign_key_constraint_onマッチャ
- データベースの外部キー制約をテストします
- create_modelマッチャ
- FactoryGirlでのモデル生成をテストします
- safely_validate_uniqueness_ofマッチャ
- InvalidForeignKeyエラーにならないようにuniqueness validationをテストします
6. モデルテストを追加する
カスタムマッチャも追加して準備が整ったのでモデルのテストを追加します。
テストするのは大きく分けてFactoryGirl、Association、Validation、データベース制約の4種類になります。
describe Blog::Post, type: :model do
  subject { build(:blog_post) }
  context 'with FactoryGirl' do
    it { should create_model }
    it { should create_model.for(2).times } #uniqueなオブジェクトを生成することを確認
  end
  context 'with associations' do
    it { should belong_to(:user) }
  end
  context 'with validations' do
    it { should validate_presence_of(:user_id) }
    it { should validate_presence_of(:permalink) }
    it { should validate_presence_of(:title) }
    it { should validate_presence_of(:content) }
    it { should ensure_length_of(:permalink).is_at_most(64) }
    it { should ensure_length_of(:title).is_at_most(128) }
    it { should safely_validate_uniqueness_of(:permalink) }
    # 他のよく使うvalidation matcher例
    # it { should allow_value(:a, :b).for(:x) }
    # it { should ensure_length_of(:x).is_at_least(8) }
    # it { should ensure_length_of(:x).is_equal_to(8) }
    # it { should validate_numericality_of(:x).only_integer }
    # it { should validate_numericality_of(:x).is_greater_than_or_equal_to(0) }
    # it { should validate_numericality_of(:x).is_less_than_or_equal_to(100) }
  end
  context 'with DB' do
    it { should have_not_null_constraint_on(:user_id) }
    it { should have_not_null_constraint_on(:permalink) }
    it { should have_not_null_constraint_on(:title) }
    it { should have_not_null_constraint_on(:content) }
    it { should have_unique_constraint_on(:permalink) }
    it { should have_foreign_key_constraint_on(:user_id) }
  end
endBlog::Userモデルについても一通りちゃんと設定してテストを実行すると
$ rspec spec/models/blog/post_spec.rb
Blog::Post
  with associations
    should belong to user
  with validations
    should ensure permalink has a length of at most 64
    should require content to be set
    should ensure title has a length of at most 128
    should require title to be set
    should require permalink to be set
    should require unique value for permalink
    should require user_id to be set
  with DB
    should have NOT NULL constraint on content
    should have UNIQUE constraint on permalink
    should have NOT NULL constraint on permalink
    should have NOT NULL constraint on title
    should have FOREIGN KEY constraint on user_id
    should have NOT NULL constraint on user_id
  with FactoryGirl
    should create 2 records
    should create 1 record
Finished in 2.15 seconds (files took 12.15 seconds to load)
16 examples, 0 failures
となってうまくテストできていることが確認できます。
まとめ
Railsではひな形を自動的に作ってくれてとても便利なのですが、それだけで十分なモデルができるわけではないのでエンタープライズ用途での利用に最低限必要な修正をご紹介しました。
もっとこうした方がいいよ!ということなどありましたら@haracane宛に教えてくれるとありがたいです。
