Tech Racho エンジニアの「?」を「!」に。
  • 開発

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

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

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

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

# 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 の自動読み込みの仕組みに関連して起こるようです。

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

上記の例の場合は、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

2. 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 に変更した場合にやりがちだと思うので気をつけたいですね。


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。