STI(Single Table Inheritance)でハマったところ

最近Rails でSTI を使う機会があったのですが、幾つかハマるポイントがありました。

親クラスを継承したクラスが読み込まれない

継承した子クラスのモデルを親クラスと同じファイルにまとめていると、エラーになる場合があります。

# app/models/user.rb
class User < ActiveRecord::Base
end

class Admin < User
end

class Guest < User
end

# rails console で実行
irb(main):001:0> Admin.all
NameError: uninitialized constant Admin

この問題はRails の自動読み込みの仕組みに関連して起こるようです。
Rails の自動読み込みの話

読み込みされていないクラス/モジュールがあった場合、名前から読み込みするファイルを判断できる

上記の例の場合は、admin.rb を読み込もうとしてエラーになります。
つまり、Adminクラスにアクセスする前にUserクラスが読み込まれていれば、NameError: uninitialized constant エラーは発生しません。
Devise等を利用していて、ログイン処理をUserクラスでまとめていたりすると before_action で毎回User にアクセスしているため
エラーが起こらないということもあると思います。
また、production モードでも起動時にmodel が読み込まれるため発生しません。
以上のような理由で場合によっては1つのファイルにまとめて記述しても問題ないかもしれませんが、大抵の場合は子クラスは別ファイルに分けたほうがいいと思います。

# app/models/user.rb
class User < ActiveRecord::Base
end
# app/models/admin.rb
class Admin < User
end
# app/models/guest.rb
class Guest < User
end

Routing がおかしくなる

子クラスのcontroller を作成せず親クラスのcontroller で処理をまとめていると
create update などのaction からのリダイレクト先が 子クラスのURLになってしまってエラーになる場合があります。

# config/routes.rb
Sample::Application.routes.draw do
  root 'users#index'
  resources :users
end

# app/controllers/users_controller.rb
def create
  @user = if admin?
    Admin.new(user_params)
  elsif guest?
    Guest.new(user_params)
  else
    raise
  end

  if @user.save
    redirect_to @user
  else
    render action: :new
  end
end
# undefined method `admin_url' for #<UsersController:0x007af349879fa8>

controller を一つにまとめたい場合は becomes を使うと上手く行くと思います。

This is mostly useful in relation to single-table inheritance structures where you want a subclass to appear as the superclass.

目的にピッタリ合いそうです。

# app/controllers/users_controller.rb
def create
  # 省略
  if @user.save
    @user = @user.becomes(User) #この行を追加
    redirect_to @user
  else
    render action: :new
  end
end

これで無事 UsersController#show が表示されました。

まとめ

2つ目のRouting に関する問題は、後からSTI に変更した場合にやりがちだと思うので気をつけたいですね。

Ruby on RailsによるWEBシステム開発、Android/iPhoneアプリ開発、電子書籍配信のことならお任せください この記事を書いた人と働こう! Ruby on Rails の開発なら実績豊富なBPS

この記事の著者

週刊Railsウォッチ

インフラ

Rubyスタイルガイドを読む

BigBinary記事より

ActiveSupport探訪シリーズ