I have a relationship were a person can have 1 item, and this item is either a A || B || C. Rails supports the belongs_to polymorphic, but I don't see how can a Model have one of several models as opposed to belong_to one of several models. Can anyone tell me how to map this relationship ?
Is this a one-to-one, or one-to-many relationship?
Which class should has the foreign key? Do your A, B, and C classes have a foreign key to person, or do you want person to have a foreign key to one of the other 3 models?
Yes I'd like person to have the foreign key to have a foreign key to one of the other 3 models.
dpal wrote in post #958489:
I have a relationship were a person can have 1 item, and this item is either a A || B || C. Rails supports the belongs_to polymorphic, but I don't see how can a Model have one of several models as opposed to belong_to one of several models. Can anyone tell me how to map this relationship ?
You want Person belongs_to Item. Remember, belongs_to is used for the table that contains the foreign key field.
Best,
Yup, then like Marnen said, Person should belong to Item:
belongs_to :item, :polymorphic => true
In your migration, you can use:
t.references :item, :polymorphic => true
Which will create item_type and item_id columns.
Then each class A, B, and C can have one person:
has_one :person, :as => :item
Awesome thank you so much, I had come to that conclusion although it didn't seem natural. This is what my spec looks like, I think its correct:
Database Associations
What we want:
There are users, construction companies, real state companies and natural persons. A user controls one of the 3 entities(construction companie, real state company or natural person). And each of this entities has listings, and in the case of construction company also projects. The relationships are as followed:
1. user has_one :construction_company or :real_state_company or :natural_person
2. construction_company has_many ---> :projects which has_many ---> : listings
3. real_state company has_many ----> :listing
4. natural_person has_one----> :listings
How this looks in code:
We need to use polymorphic associations to be able to express this relationships, because for example listings belongs to all 3 type of entities, thus listings has to be polimorphic.
:listings belongs to-> projects, real_state_company, natural_person
We start by looking at what this 3 entities have in common, mainly that they are the sellers, so we will add to the databes a foreign key that lists the seller identification(seller_id) and which type of seller he is(construction company or real state company etc).
In rails this is accomplished by:
$rails generate model Listing …. seller_id:integer seller_type:string
This will ad a seller_id and seller_type to the migrations. Rails has a way of simplifying this by replacing the 2 keys with:
t.references :seller, :polymorphic => true which will create both keys for use.
After, inside the listing model then we can say that the listing belongs to a seller instead of explicitly saying which seller(company....) this is expressed using the following code
class Listing < ActiveRecord::Base
belongs_to :seller, :polymorphic => true
end
Then we can add the relationship to each one of the entities using :as
class Real_State_Company < ActiveRecord::Base
has_many :listings, :as => :seller
end
Until here we have taken care of the relationship were a listing belongs to any 3 entities(projects, real_state_company and natural_person). Now we need to look at the case were the construction company, real state company or natural user belongs to a User. Since this is the reverse, we start looking what does the user has, he has_one of the 3 things(company, real state company or natural_person), we'll call this entities, thus in rails we can say:
class User < ActiveRecord::Base
has_one :entity, :polymorphic => :true
end
And then each of the entities will have:
class Real_State_Company < ActiveRecord::Base
belongs_to :user, :as => :entity
end
We need to modify the user model with entity_id and entity_type, or:
t.references :entity, :polymorphic => true
Unfortunatelly has_one polymorphic is not supported, therefore we need to think in reverse.
class User < ActiveRecord::Base
belongs_to :ownable, :polymorphic => :true
end
And then each of the entities will have:
class Real_State_Company < ActiveRecord::Base
has_one :user, :as => :ownable
end
We need to modify the user model with entity_id and entity_type, or:
t.references :entity, :polymorphic => true
Then to get if the user owns a company or natural person do @user.ownable, similary to see which user owns a company @company.user.
That's because if the "belongs_to" is on the company models, it's no longer a polymorphic relationship. At that point it's just a regular one-to-one relationship, like the following example. Each of the companies has a foreign key to Person.
class RealStateCompany belongs_to :user end
class ConstructionCompany belongs_to :user end
class NaturalPerson belongs_to :user end
class User has_one :real_state_company has_one :construction_company has_one :natural_person end
This works. Reversing the relationship also works. Just depends on how you want to design it and where you want the foreign keys.
Now there is also the fact that user has to know what they own. If I go with the
belongs_to :ownable, polymorphic => true.
From user then user.ownable will either return company, real_state_company or natural user. I would have to do user.ownable_type to see which one and use a string comparisson.
If I go the other route:
has_one :real_state_company has_one :construction_company has_one :natural_person
I can say,
if(user.construction_company != nil) { company = user.company redirect_to company } else if (user.real_state_company) { .... } etc.
Which one is cleaner, better design ?
Thanks -dan
dpal wrote in post #958568:
Now there is also the fact that user has to know what they own. If I go with the
belongs_to :ownable, polymorphic => true.
From user then user.ownable will either return company, real_state_company or natural user.
(I think you mean "real estate", not "real state".)
I would have to do user.ownable_type to see which one and use a string comparisson.
Or user.ownable.class and use a class comparison.
Or better yet, do neither and use object polymorphism to automatically dispatch. Remember, one of the nice things about OO development is that objects know how to respond to messages -- you often don't need conditionals.
If I go the other route:
has_one :real_state_company has_one :construction_company has_one :natural_person
I can say,
if(user.construction_company != nil) { company = user.company redirect_to company } else if (user.real_state_company) { .... }
This isn't Java. Lose the braces, parens, and "!= nil" and use "elsif" instead of "else if".
etc.
Which one is cleaner, better design ?
The polymorphic design. It allows you to actually harness the object model to your advantage.
Thanks -dan
Best,
Awesome help, thanks everyone. Just finished implementing the polymorphic way, and the code looks really clean. Thanks -dan