JanGaJan.com

Is fun? JOY!

STIを試す

Single Table Inheritance(STI)とは

単一テーブル継承というデザインパターンです。テーブルベースの継承階層を表現しています。Ruby同様、継承元は一つのテーブルだけです。
こちらに具体的なイメージがあります。
早速RailsでSTIをどうやるのか調べてみます。

環境

tool version
ruby 2.2.0
rails 4.2.0

試す

まずは準備をします。

1
2
3
$ bundle exec rails new sti_type --no-test-framework --skip-bundle
$ bundle exec rails g model user name:string age:integer type:string
$ bundle exec rake db:migrate

RailsでのSTIではmodelの type というカラムが重要な役割を果たします。

app/models/guest.rb
1
class Guest < User; end
app/models/host.rb
1
class Host < User; end

継承ツリーとしてはこんな感じになります。

app/models/host.rb
1
2
3
4
5
6
ActiveRecord::Base
        |
       User
   _____|_____
  |           |
Guest        Host

これで一通りの準備が整いました。 rails console で動作を確認してみます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
$ bundle exec rails c
> Guest.create(name: 'Mark', age: 18)
> Host.create(name: 'Yamada', age: 50)

> pp User.all
[#<Guest:0x007f84c1279a00
  id: 1,
  name: "Mark",
  age: 18,
  type: "Guest",
  created_at: Thu, 05 Mar 2015 19:43:24 UTC +00:00,
  updated_at: Thu, 05 Mar 2015 19:43:24 UTC +00:00>,
 #<Host:0x007f84c1279870
  id: 2,
  name: "Yamada",
  age: 50,
  type: "Host",
  created_at: Thu, 05 Mar 2015 19:44:02 UTC +00:00,
  updated_at: Thu, 05 Mar 2015 19:44:02 UTC +00:00>]

> pp Guest.all
[#<Guest:0x007f84c2b54b10
  id: 1,
  name: "Mark",
  age: 18,
  type: "Guest",
  created_at: Thu, 05 Mar 2015 19:43:24 UTC +00:00,
  updated_at: Thu, 05 Mar 2015 19:43:24 UTC +00:00>]

type に、自分のクラス名がGuestだったら'Guest'と入ります。 Guest だけの属性を集めたい場合は Guest.all とすることで取得できます。

type以外のカラムにクラス情報を格納したい

なにがしかの理由で type は使えない場合、 self.inheritance_column を使うことで、別のカラムをクラス名を格納するカラムに指定することができます。 例えば、 role というカラムを使いたい場合です。

準備

汚いですが前のサンプルをそのまま使います。
roleというカラムを追加します。

1
$ bundle exec rails g migration AddRoleToUser
db/migrate/20150305201347_add_role_to_user.rb
1
2
3
4
5
class AddRoleToUser < ActiveRecord::Migration
  def change
    add_column :users, :role, :string
  end
end

データを綺麗にしたいのでDB作り直します。

1
2
$ bundle exec rake db:setup
$ bundle exec rake db:migrate

大事なのはここだけです。 self.inheritance_columnrole を指定します。

app/models/user.rb
1
2
3
class User < ActiveRecord::Base
  self.inheritance_column = :role
end

確認

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
> Guest.create(name: 'Bob', age: 1000)
> Host.create(name: 'Tanaka', age: 100)
> pp User.all
[#<Guest:0x007fe248c19c60
  id: 3,
  name: "Bob",
  age: 1000,
  type: nil,
  created_at: Thu, 05 Mar 2015 20:23:55 UTC +00:00,
  updated_at: Thu, 05 Mar 2015 20:23:55 UTC +00:00,
  role: "Guest">,
 #<Host:0x007fe248c19ad0
  id: 4,
  name: "Tanaka",
  age: 100,
  type: nil,
  created_at: Thu, 05 Mar 2015 20:24:10 UTC +00:00,
  updated_at: Thu, 05 Mar 2015 20:24:10 UTC +00:00,
  role: "Host">]

type ではなく、 role を使用していることが確認できました。

注意点

注意しなければいけなそうな点は、こんな感じでしょうか。

  • 子クラス(GuestHost)ごとに特別のカラムが欲しい場合、 users に追加する必要があり、nilを許容しなければいけない
    • Guest で使うが Host では使用しないケースがあるから
  • 子クラスごとに、カラムの型を変えることはできない

Comments