rails-alerter 0.0.5

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.
Files changed (89) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +36 -0
  3. data/.rspec +2 -0
  4. data/.ruby-gemset +1 -0
  5. data/.ruby-version +1 -0
  6. data/Appraisals +11 -0
  7. data/Gemfile +4 -0
  8. data/LICENSE +22 -0
  9. data/LICENSE.txt +22 -0
  10. data/README.md +111 -0
  11. data/Rakefile +10 -0
  12. data/alerter.gemspec +60 -0
  13. data/app/builders/alerter/base_builder.rb +26 -0
  14. data/app/builders/alerter/message_builder.rb +16 -0
  15. data/app/builders/alerter/receipt_builder.rb +13 -0
  16. data/app/mailers/alerter/base_mailer.rb +15 -0
  17. data/app/mailers/alerter/message_mailer.rb +12 -0
  18. data/app/models/alerter/mailbox.rb +74 -0
  19. data/app/models/alerter/message.rb +171 -0
  20. data/app/models/alerter/notification_type.rb +20 -0
  21. data/app/models/alerter/preference.rb +19 -0
  22. data/app/models/alerter/receipt.rb +89 -0
  23. data/app/views/alerter/message_mailer/new_message_email.html.erb +20 -0
  24. data/app/views/alerter/message_mailer/new_message_email.text.erb +10 -0
  25. data/db/migrate/20150821000000_create_alerter.rb +65 -0
  26. data/lib/alerter/cleaner.rb +9 -0
  27. data/lib/alerter/engine.rb +31 -0
  28. data/lib/alerter/message_dispatcher.rb +62 -0
  29. data/lib/alerter/models/notifiable.rb +174 -0
  30. data/lib/alerter/version.rb +3 -0
  31. data/lib/generators/alerter/install_generator.rb +37 -0
  32. data/lib/generators/alerter/namespacing_compatibility_generator.rb +24 -0
  33. data/lib/generators/alerter/templates/initializer.rb +29 -0
  34. data/lib/generators/alerter/views_generator.rb +11 -0
  35. data/lib/rails-alerter.rb +71 -0
  36. data/spec/alerter/message_dispatcher_spec.rb +101 -0
  37. data/spec/alerter_spec.rb +8 -0
  38. data/spec/dummy/.gitignore +5 -0
  39. data/spec/dummy/Rakefile +7 -0
  40. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  41. data/spec/dummy/app/controllers/home_controller.rb +4 -0
  42. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  43. data/spec/dummy/app/mailers/.gitkeep +0 -0
  44. data/spec/dummy/app/models/.gitkeep +0 -0
  45. data/spec/dummy/app/models/cylon.rb +4 -0
  46. data/spec/dummy/app/models/duck.rb +3 -0
  47. data/spec/dummy/app/models/user.rb +3 -0
  48. data/spec/dummy/app/views/home/index.html.haml +7 -0
  49. data/spec/dummy/app/views/layouts/application.html.haml +11 -0
  50. data/spec/dummy/config.ru +4 -0
  51. data/spec/dummy/config/application.rb +47 -0
  52. data/spec/dummy/config/boot.rb +10 -0
  53. data/spec/dummy/config/database.yml +24 -0
  54. data/spec/dummy/config/environment.rb +5 -0
  55. data/spec/dummy/config/environments/development.rb +29 -0
  56. data/spec/dummy/config/environments/production.rb +51 -0
  57. data/spec/dummy/config/environments/test.rb +37 -0
  58. data/spec/dummy/config/initializers/alerter.rb +26 -0
  59. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  60. data/spec/dummy/config/initializers/inflections.rb +10 -0
  61. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  62. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  63. data/spec/dummy/config/initializers/session_store.rb +8 -0
  64. data/spec/dummy/config/locales/en.yml +5 -0
  65. data/spec/dummy/config/routes.rb +58 -0
  66. data/spec/dummy/config/sunspot.yml +17 -0
  67. data/spec/dummy/db/migrate/20110228120600_create_users.rb +14 -0
  68. data/spec/dummy/db/migrate/20110306002940_create_ducks.rb +14 -0
  69. data/spec/dummy/db/migrate/20110306015107_create_cylons.rb +14 -0
  70. data/spec/dummy/db/schema.rb +77 -0
  71. data/spec/dummy/public/404.html +26 -0
  72. data/spec/dummy/public/422.html +26 -0
  73. data/spec/dummy/public/500.html +26 -0
  74. data/spec/dummy/public/favicon.ico +0 -0
  75. data/spec/dummy/public/index.html +239 -0
  76. data/spec/dummy/public/robots.txt +5 -0
  77. data/spec/dummy/public/uploads/testfile.txt +1 -0
  78. data/spec/dummy/script/rails +6 -0
  79. data/spec/factories/general.rb +37 -0
  80. data/spec/integration/message_and_receipt_spec.rb +42 -0
  81. data/spec/integration/navigation_spec.rb +8 -0
  82. data/spec/mailers/message_mailer_spec.rb +44 -0
  83. data/spec/models/alerter_models_messageable_spec.rb +311 -0
  84. data/spec/models/mailbox_spec.rb +55 -0
  85. data/spec/models/message_spec.rb +51 -0
  86. data/spec/models/preference_spec.rb +35 -0
  87. data/spec/models/receipt_spec.rb +61 -0
  88. data/spec/spec_helper.rb +54 -0
  89. metadata +330 -0
