Specifying the foreign key in a has_many :through relationship - ruby-on-rails

I have the following three models: User, Project, and Assignment.
A User has_many Projects through an assignment. However, Assignment actually has two foreign keys that relate to a User: user_id (representing the user who was assigned the project) and completer_id (representing the user who completed the project).
Often, user_id and completer_id will be the same (if the user who was assigned the project completes it). However, if another user completes it, user_id and completer_id will be different.
In my User model, I have the following:
class User < ActiveRecord::Base
has_many :assignments
has_many :incomplete_assignments, :class_name => 'Assignment',
:conditions => 'completer_id IS NULL'
has_many :completed_assignments, :class_name => 'Assignment',
:foreign_key => 'completer_id'
# this is the important one
has_many :incomplete_projects,
:through => :assignments,
:source => :project,
:conditions => 'completer_id IS NULL'
end
I would like to make another association, called :completed_projects, which uses completer_id as the foreign key for the User in the :through model, rather than :user_id. Is it possible to do this?
And, as an aside, I am aware of the :foreign_key option. However, this option is ignored when using :through, so I'd like to know if there's a way to do this without it.
Finally, I should mention that I'm open to other designs, if it can't be done this way and someone can think of a better way.

You can always use SQL for the selection if ActiveRecord can't easily express the relationship out of the box:
has_many :completed_projects,
:class_name => "Project",
:finder_sql => 'SELECT p.* FROM projects p ' +
'INNER JOIN assignments a ON p.id=a.project_id ' +
'INNER JOIN users u on u.id=a.completer_id ' +
'WHERE u.id=#{id}'

Do you have other meta data in your assignments table?
If not I would just use habtm and add the completer_id to the projects table instead, it makes sense to live there anyway.
If you need/want to use has_many :through you could look at using named_scopes (Rails version permitting) so you could say user.projects.completed and user.projects.incomplete

Related

Eager Loading in a Rails 3 app

I am trying to reduce the number of database queries in my Rails 3 app.
User model:
has_many :agreements
Agreement model:
belongs_to :user
The agreements table has two user id fields... payer_id and payee_id. Is it possible to make something like the following work:
user_payer_agreements = current_user.agreements
user_payee_agreements = current_user.agreements
I could use user_id for one side of the transaction but I need to get both sides. Is it possible to specify payer_id or payee_id instead of user_id in the process of creating an association? If not, do i need use a join or a sql statement. Any help is appreciated.
You can do this:
has_many :payer_agreements, :class_name => 'Agreement', :foreign_key => :payer_id
has_many :payee_agreements, :class_name => 'Agreement', :foreign_key => :payee_id
With this you can do:
current_user.payer_agreements
current_user.payee_agreements
Is this what you are looking for?

ActiveRecord Association without Foreign Key

I am trying to build a relationship model between users. A user can either initiate a relation, or receive a relation from another user. Therefore, the relations table in the db has the foreign keys "initiator_id" and "recipient_id".
Now, I can figure what relations the user initiated or received using the following associations:
has_many :initiated_relations, :foreign_key => :initiator_id, :class_name => 'Relation', :dependent => :destroy
has_many :received_relations, :foreign_key => :recipient_id, :class_name => 'Relation', :dependent => :destroy
What I am trying to do, is build an association that will fetch me all relations that belong to a user (either initiated or received). Trying the following does not work, and complains about the lack of "user_id" field:
has_many :relations, :conditions => 'recipient_id = #{id} or initiator_id = #{id}'
How can I create an association that is solely based on the conditions field, without looking for the default foreign_key? Or is there perhaps a completely different approach to solving this?
From your comments to #neutrino's answer I understand, that you only need this "relation" for read only operations. If you're on Rails 3 you can utilize the fact, that it uses lazy fetching. The where() method returns ActiveRecord::Relation object, which you can later modify. So you can define a method like this:
def User < ActiveRecord::Base
def all_relations
Relation.where("initiator_id => ? OR recipient_id = ?", id, id)
end
end
And then you can do:
User.all_relations.where(:confirmed => true).all
Well, I can think of using finder_sql for that:
has_many :relations, :finder_sql => 'select * from relations right outer join users
on relations.recipient_id = #{id} or relations.initiator_id = #{id}'
Apart from that, you can just write a method that will return a united array of the two relations associations', but you will lose the advantage of an association interface (phew).
Perhaps someone will come up with a better solution.

Ruby on Rails: Is it possible to :include the other leg of a circular join table?

