has_one relationship to the same class - ruby-on-rails

I have the following Person class that has a parent that is also another Person. I can't seem to figure out how to get the relations to work.
class Person < ActiveRecord::Base
attr_accessible :mom, :dad
has_one :mom, :class_name => 'Person', :primary_key => "mom_id", :foreign_key => "id"
has_one :dad, :class_name => 'Person', :primary_key => "dad_id", :foreign_key => "id"
end
I have added "mom_id" and "dad_id" as integers to my model with a migration. However, when I use the rails console, I'm not able to access mom or dad attributes after settings the mom_id and dad_id. They still return nil.
Any pointers to what I am doing wrong?

I agree with #Andrew, this should be belongs_to
belongs_to :mom, :class_name => "Person", :foreign_key => "mom_id"
belongs_to :dad, :class_name => "Person", :foreign_key => "dad_id"

Related

How to << a Join Model with attributes to a collection

I have a User model which joins to another User model through a UserRelationship join table. UserRelationships have an attribute (approved/pending/revoked) which must be set, but does not default to any option. There are two associations which reflect this, traveler and delegate, so my models look like this:
User.rb
has_many :traveler_relationships, :class_name => 'UserRelationship', :foreign_key => :delegate_id
has_many :travelers, :class_name => 'User', :through => :traveler_relationships
has_many :delegate_relationships, :class_name => 'UserRelationship', :foreign_key => :user_id
has_many :delegates, :class_name => 'User', :through => :delegate_relationships
has_many :buddy_relationships, class_name: 'UserRelationship', foreign_key: :user_id
has_many :buddies, class_name: 'User', through: :buddy_relationships, source: :delegate
UserRelationship.rb
belongs_to :relationship_status
belongs_to :traveler, :class_name => 'User', :foreign_key => 'user_id'
belongs_to :delegate, :class_name => 'User'
PENDING = 1
CONFIRMED = 3
REVOKED = 5
I'm trying to write up some specs where one user is related to another and the simplest way to write it would be #user1.travelers << #user2 but this fails the database constraint that UserRelationship.relationship_status not be null.
When I try #user1.buddies.create(delegate: #user2, relationship_status: RelationshipStatus::CONFIRMED), it fails saying UnknownAttributeError on delegate. I looked at this question and tried its solution, using attr_acessible, but it didn't change the UnknownAttributeError.
What is the way to create this join record with an attribute set?
I assume UserRelationship has a foreign_key delegate_id for the delegate
class User < ActiveRecord::Base
has_many :traveler_relationships, :class_name => 'UserRelationship', :foreign_key => :user_id
has_many :delegate_relationships, :class_name => 'UserRelationship', :foreign_key => :delegate_id
has_many :travelers, :class_name => 'User', :through => :traveler_relationships
has_many :delegates, :class_name => 'User', :through => :delegate_relationships
end
class UserRelationship < ActiveRecord::Base
belongs_to :traveler, :class_name => 'User', :foreign_key => :user_id
belongs_to :delegate, :class_name => 'User', :foreign_key => :delegate_id
PENDING = 1
CONFIRMED = 3
REVOKED = 5
end
I dont think you can use the shortcut #user1.delegates << #user2 since you need to specify relationship status. Try:
#user1.traveler_relationships.create(delegate: #user2, relationship_status: RelationshipStatus::CONFIRMED)
I've excluded the buddy synonym here. It's complex enough as is. When this works you can look into adding synonyms.
You're on the right track when you try to create from buddies (I'm assuming that's your join model relation). The problem is that your column name is relationship_status and you were passing status to create. Try this:
#user1.buddies.create(delegate: #user2, relationship_status: RelationshipStatus::CONFIRMED

Rails multiple associations between two models

