8

私はレールで私たちの教会のためのメンターシッププログラムを書いています(私はまだレールに慣れていません)..

そして、私はこれをモデル化する必要があります..

contact
has_one :father, :class_name => "Contact"
has_one :mother, :class_name => "Contact"
has_many :children, :class_name => "Contact"
has_many :siblings, :through <Mother and Father>, :source => :children

したがって、基本的にオブジェクト「兄弟」は、オブジェクト自体を含まない、父と母の両方からすべての子をマップする必要があります..

これは可能ですか?

ありがとう

ダニエル

4

3 に答える 3

9

単純に見える質問が複雑な答えになるのは面白いことです。この場合、再帰的な親子関係を実装するのはかなり簡単ですが、父母と兄弟の関​​係を追加すると、いくつかのひねりが生じます。

まず、親子関係を保持するテーブルを作成します。Relationship には 2 つの外部キーがあり、どちらも Contact を指しています。

create_table :contacts do |t|
  t.string :name
end

create_table :relationships do |t|
  t.integer :contact_id
  t.integer :relation_id
  t.string :relation_type
end

Relationship モデルでは、父と母を Contact に戻します。

class Relationship < ActiveRecord::Base
  belongs_to :contact
  belongs_to :father, :foreign_key => :relation_id, :class_name => "Contact",
  :conditions => { :relationships => { :relation_type => 'father'}}
  belongs_to :mother, :foreign_key => :relation_id, :class_name => "Contact",
  :conditions => { :relationships => { :relation_type => 'mother'}}
end

Contact で逆関連付けを定義します。

class Contact < ActiveRecord::Base
  has_many :relationships, :dependent => :destroy
  has_one :father, :through => :relationships
  has_one :mother, :through => :relationships
end

これでリレーションシップを作成できます:

@bart = Contact.create(:name=>"Bart")
@homer = Contact.create(:name=>"Homer")
@bart.relationships.build(:relation_type=>"father",:father=>@homer)
@bart.save!
@bart.father.should == @homer

これはそれほど素晴らしいことではありません。本当に必要なのは、1 回の呼び出しで関係を構築することです。

class Contact < ActiveRecord::Base
  def build_father(father)
    relationships.build(:father=>father,:relation_type=>'father')
  end
end

できること:

@bart.build_father(@homer)
@bart.save!

Contact の子を見つけるには、Contact にスコープを追加し、(便宜上) インスタンス メソッドを追加します。

scope :children, lambda { |contact| joins(:relationships).\
  where(:relationships => { :relation_type => ['father','mother']}) }

def children
  self.class.children(self)
end

Contact.children(@homer) # => [Contact name: "Bart")]
@homer.children # => [Contact name: "Bart")]

兄弟はトリッキーな部分です。Contact.children メソッドを利用して、結果を操作できます。

def siblings
  ((self.father ? self.father.children : []) +
   (self.mother ? self.mother.children : [])
   ).uniq - [self]
end

これは最適ではありません。なぜなら、father.children と Mother.children が重複するため (したがって が必要になるためuniq)、必要な SQL を実行することでより効率的に実行できるからです (演習として残しておきます :)) self.father.childrenself.mother.children異母兄弟 (父親が同じで母親が異なる) の場合は重複しません。連絡先には父親も母親もいない可能性があります。

完全なモデルといくつかの仕様は次のとおりです。

# app/models/contact.rb
class Contact < ActiveRecord::Base
  has_many :relationships, :dependent => :destroy
  has_one :father, :through => :relationships
  has_one :mother, :through => :relationships

  scope :children, lambda { |contact| joins(:relationships).\
    where(:relationships => { :relation_type => ['father','mother']}) }

  def build_father(father)
    # TODO figure out how to get ActiveRecord to create this method for us
    # TODO failing that, figure out how to build father without passing in relation_type
    relationships.build(:father=>father,:relation_type=>'father')
  end

  def build_mother(mother)
    relationships.build(:mother=>mother,:relation_type=>'mother')
  end

  def children
    self.class.children(self)
  end

  def siblings
    ((self.father ? self.father.children : []) +
     (self.mother ? self.mother.children : [])
     ).uniq - [self]
  end
end

