Last active
December 27, 2015 14:28
-
-
Save bvsatyaram/7340248 to your computer and use it in GitHub Desktop.
Messaging System
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# == Schema Information | |
# | |
# Table name: conversation_messages | |
# | |
# id :integer not null, primary key | |
# body :text | |
# thread_id :integer | |
# author_id :integer | |
# created_at :datetime not null | |
# updated_at :datetime not null | |
# | |
class Conversation::Message < ActiveRecord::Base | |
belongs_to :thread, :class_name => Conversation::Thread.name, :foreign_key => 'thread_id' | |
belongs_to :author, :class_name => User.name | |
has_many :subscriptions, :class_name => Conversation::MessageSubscription.name, :foreign_key => 'message_id', :dependent => :destroy | |
#TODO: Remove redundancy | |
# This is required to have +through+ has_many association Thread#message_subscriptions, though it's presence is redundant here in this model | |
has_many :message_subscriptions, :class_name => Conversation::MessageSubscription.name, :foreign_key => 'message_id', :dependent => :destroy | |
attr_accessor :recipients | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# == Schema Information | |
# | |
# Table name: conversation_message_subscriptions | |
# | |
# id :integer not null, primary key | |
# user_id :integer | |
# message_id :integer | |
# archived :boolean default(FALSE) | |
# unread :boolean default(FALSE) | |
# created_at :datetime not null | |
# updated_at :datetime not null | |
# | |
class Conversation::MessageSubscription < ActiveRecord::Base | |
belongs_to :user | |
belongs_to :message, :class_name => Conversation::Message.name, :foreign_key => 'message_id' | |
scope :for_user, lambda { |usr| where(:user_id => usr.id) } | |
scope :archived, lambda { |is_archived| where(:archived => is_archived) } | |
scope :unread, where(:unread => true) | |
def mark_read! | |
self.update_attribute(:unread, false) | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Conversation::MessageSubscriptionObserver < ActiveRecord::Observer | |
def after_create(message_subscription) | |
if message_subscription.unread? | |
message_subscription.message.thread.subscriptions.for_user(message_subscription.user).first.increment!(:unread_count) | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Conversation::MessagesController < ApplicationController | |
before_filter :load_new_message, :only => [:new, :create] | |
def new | |
@user_data = auto_suggest_user_data | |
if params[:user_id] | |
prefill_user = User.find(params[:user_id]) | |
@prefill_user_data = [{:value => prefill_user.id.to_s, :name => prefill_user.name_for_display(current_user)}].to_json | |
else | |
@prefill_user_data = [].to_json | |
end | |
end | |
def create | |
@stand_alone_creation = params[:conversation_message][:thread_id].nil? # Message is sent from profile page (not messages page), without reference to any thread | |
@message.author = current_user | |
@save_success = true | |
body = params[:conversation_message][:body].strip | |
if body.blank? | |
@save_success = false | |
return | |
else | |
@message.body = body | |
end | |
if @stand_alone_creation | |
recipients = fetch_recipients_from_string | |
@thread = fetch_or_create_thread_for_recipients(recipients) | |
return unless @save_success | |
else | |
@thread = Conversation::Thread.find(params[:conversation_message][:thread_id]) | |
recipients = @thread.subscriptions.collect(&:user) | |
end | |
recipients.each do |user| | |
ms = @message.subscriptions.new | |
ms.user = user | |
ms.unread = (user != current_user) | |
end | |
@thread.messages << @message | |
@save_success = @thread.save | |
if @save_success && @stand_alone_creation | |
@thread_messages = {@thread => @message} | |
end | |
end | |
private | |
def load_new_message | |
@message = Conversation::Message.new | |
end | |
def fetch_recipients_from_string | |
#TODO: DRY the code. The same code is used in Gym::MembershipsController | |
recipients = params[:as_values_recipients].strip | |
if recipients.blank? | |
@save_success = false | |
return | |
else | |
recipients = recipients.split(',').reject(&:blank?) | |
recipients = User.where(:id => recipients) | |
recipients << current_user | |
return recipients.flatten.uniq | |
end | |
end | |
def fetch_or_create_thread_for_recipients(recipients) | |
return if recipients.nil? | |
thread = recipients.collect{|usr| usr.threads}.reduce(:&).select{|t| t.subscriptions.size == recipients.size}.first | |
if thread.nil? | |
thread = Conversation::Thread.new | |
recipients.each do |usr| | |
thread.subscriptions << thread.subscriptions.build(:user => usr) | |
end | |
@save_success = thread.save | |
end | |
return thread | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This gist tries to give an example of how to build a simple, yet robust, messaging system. | |
We assume that the users are handed by `User` model. | |
The following models are proposed: | |
Conversation::Thread | |
Conversation::ThreadSubscription | |
Conversation::Message | |
Conversation::MessageSubscription | |
* Licensed under the MIT license: | |
* http://www.opensource.org/licenses/MIT |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# == Schema Information | |
# | |
# Table name: conversation_threads | |
# | |
# id :integer not null, primary key | |
# created_at :datetime not null | |
# updated_at :datetime not null | |
# | |
class Conversation::Thread < ActiveRecord::Base | |
has_many :messages, :class_name => Conversation::Message.name, :foreign_key => 'thread_id', :order => 'id DESC', :dependent => :destroy | |
has_many :message_subscriptions, :class_name => Conversation::MessageSubscription, :through => :messages, :order => 'id DESC', :dependent => :destroy | |
has_many :subscriptions, :class_name => Conversation::ThreadSubscription, :foreign_key => 'thread_id', :dependent => :destroy | |
def participants(skip_user = nil) | |
participants = self.subscriptions.collect(&:user) | |
participants = participants - [skip_user] unless participants.size == 1 | |
return participants | |
end | |
def random_participant(skip_user = nil) | |
participants(skip_user).sample | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# == Schema Information | |
# | |
# Table name: conversation_thread_subscriptions | |
# | |
# id :integer not null, primary key | |
# user_id :integer | |
# thread_id :integer | |
# unread_count :integer default(0) | |
# created_at :datetime not null | |
# updated_at :datetime not null | |
# | |
class Conversation::ThreadSubscription < ActiveRecord::Base | |
belongs_to :user | |
belongs_to :thread, :class_name => Conversation::Thread.name, :foreign_key => 'thread_id' | |
scope :for_user, lambda { |user| where(:user_id => user.id) } | |
def mark_read! | |
return if self.unread_count == 0 | |
self.update_attribute(:unread_count, 0) | |
self.thread.message_subscriptions.for_user(self.user).unread.each(&:mark_read!) | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Conversation::ThreadsController < ApplicationController | |
after_filter :mark_thread_as_read, :only => [:show] | |
#TODO: authorize_resource | |
def index | |
@archived = false | |
@threads = current_user.threads | |
@thread_messages = {} | |
@threads.each do |thread| | |
@thread_messages[thread] = thread.message_subscriptions.for_user(current_user).archived(@archived).first.try(&:message) | |
end | |
@threads = @threads.select{|thread| @thread_messages[thread].present?} | |
@threads = @threads.sort_by{|thread| @thread_messages[thread].id}.reverse | |
end | |
def show | |
@thread = Conversation::Thread.find(params[:id]) | |
@page_title = @thread.participants.collect(&:name).join(', ') | |
@archived = false | |
message_ids = @thread.message_subscriptions.for_user(current_user).archived(@archived).collect(&:message_id) | |
@messages = Conversation::Message.where(:id => message_ids).order(:id) | |
@new_message = @thread.messages.new | |
end | |
private | |
def mark_thread_as_read | |
@thread.subscriptions.for_user(current_user).first.mark_read! | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class User < ActiveRecord::Base | |
has_many :threads, :through => :thread_subscriptions, :class_name => Conversation::Thread, :dependent => :destroy | |
has_many :thread_subscriptions, :class_name => Conversation::ThreadSubscription, :dependent => :destroy | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment