Ruby on Railsで多対多の実現

昨日からRailsを使ってちょっとしたプロジェクト支援ツールを作っています。
プロジェクトにはメンバーが所属することになりますが、その関連は多対多です。今日はこれを実現してみたい。

モデル作成、テーブル作成

必要なのはProjectクラスとMemberクラス。Projectクラスは既に昨日作ってあるので、今日はまずMemberクラスを作成します。

ruby script/generator scaffold member name:string password:string mail_address:string

で、お決まりパターンのDBの作成。

rake db:migrate

多対多の関連を持つには、通常DB上ではProjectsテーブルとMembersテーブルをつなぐマッピングテーブルが必要となりますがRailsであっても同様のようです。以下をMySQLにアクセスし、実行します。テーブルの命名は以下のようにしないといけません。ただprojects_membersだとうまくいきませんでした。アルファベット順ってことかな。

mysql> create table members_projects(project_id integer, member_id integer);

DBの準備が出来たらクラスを編集します。まずはProjectクラスから。SubProjectsクラスとの1対多関連に続き多対多の関連を追加。

class Project < ActiveRecord::Base
  has_many :sub_projects
  has_and_belongs_to_many :members
end

次にMemberクラス。

class Member < ActiveRecord::Base
  has_and_belongs_to_many :projects
end

テスト

では早速多対多が実現できたかどうかをテストしてみます。
まずテストデータから。以下はmembers.yml。

one:
  id: 1
  name: taro
  password: taro
  mail_address: taro@hogehoge.com

two:
  id: 2
  name: hanako
  password: hanako
  mail_address: hanako@hogehoge.com

次にmembers_projects.yml。このファイルは自動生成されないので、自分で作成します。

one:
  member_id: 1
  project_id: 1

two:
  member_id: 2
  project_id: 1

three:
  member_id: 1
  project_id: 2

project.ymlは昨日と同じ。一応載せておきます。

one:
  id: 1
  name: MyString1
  description: MyText
  from: 2008-06-15
  to: 2008-06-15

two:
  id: 2
  name: MyString2
  description: MyText
  from: 2008-06-15
  to: 2008-06-15

で、テストケースです。昨日のproject_test.rbに追加してみました。

  def test_meny_to_many
    p1 = Project.find(1)
    assert_equal(2, p1.members.size)

    p2 = Project.find(2)
    assert_equal(1, p2.members.size)
    assert_equal("taro", p2.members[0].name)
  end

一応member_test.rbも。こちらはメンバー自分自身が所属しているプロジェクトの数を確認しています。

class MemberTest < ActiveSupport::TestCase
  def test_many_to_many
    taro = Member.find(1)
    assert_equal(2, taro.projects.size)

    hanako = Member.find(2)
    assert_equal(1, hanako.projects.size)
    assert_equal("MyString1", hanako.projects[0].name)
  end
end

テストの実行。

rake

うまくいきました♪

保存

ではscript/consoleを使って実際に保存してみます。

ruby script/console

とやって、プログラムを書いていきます。

p1 = Project.create(:name=>"sample project")

taro = Member.create(:name=>"taro")
hanako = Member.create(:name=>"hanako")

p1.members << taro << hanako

createメソッドはnewとsaveが同時に行われます。つまり生成と同時に永続化するってことです。この状態で一気にtaroとhanakoを追加すると特にsaveなどせずともこの時点でmembers_projectsテーブルにデータが2件追加されていました。
できたー♪