# app/models/relationship.rb
class Relationship < ActiveRecord::Base
  belongs_to :contact
  belongs_to :father, :foreign_key => :relation_id, :class_name => "Contact",
  :conditions => { :relationships => { :relation_type => 'father'}}
  belongs_to :mother, :foreign_key => :relation_id, :class_name => "Contact",
  :conditions => { :relationships => { :relation_type => 'mother'}}
end

# spec/models/contact.rb
require 'spec_helper'

describe Contact do
  before(:each) do
    @bart = Contact.create(:name=>"Bart")
    @homer = Contact.create(:name=>"Homer")
    @marge = Contact.create(:name=>"Marge")
    @lisa = Contact.create(:name=>"Lisa")
  end

  it "has a father" do
    @bart.relationships.build(:relation_type=>"father",:father=>@homer)
    @bart.save!
    @bart.father.should == @homer
    @bart.mother.should be_nil
  end

  it "can build_father" do
    @bart.build_father(@homer)
    @bart.save!
    @bart.father.should == @homer
  end

  it "has a mother" do
    @bart.relationships.build(:relation_type=>"mother",:father=>@marge)
    @bart.save!
    @bart.mother.should == @marge
    @bart.father.should be_nil
  end

  it "can build_mother" do
    @bart.build_mother(@marge)
    @bart.save!
    @bart.mother.should == @marge
  end

  it "has children" do
    @bart.build_father(@homer)
    @bart.build_mother(@marge)
    @bart.save!
    Contact.children(@homer).should include(@bart)
    Contact.children(@marge).should include(@bart)
    @homer.children.should include(@bart)
    @marge.children.should include(@bart)
  end

  it "has siblings" do
    @bart.build_father(@homer)
    @bart.build_mother(@marge)
    @bart.save!
    @lisa.build_father(@homer)
    @lisa.build_mother(@marge)
    @lisa.save!
    @bart.siblings.should == [@lisa]
    @lisa.siblings.should == [@bart]
    @bart.siblings.should_not include(@bart)
    @lisa.siblings.should_not include(@lisa)
  end

  it "doesn't choke on nil father/mother" do
    @bart.siblings.should be_empty
  end
end
于 2011-02-14T09:24:12.343 に答える
2

私はzeteticに完全に同意します。質問は答えよりもはるかに単純に見えますが、それについて私たちができることはほとんどありません. ただし、20cを追加します。
テーブル:

    create_table :contacts do |t|
      t.string :name
      t.string :gender
    end
    create_table :relations, :id => false do |t|
      t.integer :parent_id
      t.integer :child_id
    end

テーブル リレーションには対応するモデルがありません。

class Contact < ActiveRecord::Base
  has_and_belongs_to_many :parents,
    :class_name => 'Contact',
    :join_table => 'relations',
    :foreign_key => 'child_id',
    :association_foreign_key => 'parent_id'

  has_and_belongs_to_many :children,
    :class_name => 'Contact',
    :join_table => 'relations',
    :foreign_key => 'parent_id',
    :association_foreign_key => 'child_id'

  def siblings
    result = self.parents.reduce [] {|children, p| children.concat  p.children}
    result.uniq.reject {|c| c == self}
  end

  def father
    parents.where(:gender => 'm').first
  end

  def mother
    parents.where(:gender => 'f').first
  end
end  

これで、通常の Rails 関連付けができました。だから私たちはできる

alice.parents << bob
alice.save

bob.chidren << cindy
bob.save

alice.parents.create(Contact.create(:name => 'Teresa', :gender => 'f')

などなど。

于 2011-02-14T10:49:41.467 に答える
0
  has_and_belongs_to_many :parents,
    :class_name => 'Contact',
    :join_table => 'relations',
    :foreign_key => 'child_id',
    :association_foreign_key => 'parent_id',
    :delete_sql = 'DELETE FROM relations WHERE child_id = #{id}'

  has_and_belongs_to_many :children,
    :class_name => 'Contact',
    :join_table => 'relations',
    :foreign_key => 'parent_id',
    :association_foreign_key => 'child_id',
    :delete_sql = 'DELETE FROM relations WHERE parent_id = #{id}'

この例を使用しましたが、関係レコードをクリーンアップするために :delete_sql を追加する必要がありました。最初は文字列を二重引用符で囲みましたが、エラーが発生することがわかりました。一重引用符に切り替えるとうまくいきました。

于 2011-03-16T17:21:39.533 に答える