10

I have built the following model to handle user's message exchange:

 create_table "messages", :force => true do |t|
    t.integer  "source_id"
    t.integer  "destination_id"
    t.string   "object"
    t.string   "body"
    t.datetime "created_at"
    t.datetime "updated_at"
  end

These are its associations:

class Message < ActiveRecord::Base
  belongs_to :sender, :class_name=>'User', :foreign_key=>'source_id'
  belongs_to :reciever, :class_name=>'User', :foreign_key=>'destination_id'
end

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

 has_many :sent_messages, :class_name=> 'Message', :foreign_key=>'source_id', :dependent=>:destroy
  has_many :recieved_messages, :class_name=> 'Message', :foreign_key=>'destination_id', :dependent=>:destroy

The model is correct and work properly, in fact from the message I can retrieve who is the sender and who is the receiver and from the user, I can get all the sent and received messages. Unfortunately, It does not handle any situation: What if the receiver or the sender delete the message ? The message is unique so it disappear in both sides (bad thing). How to know if one of the side had already read the message ? Any suggestion ? Do you think I have to replan the model ? Tnx

Joe
  • 1,747
  • 3
  • 17
  • 24

3 Answers3

7

this is a nice problem! I would model that to compare as closely as possible to the e-mail model. So a message always belongs to a single user, and it was either sent or received.

In short:

 create_table "messages", :force => true do |t|
    t.integer  :user_id
    t.string   :subject
    t.string   :body
    t.boolean  :sent
  end

And the model would like:

class Message < ActiveRecord::Base
  belongs_to :user

  scope :sent, where(:sent => true)
  scope :received, where(:sent => false)

end

And in the user:

class User    
  has_many :messages
end

You would then simply be able to query all sent messages by

user.messages.sent

and the received messages

user.messages.received

Sending a message does become a bit more complicated then:

class Message

  def send_message(from, recipients)
    recipients.each do |recipient|
      msg = self.clone
      msg.sent = false
      msg.user_id = recipient
      msg.save
    end
    self.update_attributes :user_id => from.id, :sent => true
  end   
end

or something along those lines: you copy the message and attach it to all recipients, and lastly make the original message the sent message.

This way each user has total control over the message.

Possible improvements:

  • also keep an explicit reference to the sender and receiver(s) in the message, to be able to allow replies and stuff
  • instead of working with a single boolean, maybe allow working with folders?

Hope this helps.

nathanvda
  • 49,707
  • 13
  • 117
  • 139
2

You can add two booleans to mark the message as deleted for both sender and receiver. Then after setting either of them check if the message can be deleted permanently.

Example:

create_table "messages", :force => true do |t|
  t.boolean :sender_deleted
  t.boolean :receiver_deleted
end

And in model:

class Message
  def self.delete_message(id)
    m = Message.find(id)
    m.destroy if m.sender_deleted && m.receiver_deleted
  end
end
Adrian Pacala
  • 1,011
  • 1
  • 8
  • 12
  • Don't you think that it's just a workaround to adjust a bad model ? A lot of doubts are arising to my model right now... for example I have to add 2 more booleans to see if the users (sender and receiver) had already read the message .... – Joe Feb 28 '11 at 11:59
  • @Joe, you need the condition somewhere. You need 4 states (for A and B, for A, for B, for none), so you need at least 3 states and then "deleting". Two booleans is the least you will need, either in this solution or another. – ANeves Feb 28 '11 at 14:29
2

You can nullify on a deleted record with :dependent=>:nullify

has_many :sent_messages, :class_name=> 'Message', :foreign_key=>'source_id', :dependent=>:nullify
has_many :recieved_messages, :class_name=> 'Message', :foreign_key=>'destination_id', :dependent=>:nullify

You'll need to handle when displaying the message that the sender/receiver of the message has been deleted, since the sender_id or destination_id will be null, but the message will stay intact.

Jesse Wolgamott
  • 40,197
  • 4
  • 83
  • 109
  • So when the sender or receiver deletes the message, the other no longer knows who received/sent it? That hardly seems proper. – ANeves Feb 28 '11 at 14:31
  • @ANeves Then you don't really want to delete users. You want to soft-delete them. – Jesse Wolgamott Feb 28 '11 at 14:39
  • @Jesse Wolgamott Don't get me wrong, but I don't want anything. Yes, my point was that deleting the connection to the user removes the message from the user's message list, but it also deleted the user from being a part of the conversation - which honestly ruins the message for the other user. – ANeves Feb 28 '11 at 15:30
  • What about making 2 models ? I'm taliking about message and message_copy, message will be the message with the body text, subject, sender_id and receiver_id and message copy will be the 2 copies delivered to the sender and the receiver. In this way, they will have indipendent copies and perform every actions they want without affecting the other side. Any suggestion about how to implement this tecnique if it is smart ? I found this tutorial that use this method... How do you judge it ? – Joe Mar 02 '11 at 14:48
  • In your message, you could store all the relevant information you need to persist post-deletion. Author Name, picture, whatever. It'll be duplicate data, but it'll be there forever. – Jesse Wolgamott Mar 02 '11 at 15:16
  • I googled for days the message_copy is the only variant I found ... Is your suggest to keep my approach and add boolean or any other values I need directly to the model I thought at the beginning on the thread ? – Joe Mar 02 '11 at 15:34
  • I like the idea of storing a message on each user's. Like Email -- I as a sender have a copy of the message, and you as a receiver have your own message. Store what's needed on each message, along with an ID to go get more info if needed (and available). – Jesse Wolgamott Mar 02 '11 at 15:48
  • It's my same reason ... logically have to be separated, each user can do what he wants with his own copy. This is definitely the best approach, I will follow this using the tutorial http://www.novawave.net/public/rails_messaging_tutorial.html adjusting it to my case. Solved, thank you for the suggestions – Joe Mar 02 '11 at 16:00
  • One thing is worry me about that tutorial ... It uses message (that stores author_id etc etc and message_copy each of them to one recipient). What if the author destroy the sent message (so it delete the message) ? At that point message_copy association is destroyed. So the author can't destroy his sent messages... bad thing – Joe Mar 03 '11 at 10:41