rails-alerter 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
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