acts_as_chattable 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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