@@ -0,0 +1,171 @@
1
+ class Alerter::Message < ActiveRecord::Base
2
+ self.table_name = :alerter_messages
3
+
4
+ attr_accessor :recipients
5
+ attr_accessible :message, :subject, :global, :expires if Alerter.protected_attributes?
6
+
7
+ belongs_to :sender, :polymorphic => :true
8
+ belongs_to :notified_object, :polymorphic => :true
9
+ belongs_to :notification_type
10
+ has_many :receipts, :dependent => :destroy, :class_name => "Alerter::Receipt"
11
+
12
+ validates :notification_type, :presence => true
13
+ validates :short_msg, :presence => true,
14
+ :length => { :maximum => Alerter.short_msg_length }
15
+ validates :long_msg, :presence => true,
16
+ :length => { :maximum => Alerter.long_msg_length }
17
+
18
+ scope :receipts, lambda { |recipient|
19
+ joins(:receipts).where('Alerter_receipts.receiver_id' => recipient.id,'Alerter_receipts.receiver_type' => recipient.class.base_class.to_s)
20
+ }
21
+
22
+ scope :inbox, lambda {|recipient|
23
+ receipts(recipient).merge(Alerter::Receipt.inbox.not_deleted)
24
+ }
25
+
26
+ scope :trash, lambda {|recipient|
27
+ receipts(recipient).merge(Alerter::Receipt.deleted)
28
+ }
29
+
30
+ scope :unread, lambda {
31
+ joins(:receipts).where('Alerter_receipts.is_read' => false)
32
+ }
33
+ scope :read, lambda {
34
+ joins(:receipts).where('Alerter_receipts.is_read' => true)
35
+ }
36
+ scope :global, lambda {
37
+ where(:global => true)
38
+ }
39
+ scope :expired, lambda {
40
+ where("Alerter_messages.expires < ?", Time.now)
41
+ }
42
+ scope :unexpired, lambda {
43
+ where("Alerter_messages.expires is NULL OR Alerter_messages.expires > ?", Time.now)
44
+ }
45
+
46
+ class << self
47
+ def message_all(recipients, short_msg, long_msg, notification_type_name, sanitize_text = true)
48
+ message = Alerter::MessageBuilder.new({
49
+ :recipients => recipients,
50
+ :short_msg => short_msg,
51
+ :long_msg => long_msg,
52
+ :notification_type => Alerter::NotificationType.find_or_create_by(name: notification_type_name),
53
+ }).build
54
+ message.save!
55
+ message.deliver sanitize_text
56
+ end
57
+ end
58
+
59
+ def expired?
60
+ expires.present? && (expires < Time.now)
61
+ end
62
+
63
+ def expire!
64
+ unless expired?
65
+ expire
66
+ save
67
+ end
68
+ end
69
+
70
+ def expire
71
+ unless expired?
72
+ self.expires = Time.now - 1.second
73
+ end
74
+ end
75
+
76
+ #Delivers a Notification. USE NOT RECOMENDED.
77
+ #Use Alerter::Models::Message.notify and Notification.notify_all instead.
78
+ def deliver(should_clean = true)
79
+ clean if should_clean
80
+ temp_receipts = recipients.map { |r| build_receipt(r, 'inbox', false) }
81
+ if temp_receipts.all?(&:valid?)
82
+ temp_receipts.each(&:save!) #Save receipts
83
+ Alerter::MessageDispatcher.new(self, recipients).call
84
+ #self.recipients = nil
85
+ end
86
+
87
+ return temp_receipts if temp_receipts.size > 1
88
+ temp_receipts.first
89
+ end
90
+
91
+ #Returns the recipients of the Notification
92
+ def recipients
93
+ return Array.wrap(@recipients) unless @recipients.blank?
94
+ @recipients = receipts.map { |receipt| receipt.receiver }
95
+ end
96
+
97
+ #Returns the receipt for the participant
98
+ def receipt_for(participant)
99
+ #Alerter::Receipt.notification(self).recipient(participant)
100
+ self.receipts.recipient(participant)
101
+ end
102
+
103
+ #Returns the receipt for the participant. Alias for receipt_for(participant)
104
+ def receipts_for(participant)
105
+ receipt_for(participant)
106
+ end
107
+
108
+ #Returns if the participant have read the Notification
109
+ def is_unread?(participant)
110
+ return false if participant.nil?
111
+ !receipt_for(participant).first.is_read
112
+ end
113
+
114
+ def is_read?(participant)
115
+ !is_unread?(participant)
116
+ end
117
+
118
+ #Returns if the participant have deleted the Notification
119
+ def is_deleted?(participant)
120
+ return false if participant.nil?
121
+ return receipt_for(participant).first.deleted
122
+ end
123
+
124
+ #Mark the notification as read
125
+ def mark_as_read(participant)
126
+ return if participant.nil?
127
+ receipt_for(participant).mark_as_read
128
+ end
129
+
130
+ #Mark the notification as unread
131
+ def mark_as_unread(participant)
132
+ return if participant.nil?
133
+ receipt_for(participant).mark_as_unread
134
+ end
135
+
136
+
137
+ #Mark the notification as deleted for one of the participant
138
+ def mark_as_deleted(participant)
139
+ return if participant.nil?
140
+ return receipt_for(participant).mark_as_deleted
141
+ end
142
+
143
+ #Mark the notification as not deleted for one of the participant
144
+ def mark_as_not_deleted(participant)
145
+ return if participant.nil?
146
+ return receipt_for(participant).mark_as_not_deleted
147
+ end
148
+
149
+ #Sanitizes the body and subject
150
+ def clean
151
+ self.short_msg = sanitize(short_msg)
152
+ self.long_msg = sanitize(long_msg)
153
+ end
154
+
155
+
156
+ def sanitize(text)
157
+ ::Alerter::Cleaner.instance.sanitize(text)
158
+ end
159
+
160
+ private
161
+
162
+ def build_receipt(receiver, mailbox_type, is_read = false)
163
+ Alerter::ReceiptBuilder.new({
164
+ :message => self,
165
+ :mailbox_type => mailbox_type,
166
+ :receiver => receiver,
167
+ :is_read => is_read
168
+ }).build
169
+ end
170
+
171
+ end
@@ -0,0 +1,20 @@
1
+ class Alerter::NotificationType < ActiveRecord::Base
2
+ self.table_name = :alerter_notification_types
3
+
4
+
5
+ attr_accessible :name if Alerter.protected_attributes?
6
+
7
+ #belongs_to :message
8
+ has_many :preferences
9
+ has_many :messages
10
+ before_save :check_notification_type
11
+
12
+
13
+ validates_uniqueness_of :name
14
+
15
+ protected
16
+ def check_notification_type
17
+ Alerter.available_notification_types.include?(self.name)
18
+ end
19
+
20
+ end
@@ -0,0 +1,19 @@
1
+ class Alerter::Preference < ActiveRecord::Base
2
+ self.table_name = :alerter_preferences
3
+ attr_accessible :alert_methods if Alerter.protected_attributes?
4
+
5
+ belongs_to :notifiable, :polymorphic => :true
6
+ belongs_to :notification_type
7
+
8
+ validates :notification_type_id, uniqueness: { scope: :notifiable_id }
9
+
10
+ serialize :alert_methods, Array
11
+
12
+ validate :alert_methods do
13
+ unless self.methods.nil? || self.alert_methods.count == 0 || (self.alert_methods - Alerter::available_notification_methods).empty?
14
+ errors.add(:alert_methods, "Must be only: #{Alerter::available_notification_methods.join(", ")}")
15
+ end
16
+ end
17
+
18
+
19
+ end
@@ -0,0 +1,89 @@
1
+ class Alerter::Receipt < ActiveRecord::Base
2
+ self.table_name = :alerter_receipts
3
+ attr_accessible :is_read, :deleted if Alerter.protected_attributes?
4
+
5
+ belongs_to :receiver, :polymorphic => :true
6
+ belongs_to :message, :class_name => "Alerter::Message", :foreign_key => "message_id"
7
+
8
+ validates_presence_of :receiver
9
+
10
+ scope :recipient, lambda { |recipient|
11
+ where(:receiver_id => recipient.id, :receiver_type => recipient.class.base_class.to_s)
12
+ }
13
+
14
+ scope :inbox, lambda {
15
+ where(:mailbox_type => "inbox")
16
+ }
17
+ scope :deleted, lambda {
18
+ where(:deleted => true)
19
+ }
20
+ scope :not_deleted, lambda {
21
+ where(:deleted => false)
22
+ }
23
+ scope :is_read, lambda {
24
+ where(:is_read => true)
25
+ }
26
+ scope :is_unread, lambda {
27
+ where(:is_read => false)
28
+ }
29
+
30
+
31
+ class << self
32
+ #Marks all the receipts from the relation as read
33
+ def mark_as_read(options={})
34
+ update_receipts({:is_read => true}, options)
35
+ end
36
+
37
+ #Marks all the receipts from the relation as unread
38
+ def mark_as_unread(options={})
39
+ update_receipts({:is_read => false}, options)
40
+ end
41
+
42
+ #Marks the receipt as deleted
43
+ def mark_as_deleted(options={})
44
+ update_receipts({:deleted => true}, options)
45
+ end
46
+
47
+ #Marks the receipt as not deleted
48
+ def mark_as_not_deleted(options={})
49
+ update_receipts({:deleted => false}, options)
50
+ end
51
+
52
+
53
+ def update_receipts(updates, options={})
54
+ ids = where(options).map { |rcp| rcp.id }
55
+ unless ids.empty?
56
+ sql = ids.map { "#{table_name}.id = ? " }.join(' OR ')
57
+ conditions = [sql].concat(ids)
58
+ Alerter::Receipt.where(conditions).update_all(updates)
59
+ end
60
+ end
61
+ end
62
+
63
+
64
+ #Marks the receipt as deleted
65
+ def mark_as_deleted
66
+ update_attributes(:deleted => true)
67
+ end
68
+
69
+ #Marks the receipt as not deleted
70
+ def mark_as_not_deleted
71
+ update_attributes(:deleted => false)
72
+ end
73
+
74
+ #Marks the receipt as read
75
+ def mark_as_read
76
+ update_attributes(:is_read => true)
77
+ end
78
+
79
+ #Marks the receipt as unread
80
+ def mark_as_unread
81
+ update_attributes(:is_read => false)
82
+ end
83
+
84
+ #Returns if the participant has read the Notification
85
+ def is_unread?
86
+ !is_read
87
+ end
88
+
89
+ end
@@ -0,0 +1,20 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
5
+ </head>
6
+ <body>
7
+ <h1>You have been alerted!</h1>
8
+ <p>
9
+ You have received a new message:
10
+ </p>
11
+ <blockquote>
12
+ <p>
13
+ <%= raw @message.long_msg %>
14
+ </p>
15
+ </blockquote>
16
+ <p>
17
+ Visit <%= link_to Alerter.root_url, Alerter.root_url %> and go to your inbox for more info.
18
+ </p>
19
+ </body>
20
+ </html>
@@ -0,0 +1,10 @@
1
+ You have been alerted!
2
+ ===============================================
3
+
4
+ You have received a new message:
5
+
6
+ -----------------------------------------------
7
+ <%= @message.long_msg.html_safe? ? @message.long_msg : strip_tags(@message.long_msg) %>
8
+ -----------------------------------------------
9
+
10
+ Visit <%= Alerter.root_url %> and go to your inbox for more info.
@@ -0,0 +1,65 @@
1
+ class CreateAlerter < ActiveRecord::Migration
2
+ def self.up
3
+ #Tables
4
+
5
+ #Receipts
6
+ create_table :alerter_receipts do |t|
7
+ t.references :receiver, :polymorphic => true
8
+ t.column :message_id, :integer, :null => false
9
+ t.column :is_read, :boolean, :default => false
10
+ t.column :deleted, :boolean, :default => false
11
+ t.column :mailbox_type, :string, :limit => 25
12
+ t.column :created_at, :datetime, :null => false
13
+ t.column :updated_at, :datetime, :null => false
14
+ end
15
+ #Messages
16
+ create_table :alerter_messages do |t|
17
+ t.column :type, :string
18
+ t.column :short_msg, :string, :default => ""
19
+ t.column :long_msg, :text, :default => ""
20
+ t.column :draft, :boolean, :default => false
21
+ t.string :notification_code, :default => nil
22
+ t.references :notified_object, :polymorphic => true
23
+ t.references :notification_type
24
+ t.column :attachment, :string
25
+ t.column :updated_at, :datetime, :null => false
26
+ t.column :created_at, :datetime, :null => false
27
+ t.boolean :global, default: false
28
+ t.datetime :expires
29
+ end
30
+
31
+ #preferences
32
+ create_table :alerter_preferences do |t|
33
+ t.references :notification_type
34
+ t.references :notifiable, :polymorphic => true
35
+ t.column :alert_methods, :text
36
+ end
37
+
38
+ #Notification Types
39
+ create_table :alerter_notification_types do |t|
40
+ t.column :name, :string, :null => :false
41
+ end
42
+
43
+ #Indexes
44
+ #Receipts
45
+ add_index "alerter_receipts","message_id"
46
+
47
+ #Foreign keys
48
+ #Conversations
49
+ #Receipts
50
+ add_foreign_key "alerter_receipts", "alerter_messages", :name => "receipts_on_message_id", :column => "message_id"
51
+
52
+ end
53
+
54
+ def self.down
55
+ #Tables
56
+ remove_foreign_key "alerter_receipts", :name => "receipts_on_message_id"
57
+
58
+ #Indexes
59
+ drop_table :alerter_receipts
60
+ drop_table :alerter_messages
61
+ drop_table :alerter_notification_types
62
+ drop_table :alerter_preferences
63
+
64
+ end
65
+ end
@@ -0,0 +1,9 @@
1
+ require 'singleton'
2
+
3
+ module Alerter
4
+ class Cleaner
5
+ include Singleton
6
+ include ActionView::Helpers::SanitizeHelper
7
+
8
+ end
9
+ end
@@ -0,0 +1,31 @@
1
+ # Database foreign keys
2
+ require 'foreigner' if Rails.version < "4.2.0"
3
+ begin
4
+ require 'sunspot_rails'
5
+ rescue LoadError
6
+ end
7
+
8
+ module Alerter
9
+ class Engine < Rails::Engine
10
+ isolate_namespace Alerter
11
+
12
+ # config.generators do |g|
13
+ # g.test_framework :rspec
14
+ # g.fixture_replacement :factory_girl, :dir => 'spec/factories'
15
+ # end
16
+
17
+ initializer "alerter.models.notifiable" do
18
+ ActiveSupport.on_load(:active_record) do
19
+ extend Alerter::Models::Notifiable::ActiveRecordExtension
20
+ end
21
+ end
22
+
23
+ initializer :append_migrations do |app|
24
+ unless app.root.to_s.match root.to_s
25
+ config.paths["db/migrate"].expanded.each do |expanded_path|
26
+ app.config.paths["db/migrate"] << expanded_path
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,62 @@
1
+ module Alerter
2
+ class MethodNotImplemented < StandardError
3
+ def initialize(data)
4
+ @data = data
5
+ end
6
+ end
7
+
8
+ class MessageDispatcher
9
+
10
+ attr_reader :message, :recipients
11
+
12
+ def initialize(message, recipients)
13
+ @message, @recipients = message, recipients
14
+ end
15
+
16
+ def call
17
+ return false unless (Alerter.notification_method - Alerter.available_notification_methods).empty? # array subtraction to see if notification methods are in the available list
18
+ Alerter.notification_method.each do |method|
19
+ case method
20
+ when 'email'
21
+ if Alerter.mailer_wants_array
22
+ send_email(filtered_recipients(method))
23
+ else
24
+ filtered_recipients(method).each do |recipient|
25
+ send_email(recipient) if recipient.notification_methods(message.notification_type).include?(method) && recipient.email.present?
26
+ end
27
+ end
28
+ when 'none', 'ios_push', 'android_push', 'sms', 'twitter'
29
+
30
+ else
31
+ raise MethodNotImplemented.new(method)
32
+ end
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def mailer
39
+ klass = message.class.name.demodulize
40
+ method = "#{klass.downcase}_mailer".to_sym
41
+ Alerter.methods.include?(method) ? Alerter.send(method) : "#{message.class}Mailer".constantize
42
+ end
43
+
44
+ # recipients can be filtered on a notification type basis
45
+ def filtered_recipients(method)
46
+ recipients.each_with_object([]) do |recipient, array|
47
+ pref = recipient.preferences.find_by(notification_type: message.notification_type)
48
+ array << recipient if pref && recipient.notification_methods(message.notification_type).include?(method)
49
+ end
50
+ end
51
+
52
+
53
+ def send_email(recipient)
54
+ if Alerter.custom_email_delivery_proc
55
+ Alerter.custom_email_delivery_proc.call(mailer, message, recipient)
56
+ else
57
+ email = mailer.send_email(message, recipient)
58
+ email.respond_to?(:deliver_now) ? email.deliver_now : email.deliver
59
+ end
60
+ end
61
+ end
62
+ end