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.
Related
I'm trying to list the model instances that do not have the association with another model created yet.
Here is how my models are related:
Ticket.rb:
has_one :purchase
has_one :user, through: :purchase
User.rb:
has_many :purchases
has_many :tickets, through: :purchases
Purchase.rb:
belongs_to :ticket
belongs_to :user
I have an SQL query but have troubles when translating it to rails:
SELECT id FROM tickets
EXCEPT
SELECT ticket_id FROM purchases;
It works great as it returns all ids of the tickets that are not purchased yet.
I've tried this:
Ticket.joins('LEFT JOIN ON tickets.id = purchases.ticket_id').where(purchases: {ticket_id: nil})
but it seems not to be the right direction.
If you're just trying to get the list of Ticket records with no associated purchases, use .includes instead. In my experience a join will fail with no associated records, and this will keep you from needing to write any actual SQL.
Ticket.includes(:purchase).where(purchases: { ticket_id: nil} )
The generated SQL query is a bit more difficult to read as a human, but I've used it several times and not seen any real difference in performance.
Ok so have created 2 models User and Following. Where User has a username attribute and Following has 2 attributes which are User associations: user_id, following_user_id. I have set up these associations in the respective models and all works good.
class User < ActiveRecord::Base
has_many :followings, dependent: :destroy
has_many :followers, :class_name => 'Following', :foreign_key => 'following_user_id', dependent: :destroy
end
class Following < ActiveRecord::Base
belongs_to :user
belongs_to :following_user, :class_name => 'User', :foreign_key => 'following_user_id'
end
Now I need to order the results when doing an ActiveRecord query by the username. I can achieve this easily for the straight-up User association (user_id) with the following code which will return to me a list of Followings ordered by the username of the association belonging to user_id:
Following.where(:user_id => 47).includes(:user).order("users.username ASC")
The problem is I cannot achieve the same result for ordering by the other association (following_user_id). I have added the association to the .includes call but i get an error because active record is looking for the association on a table titled following_users
Following.where(:user_id => 47).includes(:user => :followers).order("following_users.username ASC")
I have tried changing the association name in the .order call to names I set up in the user model as followers, followings but none work, it still is looking for a table with those titles. I have also tried user.username, but this will order based off the other association such as in the first example.
How can I order ActiveRecord results by following_user.username?
That is because there is no following_users table in your SQL query.
You will need to manually join it like so:
Following.
joins("
INNER JOIN users AS following_users ON
following_users.id = followings.following_user_id
").
where(user_id: 47). # use "followings.user_id" if necessary
includes(user: :followers).
order("following_users.username ASC")
To fetch Following rows that don't have a following_user_id, simply use an OUTER JOIN.
Alternatively, you can do this in Ruby rather than SQL, if you can afford the speed and memory cost:
Following.
where(user_id: 47). # use "followings.user_id" if necessary
includes(:following_user, {user: :followers}).
sort_by{ |f| f.following_user.try(:username).to_s }
Just FYI: That try is in case of a missing following_user and the to_s is to ensure that strings are compared for sorting. Otherwise, nil when compared with a String will crash.
In my project, I have a self-referential association.
I have a User model:
class User < ActiveRecord::Base
has_many :relationships, :dependent => :destroy
has_many :peers, :through => :relationships
end
And a Relationship model:
class Relationship < ActiveRecord::Base
belongs_to :user
belongs_to :peer, :class_name => "User"
end
When two users are peers with one another, there are obviously two records in the database.
When one user opts to end a relationship, I'd like this to destroy both records - not just one side of the relationship.
Is there a better way to go about doing this rather than loading the relationship twice in the controller (once for each side of the relationship)?
Couple of ways this can be done
Firstly is an after delete trigger, this is a pretty controversial way of doing things if you believe in the false promise of database agnosticism, however is one that works - in essence, you look at old.peer_id and old.user_id and then do a delete but reversing the roles. If you want to go down this route, you should consult your database manual as how to implement a trigger.
The second way is an after_destroy callback where you do a
after_destroy do |record|
other = Relationship.find_by_user_id_and_peer_id(record.peer_id, record.user_id)
other.destroy if other
end
The other - and probably more drastic measure is to rework the model, so that it has a boolean accepted field wherein both sides of the relationship are modelled by one record in the database, there is a constraint on records where (peer_id, user_id) = (user_id, peer_id). That way you wont have to worry about deleting both sides, nor having duplicate records.
lets say I have the Users table, and the Team table.
In Rails, I know how to link the user_id column in the Team table to the Users table. But what if I have a second column I also want to link to the user's table, such as user_id2 (this essentially creates an order in the team table)?
Is there a solution, or something I don't know about to do what I'm trying? I also don't think the "has_many" is what I'm looking for, because user_id might be the team manager, and user_id2 might be the team captain, i.e. they have different roles affiliated with them, and order is important.
Thanks!
Edit: for my purposes, I also wouldn't need more than these two user relations. (i.e. cases for three wont be relevant)
You may want to look into STI (look for Single Table Inheritance on that page) or Polymorphic Associations. Either would allow you to express your intent a bit more clearly, although there isn't enough information in your question for me to puzzle out which would fit best.
Give those a read and see whether they accomplish what you want.
First here is a way to do this in one direction (Team -> User), but it wouldn't work for the reverse direction, and there's a better option I'll get into afterwards. The first one assumes you have columns named manager_id and captain_id on the teams table.
class User < ActiveRecord::Base
end
class Team < ActiveRecord::Base
belongs_to :manager, :class_name => ::User
belongs_to :captain, :class_name => ::User
end
However, I'd be surprised if a Team only consisted of two Users (the manager and captain) - it's more likely that you'd want a join table to track all of the users' team memberships. That join table (called team_memberships in this example) could have a role column that holds the manager/captain info, as well as any other data you have. This way is a lot more flexible, and offers additional benefits, like being able to track historical team data if team members change over time, which they will.
class Team < ActiveRecord::Base
has_many :team_memberships
has_many :users, :through => :team_memberships
def captain
team_memberships.captain.first
end
def manager
team_memberships.manager.first
end
end
class TeamMembership < ActiveRecord::Base
belongs_to :user
belongs_to :team
# You'll need some database-level UNIQUE INDEXes here to make sure
# you don't get multiple captains / managers per team, and also
# some validations to help with error messages.
named_scope :captain, :conditions => {:role => "captain"}
named_scope :manager, :conditions => {:role => "manager"}
end
class User < ActiveRecord::Base
# depending on the rules about teams, maybe these should be has_many...
has_one :team_membership
has_one :team, :through => :team_memberships
end
Check out http://guides.rubyonrails.org/association_basics.html for more details.
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.