Has_Many :Through Multiple Has_One Relationships

has_many :through multiple has_one relationships?

It's funny how questions that appear simple can have complex answers. In this case, implementing the reflexive parent/child relationship is fairly simple, but adding the father/mother and siblings relationships creates a few twists.

To start, we create tables to hold the parent-child relationships. Relationship has two foreign keys, both pointing at 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

In the Relationship model we point the father and mother back to 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

and define the inverse associations in Contact:

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

Now a relationship can be created:

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

This is not so great, what we really want is to build the relationship in a single call:

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

so we can do:

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

To find the children of a Contact, add a scope to Contact and (for convenience) an instance method:

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")]

Siblings are the tricky part. We can leverage the Contact.children method and manipulate the results:

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

This is non-optimal, since father.children and mother.children will overlap (thus the need for uniq), and could be done more efficiently by working out the necessary SQL (left as an exercise :)), but keeping in mind that self.father.children and self.mother.children won't overlap in the case of half-siblings (same father, different mother), and a Contact might not have a father or a mother.

Here are the complete models and some specs:

# 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

multiple has_many through with the same model rails

I don't see any problems with what you're doing. There are other options, but this approach should work as you want. Have you tried it? I'd do something like this.

class Project < ApplicationRecord
has_many :project_members
has_many :project_managers

has_many :members, through: :project_members, :class_name => User.to_s
has_many :managers, through: :project_manager, :class_name => User.to_s
end

Another approach, since the join tables are similar is to subclass them and add a type column to the join table. Not necessarily better than what you're doing.

You could also create a project_users table (don't separate members and managers) and include a "role" column. A scope on project_user.rb would bring back managers or members.

Personally, I would go with your approach. Managers will likely have different auth and have relationships with other objects. It's simpler to query and less likely to make a mistake.

And, I wouldn't recommend a has_and_belongs_to_many, you're likely to add other columns to the join table and you'll be glad you have the model.

Multiple has_many relationships to same model

How do you get around this problem?

By giving your associations unique names. It's not that you can't unambiguously access them, it's that your second one is destroying the first one.

Instead of calling both posts, use bookmarked_posts for your second association and use source: to call posts

has_many :bookmarked_posts, through: :bookmarks, source: :post

has_one :through and has_many :through in the same association

The code above didn't work... And i found a median solution to implement the schema that i needed.

The final code looks like :

class Image < ActiveRecord :: Base
has_many :pages, :through :imageables
end

class Page < ActiveRecord :: Base
has_many :image, :through :imageables
accepts_nested_attributes :images, allow_destroy => true
end

class Imageable < ActiveRecord :: Base
belongs_to :image
belongs_to :page
validates_uniqueness_of :page_id
end

When i use rails_admin to edit my models, i get just the thing when it comes to add a new image and the validation in Imageable ensure the ditor do not mess around with the specifications...

It is little bit weird as a solution, but believe me, it is well adapted for the context of the app that i am developping...

I am posting it so if somebody had similar concern.

has_many :through with multiple sources

I don't think you can set 2 sources, but if I understood it correctly you could do something like:

has_many :connections_1, through: :answer_connections, source: :answer_1
has_many :connections_2, through: :answer_connections, source: :answer_2

def connections
connections_1.merge(connections_2) # intersection
# or connections_1.or(connections_2) for union
end

rails multiple has_many relationships between the same models

I think you have to go has_many and Single Table Inheritance(STI), as follow.

  1. Make association with restaurant and photo
class Restaurant < ActiveRecord::Base
has_many :photos
end

class Photo < ActiveRecord::Base
belongs_to :restaurant
end

  1. Then you have to use STI in Photo model. The reason is that you have almost all fields are common for lounge_photos and food_photos.

OR

Using scope directly you can differentiate it and achieve your goal.

For more details of use STI you can refer this link.



Related Topics



Leave a reply



Submit