private_mail 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/generators/private_mail/install/install_generator.rb +37 -0
- data/lib/generators/private_mail/install/models/conversation.rb +56 -0
- data/lib/generators/private_mail/install/models/mail.rb +16 -0
- data/lib/generators/private_mail/install/models/message.rb +39 -0
- data/lib/generators/private_mail/install/templates/create_conversations.rb +12 -0
- data/lib/generators/private_mail/install/templates/create_mail.rb +18 -0
- data/lib/generators/private_mail/install/templates/create_messages.rb +18 -0
- data/lib/generators/private_mail/install/templates/create_messages_recipients.rb +13 -0
- data/lib/mailbox.rb +343 -0
- data/lib/private_mail/version.rb +3 -0
- data/lib/private_mail.rb +204 -0
- metadata +80 -0
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'rails/generators/migration'
|
2
|
+
|
3
|
+
module PrivateMail
|
4
|
+
|
5
|
+
module Generators
|
6
|
+
class InstallGenerator < ::Rails::Generators::Base
|
7
|
+
include Rails::Generators::Migration
|
8
|
+
|
9
|
+
source_root File.expand_path('../templates', __FILE__)
|
10
|
+
desc "add the migrations"
|
11
|
+
|
12
|
+
def self.next_migration_number(path)
|
13
|
+
unless @prev_migration_nr
|
14
|
+
@prev_migration_nr = Time.now.utc.strftime("%Y%m%d%H%M%S").to_i
|
15
|
+
else
|
16
|
+
@prev_migration_nr += 1
|
17
|
+
end
|
18
|
+
@prev_migration_nr.to_s
|
19
|
+
end
|
20
|
+
|
21
|
+
def copy_migrations
|
22
|
+
migration_template "create_conversations.rb", "db/migrate/create_conversations.rb"
|
23
|
+
migration_template "create_messages.rb", "db/migrate/create_messages.rb"
|
24
|
+
migration_template "create_messages_recipients.rb", "db/migrate/create_messages_recipients.rb"
|
25
|
+
migration_template "create_mail.rb", "db/migrate/create_mail.rb"
|
26
|
+
end
|
27
|
+
|
28
|
+
def copy_models
|
29
|
+
copy_file "../models/mail.rb", "app/models/mail.rb"
|
30
|
+
copy_file "../models/message.rb", "app/models/message.rb"
|
31
|
+
copy_file "../models/conversation.rb", "app/models/conversation.rb"
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
class Conversation < ActiveRecord::Base
|
2
|
+
attr_reader :originator, :original_message, :last_sender, :last_message, :users
|
3
|
+
has_many :messages
|
4
|
+
has_many :mails
|
5
|
+
before_create :clean
|
6
|
+
#looks like shit but isnt too bad
|
7
|
+
#has_many :users, :through :messages, :source => :recipients, :uniq => true doesnt work due to recipients being a habtm association
|
8
|
+
has_many :recipients, :class_name => 'User', :finder_sql =>
|
9
|
+
'SELECT users.* FROM conversations
|
10
|
+
INNER JOIN messages ON conversations.id = messages.conversation_id
|
11
|
+
INNER JOIN messages_recipients ON messages_recipients.message_id = messages.id
|
12
|
+
INNER JOIN users ON messages_recipients.recipient_id = users.id
|
13
|
+
WHERE conversations.id = #{self.id} GROUP BY users.id;'
|
14
|
+
|
15
|
+
#originator of the conversation.
|
16
|
+
def originator()
|
17
|
+
@orignator = self.original_message.sender if @originator.nil?
|
18
|
+
return @orignator
|
19
|
+
end
|
20
|
+
|
21
|
+
#first message of the conversation.
|
22
|
+
def original_message()
|
23
|
+
@original_message = self.messages.find(:first, :order => 'created_at') if @original_message.nil?
|
24
|
+
return @original_message
|
25
|
+
end
|
26
|
+
|
27
|
+
#sender of the last message.
|
28
|
+
def last_sender()
|
29
|
+
@last_sender = self.last_message.sender if @last_sender.nil?
|
30
|
+
return @last_sender
|
31
|
+
end
|
32
|
+
|
33
|
+
#last message in the conversation.
|
34
|
+
def last_message()
|
35
|
+
@last_message = self.messages.find(:first, :order => 'created_at DESC') if @last_message.nil?
|
36
|
+
return @last_message
|
37
|
+
end
|
38
|
+
|
39
|
+
#all users involved in the conversation.
|
40
|
+
def users()
|
41
|
+
if(@users.nil?)
|
42
|
+
@users = self.recipients.clone
|
43
|
+
@users << self.originator unless @users.include?(self.originator)
|
44
|
+
end
|
45
|
+
return @users
|
46
|
+
end
|
47
|
+
|
48
|
+
protected
|
49
|
+
#[empty method]
|
50
|
+
#
|
51
|
+
#this gets called before_create. Implement this if you wish to clean out illegal content such as scripts or anything that will break layout. This is left empty because what is considered illegal content varies.
|
52
|
+
def clean()
|
53
|
+
return if(subject.nil?)
|
54
|
+
#strip all illegal content here. (scripts, shit that will break layout, etc.)
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class Mail < ActiveRecord::Base
|
2
|
+
self.table_name = "mail"
|
3
|
+
belongs_to :message
|
4
|
+
belongs_to :user
|
5
|
+
belongs_to :conversation
|
6
|
+
|
7
|
+
#sets the read attribute of the mail message to true.
|
8
|
+
def mark_as_read()
|
9
|
+
update_attribute('read', true)
|
10
|
+
end
|
11
|
+
|
12
|
+
#sets the read attribute of the mail message to false.
|
13
|
+
def mark_as_unread()
|
14
|
+
update_attribute('read', false)
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
class Message < ActiveRecord::Base
|
2
|
+
#any additional info that needs to be sent in a message (ex. I use these to determine request types)
|
3
|
+
serialize :headers
|
4
|
+
|
5
|
+
class_inheritable_accessor :on_deliver_callback
|
6
|
+
protected :on_deliver_callback
|
7
|
+
|
8
|
+
belongs_to :sender, :class_name => 'User', :foreign_key => 'sender_id'
|
9
|
+
belongs_to :conversation
|
10
|
+
has_and_belongs_to_many :recipients, :class_name => 'User', :join_table => 'messages_recipients', :association_foreign_key => 'recipient_id'
|
11
|
+
|
12
|
+
#delivers a message to the the given mailbox of all recipients, calls the on_deliver_callback if initialized.
|
13
|
+
#
|
14
|
+
#====params:
|
15
|
+
#mailbox_type:: the mailbox to send the message to
|
16
|
+
#clean:: calls the clean method if this is set (must be implemented)
|
17
|
+
#
|
18
|
+
def deliver(mailbox_type, clean = true)
|
19
|
+
clean() if clean
|
20
|
+
self.save()
|
21
|
+
self.recipients.each do |r|
|
22
|
+
r.mailbox[mailbox_type] << self
|
23
|
+
end
|
24
|
+
self.on_deliver_callback.call(self, mailbox_type) unless self.on_deliver_callback.nil?
|
25
|
+
end
|
26
|
+
|
27
|
+
#sets the on_deliver_callback to the passed method. The method call should expect 2 params (message, mailbox_type).
|
28
|
+
def Message.on_deliver(method)
|
29
|
+
self.on_deliver_callback = method
|
30
|
+
end
|
31
|
+
|
32
|
+
protected
|
33
|
+
#[empty method]
|
34
|
+
#
|
35
|
+
#this gets called when a message is delivered and the clean param is set (default). Implement this if you wish to clean out illegal content such as scripts or anything that will break layout. This is left empty because what is considered illegal content varies.
|
36
|
+
def clean()
|
37
|
+
#strip all illegal content here. (scripts, shit that will break layout, etc.)
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
class CreateConversations < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table :conversations do |t|
|
4
|
+
t.column :subject, :string, :default => ""
|
5
|
+
t.column :created_at, :datetime, :null => false
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.down
|
10
|
+
drop_table :conversations
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class CreateMail < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table :mail do |t|
|
4
|
+
t.column :user_id, :integer, :null => false
|
5
|
+
t.column :message_id, :integer, :null => false
|
6
|
+
t.column :conversation_id, :integer
|
7
|
+
t.column :read, :boolean, :default => false
|
8
|
+
t.column :trashed, :boolean, :default => false
|
9
|
+
t.column :mailbox, :string, :limit => 25
|
10
|
+
t.column :created_at, :datetime, :null => false
|
11
|
+
end
|
12
|
+
#i use foreign keys but its a custom method, so i'm leaving it up to you want them.
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.down
|
16
|
+
drop_table :mail
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class CreateMessages < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table :messages do |t|
|
4
|
+
t.column :body, :text
|
5
|
+
t.column :subject, :string, :default => ""
|
6
|
+
t.column :headers, :text
|
7
|
+
t.column :sender_id, :integer, :null => false
|
8
|
+
t.column :conversation_id, :integer
|
9
|
+
t.column :sent, :boolean, :default => false
|
10
|
+
t.column :created_at, :datetime, :null => false
|
11
|
+
end
|
12
|
+
#i use foreign keys but its a custom method, so i'm leaving it up to you if you want them.
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.down
|
16
|
+
drop_table :messages
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class CreateMessagesRecipients < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table :messages_recipients, :id => false do |t|
|
4
|
+
t.column :message_id, :integer, :null => false
|
5
|
+
t.column :recipient_id, :integer, :null => false
|
6
|
+
end
|
7
|
+
#i use foreign keys but its a custom method, so i'm leaving it up to you want them.
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.down
|
11
|
+
drop_table :messages_recipients
|
12
|
+
end
|
13
|
+
end
|
data/lib/mailbox.rb
ADDED
@@ -0,0 +1,343 @@
|
|
1
|
+
module PrivateMail
|
2
|
+
|
3
|
+
class Mailbox
|
4
|
+
#this is used to filter mail by mailbox type, use the [] method rather than setting this directly.
|
5
|
+
attr_accessor :type
|
6
|
+
#the user/owner of this mailbox, set when initialized.
|
7
|
+
attr_reader :user
|
8
|
+
|
9
|
+
#creates a new Mailbox instance with the given user and optional type.
|
10
|
+
def initialize(user, type = :all)
|
11
|
+
@user = user
|
12
|
+
@type = type
|
13
|
+
end
|
14
|
+
|
15
|
+
#sets the mailbox type to the symbol corresponding to the given val.
|
16
|
+
def type=(val)
|
17
|
+
@type = val.to_sym
|
18
|
+
end
|
19
|
+
|
20
|
+
#returns a count of mail messages filtered by type and filter, if set.
|
21
|
+
#
|
22
|
+
#*this performs an actual sql count rather than selecting all mail and then gettin a length on the array... not a big deal but this could be something that is checked often to notify the user when they receive a new mail.
|
23
|
+
#
|
24
|
+
#====params:
|
25
|
+
#filter:: filters the count by the 'read' attribute.
|
26
|
+
#* :all - count of both read and unread mail.
|
27
|
+
#* :read - count of read mail.
|
28
|
+
#* :unread - count of unread mail.
|
29
|
+
#options:: see mail for acceptable options.
|
30
|
+
#
|
31
|
+
#====returns:
|
32
|
+
#number of mail messages
|
33
|
+
#
|
34
|
+
#====example:
|
35
|
+
# phil = User.find(3123)
|
36
|
+
#
|
37
|
+
# #get number of unread mail messages in phil's inbox
|
38
|
+
# phil.mailbox[:inbox].mail_count(:unread)
|
39
|
+
#
|
40
|
+
def mail_count(filter = :all, options = {})
|
41
|
+
default_options = {:conditions => ["mail.user_id = ?", @user.id]}
|
42
|
+
add_mailbox_condition!(default_options, @type)
|
43
|
+
add_conditions!(default_options, "mail.read = ?", filter == :read) unless filter == :all
|
44
|
+
return count_mail(default_options, options)
|
45
|
+
end
|
46
|
+
|
47
|
+
#returns an array of all Mail for the user filtered by type, if set.
|
48
|
+
#
|
49
|
+
#====params:
|
50
|
+
#options:: all valid find options are accepted as well as an additional conversation option.
|
51
|
+
#* :conversation - Conversation object to filter mail only belonging to this conversation.
|
52
|
+
#* :conditions - same as find conditions however the array version of conditions will not work, i.e., :conditions => ['mail.read = ?', false] will not work here.
|
53
|
+
#* all other find options will work as expected.
|
54
|
+
#
|
55
|
+
#====returns:
|
56
|
+
#array of Mail.
|
57
|
+
#
|
58
|
+
#====example:
|
59
|
+
# phil = User.find(3123)
|
60
|
+
#
|
61
|
+
# #get all mail messages belonging to phil
|
62
|
+
# phil.mailbox.mail
|
63
|
+
#
|
64
|
+
# #get all mail messages in phil's inbox associated with conversation 23
|
65
|
+
# phil.mailbox[:inbox].mail(:conversation => Conversation.find(23))
|
66
|
+
#
|
67
|
+
# #get all unread mail messages belonging to phil associated with conversation 23
|
68
|
+
# phil.mailbox.mail(:conversation => Conversation.find(23), :conditions => 'mail.read = false')
|
69
|
+
#
|
70
|
+
def mail(options = {})
|
71
|
+
default_options = {}
|
72
|
+
add_mailbox_condition!(default_options, @type)
|
73
|
+
return get_mail(default_options, options)
|
74
|
+
end
|
75
|
+
|
76
|
+
#returns an array of unread Mail for the user filtered by type, if set.
|
77
|
+
#
|
78
|
+
#====params:
|
79
|
+
#options:: see mail for acceptable options.
|
80
|
+
#
|
81
|
+
#====returns:
|
82
|
+
#array of Mail.
|
83
|
+
#
|
84
|
+
#====example:
|
85
|
+
# phil = User.find(3123)
|
86
|
+
#
|
87
|
+
# #get all unread mail in phil's inbox
|
88
|
+
# phil.mailbox[:inbox].unread_mail
|
89
|
+
#
|
90
|
+
def unread_mail(options = {})
|
91
|
+
default_options = {:conditions => ["mail.read = ?", false]}
|
92
|
+
add_mailbox_condition!(default_options, @type)
|
93
|
+
return get_mail(default_options, options)
|
94
|
+
end
|
95
|
+
|
96
|
+
#returns an array of read Mail for the user filtered by type, if set.
|
97
|
+
#
|
98
|
+
#====params:
|
99
|
+
#options:: see mail for acceptable options.
|
100
|
+
#
|
101
|
+
#====returns:
|
102
|
+
#array of Mail.
|
103
|
+
#
|
104
|
+
#====example:
|
105
|
+
# phil = User.find(3123)
|
106
|
+
#
|
107
|
+
# #get all read mail in phil's inbox
|
108
|
+
# phil.mailbox[:inbox].read_mail
|
109
|
+
#
|
110
|
+
def read_mail(options = {})
|
111
|
+
default_options = {:conditions => ["mail.read = ?", true]}
|
112
|
+
add_mailbox_condition!(default_options, @type)
|
113
|
+
return get_mail(default_options, options)
|
114
|
+
end
|
115
|
+
|
116
|
+
#returns an array of the latest Mail message for each conversation the user is involved in filtered by type, if set.
|
117
|
+
#
|
118
|
+
#*possible use for this would be an inbox view of your mail so you can easily see the status of all the convos your involved in.
|
119
|
+
#
|
120
|
+
#====params:
|
121
|
+
#options:: see mail for acceptable options.
|
122
|
+
#
|
123
|
+
#====returns:
|
124
|
+
#array of Mail.
|
125
|
+
#
|
126
|
+
#====example:
|
127
|
+
# phil = User.find(3123)
|
128
|
+
#
|
129
|
+
# #get a list of the latest received mail for each conversation
|
130
|
+
# phil.mailbox[:inbox].latest_mail
|
131
|
+
#
|
132
|
+
def latest_mail(options = {})
|
133
|
+
return only_latest(mail(options))
|
134
|
+
end
|
135
|
+
|
136
|
+
#adds a mail message to the user's mailbox specified by type.
|
137
|
+
#
|
138
|
+
#*this is used when sending a new message, all the recipients get a mail added to their inbox and the sender gets a mail in their sentbox.
|
139
|
+
#
|
140
|
+
#====params:
|
141
|
+
#msg::
|
142
|
+
# Message object from which a new mail is created from.
|
143
|
+
#
|
144
|
+
#====returns:
|
145
|
+
#new Mail.
|
146
|
+
#
|
147
|
+
def add(msg)
|
148
|
+
attributes = {:message => msg, :conversation => msg.conversation}
|
149
|
+
attributes[:mailbox] = @type.to_s unless @type == :all
|
150
|
+
attributes[:read] = msg.sender.id == @user.id
|
151
|
+
mail_msg = Mail.new(attributes)
|
152
|
+
@user.mail << mail_msg
|
153
|
+
return mail_msg
|
154
|
+
end
|
155
|
+
|
156
|
+
#marks all the mail messages matched by the options and type as read.
|
157
|
+
#
|
158
|
+
#====params:
|
159
|
+
#options:: see mail for acceptable options.
|
160
|
+
#
|
161
|
+
#====returns:
|
162
|
+
#array of Mail.
|
163
|
+
#
|
164
|
+
#====example:
|
165
|
+
# phil = User.find(3123)
|
166
|
+
#
|
167
|
+
# #mark all inbox messages as read
|
168
|
+
# phil.mailbox[:inbox].mark_as_read()
|
169
|
+
#
|
170
|
+
def mark_as_read(options = {})
|
171
|
+
default_options = {}
|
172
|
+
add_mailbox_condition!(default_options, @type)
|
173
|
+
return update_mail("mail.read = true", default_options, options)
|
174
|
+
end
|
175
|
+
|
176
|
+
#marks all the mail messages matched by the options and type as unread, except for sent messages.
|
177
|
+
#
|
178
|
+
#====params:
|
179
|
+
#options:: see mail for acceptable options.
|
180
|
+
#
|
181
|
+
#====returns:
|
182
|
+
#array of Mail.
|
183
|
+
#
|
184
|
+
#====example:
|
185
|
+
# phil = User.find(3123)
|
186
|
+
#
|
187
|
+
# #mark all inbox messages as unread
|
188
|
+
# phil.mailbox[:inbox].mark_as_unread()
|
189
|
+
#
|
190
|
+
def mark_as_unread(options = {})
|
191
|
+
default_options = {:conditions => ["mail.mailbox != ?",@user.mailbox_types[:sent].to_s]}
|
192
|
+
add_mailbox_condition!(default_options, @type)
|
193
|
+
return update_mail("mail.read = false", default_options, options)
|
194
|
+
end
|
195
|
+
|
196
|
+
#moves all mail matched by the options to the given mailbox. sent messages stay in the sentbox.
|
197
|
+
#
|
198
|
+
#====params:
|
199
|
+
#mailbox:: the mailbox_type to move the mail messages to. (ex. :inbox, :trash)
|
200
|
+
#options:: see mail for acceptable options.
|
201
|
+
#
|
202
|
+
def move_to(mailbox, options = {})
|
203
|
+
mailbox = mailbox.to_sym
|
204
|
+
trash = mailbox == @user.mailbox_types[:deleted].to_sym
|
205
|
+
default_options = {}
|
206
|
+
add_mailbox_condition!(default_options, @type)
|
207
|
+
if(!trash)
|
208
|
+
#conditional update because sentmail is always sentmail - I believe case if the most widely supported conditional, mysql also has an if which would work as well but i think mysql is the only one to support it
|
209
|
+
return update_mail("mail.trashed = false, mail.mailbox =
|
210
|
+
CASE mail.mailbox
|
211
|
+
WHEN '#{@user.mailbox_types[:sent].to_s}' THEN mail.mailbox
|
212
|
+
ELSE '#{mailbox.to_s}'
|
213
|
+
END", default_options, options)
|
214
|
+
end
|
215
|
+
return update_mail("mail.trashed = true", default_options, options)
|
216
|
+
end
|
217
|
+
|
218
|
+
#permanantly deletes all the mail messages matched by the options. Use move_to(:trash) instead if you want to send to user's trash without deleting.
|
219
|
+
#
|
220
|
+
#====params:
|
221
|
+
#options:: see mail for acceptable options.
|
222
|
+
#
|
223
|
+
def delete(options = {})
|
224
|
+
default_options = {:conditions => ["mail.user_id = ?", @user.id]}
|
225
|
+
add_mailbox_condition!(default_options, @type)
|
226
|
+
return delete_mail(default_options, options)
|
227
|
+
end
|
228
|
+
|
229
|
+
#alias for add
|
230
|
+
def <<(msg)
|
231
|
+
return self.add(msg)
|
232
|
+
end
|
233
|
+
|
234
|
+
#deletes all messages that have been trashed and match the options if passed.
|
235
|
+
#
|
236
|
+
#====params:
|
237
|
+
#options:: see mail for acceptable options.
|
238
|
+
#
|
239
|
+
def empty_trash(options = {})
|
240
|
+
default_options = {:conditions => ["mail.user_id = ? AND mail.trashed = ?", @user.id, true]}
|
241
|
+
add_mailbox_condition!(default_options, @type)
|
242
|
+
return delete_mail(default_options, options)
|
243
|
+
end
|
244
|
+
|
245
|
+
#return true if the user is involved in the given conversation.
|
246
|
+
def has_conversation?(conversation)
|
247
|
+
return mail_count(:all, :conversation => conversation) != 0
|
248
|
+
end
|
249
|
+
|
250
|
+
#sets the mailbox type and returns itself.
|
251
|
+
#
|
252
|
+
#====params:
|
253
|
+
#mailbox_type::
|
254
|
+
# type of mailbox to filter mail by, this can be anything, but the three most likely values for this will be the received, sent, and trash values set within the acts_as_messageable method.
|
255
|
+
#
|
256
|
+
#====returns:
|
257
|
+
#self
|
258
|
+
#
|
259
|
+
#====example:
|
260
|
+
# phil = User.find(3123)
|
261
|
+
#
|
262
|
+
# #all mails in the user's inbox
|
263
|
+
# phil.mailbox[:inbox].mail
|
264
|
+
#
|
265
|
+
def [](mailbox_type)
|
266
|
+
self.type = mailbox_type
|
267
|
+
return self
|
268
|
+
end
|
269
|
+
|
270
|
+
private
|
271
|
+
|
272
|
+
def get_mail(default_options, options)
|
273
|
+
build_options(default_options, options) unless options.empty?
|
274
|
+
return @user.mail.find(:all, default_options)
|
275
|
+
end
|
276
|
+
|
277
|
+
def update_mail(updates, default_options, options)
|
278
|
+
build_options(default_options, options) unless options.empty?
|
279
|
+
return @user.mail.update_all(updates, default_options[:conditions])
|
280
|
+
end
|
281
|
+
|
282
|
+
def delete_mail(default_options, options)
|
283
|
+
build_options(default_options, options) unless options.empty?
|
284
|
+
return Mail.delete_all(default_options[:conditions])
|
285
|
+
end
|
286
|
+
|
287
|
+
def count_mail(default_options, options)
|
288
|
+
build_options(default_options, options) unless options.empty?
|
289
|
+
return Mail.count(:all, default_options)
|
290
|
+
end
|
291
|
+
|
292
|
+
def build_options(default_options, options)
|
293
|
+
add_conversation_condition!(default_options, options[:conversation]) unless options[:conversation].nil?
|
294
|
+
options.delete(:conversation)
|
295
|
+
add_conditions!(default_options, options[:conditions]) unless options[:conditions].nil?
|
296
|
+
options.delete(:conditions)
|
297
|
+
default_options.merge!(options)
|
298
|
+
end
|
299
|
+
|
300
|
+
def only_latest(mail)
|
301
|
+
convos = []
|
302
|
+
latest = []
|
303
|
+
mail.each do |m|
|
304
|
+
next if(convos.include?(m.conversation_id))
|
305
|
+
convos << m.conversation_id
|
306
|
+
latest << m
|
307
|
+
end
|
308
|
+
return latest
|
309
|
+
end
|
310
|
+
|
311
|
+
def add_mailbox_condition!(options, mailbox_type)
|
312
|
+
return if mailbox_type == :all
|
313
|
+
return add_conditions!(options, "mail.mailbox = ? AND mail.trashed = ?", mailbox_type.to_s, false) unless mailbox_type == @user.mailbox_types[:deleted]
|
314
|
+
return add_conditions!(options, "mail.trashed = ?", true)
|
315
|
+
end
|
316
|
+
|
317
|
+
def add_conversation_condition!(options, conversation)
|
318
|
+
options.merge!({:order => 'created_at ASC'})
|
319
|
+
if(conversation.is_a?(Array))
|
320
|
+
conversation.map! {|c| c.is_a?(Integer) ? c : c.id}
|
321
|
+
else
|
322
|
+
conversation = conversation.is_a?(Integer) ? [conversation] : [conversation.id]
|
323
|
+
end
|
324
|
+
return add_conditions!(options, "conversation_id IN (?)", conversation)
|
325
|
+
end
|
326
|
+
|
327
|
+
def add_conditions!(options, conditions, *values)
|
328
|
+
return nil unless options.is_a?(Hash)
|
329
|
+
if(options[:conditions].nil?)
|
330
|
+
options[:conditions] = values.length == 0 ? conditions : [conditions]
|
331
|
+
elsif(options[:conditions].is_a?(Array))
|
332
|
+
options[:conditions][0] = "(#{options[:conditions][0]}) AND (#{conditions})"
|
333
|
+
else
|
334
|
+
options[:conditions] = "(#{options[:conditions]}) AND (#{conditions})"
|
335
|
+
end
|
336
|
+
values.each do |val|
|
337
|
+
options[:conditions].push(val)
|
338
|
+
end
|
339
|
+
return options
|
340
|
+
end
|
341
|
+
|
342
|
+
end
|
343
|
+
end
|
data/lib/private_mail.rb
ADDED
@@ -0,0 +1,204 @@
|
|
1
|
+
# Private Mail implements simple private messaging between users in a Rails application.
|
2
|
+
|
3
|
+
require 'logger'
|
4
|
+
require 'active_record'
|
5
|
+
require 'mailbox'
|
6
|
+
|
7
|
+
module PrivateMail
|
8
|
+
def self.included(mod)
|
9
|
+
mod.extend(ClassMethods)
|
10
|
+
end
|
11
|
+
|
12
|
+
# declare the class level helper methods which
|
13
|
+
# will load the relevant instance methods
|
14
|
+
# defined below when invoked
|
15
|
+
module ClassMethods
|
16
|
+
#enables a class to send and receive messages to members of the same class - currently assumes the model is of class type 'User',
|
17
|
+
#some modifications to the migrations and model classes will need to be made to use a model of different type.
|
18
|
+
#
|
19
|
+
#====options:
|
20
|
+
#* :received - the mailbox type to store received messages (defaults to :inbox)
|
21
|
+
#
|
22
|
+
#* :sent - the mailbox type to store sent messages (defaults to :sentbox)
|
23
|
+
#
|
24
|
+
#* :deleted - the mailbox type to store deleted messages (defaults to :trash)
|
25
|
+
#
|
26
|
+
#====example:
|
27
|
+
# acts_as_messageable :received => :in, :sent => :sent, :deleted => :garbage
|
28
|
+
def acts_as_messageable(options = {})
|
29
|
+
cattr_accessor :mailbox_types
|
30
|
+
has_many :mail, :order => 'created_at DESC', :dependent => :delete_all
|
31
|
+
|
32
|
+
self.mailbox_types = {
|
33
|
+
:received => :inbox,
|
34
|
+
:sent => :sentbox,
|
35
|
+
:deleted => :trash
|
36
|
+
}.merge(options)
|
37
|
+
|
38
|
+
include PrivateMail::InstanceMethods
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Adds instance methods.
|
43
|
+
module InstanceMethods
|
44
|
+
#returns an instance of class type Mailbox - this object essentially wraps the user's mail messages and provides a clean interface for accessing them.
|
45
|
+
#see Mailbox for more details.
|
46
|
+
#
|
47
|
+
#====example:
|
48
|
+
# phil = User.find(3123)
|
49
|
+
# phil.mailbox[:inbox].unread_mail #returns all unread mail in your inbox
|
50
|
+
# phil.mailbox[:sentbox].mail #returns all sent mail messages
|
51
|
+
#
|
52
|
+
def mailbox()
|
53
|
+
@mailbox = Mailbox.new(self) if @mailbox.nil?
|
54
|
+
@mailbox.type = :all
|
55
|
+
return @mailbox
|
56
|
+
end
|
57
|
+
#creates new Message and Conversation objects from the given parameters and delivers Mail to each of the recipients' inbox.
|
58
|
+
#
|
59
|
+
#====params:
|
60
|
+
#recipients::
|
61
|
+
# a single user object or array of users to deliver the message to.
|
62
|
+
#msg_body::
|
63
|
+
# the body of the message.
|
64
|
+
#subject::
|
65
|
+
# the subject of the message, defaults to empty string if not provided.
|
66
|
+
#====returns:
|
67
|
+
#the sent Mail.
|
68
|
+
#
|
69
|
+
#====example:
|
70
|
+
# phil = User.find(3123)
|
71
|
+
# todd = User.find(4141)
|
72
|
+
# phil.send_message(todd, 'whats up for tonight?', 'hey guy') #sends a Mail message to todd's inbox, and a Mail message to phil's sentbox
|
73
|
+
#
|
74
|
+
def send_message(recipients, msg_body, subject = '')
|
75
|
+
convo = Conversation.create({:subject => subject})
|
76
|
+
message = Message.create({:sender => self, :conversation => convo, :body => msg_body, :subject => subject})
|
77
|
+
message.recipients = recipients.is_a?(Array) ? recipients : [recipients]
|
78
|
+
message.deliver(self.mailbox_types[:received])
|
79
|
+
return mailbox[:sentbox] << message
|
80
|
+
end
|
81
|
+
#creates a new Message associated with the given conversation and delivers the reply to each of the given recipients.
|
82
|
+
#
|
83
|
+
#*explicitly calling this method is rare unless you are replying to a subset of the users involved in the conversation or
|
84
|
+
#if you are including someone that is not currently in the conversation.
|
85
|
+
#reply_to_sender, reply_to_all, and reply_to_conversation will suffice in most cases.
|
86
|
+
#
|
87
|
+
#====params:
|
88
|
+
#conversation::
|
89
|
+
# the Conversation object that the mail you are responding to belongs.
|
90
|
+
#recipients::
|
91
|
+
# a single User object or array of Users to deliver the reply message to.
|
92
|
+
#reply_body::
|
93
|
+
# the body of the reply message.
|
94
|
+
#subject::
|
95
|
+
# the subject of the message, defaults to 'RE: [original subject]' if one isnt given.
|
96
|
+
#====returns:
|
97
|
+
#the sent Mail.
|
98
|
+
#
|
99
|
+
def reply(conversation, recipients, reply_body, subject = nil)
|
100
|
+
return nil if(reply_body.blank?)
|
101
|
+
subject = subject || "RE: #{conversation.subject}"
|
102
|
+
response = Message.create({:sender => self, :conversation => conversation, :body => reply_body, :subject => subject})
|
103
|
+
response.recipients = recipients.is_a?(Array) ? recipients : [recipients]
|
104
|
+
response.deliver(self.mailbox_types[:received])
|
105
|
+
return mailbox[self.mailbox_types[:sent]] << response
|
106
|
+
end
|
107
|
+
#sends a Mail to the sender of the given mail message.
|
108
|
+
#
|
109
|
+
#====params:
|
110
|
+
#mail::
|
111
|
+
# the Mail object that you are replying to.
|
112
|
+
#reply_body::
|
113
|
+
# the body of the reply message.
|
114
|
+
#subject::
|
115
|
+
# the subject of the message, defaults to 'RE: [original subject]' if one isnt given.
|
116
|
+
#====returns:
|
117
|
+
#the sent Mail.
|
118
|
+
#
|
119
|
+
def reply_to_sender(mail, reply_body, subject = nil)
|
120
|
+
return reply(mail.conversation, mail.message.sender, reply_body, subject)
|
121
|
+
end
|
122
|
+
#sends a Mail to all of the recipients of the given mail message (excluding yourself).
|
123
|
+
#
|
124
|
+
#====params:
|
125
|
+
#mail::
|
126
|
+
# the Mail object that you are replying to.
|
127
|
+
#reply_body::
|
128
|
+
# the body of the reply message.
|
129
|
+
#subject::
|
130
|
+
# the subject of the message, defaults to 'RE: [original subject]' if one isnt given.
|
131
|
+
#====returns:
|
132
|
+
#the sent Mail.
|
133
|
+
#
|
134
|
+
def reply_to_all(mail, reply_body, subject = nil)
|
135
|
+
msg = mail.message
|
136
|
+
recipients = msg.recipients.clone()
|
137
|
+
if(msg.sender != self)
|
138
|
+
recipients.delete(self)
|
139
|
+
if(!recipients.include?(msg.sender))
|
140
|
+
recipients << msg.sender
|
141
|
+
end
|
142
|
+
end
|
143
|
+
return reply(mail.conversation, recipients, reply_body, subject)
|
144
|
+
end
|
145
|
+
#sends a Mail to all users involved in the given conversation (excluding yourself).
|
146
|
+
#
|
147
|
+
#*this may have undesired effects if users have been added to the conversation after it has begun.
|
148
|
+
#
|
149
|
+
#====params:
|
150
|
+
#conversation::
|
151
|
+
# the Conversation object that the mail you are responding to belongs.
|
152
|
+
#reply_body::
|
153
|
+
# the body of the reply message.
|
154
|
+
#subject::
|
155
|
+
# the subject of the message, defaults to 'RE: [original subject]' if one isnt given.
|
156
|
+
#====returns:
|
157
|
+
#the sent Mail.
|
158
|
+
#
|
159
|
+
def reply_to_conversation(conversation, reply_body, subject = nil)
|
160
|
+
#move conversation to inbox if it is currently in the trash - doesnt make much sense replying to a trashed convo.
|
161
|
+
if((mailbox[self.mailbox_types[:deleted]].has_conversation?(conversation)))
|
162
|
+
mailbox.move_to(self.mailbox_types[:received], :conversation => conversation)
|
163
|
+
end
|
164
|
+
#remove self from recipients unless you are the originator of the convo
|
165
|
+
recipients = conversation.original_message.recipients.clone()
|
166
|
+
if(conversation.originator != self)
|
167
|
+
recipients.delete(self)
|
168
|
+
if(!recipients.include?(conversation.originator))
|
169
|
+
recipients << conversation.originator
|
170
|
+
end
|
171
|
+
end
|
172
|
+
return reply(conversation,recipients, reply_body, subject)
|
173
|
+
end
|
174
|
+
#returns the mail given as the parameter, marked as read.
|
175
|
+
def read_mail(mail)
|
176
|
+
return mail.mark_as_read()
|
177
|
+
end
|
178
|
+
#returns an array of the user's Mail associated with the given conversation.
|
179
|
+
#All mail is marked as read but the returning array is built before this so you can see which messages were unread when viewing the conversation.
|
180
|
+
#
|
181
|
+
#This returns deleted/trashed messages as well for the purpose of reading trashed convos, to disable this send the option ':conditions => "mail.trashed != true"'
|
182
|
+
#
|
183
|
+
#====params:
|
184
|
+
#conversation::
|
185
|
+
# the Conversation object that you want to read.
|
186
|
+
#options::
|
187
|
+
# any options to filter the conversation, these are used as find options so all valid options for find will work.
|
188
|
+
#
|
189
|
+
#====returns:
|
190
|
+
#array of Mail belonging to the given conversation.
|
191
|
+
#
|
192
|
+
def read_conversation(conversation, options = {})
|
193
|
+
convo_mail = self.mailbox.mail(options.merge(:conversation => conversation))
|
194
|
+
self.mailbox.mark_as_read(:conversation => conversation)
|
195
|
+
return convo_mail
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
# reopen ActiveRecord and include all the above to make
|
201
|
+
# them available to all our models if they want it
|
202
|
+
ActiveRecord::Base.class_eval do
|
203
|
+
include PrivateMail
|
204
|
+
end
|
metadata
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: private_mail
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- John McDowall
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-10-31 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rails
|
16
|
+
requirement: &2170459640 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *2170459640
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rspec
|
27
|
+
requirement: &2170459220 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *2170459220
|
36
|
+
description: ''
|
37
|
+
email: john@mcdowall.info
|
38
|
+
executables: []
|
39
|
+
extensions: []
|
40
|
+
extra_rdoc_files: []
|
41
|
+
files:
|
42
|
+
- lib/generators/private_mail/install/install_generator.rb
|
43
|
+
- lib/generators/private_mail/install/models/conversation.rb
|
44
|
+
- lib/generators/private_mail/install/models/mail.rb
|
45
|
+
- lib/generators/private_mail/install/models/message.rb
|
46
|
+
- lib/generators/private_mail/install/templates/create_conversations.rb
|
47
|
+
- lib/generators/private_mail/install/templates/create_mail.rb
|
48
|
+
- lib/generators/private_mail/install/templates/create_messages.rb
|
49
|
+
- lib/generators/private_mail/install/templates/create_messages_recipients.rb
|
50
|
+
- lib/mailbox.rb
|
51
|
+
- lib/private_mail/version.rb
|
52
|
+
- lib/private_mail.rb
|
53
|
+
homepage: ''
|
54
|
+
licenses: []
|
55
|
+
post_install_message:
|
56
|
+
rdoc_options: []
|
57
|
+
require_paths:
|
58
|
+
- lib
|
59
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
60
|
+
none: false
|
61
|
+
requirements:
|
62
|
+
- - ! '>='
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: '0'
|
65
|
+
segments:
|
66
|
+
- 0
|
67
|
+
hash: -1834868077319262402
|
68
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
69
|
+
none: false
|
70
|
+
requirements:
|
71
|
+
- - ! '>='
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0'
|
74
|
+
requirements: []
|
75
|
+
rubyforge_project:
|
76
|
+
rubygems_version: 1.8.11
|
77
|
+
signing_key:
|
78
|
+
specification_version: 3
|
79
|
+
summary: ''
|
80
|
+
test_files: []
|