Reputation: 730
I am trying to figure out how to use ActiveRecord relationships to relate a model that can contain multiple instances of another model within one row. For example, having has_many :dogs in one model, and belongs_to :dog in the other means that in the one model, "dog_id" will refer to an instance of :dog. However, I want to be able to have multiple instances of :dog within my related (has_many) model, such as dog_id1, dog_id2, dog_id3, etc. See the code below to understand what I mean. How can I do this?
I have the following models:
--tp.rb
class Tp < ActiveRecord::Base
has_many :dogs
has_many :cats
has_many :stars
attr_accessible :dog_id1, :dog_id2, :dog_id3, :dog_id4, :cat_id1, :cat_id2, :cat_id3, :cat_id4, :star_id, :tp_id
end
--dog.rb
class Dog < ActiveRecord::
belongs_to :tp
attr_accessible :dog_id, :dog_name
end
--cat.rb
class Cat < ActiveRecord::Base
belongs_to :tp
attr_accessible :cat_id, :cat_name
end
--star.rb
class Star < ActiveRecord::Base
belongs_to :tp
attr_accessible :patient_id
end
Upvotes: 0
Views: 264
Reputation: 9148
I think you have your belongs_to
/has_many
a little backwards.
For your example,
class Tp < ActiveRecord::Base
has_many :dogs
has_many :cats
has_many :stars
end
Tp
doesn't have any dog_id
, cat_id
, or star_id
in its database row. When you set belongs_to
in the other models,
class Dog < ActiveRecord::Base
belongs_to :tp
end
class Cat < ActiveRecord::Base
belongs_to :tp
end
class Star < ActiveRecord::Base
belongs_to :tp
end
You should add a tp_id
to each model (dogs
table, cats
table, and stars
table).
Then, calling
tp = Tp.find(123)
Finds Tp
with id
equal to 123
SELECT * FROM tps WHERE id = 123;
And calling
cats = tp.cats
dogs = tp.dogs
stars = tp.stars
Finds all Cat
, Dog
, and Star
instances with tp_id
equal to 123
SELECT * FROM cats WHERE tp_id = 123;
SELECT * FROM dogs WHERE tp_id = 123;
SELECT * FROM stars WHERE tp_id = 123;
If you need your Cat
instances to belong to many Tp
instances, and Tp
instances to have many Cat
instances, then you should look at Rails's has_and_belongs_to_many or has_many :through.
A has_and_belongs_to
relationship would require new tables cats_tps
, dogs_tps
, and stars_tps
. These tables would have a schema of
cats_tps
cat_id
tp_id
dogs_tps
dog_id
tp_id
stars_tps
star_id
tp_id
Then in your models
class Tp < ActiveRecord::Base
has_and_belongs_to_many :dogs
has_and_belongs_to_many :cats
has_and_belongs_to_many :stars
end
class Dog < ActiveRecord::Base
has_and_belongs_to_many :tps
end
class Cat < ActiveRecord::Base
has_and_belongs_to_many :tps
end
class Star < ActiveRecord::Base
has_and_belongs_to_many :tps
end
Now, running
tp = Tp.find(123)
cats = tp.cats
Generates the SQL
SELECT "cats".* FROM "cats" INNER JOIN "cats_tps" ON "cats"."id" = "cats_tps"."cat_id" WHERE "cats_tps"."tp_id" = 123;
Which is essentialy the query (get me a list of all the cat_ids that belong to Tp 123) and then (get me all the cats that match these cat ids).
Generating the cats_tps
join table can be done with a migration like
class CreateCatsTps < ActiveRecord::Migration
def change
create_table :cats_tps, :id => false do |t|
t.belongs_to :cat
t.belongs_to :tp
end
end
end
This works great for simple joins, but you may want to look into using has_many :through
. This is because the cats_tps
table holds no information about when or why this Cat
belongs to a Tp
or this Tp
belongs to the Cat
. Likewise, if you add Bird
, Horse
, Frog
, and Snake
models, you will have to create birds_tps
, horses_tps
, frogs_tps
, and snakes_tps
tables. Yuck.
To create a has_many :through
relationship, you create a new model that makes sense semantically that links a Tp
to a Cat
. For instance, let's say that a Tp
walks cats. You could create an Walk
model that links a Cat
to a Tp
.
class Walk < ActiveRecord::Base
belongs_to :cat
belongs_to :tp
attr_accessible :price, :duration, :interval # these attributes describe the Walk relationship
end
class Cat < ActiveRecord::Base
has_many :walks
has_many :tps, :through => :walks
end
class Tp < ActiveRecord::Base
has_many :walks
has_many :cats, :through => :walks
end
Now, the relationship is similar to a has_and_belongs_to_many
, but you can include metadata about the walking relationship. Additionally, say that a Tp
also walks dogs. You could convert the belongs_to :cat
into a polymorphic belongs_to :animal
relationship so that a Tp
can walk a cat, dog, mouse, rabbit, horse, ... you name it.
class Walk < ActiveRecord::Base
belongs_to :animal, :polymorphic => true
belongs_to :tp
attr_accessible :price, :duration, :interval # these attributes describe the Walk relationship
end
class Cat < ActiveRecord::Base
has_many :walks, :as => :animal
has_many :tps, :through => :walks
end
class Dog < ActiveRecord::Base
has_many :walks, :as => :animal
has_many :tps, :through => :walks
end
class Tp < ActiveRecord::Base
has_many :walks
has_many :cats, :through => :walks, :source => :animal, :source_type => 'Cat'
has_many :dogs, :through => :walks, :source => :animal, :source_type => 'Dog'
end
This relationship is created with a migration like
class CreateWalks < ActiveRecord::Migration
def change
create_table :walks do |t|
t.belongs_to :animal, :polymorphic => true
t.belongs_to :tp
end
end
end
Upvotes: 1
Reputation: 251
remove the :dog_id(s)
from the TP model and put :tp_id
in your dog model. This will allow you to have a one-to-many relationship from TP to Dog
Upvotes: 0
Reputation: 1132
This is wrong
attr_accessible :dog_id1, :dog_id2, :dog_id3, :dog_id4, :cat_id1, :cat_id2, :cat_id3, :cat_id4, :star_id, :tp_id
Your Dog model has a tp_id
and the has_many
and belongs_to
allow you to do tp_instance.dogs
to get an array of Dog models that have a matching tp_id
Upvotes: 0