- 更新日: 2015年8月4日
- Rails
Rails ActiveRecordのSTI(Single Table Inheritance)の使い方
Rails の STI(Single Table Inheritance)機能を使うと、共通の属性を持つ親クラス(スーパークラス)と、各々独自の属性を持つ子クラス(サブクラス)となるモデルの関係をすっきり表現できます。Martin Fowler 氏のサイトの Single Table Inheritance 説明での例を参考にしました。
P of EAA: Single Table Inheritance
— 環境 —
Rails 4.1.6
activerecord 4.1.6
以下のような継承関係の STI 階層のモデルがあるとして、Player モデルが継承関係で一番上の親クラスとなる。
1 2 3 4 5 6 |
Player(:name) |- Footballer(:club) |- Cricketer(:batting_average) |- Bowler(:bowling_average) |
そして、全てのモデルに共通な属性として :name があり、Footballer に固有な属性として :club があり、Cricketer に固有な属性として :batting_average があり、Bowler に固有な属性として :bowling_average があることになります。STI 利用時のこれらの属性は、テーブルのカラムとしては、全て一つのDBテーブルに格納することとなる(後述する players テーブル)。
以降、上記モデルを実際に作成していき、STI でのモデル操作を試してみました。
type カラムを持つDBテーブルのマイグレーション
まずは、players テーブル用のマイグレーションファイルの作成。
1 2 3 |
$ bundle exec rails generate migration create_players |
db/migrate/***_create_players.rb
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class CreatePlayers < ActiveRecord::Migration def change create_table :players do |t| t.string :name t.string :club t.decimal :batting_average t.decimal :bowling_average t.string :type t.timestamps end end end |
STI を利用する場合は、必ず type カラムを作成するようにします。この type カラムにモデル名が入ります。
マイグレート実行。
1 2 3 |
$ bundle exec rails db:migrate |
モデルのファイルを作成
モデルのファイルでは、継承関係に気をつける。とりあえず動作確認のため、validation などは省略です。
app/models/player.rb(Player モデル)
1 2 |
class Player < ActiveRecord::Base end |
app/models/footballer.rb(Footballer モデル)
1 2 |
class Footballer < Player end |
app/models/cricketer.rb(Cricketer モデル)
1 2 |
class Cricketer < Player end |
app/models/bowler.rb(Bowler モデル)
1 2 |
class Bowler < Cricketer end |
pry(rails console)で動作確認
rails console で動作確認用に適当にいくつかデータを作成します。
1 2 3 4 5 6 |
$ bundle exec rails c pry(main)> Footballer.create(name: "Raul", club: "Real") pry(main)> Cricketer.create(name: "Mike", batting_average: 234) pry(main)> Bowler.create(name: "David", bowling_average: 123) |
この ActiveRecord#create の操作よりレコードが保存され、players テーブルの各レコードの type カラムに、モデル名 ‘Footballer’, ‘Cricketer’, ‘Bowler’ が各々保存されました。players テーブルの type カラムで、レコードがどのモデルのオブジェクトかを判断するわけですね。
続いて、モデルに対する操作で、どのような SQL クエリーが発行されるかを確認してみました。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
pry(main)> Bowler.count # => SELECT COUNT(*) FROM "players" WHERE "players"."type" IN ('Bowler') pry(main)> Footballer.count # => SELECT COUNT(*) FROM "players" WHERE "players"."type" IN ('Footballer') pry(main)> Cricketer.count # => SELECT COUNT(*) FROM "players" WHERE "players"."type" IN ('Cricketer', 'Bowler') pry(main)> Player.count # => SELECT COUNT(*) FROM "players" WHERE "players"."type" IN ('Player', 'Footballer', 'Cricketer', 'Bowler') |
すごく賢い!親クラスのモデルで検索時は、その子クラスを含めて IN で検索するようになっています。
次に ActiveRecord の find_by を試してみました。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
> Bowler.find_by(name: "David") # => SELECT "players".* FROM "players" WHERE "players"."type" IN ('Bowler') AND "players"."name" = 'David' LIMIT 1 > Footballer.find_by(name: "Raul") # => SELECT "players".* FROM "players" WHERE "players"."type" IN ('Footballer') AND "players"."name" = 'Raul' LIMIT 1 > Cricketer.find_by(name: "Mike") # => SELECT "players".* FROM "players" WHERE "players"."type" IN ('Cricketer', 'Bowler') AND "players"."name" = 'Mike' LIMIT 1 > Player.find_by(name: "Raul") # => SELECT "players".* FROM "players" WHERE "players"."type" IN ('Player', 'Footballer', 'Cricketer', 'Bowler') AND "players"."name" = 'Raul' LIMIT 1 |
これまた賢い!自動で検索対象となるモデルを type カラムに対して IN で絞り込んで検索しています。
以上、簡単でしたが Rails ActiveRecord の STI(Single Table Inheritance)の使い方でした。Rails やっぱり凄いですね〜、Rails を使ってて久しぶりに便利さに感動しました^^。STI でコントラーラーもひとつにまとめるには、以下クラスメソッドさんのブログ記事が分かりやすいです。
[Rails] STI(Single Table Inheritance)でコントローラも一つに纏める | Developers.IO
- – 参考リンク –
- Rails4でSTI(単一継承テーブル)を行う – Rails Webook
- 【Rails】ActiveRecord:単一テーブル継承(sti)とポリモーフィック関連を未だにぱっと思い出せないのでまとめ。 – 記すに足らず。
- STI(Single Table Inheritance)でハマったところ | TechRacho
- ActiveRecord::Inheritance
- Rails の関連記事
- RailsでMySQLパーティショニングのマイグレーション
- Rails ActiveRecordでdatetime型カラムのGROUP BY集計にタイムゾーンを考慮する
- RailsプラグインGemの作成方法、RSpecテストまで含めたrails pluginの作り方
- RailsでAMPに対応するgemをリリースしました
- Railsでrequest.urlとrequest.original_urlの違い
- Railsでwheneverによるcronバッチ処理
- Google AnalyticsのRails Turbolinks対応
- Railsアプリにソーシャル・シェアボタンを簡単設置
- Rails監視ツール用にErrbitをHerokuで運用
- Facebook APIバージョンのアップグレード手順(Rails OmniAuth)
Leave Your Message!