I have Flight, Person, and Glider models in a Rails 3 app. I've defined custom relationships because I need more than one foreign key referencing a Person from the flights table. Associations work but ONE-WAY only.
class Flight < ActiveRecord::Base
belongs_to :pilot, :class_name => "Person"
belongs_to :instructor, :class_name => "Person"
belongs_to :towplane_pilot, :class_name => "Person"
belongs_to :airplane_instructor, :class_name => "Person"
belongs_to :glider
belongs_to :rep_glider, :class_name => "Glider"
belongs_to :departure_airfield, :class_name => "Airfield"
belongs_to :arrival_airfield, :class_name => "Airfield"
end
class Glider < Aircraft
has_many :flights
has_many :replaced_flights, :foreign_key => "rep_glider_id", :class_name => "Flight"
end
class Person < ActiveRecord::Base
has_many :flights, :foreign_key => "pilot_id", :class_name => "Flight"
has_many :instructed_flights, :foreign_key => "instructor_id", :class_name => "Flight"
has_many :towed_flights, :foreign_key => "towplane_pilot_id", :class_name => "Flight"
has_many :instructed_towing_flights, :foreign_key => "airplane_instructor_id", :class_name => "Flight"
end
####What works#####
Flight.first.glider
Flight.first.rep_glider
Flight.first.pilot
Flight.first.instructor
Flight.first.towplane_pilot
Flight.first.airplane_instructor
Glider.first.flights
Glider.first.replaced_flights
####What doesn't work#### ----> NoMEthodError 'match'
Person.first.flights
Person.first.instructed_flights
Person.first.towed_flights.
Person.first.instructed_towing_flights
I'm almost there, but I don't understand how Glider.first.flights does work when Person.first.flights doesn't.
UPDATE: Associations with 'Airfield' works... so I'm clueless as to why it doesn't work with 'Person'
class Airfield < ActiveRecord::Base
has_many :takeoff_flights, :foreign_key => "departure_airfield_id", :class_name => "Flight"
has_many :grounded_flights, :foreign_key => "arrival_airfield_id", :class_name => "Flight"
end
###Works Correctly
Airfield.first.takeoff_flights
Airfield.first.grounded_flights
Flight.first.departure_airfield
Flight.first.arrival_airfield
Do your pilots have types? like a pilot_type column? I just also started reading into these kinds of patterns and luckily it's still a bit fresh(hopefully. please correct me if Im wron rails ninjas! :))
You are in need of the polymorphic pattern as discussed here:
http://asciicasts.com/episodes/154-polymorphic-association
I've been told that the association between these models is set correctly.
I added a new record to the flights table, and now the associations work correctly with this new record and all the previous ones. I'm not really sure how it is working now, but it sure does.

Rails multiple has_one associations

I have multiple models with created_by and modified_by columns. This is what I have for a Deal Model.
class Deal
has_one :user , :foreign_key => 'created_by'
has_one :user , :foreign_key => 'modified_by'
end
class User
belongs_to :created_by , :class_name => 'Deal' , :foreign_key => 'created_by'
belongs_to :modified_by , :class_name => 'Deal' , :foreign_key => 'modified_by'
end
When I create the deal, looks like it is saving correctly. But in the show view when I try to get #deal.created_by.email I get an "undefined method email" error. Can some tell me how to get this working please?
Also since I have multiple models with these two columns, there can be many belongs_to in User model. Is there an elegant solution for this case?
First thing you have to add is the specification of accessible attributes.
In User you would have to add:
attr_accessible :email, :created_by, :modified_by
In Deal:
attr_accessible :created_by, :modified_by
But you should also change the direction of your relation. The foreign_key is always on the belongs_to side.
This is what worked for me:
class Deal < ActiveRecord::Base
belongs_to :created_by, :class_name => "User", :foreign_key => "created_by"
belongs_to :modified_by, :class_name => "User", :foreign_key =>"modified_by"
attr_accessible :created_by, :modified_by, :name
end
class User < ActiveRecord::Base
has_many :created_deals, :class_name => "Deal", :foreign_key => "created_by"
has_many :modified_deals, :class_name => "Deal", :foreign_key => "modified_by"
attr_accessible :created_deals, :modified_deals, :name
end
If you have more models, which look similiar you could probably use polymorphic associations: http://guides.rubyonrails.org/association_basics.html#polymorphic-associations
First of all, from my experience it is generally a bad idea to have associations using the foreign key as name. Especially when writing fixtures it seems rails will get confused between setting the actual value "created_by" or the model in the created_by association. In my models I generally use these associations for the cases you describe:
belongs_to :creator, :class_name => "User", :foreign_key => 'created_by'
belongs_to :modifier, :class_name => "User", :foreign_key => 'modified_by'
You can use association names like 'creating_user' instead if you prefer. If you really want created_by as association name you should have created_by_id or something similar as foreign key, just as long as its not equal to the association name.
Then I am a bit confused by your pasted code. Your choice "Deal has_one User" and "User belongs_to Deal" means that the users table will have the columns created_by and modified_by (foreign keys) containing Deal Ids, basically meaning that users get created by a single deal? However it seems like deals should get created by users and not the other way round. Your example of deal.created_by.email can not work at all with your associations, since deal would not have an association called "created_by", only "user", of which you have two associations with the same name in a single model which can not work at all in the first place.
Fixing your associations similar to what Patrick suggested:
class Deal < ActiveRecord::Base
belongs_to :creator, :class_name => "User", :foreign_key => "created_by"
belongs_to :modifier, :class_name => "User", :foreign_key =>"modified_by"
end
class User < ActiveRecord::Base
has_many :created_deals, :class_name => "Deal", :foreign_key => "created_by"
has_many :modified_deals, :class_name => "Deal", :foreign_key => "modified_by"
end

rails way user to user messaging, do I need a join table?

