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.
- Make association with
restaurant
andphoto
class Restaurant < ActiveRecord::Base
has_many :photos
end
class Photo < ActiveRecord::Base
belongs_to :restaurant
end
- Then you have to use STI in
Photo
model. The reason is that you have almost all fields are common forlounge_photos
andfood_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
How to Upsert Multiple Rows with Individual Values in One Statement
Access/SQL - Too Few Parameters
How to Write This SQL Query in Mongodb Syntax
How to Show Blank Record in SQL If Duplicate Rows Exists
Concatenate a Selected Column in a Single Query
SQL Error: "Name Already Used by an Existing Constraint"
Randomly Select a Row with SQL in Access
Query SQL to Subtract Two Fields
Pivot a Table on a Value But Group the Data on One Line by Another
Finding All Children in a Hierarchy SQL
How to Concatenate Values with Same Id in SQL
Conditional SQLite Check Constraint
Using Isnull or Select Coalesce in Linq..
How to Avoid "Table Mutating" Errors