データベースのデータ間の関連を具体的に作成します。関連には、異なるテーブル間の関連以外に一つのテーブルのデータ間の関連いわゆる自己関連(SQLでは自己結合といわれています)というものがあります。
ここでは、自己関連を作りRails consoleで動作を確認します。
1対1関連については2モデル間の1:1関連を参照してください。
1対多関連については2モデル間の1:多関連を参照してください。
多対多関連については2モデル間の多:多関連を参照してください。
Model
今回、登場するモデルは、Materialモデルです。 テーブルでいえば、materialsテーブルです。
1モデルでの親子関係 -自己関連-
自己関連を「ファイル」を例として考えてみます。
元となるファイルがあります。そのファイルをコピーし、手を加えたものが複数できるというケースを考えてみます。
すなわち、親ファイル(parent)があって、1つの親ファイル(parent)から派生的に作成される子ファイル(children)がある、という状況です。parent : children は、1対多の関連になります。
親のファイル(parent)と子のファイル(children)はファイルという同じ範疇のものなので、1つのモデルで親子関係を実現することになります。そうすると親も子も同じモデルのインスタンスですから自分自身のモデル間の関連、自己関連になるわけです。子に関連付けた孫を作ることもできるので木構造を表現することができます。
ここではファイルという意味付けを行ないましたが、この図のノード(結節点)をディレクトリと思って見直してみるとディレクトリ構造を表しているように見ることもできます。
Materialsモデルの親子関連
Materialモデル に自己関連を作ります。
- Materialモデルを作る
references を使ってフィールドを作ると、indexもつけてくれます。 Railsアプリケーションを作ったディレクトリで
を実行しモデルを作成します。
モデルに以下のような記述を加えます。ここで、material_idフィールド(material:referencesの指定で作成されました)には参照相手のidが入る訳ですが、そのidは「子」にとっての「親」のidになります。よって「子」の方にbelongs_to
を記述します。反対の「親」の側には「1対多」関連なのでhas_many
を記述します。
- material.rb
繰り返しになりますが、「親が子を(たくさん)持つ」なのでhas_many :children
は「親」の側の指定です。「子が従属するのは親に」なのでbelongs_to :parent
は「子」の側の指定です。
こうすることで、親からは「.children
」で派生ファイル(の配列)に、子からは「.parent
」で親ファイルにたどり着くことができるわけです。
クラス図はこのようになります。
また、親側にdependent: :destroy
と記述することによって、親の削除に依存してその子供たちも一緒に削除されます。上の記述では子を削除しても親は(もちろんほかの子も)削除されません。
Rails console で確認
Rails console で確認してみます。
- 一つの親に子を3つ作り、その親の子を確認、うち一つの子の親を確認します。
- この一つをdestroyし、ほかに影響がないことを確かめます。
- 親を削除し、子がすべて(残っている2つ)がともに削除されることを確かめます。
- また孫を作り、「親」-「子」-「孫」の関係を確かめます。
では、確認していきます。
rails consoleコマンドに--sandbox
オプションをつけると、終了時にデータベースに行った作業をロールバックして初めの状態に戻してくれます。
また、Hirbを起動して出力を見やすくしています。
まず「親」を一つとその「子供たち」を3つ登録します。 コンソールに打ち込んだのは次のものです。
- par = Material.create(name: ‘親文書’)
- child1 = Material.create(name: ‘子文書’, parent: par)
- child2 = Material.create(name: ‘子文書2’, parent: par)
- child3 = Material.create(name: ‘子文書3’, parent: par)
- Material.all
1で親を作り、2~4で子を作っています。できたデータを5で確認しています。
実行結果です(途中は省略しています)。
では、親から3つの子を受け取ってみます。
親のインスタンスがpar
でしたのでpar.children
が子供たちになります。
3つの子供たちが受け取れています。
では、子供から親を取得してみます。
子child1
の親は、child1.parent
です。
親が受け取れました。 親のnameなら直接取得できます。
では、子を一つ削除します。 削除し、確認するために次を打ち込みます。
- child2.destroy
- Material.all
その結果です。
真ん中の子がいなくなりました。
いよいよ、親を削除します。確認のためこの親とは無関係なデータを加えてから親を削除することにします。
削除を確認するために次を打ち込みます。
- Material.all
- par.destroy
- Material.all
その結果です。
親を削除すると、親とその親に紐付いた子供たちが削除されました。
そうでした、孫も作れますね。
子供を親にした子が孫になります。孫 = Material.create(name: '孫', parent: 子のインスタンス)
です。
孫から親を引くには孫.parent
孫から祖父を引くには孫.parent.parent
でできそうですね。
なんて簡単!!やっぱりRailsはすばらしい。