Quick question (I think). I have users, and I would like to allow them to send messages to one another. My question is, should I have a user table, a message table, and a users to messages join table, or should I just store to_user_id and from_user_id in the messages table. If the latter, what would the association look like for that? Can you even reference a 'local' key for an association?
You can do that with a couple of simple has_many associations. Since it's self-referential, you'll need to override some of the Rails magic to make it work.
class User < ActiveRecord::Base
has_many :sent_messages, :class_name => 'Message', :foreign_key => 'sender_id'
has_many :received_messages, :class_name => 'Message', :foreign_key => 'recipient_id'
end
class Message < ActiveRecord::Base
belongs_to :sender, :class_name => 'User'
belongs_to :recipient, :class_name => 'User'
end
Rails doesn't have a cleaner way to doing self-referential associations that I know of.
I think the latter sounds fine. This is just off the top of my head but I know AR's associations have options like this...
class Message < ActiveRecord::Base
belongs_to :sender, :class_name => :user, :foreign_key => :from_user_id
belongs_to :recipient, :class_name => :user, :foreign_key => :to_user_id
#...
end
class User < ActiveRecord::Base
has_many :received_messages, :class_name => :message, :foreign_key => :to_user_id
has_many :sent_messages, :class_name => :message, :foreign_key => :from_user_id
end

Rails Model has_many with multiple foreign_keys

Relatively new to rails and trying to model a very simple family "tree" with a single Person model that has a name, gender, father_id and mother_id (2 parents). Below is basically what I want to do, but obviously I can't repeat the :children in a has_many (the first gets overwritten).
class Person < ActiveRecord::Base
belongs_to :father, :class_name => 'Person'
belongs_to :mother, :class_name => 'Person'
has_many :children, :class_name => 'Person', :foreign_key => 'mother_id'
has_many :children, :class_name => 'Person', :foreign_key => 'father_id'
end
Is there a simple way to use has_many with 2 foreign keys, or maybe change the foreign key based on the object's gender? Or is there another/better way altogether?
Thanks!
Found a simple answer on IRC that seems to work (thanks to Radar):
class Person < ActiveRecord::Base
belongs_to :father, :class_name => 'Person'
belongs_to :mother, :class_name => 'Person'
has_many :children_of_father, :class_name => 'Person', :foreign_key => 'father_id'
has_many :children_of_mother, :class_name => 'Person', :foreign_key => 'mother_id'
def children
children_of_mother + children_of_father
end
end
To improve on Kenzie's answer, you can achieve an ActiveRecord Relation by defining Person#children like:
def children
children_of_mother.merge(children_of_father)
end
see this answer for more details
Used named_scopes over the Person model
do this:
class Person < ActiveRecord::Base
def children
Person.with_parent(id)
end
named_scope :with_parent, lambda{ |pid|
{ :conditions=>["father_id = ? or mother_id=?", pid, pid]}
}
end
I believe you can achieve the relationships you want using :has_one.
class Person < ActiveRecord::Base
has_one :father, :class_name => 'Person', :foreign_key => 'father_id'
has_one :mother, :class_name => 'Person', :foreign_key => 'mother_id'
has_many :children, :class_name => 'Person'
end
I'll confirm and edit this answer after work ; )
My answer to Associations and (multiple) foreign keys in rails (3.2) : how to describe them in the model, and write up migrations is just for you!
As for your code,here are my modifications
class Person < ActiveRecord::Base
belongs_to :father, :class_name => 'Person'
belongs_to :mother, :class_name => 'Person'
has_many :children, ->(person) { unscope(where: :person_id).where("father_id = ? OR mother_id = ?", person.id, person.id) }, class_name: 'Person'
end
So any questions?
I prefer to use scopes for this issue. Like this:
class Person < ActiveRecord::Base
belongs_to :father, :class_name => 'Person'
belongs_to :mother, :class_name => 'Person'
has_many :children_of_father, :class_name => 'Person', :foreign_key => 'father_id'
has_many :children_of_mother, :class_name => 'Person', :foreign_key => 'mother_id'
scope :children_for, lambda {|father_id, mother_id| where('father_id = ? AND mother_id = ?', father_id, mother_id) }
end
This trick make it easy to get children without use instances:
Person.children_for father_id, mother_id
Not a solution to the general question as stated ("has_many with multiple foreign keys"), but, given a person can either be a mother or a father, but not both, I would add a gender column and go with
has_many :children_of_father, :class_name => 'Person', :foreign_key => 'father_id'
has_many :children_of_mother, :class_name => 'Person', :foreign_key => 'mother_id'
def children
gender == "male" ? children_of_father : children_of_mother
end
I was looking for the same feature, if you don't want to return an array but a ActiveRecord::AssociationRelation, you can use << instead of +.
(See the ActiveRecord documentation)
class Person < ActiveRecord::Base
belongs_to :father, :class_name => 'Person'
belongs_to :mother, :class_name => 'Person'
has_many :children_of_father, :class_name => 'Person', :foreign_key => 'father_id'
has_many :children_of_mother, :class_name => 'Person', :foreign_key => 'mother_id'
def children
children_of_mother << children_of_father
end
end

Resources