0

I have a User model and a Message one with sender_id and recipient_id.

I would like to retrieve all users I have contacted or have been contacted by.

I tried :

  has_many :message_received_by_user, class_name: 'Message', foreign_key: :recipient_id, select: "DISTINCT ON (messages.sender_id) messages.sender_id, messages.created_at, messages.body" , order: "messages.sender_id, messages.created_at DESC"

But this only give me one side (those who contacted me).

How can I have both sides ?

Many thanks

titibouboul
  • 1,348
  • 3
  • 16
  • 30

3 Answers3

2

Below this section is my original answer; I mis-read the question and thought the OP only wanted a list of users who had RECEIVED messages from the specified user; I didn't notice the part about also users who SENT messages to the specified user as well. Here is my update. I'll leave the original answer below in case it helps.

In this version of the answer, I would alter the join and the where clause so that the query examines either the sender_id or recipient_id column:

User.joins("INNER JOIN messages ON (messages.sender_id = users.id OR messages.recipient_id = users.id)")
    .where("messages.sender_id = ? OR messages.recipient_id = ?", current_user.id, current_user.id).uniq

Apologies for the misunderstanding.


ORIGINAL ANSWER:

In order to get all the users you've contacted, I am thinking the easiest approach is to do this through the normal active record query methods. First, set up the has_many in your User model like so:

In your User model

has_many :messages_sent, class_name: "Message", foreign_key: :sender_id
has_many :messages_received, class_name: "Message", foreign_key: :recipient_id 

And then, you can simply query using the relationship like so:

User.joins(:messages_received).where(:messages=>{sender_id: current_user.id}).uniq

Note: replace current_user with whatever you use to store the target user.

Edit

Someone asked why this is better than using the direct :messages_sent relationship. The advantage this approach has is that it is all done on the database level, where proper indexing can do its job. If you have a very large table, and this user has sent a large number of messages, using the active record has_many relationship will require you to iterate over a large collection, attempting to find unique users.

Just to add a bit more clarification, this query should render an SQL query that looks something like this:

SELECT users.* 
FROM users INNER JOIN messages ON messages.recipient_id = users.id 
WHERE messages.sender_id = 1

In plain English, this is basically saying to the database: "Please give me all the users who RECEIVED a message SENT by user #1".

Paul Richter
  • 10,908
  • 10
  • 52
  • 85
  • Yeah that was me! I deleted the comment because I didn't read the question correctly! Your answer is still missing a way to get users that either sent or received messages to the target user, but i'm not sure that's possible in one query without writing SQL – Slicedpan Nov 13 '13 at 14:24
  • @Slicedpan No problem, it was a fair question. Hmm I'm not quite sure what you mean (re: second part of your comment). The active record query methods are being invoked on the `User` model. Ultimately, this will generate a query that looks like this: `SELECT users.* FROM users INNER JOIN messages ON messages.recipient_id = users.id WHERE messages.sender_id = 1`. As you can see (unless I'm really missing something), you do indeed get back a list of users who received messages from the target user. – Paul Richter Nov 13 '13 at 14:34
  • 2
    I think the query you need is `SELECT users.* FROM users INNER JOIN messages AS r_msg ON r_msg.recipient_id = users.id INNER JOIN messages AS s_msg ON s_msg.sender_id = users.id WHERE s_msg.recipient_id = 1 OR r_msg.recipient_id = 1;` i.e. all users who received a message from user 1 or sent a message to user 1 – Slicedpan Nov 13 '13 at 14:38
  • @Slicedpan Oh nuts, I see what you're saying. I mis-read the question and thought the OP only wanted users who RECEIVED messages, not users who SENT messages to this user. Thank you! I'll update. – Paul Richter Nov 13 '13 at 14:42
  • I think doing `User.joins(:messages_sent, :messages_received)` will automatically create aliases, but I can't find a definitive source as to what the alias will be. – Slicedpan Nov 13 '13 at 14:48
  • @Slicedpan I've never noticed it creating aliases, but not sure. Answer updated; The INNER JOIN also needs to `OR` as well I believe, otherwise having the second INNER JOIN will cut out anything that was matched by the first INNER JOIN, since INNER JOIN intersects rows. – Paul Richter Nov 13 '13 at 15:02
  • to get last message from these users : `Message.select("DISTINCT ON (messages.sender_id) messages.*") .joins("INNER JOIN users ON (messages.sender_id = users.id OR messages.recipient_id = users.id)") .where("messages.sender_id = ? OR messages.recipient_id = ?", current_user.id, current_user.id) .order("messages.sender_id,messages.created_at DESC")` – titibouboul Nov 13 '13 at 15:07
  • Think you have it now, I would suggest abstracting that into a scope: `scope :corresponded_with, ->(u) { #the query }`, so you can do `User.corresponded_with(current_user)` or whatever – Slicedpan Nov 13 '13 at 15:27
  • @titibouboul I agree with Slicedpan, you can put this in a scope and clean things up very nicely. – Paul Richter Nov 13 '13 at 16:54
0

You can do like this:

your migration will look like this:

class CreateMessages < ActiveRecord::Migration
  create_table :messages do |t|
    def up
      t.references :sender
      t.references :recipient
      t.string :message
    end
  end
end

You model:-

class Message < ActiveRecord::Base
  belongs_to :sender, class_name => 'User'
  belongs_to :recipient, class_name => 'User'
end

And these other are the associations on the other side (the user model):

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

This will allow you to get all of a user's sent or received messages like this:

@user.sent_messages
@user.received_messages

Thanks

Rails Guy
  • 3,836
  • 1
  • 20
  • 18
0

refer railscast http://railscasts.com/episodes/163-self-referential-association or How to Implement a Friendship Model in Rails 3 for a Social Networking Application?

I implement it in mongoid as

User model

has_many :friendships, inverse_of: :owner

def friends
  #retrive all the friendships and collect users have sent me a request or being sent a  request.
  fs = Friendship.any_of({:friend_id.in => [self.id]}, {:owner_id.in => [self.id]}).where(state: 'accepted')
  User.in(id: fs.collect{|i| [i.friend_id, i.owner_id]}.flatten - [self.id])
end

Friendship model

 belongs_to :owner, class_name: 'User' #user who sent friend request is the owner
 belongs_to :friend, class_name: "User" #user to whom request is sent.
Community
  • 1
  • 1
Prasad Surase
  • 6,486
  • 6
  • 39
  • 58