acts_as_messagable 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ .idea/
@@ -0,0 +1 @@
1
+ owngems
@@ -0,0 +1 @@
1
+ ruby-1.8.7
@@ -0,0 +1 @@
1
+ lib/**/*.rb --no-private
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in acts_as_messagable.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,201 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "{}"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright {yyyy} {name of copyright owner}
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Stefan Exner
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,29 @@
1
+ # ActsAsMessagable
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'acts_as_messagable'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install acts_as_messagable
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'acts_as_messagable/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'acts_as_messagable'
8
+ spec.version = ActsAsMessagable::VERSION
9
+ spec.authors = ['Stefan Exner']
10
+ spec.email = ['stex@sterex.de']
11
+ spec.description = %q{Handles Message sending between ActiveRecord instances}
12
+ spec.summary = %q{Inter-Model message sending}
13
+ spec.homepage = 'https://github.com/Stex/acts_as_messagable'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_development_dependency 'bundler', '~> 1.3'
22
+ spec.add_development_dependency 'rake'
23
+ spec.add_development_dependency 'yard'
24
+ spec.add_development_dependency 'redcarpet', '~> 2.3.0'
25
+
26
+ spec.add_dependency 'activerecord', '< 3'
27
+ end
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require File.dirname(__FILE__) + '/rails/init'
@@ -0,0 +1 @@
1
+ require 'acts_as_messagable/version'
@@ -0,0 +1,3 @@
1
+ module ActsAsMessagable
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,338 @@
1
+ module Stex
2
+ module Acts
3
+ module Messagable
4
+ #
5
+ # @return [Bool] +true+ if the given model +acts_as_messagable+
6
+ #
7
+ def self.messagable?(model)
8
+ model.respond_to?(:acts_as_messagable_options)
9
+ end
10
+
11
+ #
12
+ # Sets the class which is used to create messages in
13
+ # the system. Defaults to +Notification+
14
+ #
15
+ # @param [ActiveRecord::Base, String, Symbol] klass
16
+ # Class to be used for message handling
17
+ #
18
+ def self.message_class=(klass)
19
+ @@message_class_name = klass.to_s
20
+ end
21
+
22
+ #
23
+ # @return [ActiveRecord::Base] (Notification)
24
+ # The class used to create new messages in the system
25
+ #
26
+ def self.message_class
27
+ self.message_class_name.constantize
28
+ end
29
+
30
+ #
31
+ # @return [String]
32
+ # The message class' name
33
+ #
34
+ def self.message_class_name
35
+ @@message_class_name ||= 'Notification'
36
+ @@message_class_name.classify
37
+ end
38
+
39
+ #
40
+ # Helper method to run certain +acts_as_messagable+ options.
41
+ # It basically determines the value's class and either
42
+ # directly runs given Proc objects or tries to handle symbols
43
+ # as instance method. The +:if+ options on ActiveRecord validations
44
+ # work the same way.
45
+ #
46
+ # @param [Messagable] instance
47
+ # The sender or recipient the given option was defined in
48
+ #
49
+ # @param [Proc, String, Symbol] proc_or_symbol
50
+ # Either a Proc object or an instance method name.
51
+ # If a +String+ or +Symbol+ is given, a method with this name
52
+ # has to be defined in +instance+
53
+ #
54
+ # @param [Object] default (nil)
55
+ # If +proc_or_symbol+ is neither +Proc+ nor +String+ or +Symbol+,
56
+ # this value is returned instead.
57
+ #
58
+ # @return [Object] The executed method or +default+
59
+ #
60
+ def self.run_method(instance, proc_or_symbol, default = nil)
61
+ if proc_or_symbol.is_a?(Symbol) || proc_or_symbol.is_a?(String)
62
+ raise Exception.new("Expected #{proc_or_symbol} to be an instance method of #{instance.class.name}") unless instance.respond_to?(proc_or_symbol)
63
+ instance.send(proc_or_symbol)
64
+ elsif proc_or_symbol.is_a?(Proc)
65
+ proc_or_symbol.call(instance)
66
+ else
67
+ default
68
+ end
69
+ end
70
+
71
+ #
72
+ # Follows possible forward and optional recipient chains down
73
+ # to the white rabb... final recipient which then either handles
74
+ # the message hash or receives a message through the system
75
+ #
76
+ # @param [Array<Messagable>] recipients
77
+ # recipients to be inspected
78
+ #
79
+ # @return [Array<Messagable>]
80
+ # Messagables which do not have a +:forward+-option set.
81
+ #
82
+ def self.determine_message_recipients(recipients = [])
83
+ result = []
84
+
85
+ Array(recipients).each do |recipient_or_array|
86
+
87
+ #Determine optional recipients and add them to the result set
88
+ if recipient_or_array.is_a?(Array)
89
+ recipient = recipient_or_array.first
90
+ optional_recipients = Array(recipient_or_array.last).map { |i| recipient.messagable_accessor(:optional_recipients).assoc(i.to_sym).second }.flatten
91
+
92
+ result += determine_message_recipients(optional_recipients)
93
+ else
94
+ recipient = recipient_or_array
95
+ end
96
+
97
+ raise ArgumentError.new('Invalid Recipient: ' + recipient.inspect) unless Stex::Acts::Messagable.messagable?(recipient)
98
+
99
+ #Handle +forward_to+ options on the recipient record.
100
+ #If it's set, we don't want to add the current recipient to the result list,
101
+ #but rather the ones it's pointing at.
102
+ if recipient.messagable_accessor(:forward_to)
103
+ result += determine_message_recipients(recipient.messagable_accessor(:forward_to))
104
+ else
105
+ result << recipient
106
+ end
107
+ end
108
+
109
+ result
110
+ end
111
+
112
+ #
113
+ # Includes the class methods
114
+ #
115
+ # @private
116
+ #
117
+ def self.included(base)
118
+ base.class_eval do
119
+ base.send :extend, ClassMethods
120
+ end
121
+ end
122
+
123
+ module ClassMethods
124
+ #
125
+ # Adds the functionality to send and receive messages to this +ActiveRecord+ class
126
+ #
127
+ # @param [Hash] options
128
+ # Available Options and overrides for this model class
129
+ #
130
+ # @option options [Proc, Symbol] :sender_name ("Class.human_name #self.id")
131
+ # Proc or Proc Name to determine the sender name.
132
+ #
133
+ # @option options [Proc, Symbol] :recipient_name ("Class.human_name #self.id")
134
+ # Proc or Instance Method Name to determine the recipient name.
135
+ #
136
+ # @option options [Proc, Symbol] :forward_to
137
+ # Proc or Instance Method Name. If specified, the system will forward
138
+ # received messages to the method's return value instead of to
139
+ # the actual record
140
+ #
141
+ # @option options [Array<Array<Symbol, Proc|Symbol, String>>] :optional_recipients
142
+ # One ore more recipients that may be included in a message
143
+ # to an instance of this class. These should be selectable on the view part
144
+ # of the application, e.g. checkboxes next to the actual recipient.
145
+ # The elements are used as follows:
146
+ #
147
+ # 1. An identifier to access this optional recipient group,
148
+ # 2. The actual selector that should return the recipients,
149
+ # 3. A string or Proc that tells the system how to display this optional recipient
150
+ #
151
+ # @option options [Proc, Symbol] :handler
152
+ # If given, messages are sent to the given proc object / instance method instead of
153
+ # actually created in the database.
154
+ # The handler method has to accept 2 arguments, the message sender and a +Hash+ of options
155
+ # These options include:
156
+ # - :subject
157
+ # - :content
158
+ # - :additional_recipients (optional)
159
+ #
160
+ # @option options [Bool, Symbol, Proc] :store_additional_recipients
161
+ # If set or evaluating to +true+, all recipients (CC) are stored in a +Message+ record.
162
+ # This might make sense in cases when e.g. an email should contain a list of
163
+ # all the people that received it.
164
+ # If the value is a Symbol, the system will assume that there is an instance method with that name.
165
+ #
166
+ # @example Forwarding group messages to its students and optionally include the tutors
167
+ # acts_as_messagable :forward_to => :students,
168
+ # :optional_recipients => [[:tutors, lambda { |r| r.tutors }, Tutor.human_name(:count => 2)]]
169
+ # # The selector could simplified by +:tutors+ instead of the lambda expression
170
+ #
171
+ def acts_as_messagable(options = {})
172
+ klass = Stex::Acts::Messagable.message_class_name
173
+
174
+ #Add has_many associations for easy message management
175
+ has_many :received_messages, :class_name => klass, :as => :recipient, :conditions => {:sender_copy => false}
176
+ has_many :unread_messages, :class_name => klass, :as => :recipient, :conditions => {:read_at => nil, :sender_copy => false}
177
+ has_many :read_messages, :class_name => klass, :as => :recipient, :conditions => ['read_at IS NOT NULL AND sender_copy = ?', false]
178
+ has_many :sent_messages, :class_name => klass, :as => :sender, :conditions => {:sender_copy => true}
179
+
180
+ cattr_accessor :acts_as_messagable_options
181
+
182
+ coptions = {}
183
+
184
+ #Sender and recipient procs
185
+ coptions[:sender_name] = options[:sender_name] || lambda { |r| "#{r.class.human_name}: #{r.id}" }
186
+ coptions[:recipient_name] = options[:recipient_name] || lambda { |r| "#{r.class.human_name}: #{r.id}" }
187
+
188
+ #Forward and message handler proc
189
+ coptions[:forward_to] = options[:forward_to] if options.has_key?(:forward_to)
190
+ coptions[:handler] = options[:handler] if options.has_key?(:handler)
191
+
192
+ #optional recipients
193
+ coptions[:optional_recipients] = options[:optional_recipients] if options[:optional_recipients]
194
+
195
+ coptions[:store_additional_recipients] = options[:store_additional_recipients]
196
+
197
+ self.acts_as_messagable_options = coptions.freeze
198
+
199
+ include Stex::Acts::Messagable::InstanceMethods
200
+ end
201
+ end
202
+
203
+ module InstanceMethods
204
+
205
+ #
206
+ # Accessor for acts_as_messagable options and helpers
207
+ #
208
+ # @param [String, Symbol] request
209
+ # Requested option or helper, available values are:
210
+ # - +:sender_name+ Returns a formatted representation of +self+
211
+ # which is used in message sender display fields
212
+ #
213
+ # - +:recipient_name+ Returns a formatted representation of +self+
214
+ # which is used in message recipient display fields
215
+ #
216
+ # - +:forward_to+ Returns the recipient(s) a message should be forwarded
217
+ # to instead of being delivered to +self+ or +nil+ if not defined (see #acts_as_messagable)
218
+ #
219
+ # - +:optional_recipients+ Returns an Array of Array<Symbol, *Messagable, String>,
220
+ # for more details see #acts_as_messagable
221
+ #
222
+ # - +:store_additional_recipients+ Returns true|false, depending on the given option
223
+ # in #acts_as_messagable
224
+ #
225
+ def messagable_accessor(request)
226
+ @cached_accessors ||= {}
227
+ options = self.class.acts_as_messagable_options
228
+
229
+ @cached_accessors[request.to_sym] ||= case request.to_sym
230
+ when :sender_name
231
+ Stex::Acts::Messagable.run_method(self, options[:sender_name])
232
+ when :recipient_name
233
+ Stex::Acts::Messagable.run_method(self, options[:recipient_name])
234
+ when :forward_to
235
+ Stex::Acts::Messagable.run_method(self, options[:forward_to])
236
+ when :optional_recipients
237
+ Array(options[:optional_recipients]).map do |identifier, proc, name|
238
+ recipient_name = name.is_a?(String) ? name : Stex::Acts::Messagable.run_method(self, name)
239
+ [identifier, Stex::Acts::Messagable.run_method(self, proc), recipient_name]
240
+ end
241
+ when :store_additional_recipients
242
+ Stex::Acts::Messagable.run_method(self, options[:store_additional_recipients],
243
+ options[:store_additional_recipients])
244
+ else
245
+ raise ArgumentError.new('Invalid Request Argument: ' + request.to_s)
246
+ end
247
+ end
248
+
249
+ #
250
+ # Sends a message from +self+ to the given list of recipients
251
+ # The whole sending happens in a transaction, so if one message creation
252
+ # fails, none of them are sent.
253
+ #
254
+ # @param [Array<Messagable>|Array<Hash>|Messagable] recipients
255
+ # The message recipient(s), all have to be +messagable+
256
+ # The value will be handled as an Array in every case, so if you only
257
+ # have one recipient, you don't have to put it in brackets.
258
+ # The array elements may either be +messagable+ objects or arrays,
259
+ # mapping +messagable+ to Array<optional recipient identifiers>, see example.
260
+ #
261
+ # @param [String] subject
262
+ # The message message's subject
263
+ #
264
+ # @param [String] content
265
+ # The message message
266
+ #
267
+ # @example Recipient with optional recipient (see #acts_as_messagable)
268
+ # my_user.send_message([[my_group, [:tutors]]], 'Subject', 'Body')
269
+ #
270
+ def send_message(recipients, subject, content, options = {})
271
+ recipients_with_cause = []
272
+ all_recipients = []
273
+
274
+ #Create a new list of recipients together with the original recipients
275
+ #that caused their existance in this list.
276
+ #Also, determine whether additional recipients should be saved or not
277
+ Array(recipients).each do |recipient_or_array|
278
+ local_recipients = Stex::Acts::Messagable.determine_message_recipients([recipient_or_array])
279
+ recipient = recipient_or_array.is_a?(Array) ? recipient_or_array.first : recipient_or_array
280
+
281
+ all_recipients += local_recipients
282
+
283
+ recipients_with_cause << {:cause => recipient,
284
+ :store_additionals => recipient.class.acts_as_messagable_options[:store_additional_recipients] && local_recipients.size > 1,
285
+ :recipients => local_recipients.uniq}
286
+
287
+ end
288
+
289
+ all_recipients.uniq!
290
+ processed_recipients = []
291
+
292
+ Stex::Acts::Messagable.message_class.transaction do
293
+ recipients_with_cause.each do |recipient_data|
294
+ recipient_data[:recipients].each do |recipient|
295
+
296
+ next if processed_recipients.include?(recipient)
297
+ processed_recipients << recipient
298
+
299
+ message = options.clone
300
+ message[:subject] = subject
301
+ message[:content] = content
302
+ message[:original_recipients] = Array(recipients)
303
+
304
+ #If the original recipient was set to store additional recipients, we store
305
+ #all recipients that were caused by the same original recipient
306
+ if recipient_data[:store_additionals]
307
+ message[:additional_recipients] = recipient_data[:recipients] - [recipient]
308
+ # If the recipient itself was set to store additional recipients, we store
309
+ # all recipients this message is sent to, regardless of the original recipients
310
+ elsif recipient.class.acts_as_messagable_options[:store_additional_recipients] && final_recipients.size > 1
311
+ message[:additional_recipients] = all_recipients - [recipient]
312
+ end
313
+
314
+ #It might happen that a messagable does not actually want to
315
+ #receive messages, but instead do something different.
316
+ if recipient.class.acts_as_messagable_options[:handler]
317
+ recipient.class.acts_as_messagable_options[:handler].call(self, message)
318
+ else
319
+ #Otherwise, a new message is created and sent to the given recipient
320
+ Stex::Acts::Messagable.message_class.create!(message.merge({:sender => self, :recipient => recipient}))
321
+ end
322
+ end
323
+ end
324
+
325
+ #Create a copy of the message for the sender
326
+ Stex::Acts::Messagable.message_class.create!(:subject => subject,
327
+ :content => content,
328
+ :original_recipients => Array(recipients),
329
+ :additional_recipients => all_recipients,
330
+ :sender => self,
331
+ :recipient => self,
332
+ :sender_copy => true)
333
+ end
334
+ end
335
+ end
336
+ end
337
+ end
338
+ end
@@ -0,0 +1,181 @@
1
+ module Stex
2
+ module Acts
3
+ module Messagable
4
+ module Extensions
5
+ def self.included(base)
6
+ base.class_eval do
7
+ base.send :extend, ClassMethods
8
+ end
9
+ end
10
+
11
+ #----------------------------------------------------------------
12
+ # Class Methods
13
+ #----------------------------------------------------------------
14
+
15
+ module ClassMethods
16
+ def messagable?(recipient)
17
+ Stex::Acts::Messagable.messagable?(recipient)
18
+ end
19
+ end
20
+
21
+ #----------------------------------------------------------------
22
+ # Instance Methods
23
+ #----------------------------------------------------------------
24
+
25
+
26
+ #
27
+ # @return [Bool] +true+ if the notification was marked as read before
28
+ #
29
+ def read?
30
+ read_at.present?
31
+ end
32
+
33
+ #
34
+ # @see #read?
35
+ #
36
+ def unread?
37
+ !read?
38
+ end
39
+
40
+ #
41
+ # Sets the +read_at+ attribute to the current time
42
+ # and saves the record without validations
43
+ #
44
+ def mark_as_read!
45
+ self.read_at = Time.now
46
+ save(false)
47
+ end
48
+
49
+ #----------------------------------------------------------------
50
+ # Metadata Handling
51
+ #----------------------------------------------------------------
52
+
53
+ def url
54
+ metadata[:url]
55
+ end
56
+
57
+ def url=(url)
58
+ metadata[:url] = url
59
+ end
60
+
61
+ #
62
+ # @return [Messagable]
63
+ # The additional recipients which were determined by the +send_message+ function
64
+ #
65
+ def additional_recipients
66
+ meta_record_list(:additional_recipients)
67
+ end
68
+
69
+ #
70
+ # Sets additional recipients in this message's metadata.
71
+ # This is automatically done by the +send_message+ function.
72
+ #
73
+ # @param [Array<Messagable>] recipients
74
+ # The list of additional recipients
75
+ #
76
+ def additional_recipients=(recipients = [])
77
+ meta_record_list_update(:additional_recipients, recipients)
78
+ end
79
+
80
+ #
81
+ # @return [Array<String>]
82
+ # The +recipient_name+s of all additional recipients. This uses the
83
+ # +recipient_name+ option set in the recipient's model class.
84
+ #
85
+ def additional_recipient_names
86
+ additional_recipients.map {|r| r.messagable_accessor(:recipient_name) }
87
+ end
88
+
89
+ #
90
+ # @return [Array<Messagable>]
91
+ # The original recipients which were initially given to the
92
+ # +send_message+ function (so no derived recipients)
93
+ #
94
+ def original_recipients
95
+ result = []
96
+ metadata[:original_recipients].each do |recipient|
97
+
98
+ #Optional recipients included
99
+ if recipient.first.is_a?(Array)
100
+ recipient_class, recipient_id = recipient.first
101
+ result << [recipient_class.constantize.find(recipient_id), recipient.last]
102
+ else
103
+ recipient_class, recipient_id = recipient
104
+ result << recipient_class.constantize.find(recipient_id)
105
+ end
106
+ end
107
+ result
108
+ end
109
+
110
+ #
111
+ # @return [Array<String>]
112
+ # The names of the +original_recipients+,
113
+ # uses the +recipient_name+ option set in the recipient's model class
114
+ #
115
+ def original_recipient_names
116
+ original_recipients.map do |recipient_or_array|
117
+ if recipient_or_array.is_a?(Array)
118
+ recipient = recipient_or_array.first
119
+ optional_names = recipient_or_array.last.map {|i| recipient.messagable_accessor(:optional_recipients).assoc(i.to_sym).third }
120
+ [recipient.messagable_accessor(:recipient_name), *optional_names]
121
+ else
122
+ recipient_or_array.messagable_accessor(:recipient_name)
123
+ end
124
+ end
125
+ end
126
+
127
+ #
128
+ # Stores the original recipients of this notification.
129
+ #
130
+ # @param recipients
131
+ # An array of +Messagable+ which may also include optional recipients
132
+ # as symbols (see +{Stex::Acts::Messagable#send_notification}+)
133
+ #
134
+ def original_recipients=(recipients = [])
135
+ result = []
136
+
137
+ recipients.each do |recipient_or_array|
138
+ if recipient_or_array.is_a?(Array)
139
+ recipient = recipient_or_array.first
140
+ result << [[recipient.class.to_s, recipient.id], recipient_or_array.last]
141
+ else
142
+ result << [recipient_or_array.class.to_s, recipient_or_array.id]
143
+ end
144
+ end
145
+
146
+ metadata[:original_recipients] = result
147
+ end
148
+
149
+ private
150
+
151
+ #
152
+ # @private
153
+ #
154
+ def meta_record_list(key)
155
+ return [] unless metadata[key]
156
+ @meta_record_list ||= {}
157
+ @meta_record_list[key] ||= metadata[key].map {|class_name, id| class_name.constantize.find(id)}
158
+ end
159
+
160
+ #
161
+ # @private
162
+ #
163
+ def meta_record_list_update(key, list = [])
164
+ @meta_record_list ||= {}
165
+ @meta_record_list[key] = list
166
+ metadata[key] = @meta_record_list[key].map {|r| [r.class.to_s, r.id] }
167
+ end
168
+
169
+ #
170
+ # Accessor method for all metadata
171
+ # Metadata can then be accessed using []
172
+ #
173
+ # @private
174
+ #
175
+ def metadata
176
+ self.serialized_metadata ||= {}
177
+ end
178
+ end
179
+ end
180
+ end
181
+ end
@@ -0,0 +1,6 @@
1
+ require 'stex/acts/messagable'
2
+ require 'stex/acts/messagable/extensions'
3
+
4
+ ActiveRecord::Base.class_eval do
5
+ include Stex::Acts::Messagable
6
+ end
metadata ADDED
@@ -0,0 +1,154 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: acts_as_messagable
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Stefan Exner
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2014-03-05 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: bundler
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ~>
27
+ - !ruby/object:Gem::Version
28
+ hash: 9
29
+ segments:
30
+ - 1
31
+ - 3
32
+ version: "1.3"
33
+ type: :development
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: rake
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ hash: 3
44
+ segments:
45
+ - 0
46
+ version: "0"
47
+ type: :development
48
+ version_requirements: *id002
49
+ - !ruby/object:Gem::Dependency
50
+ name: yard
51
+ prerelease: false
52
+ requirement: &id003 !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ hash: 3
58
+ segments:
59
+ - 0
60
+ version: "0"
61
+ type: :development
62
+ version_requirements: *id003
63
+ - !ruby/object:Gem::Dependency
64
+ name: redcarpet
65
+ prerelease: false
66
+ requirement: &id004 !ruby/object:Gem::Requirement
67
+ none: false
68
+ requirements:
69
+ - - ~>
70
+ - !ruby/object:Gem::Version
71
+ hash: 3
72
+ segments:
73
+ - 2
74
+ - 3
75
+ - 0
76
+ version: 2.3.0
77
+ type: :development
78
+ version_requirements: *id004
79
+ - !ruby/object:Gem::Dependency
80
+ name: activerecord
81
+ prerelease: false
82
+ requirement: &id005 !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - <
86
+ - !ruby/object:Gem::Version
87
+ hash: 5
88
+ segments:
89
+ - 3
90
+ version: "3"
91
+ type: :runtime
92
+ version_requirements: *id005
93
+ description: Handles Message sending between ActiveRecord instances
94
+ email:
95
+ - stex@sterex.de
96
+ executables: []
97
+
98
+ extensions: []
99
+
100
+ extra_rdoc_files: []
101
+
102
+ files:
103
+ - .gitignore
104
+ - .ruby-gemset
105
+ - .ruby-version
106
+ - .yardopts
107
+ - Gemfile
108
+ - LICENSE
109
+ - LICENSE.txt
110
+ - README.md
111
+ - Rakefile
112
+ - acts_as_messagable.gemspec
113
+ - init.rb
114
+ - lib/acts_as_messagable.rb
115
+ - lib/acts_as_messagable/version.rb
116
+ - lib/stex/acts/messagable.rb
117
+ - lib/stex/acts/messagable/extensions.rb
118
+ - rails/init.rb
119
+ homepage: https://github.com/Stex/acts_as_messagable
120
+ licenses:
121
+ - MIT
122
+ post_install_message:
123
+ rdoc_options: []
124
+
125
+ require_paths:
126
+ - lib
127
+ required_ruby_version: !ruby/object:Gem::Requirement
128
+ none: false
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ hash: 3
133
+ segments:
134
+ - 0
135
+ version: "0"
136
+ required_rubygems_version: !ruby/object:Gem::Requirement
137
+ none: false
138
+ requirements:
139
+ - - ">="
140
+ - !ruby/object:Gem::Version
141
+ hash: 3
142
+ segments:
143
+ - 0
144
+ version: "0"
145
+ requirements: []
146
+
147
+ rubyforge_project:
148
+ rubygems_version: 1.7.2
149
+ signing_key:
150
+ specification_version: 3
151
+ summary: Inter-Model message sending
152
+ test_files: []
153
+
154
+ has_rdoc: