acts_as_chattable 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec :path => File.expand_path('../..', __FILE__)
4
+ gem "activerecord", "~> 3.0.0"
5
+ gem "activesupport", "~> 3.0.0"
6
+ gem "railties", "~> 3.0.0"
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec :path => File.expand_path('../..', __FILE__)
4
+ gem "activerecord", "~> 3.1.0"
5
+ gem "activesupport", "~> 3.1.0"
6
+ gem "railties", "~> 3.1.0"
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec :path => File.expand_path('../..', __FILE__)
4
+ gem "activerecord", "~> 3.2.0"
5
+ gem "activesupport", "~> 3.2.0"
6
+ gem "railties", "~> 3.2.0"
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec :path => File.expand_path('../..', __FILE__)
4
+ gem "activerecord", "~> 4.0.0"
5
+ gem "activesupport", "~> 4.0.0"
6
+ gem "railties", "~> 4.0.0"
@@ -0,0 +1,17 @@
1
+ module ActsAsChattable
2
+ autoload :Model, 'acts_as_chattable/model'
3
+ autoload :Message, 'acts_as_chattable/message'
4
+ autoload :Relation, 'acts_as_chattable/relation'
5
+ autoload :Rails3, 'acts_as_chattable/rails3'
6
+ autoload :Rails4, 'acts_as_chattable/rails4'
7
+
8
+ def self.rails_api
9
+ if Rails::VERSION::MAJOR >= 4
10
+ Rails4
11
+ else
12
+ Rails3
13
+ end
14
+ end
15
+ end
16
+
17
+ require 'acts_as_chattable/railtie'
@@ -0,0 +1,75 @@
1
+ module ActsAsChattable
2
+ class Message < ::ActiveRecord::Base
3
+ belongs_to :received_messageable, :polymorphic => true
4
+ belongs_to :sent_messageable, :polymorphic => true
5
+
6
+ attr_accessor :removed, :restored
7
+ cattr_accessor :required
8
+
9
+ ActsAsChattable.rails_api.new(self).default_scope("created_at desc")
10
+
11
+ scope :are_from, lambda { |*args| where(:sent_messageable_id => args.first, :sent_messageable_type => args.first.class.name) }
12
+ scope :are_to, lambda { |*args| where(:received_messageable_id => args.first, :received_messageable_type => args.first.class.name) }
13
+ scope :connected_with, lambda { |*args| where("(sent_messageable_type = :sent_type and
14
+ sent_messageable_id = :sent_id and
15
+ sender_delete = :s_delete and sender_permanent_delete = :s_perm_delete) or
16
+ (received_messageable_type = :received_type and
17
+ received_messageable_id = :received_id and
18
+ recipient_delete = :r_delete and recipient_permanent_delete = :r_perm_delete)",
19
+ :sent_type => args.first.class.resolve_active_record_ancestor.to_s,
20
+ :sent_id => args.first.id,
21
+ :received_type => args.first.class.resolve_active_record_ancestor.to_s,
22
+ :received_id => args.first.id,
23
+ :r_delete => args.last,
24
+ :s_delete => args.last,
25
+ :r_perm_delete => false,
26
+ :s_perm_delete => false)
27
+ }
28
+ scope :readed, lambda { where(:opened => true) }
29
+ scope :unreaded, lambda { where(:opened => false) }
30
+ scope :deleted, lambda { where(:recipient_delete => true, :sender_delete => true) }
31
+
32
+ def open?
33
+ self.opened?
34
+ end
35
+
36
+ def open
37
+ update_attributes!(:opened => true)
38
+ end
39
+ alias :mark_as_read :open
40
+ alias :read :open
41
+
42
+ def close
43
+ update_attributes!(:opened => false)
44
+ end
45
+ alias :mark_as_unread :close
46
+ alias :unread :close
47
+
48
+ alias :from :sent_messageable
49
+ alias :to :received_messageable
50
+
51
+ def real_receiver(user)
52
+ user == from ? to : from
53
+ end
54
+
55
+ def participant?(user)
56
+ (to == user) || (from == user)
57
+ end
58
+
59
+ def conversation
60
+ root.subtree
61
+ end
62
+
63
+ def delete
64
+ self.removed = true
65
+ end
66
+
67
+ def restore
68
+ self.restored = true
69
+ end
70
+
71
+ def people
72
+ conversation.map{ |x| x.from }.uniq!
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,159 @@
1
+ module ActsAsChattable
2
+ module Model
3
+
4
+ def self.included(base)
5
+ base.extend ClassMethods
6
+ end
7
+
8
+ module ClassMethods
9
+ mattr_accessor :messages_class_name
10
+
11
+ # Method make ActiveRecord::Base object chattable
12
+ # @param [Symbol] :table_name - table name for messages
13
+ # @param [String] :class_name - message class name
14
+ # @param [Array, Symbol] :required - required fields in message
15
+ # @param [Symbol] :dependent - dependent option from ActiveRecord has_many method
16
+ def acts_as_chattable(options = {})
17
+ default_options = {
18
+ :table_name => "messages",
19
+ :class_name => "ActsAsChattable::Message",
20
+ :required => [:body],
21
+ :dependent => :nullify
22
+ }
23
+ options = default_options.merge(options)
24
+
25
+ has_many :received_messages_relation,
26
+ :as => :received_messageable,
27
+ :class_name => options[:class_name],
28
+ :dependent => options[:dependent]
29
+ has_many :sent_messages_relation,
30
+ :as => :sent_messageable,
31
+ :class_name => options[:class_name],
32
+ :dependent => options[:dependent]
33
+
34
+ self.messages_class_name = options[:class_name].constantize
35
+
36
+ if self.messages_class_name.respond_to?(:table_name=)
37
+ self.messages_class_name.table_name = options[:table_name]
38
+ else
39
+ self.messages_class_name.set_table_name(options[:table_name])
40
+ ActiveSupport::Deprecation.warn("Calling set_table_name is deprecated. Please use `self.table_name = 'the_name'` instead.")
41
+ end
42
+
43
+ self.messages_class_name.required = Array.wrap(options[:required])
44
+ self.messages_class_name.validates_presence_of self.messages_class_name.required
45
+
46
+ include ActsAsChattable::Model::InstanceMethods
47
+ end
48
+
49
+ # Method recognize real object class
50
+ # @return [ActiveRecord::Base] class or relation object
51
+ def resolve_active_record_ancestor
52
+ self.reflect_on_association(:received_messages_relation).active_record
53
+ end
54
+
55
+ end
56
+
57
+ module InstanceMethods
58
+ # @return [ActiveRecord::Relation] all messages connected with user
59
+ def messages(trash = false)
60
+ result = self.class.messages_class_name.connected_with(self, trash)
61
+ result.relation_context = self
62
+
63
+ result
64
+ end
65
+
66
+ # @return [ActiveRecord::Relation] returns all messages from inbox
67
+ def received_messages
68
+ result = ActsAsChattable.rails_api.new(received_messages_relation)
69
+ result = result.scoped.where(:recipient_delete => false)
70
+ result.relation_context = self
71
+
72
+ result
73
+ end
74
+
75
+ # @return [ActiveRecord::Relation] returns all messages from outbox
76
+ def sent_messages
77
+ result = ActsAsChattable.rails_api.new(sent_messages_relation)
78
+ result = result.scoped.where(:sender_delete => false)
79
+ result.relation_context = self
80
+
81
+ result
82
+ end
83
+
84
+ # @return [ActiveRecord::Relation] returns all messages from trash
85
+ def deleted_messages
86
+ messages true
87
+ end
88
+
89
+ # Method sens message to another user
90
+ # @param [ActiveRecord::Base] to
91
+ # @param [String] topic
92
+ # @param [String] body
93
+ #
94
+ # @return [ActsAsChattable::Message] the message object
95
+ def send_message(to, *args)
96
+ message_attributes = {}
97
+
98
+ case args.first
99
+ when String
100
+ self.class.messages_class_name.required.each_with_index do |attribute, index|
101
+ message_attributes[attribute] = args[index]
102
+ end
103
+ when Hash
104
+ message_attributes = args.first
105
+ end
106
+
107
+ message = self.class.messages_class_name.new message_attributes
108
+ message.received_messageable = to
109
+ message.sent_messageable = self
110
+ message.save
111
+
112
+ message
113
+ end
114
+
115
+ # Method send message to another user
116
+ # and raise exception in case of validation errors
117
+ # @param [ActiveRecord::Base] to
118
+ # @param [String] topic
119
+ # @param [String] body
120
+ #
121
+ # @return [ActsAsChattable::Message] the message object
122
+ def send_message!(to, *args)
123
+ send_message(to, *args).save!
124
+ end
125
+
126
+ # Mark message as deleted
127
+ def delete_message(message)
128
+ current_user = self
129
+
130
+ case current_user
131
+ when message.to
132
+ attribute = message.recipient_delete ? :recipient_permanent_delete : :recipient_delete
133
+ when message.from
134
+ attribute = message.sender_delete ? :sender_permanent_delete : :sender_delete
135
+ else
136
+ raise "#{current_user} can't delete this message"
137
+ end
138
+
139
+ message.update_attributes!(attribute => true)
140
+ end
141
+
142
+ # Mark message as restored
143
+ def restore_message(message)
144
+ current_user = self
145
+
146
+ case current_user
147
+ when message.to
148
+ attribute = :recipient_delete
149
+ when message.from
150
+ attribute = :sender_delete
151
+ else
152
+ raise "#{current_user} can't restore this message"
153
+ end
154
+
155
+ message.update_attributes!(attribute => false)
156
+ end
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,15 @@
1
+ module ActsAsChattable
2
+ class Rails3
3
+ def initialize(subject)
4
+ @subject = subject
5
+ end
6
+
7
+ def default_scope(order_by)
8
+ @subject.send(:default_scope, order(order_by))
9
+ end
10
+
11
+ def method_missing(name, *args)
12
+ @subject.send(name, *args)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,19 @@
1
+ module ActsAsChattable
2
+ class Rails4
3
+ def initialize(subject)
4
+ @subject = subject
5
+ end
6
+
7
+ def default_scope(order_by)
8
+ @subject.default_scope { order(order_by) }
9
+ end
10
+
11
+ def scoped
12
+ @subject.scope
13
+ end
14
+
15
+ def method_missing(name, *args)
16
+ @subject.send(name, *args)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,14 @@
1
+ require 'active_record/railtie'
2
+ require 'active_support/core_ext'
3
+
4
+ module ActsAsChattable
5
+ class Railtie < Rails::Railtie
6
+ if defined?(ActiveRecord::Base)
7
+ ActiveRecord::Base.send :include, ActsAsChattable::Model
8
+ end
9
+
10
+ if defined?(ActiveRecord::Relation)
11
+ ActiveRecord::Relation.send :include, ActsAsChattable::Relation
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,17 @@
1
+ module ActsAsChattable
2
+ module Relation
3
+ attr_accessor :relation_context
4
+
5
+ def process(context = self.relation_context, &block)
6
+ self.each do |message|
7
+ block.call(message) if block_given?
8
+ context.delete_message(message) if message.removed
9
+ context.restore_message(message) if message.restored
10
+ end
11
+ end
12
+
13
+ def conversations
14
+ map { |r| r.root.subtree.order("id desc").first }.uniq
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,21 @@
1
+ require 'rails/generators/migration'
2
+ require 'rails/generators/active_record'
3
+
4
+ module ActsAsChattable
5
+ class MigrationGenerator < Rails::Generators::Base
6
+ include Rails::Generators::Migration
7
+
8
+ namespace "acts_as_chattable:migration"
9
+
10
+ source_root File.join(File.dirname(__FILE__), 'templates')
11
+
12
+ def self.next_migration_number(dirname)
13
+ ActiveRecord::Generators::Base.next_migration_number(dirname)
14
+ end
15
+
16
+ def create_migration_file
17
+ migration_template 'migration.rb', 'db/migrate/create_messages_table.rb' rescue nil
18
+ migration_template 'migration_permanent.rb', 'db/migrate/add_recipient_permanent_delete_and_sender_permanent_delete_to_messages.rb' rescue nil
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,20 @@
1
+ class CreateMessagesTable < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :messages do |t|
4
+ t.text :body
5
+ t.string :attachment
6
+ t.references :received_messageable, :polymorphic => true
7
+ t.references :sent_messageable, :polymorphic => true
8
+ t.boolean :opened, :default => false
9
+ t.boolean :recipient_delete, :default => false
10
+ t.boolean :sender_delete, :default => false
11
+ t.timestamps
12
+ end
13
+
14
+ add_index :messages, [:sent_messageable_id, :received_messageable_id], :name => "acts_as_chattable_ids"
15
+ end
16
+
17
+ def self.down
18
+ drop_table :messages
19
+ end
20
+ end
@@ -0,0 +1,11 @@
1
+ class AddRecipientPermanentDeleteAndSenderPermanentDeleteToMessages < ActiveRecord::Migration
2
+ def self.up
3
+ add_column :messages, :recipient_permanent_delete, :boolean, :default => false
4
+ add_column :messages, :sender_permanent_delete, :boolean, :default => false
5
+ end
6
+
7
+ def self.down
8
+ remove_column :messages, :recipient_permanent_delete
9
+ remove_column :messages, :sender_permanent_delete
10
+ end
11
+ end
@@ -0,0 +1,253 @@
1
+ require 'spec_helper'
2
+
3
+ describe "ActsAsChattable" do
4
+ before do
5
+ User.acts_as_chattable
6
+ @message = send_message
7
+ end
8
+
9
+ describe "send messages" do
10
+ it "alice should have one message" do
11
+ @alice.messages.count.should == 1
12
+ end
13
+
14
+ it "alice should have one message from bob" do
15
+ @alice.messages.are_from(@bob).count.should == 1
16
+ end
17
+
18
+ it "bob should have one message" do
19
+ @bob.messages.count.should == 1
20
+ end
21
+
22
+ it "bob should have one message to alice in outbox" do
23
+ @bob.sent_messages.are_to(@alice).count.should == 1
24
+ end
25
+
26
+ it "bob should have one open message from alice" do
27
+ @alice.messages.are_from(@bob).process { |m| m.open }
28
+ @alice.messages.readed.count.should == 1
29
+ end
30
+ end
31
+
32
+ describe "send messages with bang" do
33
+ it "should return message object" do
34
+ @alice.send_message!(@bob, :body => "body", :attachment => "test").should
35
+ be_kind_of ActsAsChattable::Message
36
+ end
37
+ end
38
+
39
+ describe "inheritance models" do
40
+ it "men send message to alice" do
41
+ send_message(@men, @alice)
42
+ @men.sent_messages.size.should be_equal(1)
43
+ @alice.received_messages.size.should be_equal(2)
44
+ end
45
+
46
+ it "messages method should receive all messages connected with user" do
47
+ send_message(@men, @alice)
48
+ @men.messages.size.should be_equal(1)
49
+ end
50
+
51
+ it "men send message and receive from alice" do
52
+ send_message(@men, @alice)
53
+ send_message(@alice, @men)
54
+
55
+ @men.messages.size.should be_equal(2)
56
+ @men.sent_messages.size.should be_equal(1)
57
+ @men.received_messages.size.should be_equal(1)
58
+ end
59
+ end
60
+
61
+ describe "delete messages" do
62
+
63
+ it "bob should have one deleted message from alice" do
64
+ @bob.messages.process do |m|
65
+ m.delete
66
+ end
67
+
68
+ @bob.messages.each do |m|
69
+ m.recipient_delete.should == true
70
+ m.sender_delete.should == false
71
+ end
72
+
73
+ @bob.deleted_messages.count.should == 1
74
+ @bob.messages.count.should == 0
75
+ end
76
+
77
+ it "received_messages and sent_messages should work with .process method" do
78
+ @bob.sent_messages.count.should == 1
79
+ @alice.received_messages.count.should == 1
80
+
81
+ @bob.sent_messages.process { |m| m.delete }
82
+ @bob.sent_messages.count.should == 0
83
+ @alice.received_messages.count.should == 1
84
+
85
+ @alice.received_messages.process { |m| m.delete }
86
+ @alice.received_messages.count.should == 0
87
+ end
88
+
89
+ it "message should permanent delete" do
90
+ @alice.messages.process { |m| m.delete }
91
+ @alice.messages.count.should == 0
92
+
93
+ @alice.deleted_messages.count.should == 1
94
+ @alice.deleted_messages.process { |m| m.delete }
95
+ @alice.deleted_messages.count.should == 0
96
+
97
+ @message.reload
98
+ @message.recipient_permanent_delete.should == true
99
+
100
+ @bob.sent_messages.count.should == 1
101
+ end
102
+
103
+ it "pat should not able to delete message" do
104
+ lambda { @pat.delete_message(@message) }.should raise_error
105
+ end
106
+ end
107
+
108
+ describe "restore message" do
109
+ it "alice should restore message" do
110
+ @alice.received_messages.process { |m| m.delete }
111
+ @alice.restore_message(@message.reload)
112
+ @alice.received_messages.count.should == 1
113
+ end
114
+
115
+ it "should works with relation" do
116
+ @alice.received_messages.process { |m| m.delete }
117
+ @alice.received_messages.count.should == 0
118
+ @alice.deleted_messages.process { |m| m.restore }
119
+ @alice.received_messages.count.should == 1
120
+ end
121
+
122
+ it "pat should not able to restore message" do
123
+ lambda { @pat.restore_message(@message) }.should raise_error
124
+ end
125
+ end
126
+
127
+ describe "read/unread feature" do
128
+ it "alice should have one unread message from bob" do
129
+ @alice.messages.are_from(@bob).unreaded.count.should == 1
130
+ @alice.messages.are_from(@bob).readed.count.should == 0
131
+ end
132
+
133
+ it "alice should able to read message from bob" do
134
+ @alice.messages.are_from(@bob).first.read
135
+ @alice.messages.are_from(@bob).unreaded.count.should == 0
136
+ end
137
+
138
+ it "alice should able to unread message from bob" do
139
+ @alice.messages.are_from(@bob).first.read
140
+ @alice.messages.are_from(@bob).first.unread
141
+ @alice.messages.are_from(@bob).unreaded.count.should == 1
142
+ end
143
+
144
+ it "alice should able to get datetime when he read bob message" do
145
+ @alice.messages.are_from(@bob).first.read
146
+ read_datetime = @alice.messages.are_from(@bob).first.updated_at
147
+ @alice.messages.are_from(@bob).reorder("updated_at asc").first.updated_at.should == read_datetime
148
+ end
149
+ end
150
+
151
+ it "finds proper message" do
152
+ @bob.messages.find(@message.id) == @message
153
+ end
154
+
155
+ it "message should have proper body" do
156
+ @bob.messages.count.should == 1
157
+ @bob.messages.first.body == "Body"
158
+ end
159
+
160
+ # describe "conversation" do
161
+ # it "bob send message to alice, and alice reply to bob message and show proper tree" do
162
+ # @reply_message = @alice.reply_to(@message, "Re: Topic", "Body")
163
+
164
+ # @reply_message.conversation.size.should == 2
165
+ # @reply_message.conversation.last.topic.should == "Topic"
166
+ # @reply_message.conversation.first.topic.should == "Re: Topic"
167
+ # end
168
+
169
+ # it "bob send message to alice, alice answer, and bob answer for alice answer" do
170
+ # @reply_message = @alice.reply_to(@message, "Re: Topic", "Body")
171
+ # @reply_reply_message = @bob.reply_to(@reply_message, "Re: Re: Topic", "Body")
172
+
173
+ # [@message, @reply_message, @reply_reply_message].each do |m|
174
+ # m.conversation.size.should == 3
175
+ # end
176
+
177
+ # @message.conversation.first.should == @reply_reply_message
178
+ # @reply_reply_message.conversation.first.should == @reply_reply_message
179
+ # end
180
+ # end
181
+
182
+ # describe "conversations" do
183
+ # before do
184
+ # @reply_message = @message.reply("Re: Topic", "Body")
185
+ # @reply_reply_message = @reply_message.reply("Re: Re: Topic", "Body")
186
+ # end
187
+
188
+ # it "bob send message to alice and alice reply" do
189
+ # @bob.messages.conversations.should == [@reply_reply_message]
190
+ # @reply_message.conversation.should == [@reply_reply_message, @reply_message, @message]
191
+ # end
192
+
193
+ # it "show conversations in proper order" do
194
+ # @sec_message = @bob.send_message(@alice, "Hi", "Alice!")
195
+ # @sec_reply = @sec_message.reply("Re: Hi", "Fine!")
196
+ # @bob.received_messages.conversations.map(&:id).should == [@sec_reply.id, @reply_reply_message.id]
197
+ # @sec_reply.conversation.to_a.should == [@sec_reply, @sec_message]
198
+ # end
199
+ # end
200
+
201
+ # describe "search text from messages" do
202
+ # before do
203
+ # @reply_message = @message.reply("Re: Topic", "Body : I am fine")
204
+ # @reply_reply_message = @reply_message.reply("Re: Re: Topic", "Fine too")
205
+ # end
206
+
207
+ # it "bob should be able to search text from messages" do
208
+ # recordset = @bob.messages.search("I am fine")
209
+ # recordset.count.should == 1
210
+ # recordset.should_not be_nil
211
+ # end
212
+ # end
213
+
214
+ # describe "send messages with hash" do
215
+ # it "send message with hash" do
216
+ # @message = @bob.send_message(@alice, {:body => "Body", :topic => "Topic"})
217
+ # @message.topic.should == "Topic"
218
+ # @message.body.should == "Body"
219
+ # end
220
+ # end
221
+
222
+ it "messages should return in right order :created_at" do
223
+ @message = send_message(@bob, @alice, "Example", "Example Body")
224
+ @alice.messages.last.body.should == "Body"
225
+ end
226
+
227
+ it "received_messages should return unloaded messages" do
228
+ @alice.received_messages.loaded?.should be_false
229
+ end
230
+
231
+ it "sent_messages should return unloaded messages" do
232
+ @bob.sent_messages.loaded?.should be_false
233
+ end
234
+
235
+ describe "send messages between two different models (the same id)" do
236
+ it "should have the same id" do
237
+ @alice.id.should be_equal(@admin.id)
238
+ end
239
+
240
+ it "bob send message to admin (different model) with the same id" do
241
+ @bob.send_message(@alice, "hello", "alice")
242
+ @alice.messages.are_to(@alice).size.should be_equal(2)
243
+ @alice.messages.are_to(@admin).size.should be_equal(0)
244
+ end
245
+
246
+ it "admin send message to bob" do
247
+ @admin.send_message(@bob, "hello", "bob")
248
+ @bob.messages.are_from(@admin).size.should be_equal(1)
249
+ @bob.messages.are_from(@alice).size.should be_equal(0)
250
+ end
251
+ end
252
+
253
+ end