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
Related
I'm setting up a Rails project for a client, and they want Users (a model) to be able to follow each other (as in Twitter). They also want to be able to keep track of when one user started following another.
Since I need to keep track of the creation date, I figured, a has_many X, :through => Y relation would be the way to go, so the Y will keep track of the date it was created.
I have my Follow model set up:
class Follow < ActiveRecord::Base
attr_accessible :actor_id, :observer_id, :follower, :followee
attr_readonly :actor_id, :observer_id, :follower, :followee
belongs_to :follower, :class_name => 'User', :foreign_key => :observer_id
belongs_to :followee, :class_name => 'User', :foreign_key => :actor_id
validates_presence_of :follower, :followee
validates_uniqueness_of :actor_id, :scope => :observer_id
end
The question is how do I set up the relations in the User model?
Ideally I'd like it to have the following:
:follows would be the associated Follow objects where self is the follower (observer_id)
:followed would be the associated Follow objects where self is the followee (actor_id)
:following would be the associated User objects where self is the follower (observer_id)
:followers would be the associated User objects where self is the followee (actor_id)
I'm not sure how to write the has_many :through parts, though? Should I be using :source => X or foreign_key => X? And which key (actor_id or observer_id) should I put in each?
Edit: I'm currently doing this
has_many :follows, :foreign_key => :observer_id
has_many :followed, :class_name => 'Follow', :foreign_key => :actor_id
has_many :following, :class_name => 'User', :through => :follows, :source => :followee, :uniq => true
has_many :followers, :class_name => 'User', :through => :follows, :source => :follower, :uniq => true
and it's mostly working. All of them except :followers work fine, but user.followers is doing something weird. It seems like it's checking whether user is followING someone, and if they are then user.followers returns an array containing only user; if they're not, it returns an empty array.
Does anyone have any advice?
It looks like this is the correct format:
has_many :follows, :foreign_key => :observer_id
has_many :followed, :class_name => 'Follow', :foreign_key => :actor_id
has_many :following, :class_name => 'User', :through => :follows, :source => :followee, :uniq => true
has_many :followers, :class_name => 'User', :through => :followed, :source => :follower, :uniq => true
For Rails newbies, the :uniq => true is important (as I discovered while doing this) because it keeps has_many X, :through => Y relations from returning duplicates (that is, without it, you might get multiple distinct objects, each with their own object ID, all referring to the same record/row).
class Profile
has_many :projects, :through => "teamss"
has_many :teams, :foreign_key => "member_id"
has_many :own_projects, :class_name => "Project", :foreign_key => :profile_id
has_many :own_teams, :through => :own_projects, :source => :teams
end
class Project
belongs_to :profile, :class_name => "Profile"
has_many :teams
has_many :members, :class_name => "Profile", :through => "teams", :foreign_key => "member_id"
end
class Team
belongs_to :member, :class_name => 'Profile'
belongs_to :project
end
I need to create model Evaluation. What I want to do is generate a link in project#view page for each member of the project, including the owner, in order to make an Evaluation The person will click on the link and evaluate the person related to this link. The owner of the Project will evaluate all the members, and all the members will evaluate the owner.
I have defined model Evaluation as following, but I think I miss something:
class Evaluations < ActiveRecord::Base
belongs_to :evaluated, :class_name => 'Profile', :foreign_key => "evaluated_id"
belongs_to: :profile, :class_name => 'Profile', :foreign_key => "profile_id"
end
Remembering that Evaluation table will have tons of attributes, that's why I'm going not going with has_many_and_belongs_to_many.
How can I create this model in order to do what I want and be able to acess all I need via project#show page?
Thanks!
Edited
Changes done:
class Profile
has_many :evaluations, :dependent => :destroy, :foreign_key => :evaluation_id
has_many :evaluators, :through => :evaluations, :foreign_key => :profile_id
end
class Project
has_many :evaluations,:foreign_key => "project_id"
end
class Evaluations < ActiveRecord::Base
belongs_to :evaluated, :class_name => 'Profile', :foreign_key => "evaluated_id"
belongs_to: :profile, :class_name => 'Profile', :foreign_key => "profile_id"
belongs_to: :project, :class_name => 'Project', :foreign_key => "project_id"
end
You have your team and profile (member) associations a little messed up. A team has many profiles (members), and a profile (member) belongs to a team. Beyond that, you may just be over-complicating things. I'm curious as to why you wouldn't just call the Profile Model Member instead and keep things that much simpler?
That being said, you are doing a pretty complex set of associations. I would highly recommend you read the ActiveRecord Associations doc closely to get a firm understanding of how these things work, especially how primary and foreign keys are automagically determined.
I think below will get you what you want.
class Team
has_and_belongs_to_many :members,
:class_name => "Profile"
belongs_to :project
belongs_to :team_leader,
:class_name => "Profile"
end
class Profile
has_and_belongs_to_many :teams
has_many :projects,
:through => :teams
has_many :owned_projects,
:class_name => "Project",
:foreign_key => "owner_id"
has_many :owned_teams,
:class_name => "Team",
:foreign_key => "team_leader_id"
has_many :evaluations
has_many :evaluations_given,
:class_name => "Evaluation",
:foreign_key => "evaluator_id"
end
class Project
has_many :teams
belongs_to :owner,
:class_name => "Profile"
has_many :members,
:through => :teams
has_many :evaluations
end
class Evaluation
belongs_to :profile
belongs_to :evaluator,
:class_name => "Profile",
:foreign_key => "evaluator_id"
belongs_to :project
end
In support of these model definitions would be the following tables/keys:
evaluation
id
profile_id
evaluator_id
project_id
teams
id
project_id
team_leader_id
profiles
id
project
id
owner_id
profiles_teams
profile_id
team_id
With this, you should be able to access collections/objects that you need to get your links right such as:
project.members
project.owner
project.teams
team.project
profile.projects
profile.owned_projects
profile.teams
team.members
team.leader
project.evaluations
profile.evaluations
evaluation.evaluator
profile.evaluations_given
Note, also, that your nested resources are going to be just as complex, if you try to stay with a RESTFUL framework. Read up!
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.
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
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