I'm working on an application that models friendships between users.
class User
has_many :friendships
has_many :friends,
:through => :friendships,
:conditions => "status = #{Friendship::FULL}"
end
class Friendship
belongs_to :user
belongs_to :friend, :class_name => "User", :foreign_key => "friend_id"
end
When two users become friends, two instances of the friendship class are created, one for each direction of the friendship. This is necessary because different permissions can be set in each direction, and also for ease of lookups so we always search by the user_id without creating a second index and needing to double up searches.
Is it possible, using either find or a named scope to pull up a friend along with the counter-friendship? I'd like to do this because I'm allowing users to edit friendship permissions which are stored in their own leg of the friendship, and thus when I'm displaying a friend's info to the user I need to check the opposite leg to see how permissions are set. (As an aside, does this seem like a logical way to do things? Either you store into the opposite side, or you have to consult the opposite side before display, both kind of unpleasant.)
I've managed to whip up a batch of SQL that does the job:
def self.secure_friends(user_id)
User.find_by_sql("SELECT u.*, cf.permissions
FROM users u
INNER JOIN friendships f ON u.id = f.friend_id
INNER JOIN friendships cf ON u.id = cf.user_id AND cf.friend_id = #{user_id}
WHERE ((f.user_id = #{user_id}) AND (f.status = #{Friendship::FULL}))")
end
The function returns all of a user's friends names and ids, along with the permissions. However, this really doesn't seem ideal, and means I have to access the permissions by calling friend.permissions, where permissions isn't actually a member of friend but is just merged in as an attribute by find_by_sql. All in all, I'd really rather be doing this with a named_scope or a find call, but unfortunately, every attempt I've made to use that approach has resulted in errors as Rails chokes on having the same Friendship join table getting joined to the query twice.
I'm not sure how much you want to do, and how much you'd allow a plugin to do -- but I've used this has_many_friends plugin with great success: has_many_friends plugin at Github
You can look at the source and be inspired... Just need a Friendship model that has things like:
belongs_to :friendshipped_by_me, :foreign_key => "user_id", :class_name => "User"
belongs_to :friendshipped_for_me, :foreign_key => "friend_id", :class_name => "User"
and then later, in your user class, have:
has_many :friends_by_me,
:through => :friendships_by_me,
:source => :friendshipped_for_me
Or, just
/app/models/user
has_many_friends
Later:
#current_user.friends.each{|friend| friend.annoy!}

Ordering of has_and_belongs_to_many associations

In my rails app I have two models that are related by has_and_belongs_to_many. This means there is a join table.
Imagine the scenario where I am adding users to a game. If I want to add a user, I do:
#game.users << #user
Supposing that I want to know in what order I added these users. I can do this:
#game.users.each do....
My questions are:
Is the ordering if this list guaranteed to read the same way each time?
If that's the case, What's a clean way to reorder the users in the game?
To expand on the bottom of danivo's answer:
If you want to order by the time they are added to this list then I recommend that instead of using a has_and_belongs_to_many you actually use a has_many :through:
game.rb
has_many :played_games
has_many :users, :through => :played_games, :order => "played_games.created_at ASC"
user.rb
has_many :played_games
has_many :games, :through => :played_games
played_game.rb
belongs_to :game
belongs_to :user
Of course, changes pending on the names...
In the played_games table if you have a column called created_at the second has_many in games will order by this field and return the users in the order of which they were added to the game.
I'm not certain but I would say that the order is as guaranteed to be the same as your database guarantees the order of the result set without an order by clause.
You can add a :order => "last_name, first_name asc" to the :has_and_belongs_to_many relationship statement. This will set a default behavior for the order of the items that comes back.
You can also do #game.users.find(:all, :order => "last_name, first_name asc") or create named scopes for common ordering that you will need. Check out the api for details
If knowing the order you added them is really important, you probably want to switch to a has_many :through relationship so that the join table has an entity of its own. Then you will have a created_on and updated_on timestamp for that relationship managed by rails.

Set-up Many-to-Many in Rails with destroy allowed for only one of the many

I am trying to model a very simple system, but the Rails way of doing this is escaping me. I'd like to have two entities, a User and a Bet. A bet has two users and a user has many bets. I'd like the creating User of the bet to be able to mark it as resolved (do any form of update/delete on it).
My initial thinking was to have a User act two ways, as a better or a responder. many_to_many associations in rails may be the right option, with an extra column on the bet table to note who owns the model. But, then you are repeating one of the User's id's twice.
Thanks in advance!
I think this is what you want:
class User
has_many( :bets, :foreign_key => 'bettor_id', :class_name => 'Bet' )
has_many( :responses, :foreign_key => 'responder_id', :class_name => 'Bet' )
end
class Bet
belongs_to( :bettor, :class_name => 'User' )
belongs_to( :responder, :class_name => 'User' )
end

Resources