voltron-notify 0.1.2

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 1dc5675852a76e757329016521d703d1d9c11c7a
4
+ data.tar.gz: 1aaa6e0a848e1cf5d8c1daa8a24312adf0980ca9
5
+ SHA512:
6
+ metadata.gz: 9e5bee262edb8cc3d12ff3c084767b526b77a936a6a43bcfb90248c9194be5436d4b92beeedfca215e7533897c63e4ce56b75b0c9fc8bb6d05e3c70567c3a0f7
7
+ data.tar.gz: f5d7d06d1231ec5918f07519f634824a700c0725e8b0a64c7a04f181da617f294ef1dbf30259bf0346e9d4acb5b6fe241eeaaac067a6ee23d1d57a1d78a093ed
data/.gitignore ADDED
@@ -0,0 +1,13 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ tmp/
10
+ log/
11
+ voltron-*.gem
12
+ *.bak
13
+ *.sqlite3
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.2.3
5
+ before_install: gem install bundler -v 1.12.5
@@ -0,0 +1,49 @@
1
+ # Contributor Code of Conduct
2
+
3
+ As contributors and maintainers of this project, and in the interest of
4
+ fostering an open and welcoming community, we pledge to respect all people who
5
+ contribute through reporting issues, posting feature requests, updating
6
+ documentation, submitting pull requests or patches, and other activities.
7
+
8
+ We are committed to making participation in this project a harassment-free
9
+ experience for everyone, regardless of level of experience, gender, gender
10
+ identity and expression, sexual orientation, disability, personal appearance,
11
+ body size, race, ethnicity, age, religion, or nationality.
12
+
13
+ Examples of unacceptable behavior by participants include:
14
+
15
+ * The use of sexualized language or imagery
16
+ * Personal attacks
17
+ * Trolling or insulting/derogatory comments
18
+ * Public or private harassment
19
+ * Publishing other's private information, such as physical or electronic
20
+ addresses, without explicit permission
21
+ * Other unethical or unprofessional conduct
22
+
23
+ Project maintainers have the right and responsibility to remove, edit, or
24
+ reject comments, commits, code, wiki edits, issues, and other contributions
25
+ that are not aligned to this Code of Conduct, or to ban temporarily or
26
+ permanently any contributor for other behaviors that they deem inappropriate,
27
+ threatening, offensive, or harmful.
28
+
29
+ By adopting this Code of Conduct, project maintainers commit themselves to
30
+ fairly and consistently applying these principles to every aspect of managing
31
+ this project. Project maintainers who do not follow or enforce the Code of
32
+ Conduct may be permanently removed from the project team.
33
+
34
+ This code of conduct applies both within project spaces and in public spaces
35
+ when an individual is representing the project or its community.
36
+
37
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
38
+ reported by contacting a project maintainer at eric.hainer@gmail.com. All
39
+ complaints will be reviewed and investigated and will result in a response that
40
+ is deemed necessary and appropriate to the circumstances. Maintainers are
41
+ obligated to maintain confidentiality with regard to the reporter of an
42
+ incident.
43
+
44
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage],
45
+ version 1.3.0, available at
46
+ [http://contributor-covenant.org/version/1/3/0/][version]
47
+
48
+ [homepage]: http://contributor-covenant.org
49
+ [version]: http://contributor-covenant.org/version/1/3/0/
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+ source 'http://gem.minow.io'
3
+
4
+ # Specify your gem's dependencies in voltron-notify.gemspec
5
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Eric Hainer
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,106 @@
1
+ [![Build Status](https://travis-ci.org/ehainer/voltron-notify.svg?branch=master)](https://travis-ci.org/ehainer/voltron-notify)
2
+
3
+ # Voltron::Notify
4
+
5
+ Voltron Notify is an attempt to join Twilio's SMS api with Rails' default mailer functionality into one single method.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'voltron-notify'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install voltron-notify
22
+
23
+ Then run the following to create the voltron.rb initializer (if not exists already) and add the notify config:
24
+
25
+ $ rails g voltron:notify:install
26
+
27
+ ## Usage
28
+
29
+ Once installed and configured, add `notifyable` at the top of any model you wish to be able to send notifications, such as:
30
+
31
+ ```ruby
32
+ class User < ActiveRecord::Base
33
+
34
+ notifyable
35
+
36
+ end
37
+ ```
38
+
39
+ `notifyable` will create a notifications association on whatever model it is called on. Once done, you can utilize Voltron Notify like so:
40
+
41
+ ```ruby
42
+ @user = User.find(1)
43
+
44
+ @user.notifications.create do |n|
45
+ # First argument is SMS message text, second argument is hash containing zero or more of: [:to, :from]
46
+ n.sms "This is my message", to: "1 (234) 567-8910"
47
+
48
+ # and/or ...
49
+
50
+ # First argument is email subject, remaining arguments can consist of [:to, :from] or any other param you'd like,
51
+ # they will all be converted to @variables for use in the mailer template
52
+ n.email "This is the mail subject", { to: "info@example.com" }, { param_one: "Hi there", param_two: "" }
53
+ end
54
+ ```
55
+
56
+ While you may specify the :to and :from as one of the arguments, by default the :from value of each notification type comes from `Voltron.config.notify.email_from` and `Voltron.config.notify.sms_from`. The value of :to by default will attempt to be retrieved by calling `.phone` or `.email` on the notifyable model itself. So given a User model with attributes (or methods) `email` and `phone`, the following will send notifications to those values:
57
+
58
+ ```ruby
59
+ @user = User.find(1) #<User id: 1, phone: "1234567890", email: "info@example.com", created_at: "2016-09-23 16:49:20", updated_at: "2016-09-23 16:49:20">
60
+
61
+ @user.notifications.create do |n|
62
+ n.sms "Hello from SMS" # Will send to +1 (123) 456-7890
63
+ n.email "Hello from Email" # Will send to info@example.com
64
+ end
65
+
66
+ # @user.notifications.build { |n| ... } ... followed by @user.save works the same way
67
+ ```
68
+
69
+ Optionally, you may pass a block to the `sms` or `email` methods that allows for additional functionality, like including attachments or overriding the `email` method default mailer/method:
70
+
71
+ ```ruby
72
+ @user.notifications.create do |n|
73
+ n.sms "Hello from SMS" do
74
+ attach "picture.jpg" # Attach an image using the rails asset pipeline by specifying just the filename
75
+ attach "http://www.someimagesite.com/example/demo/image.png" # Or just provide a url to a supported file beginning with "http"
76
+ end
77
+
78
+ n.email "Hello from Email" do
79
+ attach "picture.jpg" # Uses the asset pipeline like above
80
+ attach "http://www.example.com/picture.jpg" # This WILL NOT work, email attachments don't work that way
81
+
82
+ mailer SiteMailer # Default: Voltron::NotificationMailer
83
+ method :send_my_special_notification # Default: :notify
84
+ arguments @any, list, of.arguments, :you, would, @like # In this case, the arguments used by SiteMailer.send_my_special_notification()
85
+ end
86
+ end
87
+ ```
88
+
89
+ Note that both SMS and Email notifications have validations on the :to/:from fields, the email subject, and the SMS body text. Since `notifications` is an association, any errors in the actual notification content will bubble up, possibly preventing the `notifyable` model from saving. For that reason, it may be more logical to instead use a @notifyable.notifications.build / @notifyable.save syntax to properly handle errors that may occur.
90
+
91
+ ## Integration with ActiveJob
92
+
93
+ Voltron Notify supports sending both email (via deliver_later) and SMS (via Voltron::SmsJob and perform_later). To have all notifications be handled by ActiveJob in conjunction with Sidekiq/Resque/whatever you need only set the config value `Voltron.config.notify.use_queue` to `true`. If ActiveJob is configured properly notifications will send that way instead. You may also optionally set the delay for each notification by setting the value of `Voltron.config.notify.delay` to any time value (i.e. 5.minutes, 3.months, 0.seconds)
94
+
95
+ ## Development
96
+
97
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
98
+
99
+ ## Contributing
100
+
101
+ Bug reports and pull requests are welcome on GitHub at https://github.com/ehainer/voltron-notify. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
102
+
103
+ ## License
104
+
105
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
106
+
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,7 @@
1
+ class Voltron::SmsJob < ActiveJob::Base
2
+
3
+ def perform(sms)
4
+ sms.deliver_now
5
+ end
6
+
7
+ end
@@ -0,0 +1,13 @@
1
+ class Voltron::NotificationMailer < ApplicationMailer
2
+ default from: Voltron.config.notify.email_from
3
+
4
+ def notify(mail_args, var_args = {}, attachment_args = {})
5
+ # Make all passed in variables instance variables so they can be used in the template
6
+ var_args.each { |name, value| instance_variable_set "@#{name}", value }
7
+
8
+ # Add all of the attachments
9
+ attachment_args.each { |name, file| attachments[name] = File.read(file) }
10
+
11
+ mail mail_args
12
+ end
13
+ end
@@ -0,0 +1,102 @@
1
+ class Voltron::Notification::EmailNotification < ActiveRecord::Base
2
+
3
+ belongs_to :notification
4
+
5
+ after_initialize :setup
6
+
7
+ before_create :deliver_now, unless: :use_queue?
8
+
9
+ after_create :deliver_later, if: :use_queue?
10
+
11
+ attr_accessor :vars, :attachments
12
+
13
+ def setup
14
+ @request = []
15
+ @response = []
16
+ @vars ||= {}
17
+ @attachments ||= {}
18
+ @mailer_arguments = nil
19
+ self.mailer_class ||= "Voltron::NotificationMailer"
20
+ self.mailer_method ||= "notify"
21
+ end
22
+
23
+ def request
24
+ # Wrap entire request in container hash so that we can call deep_symbolize_keys on it (in case it's an array)
25
+ # Wrap entire request in array and flatten so we can be sure the result is an array
26
+ [{ request: (JSON.parse(request_json) rescue Hash.new) }.deep_symbolize_keys[:request]].flatten
27
+ end
28
+
29
+ def response
30
+ # Wrap entire response in container hash so that we can call deep_symbolize_keys on it (in case it's an array)
31
+ # Wrap entire response in array and flatten so we can be sure the result is an array
32
+ [{ response: (JSON.parse(response_json) rescue Hash.new) }.deep_symbolize_keys[:response]].flatten
33
+ end
34
+
35
+ def after_deliver
36
+ self.request_json = @request.to_json
37
+ self.response_json = @response.to_json
38
+ end
39
+
40
+ def deliver_now
41
+ mail.deliver_now
42
+ @response << ActionMailer::Base.deliveries.last
43
+ after_deliver
44
+ end
45
+
46
+ def deliver_later
47
+ @response << mail.deliver_later(wait: Voltron.config.notify.delay)
48
+ after_deliver
49
+ end
50
+
51
+ def attach(file, name = nil)
52
+ name = File.basename(file) if name.blank?
53
+ path = file
54
+
55
+ if file.is_a?(File)
56
+ path = file.path
57
+ file.close
58
+ elsif !File.exists?(path)
59
+ path = Voltron.asset.find(path)
60
+ end
61
+
62
+ attachments[name] = path
63
+ end
64
+
65
+ def mailer(klass = nil)
66
+ self.mailer_class = (klass || mailer_class).to_s.classify.constantize
67
+ end
68
+
69
+ def method(meth = nil)
70
+ self.mailer_method = meth || mailer_method
71
+ end
72
+
73
+ def arguments(*args)
74
+ @mailer_arguments = *args
75
+ end
76
+
77
+ # TODO: Move this to actual validates_* methods
78
+ def error_messages
79
+ output = []
80
+ output << "recipient cannot be blank" if to.blank?
81
+ output << "subject cannot be blank" if subject.blank?
82
+ output
83
+ end
84
+
85
+ private
86
+
87
+ def use_queue?
88
+ Voltron.config.notify.use_queue
89
+ end
90
+
91
+ def mail
92
+ # If no mailer arguments, use default order of arguments as defined in Voltron::NotificationMailer.notify
93
+ if @mailer_arguments.blank?
94
+ @request << { to: to, from: from, subject: subject }.compact.merge(vars: vars, attachments: attachments)
95
+ mailer.send method, { to: to, from: from, subject: subject }.compact, vars, attachments
96
+ else
97
+ @request << @mailer_arguments.compact
98
+ mailer.send method, *@mailer_arguments.compact
99
+ end
100
+ end
101
+
102
+ end
@@ -0,0 +1,5 @@
1
+ class Voltron::Notification::SmsNotification::Attachment < ActiveRecord::Base
2
+
3
+ belongs_to :sms_notification
4
+
5
+ end
@@ -0,0 +1,132 @@
1
+ require "twilio-ruby"
2
+
3
+ class Voltron::Notification::SmsNotification < ActiveRecord::Base
4
+
5
+ has_many :attachments
6
+
7
+ belongs_to :notification
8
+
9
+ after_initialize :setup
10
+
11
+ before_create :deliver_now, unless: :use_queue?
12
+
13
+ after_create :deliver_later, if: :use_queue?
14
+
15
+ include Rails.application.routes.url_helpers
16
+
17
+ def setup
18
+ @request = []
19
+ @response = []
20
+ end
21
+
22
+ def request
23
+ # Wrap entire request in container hash so that we can call deep_symbolize_keys on it (in case it's an array)
24
+ # Wrap entire request in array and flatten so we can be sure the result is an array
25
+ [{ request: (JSON.parse(request_json) rescue nil) }.deep_symbolize_keys[:request]].flatten.compact
26
+ end
27
+
28
+ def response
29
+ # Wrap entire response in container hash so that we can call deep_symbolize_keys on it (in case it's an array)
30
+ # Wrap entire response in array and flatten so we can be sure the result is an array
31
+ [{ response: (JSON.parse(response_json) rescue nil) }.deep_symbolize_keys[:response]].flatten.compact
32
+ end
33
+
34
+ def after_deliver
35
+ if use_queue?
36
+ # if use_queue?, meaning if this was sent via ActiveJob, we need to update ourself
37
+ # since we got to here within after_create, meaning setting the attributes alone won't cut it
38
+ self.update(request_json: @request.to_json, response_json: @response.to_json, sid: @response.first[:sid], status: @response.first[:status])
39
+ else
40
+ # We are before_create so we can just set the attribute values, it will be saved after this
41
+ self.request_json = @request.to_json
42
+ self.response_json = @response.to_json
43
+ self.sid = @response.first[:sid]
44
+ self.status = @response.first[:status]
45
+ end
46
+ end
47
+
48
+ def deliver_now
49
+ all_attachments = attachments.map(&:attachment)
50
+
51
+ # If sending more than 1 attachment, iterate through all but one attachment and send each without a body...
52
+ if all_attachments.count > 1
53
+ begin
54
+ client.messages.create({ from: from_formatted, to: to_formatted, media_url: all_attachments.shift }.compact)
55
+ @request << Rack::Utils.parse_nested_query(client.last_request.body)
56
+ @response << JSON.parse(client.last_response.body)
57
+ end until all_attachments.count == 1
58
+ end
59
+
60
+ # ... Then send the last attachment (if any) with the actual text body. This way we're not sending multiple SMS's with same body
61
+ client.messages.create({ from: from_formatted, to: to_formatted, body: message, media_url: all_attachments.shift }.compact)
62
+ @request << Rack::Utils.parse_nested_query(client.last_request.body)
63
+ @response << JSON.parse(client.last_response.body)
64
+ after_deliver
65
+ end
66
+
67
+ def deliver_later
68
+ job = Voltron::SmsJob.set(wait: Voltron.config.notify.delay).perform_later self
69
+ @request << job.to_json
70
+ @response << { sid: nil, status: "enqueued" }
71
+ after_deliver
72
+ end
73
+
74
+ def attach(url)
75
+ if url.starts_with? "http"
76
+ attachments.build attachment: url
77
+ else
78
+ attachments.build attachment: Voltron.config.base_url + ActionController::Base.helpers.asset_url(url)
79
+ end
80
+ end
81
+
82
+ def valid_phone?
83
+ begin
84
+ return true if to.blank? # Handle a blank `to` separately in the errors method below
85
+ to_formatted
86
+ true
87
+ rescue => e
88
+ Voltron.log e.message, "Notify", :light_red
89
+ false
90
+ end
91
+ end
92
+
93
+ # TODO: Move this to actual validates_* methods
94
+ def error_messages
95
+ output = []
96
+ output << "recipient cannot be blank" if to.blank?
97
+ output << "recipient is not a valid phone number" unless valid_phone?
98
+ output << "sender cannot be blank" if from.blank?
99
+ output << "message cannot be blank" if message.blank?
100
+ output
101
+ end
102
+
103
+ private
104
+
105
+ def use_queue?
106
+ Voltron.config.notify.use_queue
107
+ end
108
+
109
+ def to_formatted
110
+ format to
111
+ end
112
+
113
+ def from_formatted
114
+ format from
115
+ end
116
+
117
+ def format(input)
118
+ # Try to format the number via Twilio's api
119
+ # raises an exception if the input was invalid
120
+ number = lookup.phone_numbers.get input
121
+ number.phone_number
122
+ end
123
+
124
+ def client
125
+ @client ||= ::Twilio::REST::Client.new Voltron.config.notify.sms_account_sid, Voltron.config.notify.sms_auth_token
126
+ end
127
+
128
+ def lookup
129
+ @lookup ||= ::Twilio::REST::LookupsClient.new Voltron.config.notify.sms_account_sid, Voltron.config.notify.sms_auth_token
130
+ end
131
+
132
+ end
@@ -0,0 +1,79 @@
1
+ module Voltron
2
+ class Notification < ActiveRecord::Base
3
+
4
+ belongs_to :notifyable, polymorphic: true
5
+
6
+ has_many :sms_notifications
7
+
8
+ has_many :email_notifications
9
+
10
+ before_validation :prepare
11
+
12
+ before_validation :validate
13
+
14
+ PERMITTED_ATTRIBUTES = [:to, :from]
15
+
16
+ def email(subject, **args, &block)
17
+ # Get the remaining args as params, that will eventually become assigns in the mailer template
18
+ params = { subject: subject }.merge(**args)
19
+
20
+ # Build the options hash from the provided arguments
21
+ options = { subject: subject }.merge(**args.select { |k,v| PERMITTED_ATTRIBUTES.include?(k.to_sym) })
22
+
23
+ # Build a new SMS notification object
24
+ notification_email = email_notifications.build(options)
25
+
26
+ # Set the email vars (assigns)
27
+ notification_email.vars = params
28
+
29
+ # If a block is provided, allow calls to methods like `attach`
30
+ notification_email.instance_exec &block if block_given?
31
+ end
32
+
33
+ def sms(message, **args, &block)
34
+ # Build the options hash from the provided arguments
35
+ options = { message: message, from: Voltron.config.notify.sms_from }.merge(**args)
36
+
37
+ # Build a new SMS notification object
38
+ notification_sms = sms_notifications.build(options)
39
+
40
+ # If a block is provided, allow calls to methods like `attach`
41
+ notification_sms.instance_exec &block if block_given?
42
+ end
43
+
44
+ # Called from the before_validation callback within `notifyable`
45
+ # makes one final pass to set the email and phone as the to attribute
46
+ # If already set however, this does nothing. We do this because simply calling
47
+ # `notifyable` here returns nil until it's actually saved
48
+ def to(email, phone)
49
+ email_notifications.each { |n| n.to ||= email }
50
+ sms_notifications.each { |n| n.to ||= phone }
51
+ end
52
+
53
+ private
54
+
55
+ def validate
56
+ # Add SMS related errors to self
57
+ sms_notifications.each do |n|
58
+ n.error_messages.each do |error|
59
+ self.errors.add :sms, error
60
+ end
61
+ end
62
+
63
+ # Add Email related errors to self
64
+ email_notifications.each do |n|
65
+ n.error_messages.each do |error|
66
+ self.errors.add :email, error
67
+ end
68
+ end
69
+ end
70
+
71
+ def prepare
72
+ # Set the to value for both the email and phone, if any on this model
73
+ # This method is also called from the before_validation block in the notify module
74
+ # but we do it here in case the notification was created using resource.create
75
+ # instead of resource.build -> resource.save
76
+ to notifyable.try(:email), notifyable.try(:phone)
77
+ end
78
+ end
79
+ end
@@ -0,0 +1 @@
1
+ <%= @body %>
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "voltron/notify"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,14 @@
1
+ class CreateVoltronNotificationEmailNotifications < ActiveRecord::Migration
2
+ def change
3
+ create_table :voltron_notification_email_notifications do |t|
4
+ t.string :to
5
+ t.string :from
6
+ t.string :subject
7
+ t.string :mailer_class
8
+ t.string :mailer_method
9
+ t.text :request_json
10
+ t.text :response_json
11
+ t.integer :notification_id
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,10 @@
1
+ class CreateVoltronNotificationSmsNotificationAttachments < ActiveRecord::Migration
2
+ def change
3
+ create_table :voltron_notification_sms_notification_attachments do |t|
4
+ t.integer :sms_notification_id
5
+ t.string :attachment
6
+
7
+ t.timestamps null: false
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,16 @@
1
+ class CreateVoltronNotificationSmsNotifications < ActiveRecord::Migration
2
+ def change
3
+ create_table :voltron_notification_sms_notifications do |t|
4
+ t.string :to
5
+ t.string :from
6
+ t.text :message
7
+ t.text :request_json
8
+ t.text :response_json
9
+ t.integer :notification_id
10
+ t.string :status
11
+ t.string :sid
12
+
13
+ t.timestamps null: false
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,10 @@
1
+ class CreateVoltronNotifications < ActiveRecord::Migration
2
+ def change
3
+ create_table :voltron_notifications do |t|
4
+ t.string :notifyable_type
5
+ t.integer :notifyable_id
6
+
7
+ t.timestamps null: false
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,94 @@
1
+ module Voltron
2
+ module Notify
3
+ module Generators
4
+ class InstallGenerator < Rails::Generators::Base
5
+
6
+ source_root File.expand_path("../../../templates", __FILE__)
7
+
8
+ desc "Add Voltron Notify initializer"
9
+
10
+ def inject_initializer
11
+
12
+ voltron_initialzer_path = Rails.root.join("config", "initializers", "voltron.rb")
13
+
14
+ unless File.exist? voltron_initialzer_path
15
+ unless system("cd #{Rails.root.to_s} && rails generate voltron:install")
16
+ puts "Voltron initializer does not exist. Please ensure you have the 'voltron' gem installed and run `rails g voltron:install` to create it"
17
+ return false
18
+ end
19
+ end
20
+
21
+ current_initiailzer = File.read voltron_initialzer_path
22
+
23
+ unless current_initiailzer.match(Regexp.new(/# === Voltron Notify Configuration ===/))
24
+ inject_into_file(voltron_initialzer_path, after: "Voltron.setup do |config|\n") do
25
+ <<-CONTENT
26
+
27
+ # === Voltron Notify Configuration ===
28
+
29
+ # Whether or not to use the ActiveJob queue to handle sending email/sms messages
30
+ # A queue is still only used if configured via config.active_job.queue_adapter
31
+ # config.notify.use_queue = false
32
+
33
+ # How long to delay sending email/sms messages. Use this in conjunction with config.notify.use_queue
34
+ # config.notify.delay = 0.seconds
35
+
36
+ # Twilio account id number
37
+ # config.notify.sms_account_sid = ""
38
+
39
+ # Twilio authentication token
40
+ # config.notify.sms_auth_token = ""
41
+
42
+ # Default from phone number. Must be the number provided by Twilio.
43
+ # Avoid the overhead of pre-formatting the number by entering in the format "+1234567890"
44
+ # config.notify.sms_from = ""
45
+
46
+ # Default from email address. If not specified the default from in the mailer or the :from param on mail() is used
47
+ # config.notify.email_from = "no-reply@example.com"
48
+ CONTENT
49
+ end
50
+ end
51
+ end
52
+
53
+ def copy_migrations
54
+ copy_migration "create_voltron_notifications"
55
+ copy_migration "create_voltron_notification_sms_notifications"
56
+ copy_migration "create_voltron_notification_email_notifications"
57
+ copy_migration "create_voltron_notification_sms_notification_attachments"
58
+ end
59
+
60
+ def copy_views
61
+ copy_file "../../../app/views/voltron/notification_mailer/notify.html.erb", Rails.root.join("app", "views", "voltron", "notification_mailer", "notify.html.erb")
62
+ end
63
+
64
+ protected
65
+
66
+ def copy_migration(filename)
67
+ if migration_exists?(Rails.root.join("db", "migrate"), filename)
68
+ say_status("skipped", "Migration #{filename}.rb already exists")
69
+ else
70
+ copy_file "db/migrate/#{filename}.rb", Rails.root.join("db", "migrate", "#{migration_number}_#{filename}.rb")
71
+ end
72
+ end
73
+
74
+ def migration_exists?(dirname, filename)
75
+ Dir.glob("#{dirname}/[0-9]*_*.rb").grep(/\d+_#{filename}.rb$/).first
76
+ end
77
+
78
+ def migration_id_exists?(dirname, id)
79
+ Dir.glob("#{dirname}/#{id}*").length > 0
80
+ end
81
+
82
+ def migration_number
83
+ @migration_number ||= Time.now.strftime("%Y%m%d%H%M%S").to_i
84
+
85
+ while migration_id_exists?(Rails.root.join("db", "migrate"), @migration_number) do
86
+ @migration_number += 1
87
+ end
88
+
89
+ @migration_number
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,26 @@
1
+ module Voltron
2
+ class Config
3
+
4
+ def notify
5
+ @notify ||= Notify.new
6
+ end
7
+
8
+ class Notify
9
+
10
+ attr_accessor :use_queue, :delay
11
+
12
+ # SMS config settings
13
+ attr_accessor :sms_account_sid, :sms_auth_token, :sms_from
14
+
15
+ # Email config settings
16
+ attr_accessor :email_from
17
+
18
+ def initialize
19
+ @use_queue ||= false
20
+ @delay ||= 0.seconds
21
+ @email_from ||= "no-reply@example.com"
22
+ end
23
+
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,12 @@
1
+ module Voltron
2
+ module Notify
3
+ class Engine < Rails::Engine
4
+
5
+ isolate_namespace Voltron
6
+
7
+ initializer "voltron.notify.initialize" do
8
+ ::ActiveRecord::Base.send :extend, ::Voltron::Notify
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,5 @@
1
+ module Voltron
2
+ module Notify
3
+ VERSION = "0.1.2".freeze
4
+ end
5
+ end
@@ -0,0 +1,47 @@
1
+ require "voltron"
2
+ require "voltron/notify/version"
3
+ require "voltron/config/notify"
4
+
5
+ module Voltron
6
+ module Notify
7
+
8
+ def notifyable
9
+ include InstanceMethods
10
+
11
+ before_validation :validate_notifications
12
+
13
+ after_validation :clean_notification_validation
14
+
15
+ has_many :notifications, as: :notifyable, class_name: "::Voltron::Notification"
16
+ end
17
+
18
+ module InstanceMethods
19
+
20
+ private
21
+
22
+ def validate_notifications
23
+ # Find all notification records that haven't been saved yet
24
+ self.notifications.select(&:new_record?).each do |notification|
25
+
26
+ # Set the to value for both the email and phone, if any on this model
27
+ notification.to self.try(:email), self.try(:phone)
28
+
29
+ # If not valid, populate the childs error messages in this models errors object
30
+ unless notification.valid?
31
+ notification.errors.messages.each do |k, errors|
32
+ errors.each { |error| self.errors.add k, error }
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ def clean_notification_validation
39
+ # Cleanup, remove the notifications key from the error messages,
40
+ # All of the actual errors are populated into "notifications.<type>" keys above
41
+ self.errors.delete :notifications
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ require "voltron/notify/engine" if defined?(Rails)
@@ -0,0 +1,33 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "voltron/notify/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "voltron-notify"
8
+ spec.version = Voltron::Notify::VERSION
9
+ spec.authors = ["Eric Hainer"]
10
+ spec.email = ["eric@commercekitchen.com"]
11
+
12
+ spec.summary = %q{Send notifications easier}
13
+ spec.homepage = "https://github.com/ehainer/voltron-notify"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ spec.bindir = "exe"
18
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "rails", ">= 4.2"
22
+ spec.add_dependency "twilio-ruby", "~> 4.11"
23
+ spec.add_dependency "rack", ">= 1.6"
24
+ spec.add_dependency "voltron", "~> 0.1.2", ">= 0.1.0"
25
+
26
+ spec.add_development_dependency "simplecov", "0.11.0"
27
+ spec.add_development_dependency "bundler", ">= 1.12"
28
+ spec.add_development_dependency "rake", ">= 10.0"
29
+ spec.add_development_dependency "rspec", ">= 3.0"
30
+ spec.add_development_dependency "rspec-rails", ">= 3.4"
31
+ spec.add_development_dependency "sqlite3", ">= 1.2"
32
+ spec.add_development_dependency "factory_girl_rails", ">= 4.7"
33
+ end
metadata ADDED
@@ -0,0 +1,231 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: voltron-notify
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.2
5
+ platform: ruby
6
+ authors:
7
+ - Eric Hainer
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-09-26 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '4.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '4.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: twilio-ruby
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '4.11'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '4.11'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rack
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '1.6'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '1.6'
55
+ - !ruby/object:Gem::Dependency
56
+ name: voltron
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 0.1.2
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: 0.1.0
65
+ type: :runtime
66
+ prerelease: false
67
+ version_requirements: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - "~>"
70
+ - !ruby/object:Gem::Version
71
+ version: 0.1.2
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: 0.1.0
75
+ - !ruby/object:Gem::Dependency
76
+ name: simplecov
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - '='
80
+ - !ruby/object:Gem::Version
81
+ version: 0.11.0
82
+ type: :development
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - '='
87
+ - !ruby/object:Gem::Version
88
+ version: 0.11.0
89
+ - !ruby/object:Gem::Dependency
90
+ name: bundler
91
+ requirement: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '1.12'
96
+ type: :development
97
+ prerelease: false
98
+ version_requirements: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '1.12'
103
+ - !ruby/object:Gem::Dependency
104
+ name: rake
105
+ requirement: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '10.0'
110
+ type: :development
111
+ prerelease: false
112
+ version_requirements: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '10.0'
117
+ - !ruby/object:Gem::Dependency
118
+ name: rspec
119
+ requirement: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: '3.0'
124
+ type: :development
125
+ prerelease: false
126
+ version_requirements: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ version: '3.0'
131
+ - !ruby/object:Gem::Dependency
132
+ name: rspec-rails
133
+ requirement: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ version: '3.4'
138
+ type: :development
139
+ prerelease: false
140
+ version_requirements: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - ">="
143
+ - !ruby/object:Gem::Version
144
+ version: '3.4'
145
+ - !ruby/object:Gem::Dependency
146
+ name: sqlite3
147
+ requirement: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - ">="
150
+ - !ruby/object:Gem::Version
151
+ version: '1.2'
152
+ type: :development
153
+ prerelease: false
154
+ version_requirements: !ruby/object:Gem::Requirement
155
+ requirements:
156
+ - - ">="
157
+ - !ruby/object:Gem::Version
158
+ version: '1.2'
159
+ - !ruby/object:Gem::Dependency
160
+ name: factory_girl_rails
161
+ requirement: !ruby/object:Gem::Requirement
162
+ requirements:
163
+ - - ">="
164
+ - !ruby/object:Gem::Version
165
+ version: '4.7'
166
+ type: :development
167
+ prerelease: false
168
+ version_requirements: !ruby/object:Gem::Requirement
169
+ requirements:
170
+ - - ">="
171
+ - !ruby/object:Gem::Version
172
+ version: '4.7'
173
+ description:
174
+ email:
175
+ - eric@commercekitchen.com
176
+ executables: []
177
+ extensions: []
178
+ extra_rdoc_files: []
179
+ files:
180
+ - ".gitignore"
181
+ - ".rspec"
182
+ - ".travis.yml"
183
+ - CODE_OF_CONDUCT.md
184
+ - Gemfile
185
+ - LICENSE.txt
186
+ - README.md
187
+ - Rakefile
188
+ - app/jobs/voltron/sms_job.rb
189
+ - app/mailers/voltron/notification_mailer.rb
190
+ - app/models/voltron/notification.rb
191
+ - app/models/voltron/notification/email_notification.rb
192
+ - app/models/voltron/notification/sms_notification.rb
193
+ - app/models/voltron/notification/sms_notification/attachment.rb
194
+ - app/views/voltron/notification_mailer/notify.html.erb
195
+ - bin/console
196
+ - bin/setup
197
+ - lib/generators/templates/db/migrate/create_voltron_notification_email_notifications.rb
198
+ - lib/generators/templates/db/migrate/create_voltron_notification_sms_notification_attachments.rb
199
+ - lib/generators/templates/db/migrate/create_voltron_notification_sms_notifications.rb
200
+ - lib/generators/templates/db/migrate/create_voltron_notifications.rb
201
+ - lib/generators/voltron/notify/install_generator.rb
202
+ - lib/voltron/config/notify.rb
203
+ - lib/voltron/notify.rb
204
+ - lib/voltron/notify/engine.rb
205
+ - lib/voltron/notify/version.rb
206
+ - voltron-notify.gemspec
207
+ homepage: https://github.com/ehainer/voltron-notify
208
+ licenses:
209
+ - MIT
210
+ metadata: {}
211
+ post_install_message:
212
+ rdoc_options: []
213
+ require_paths:
214
+ - lib
215
+ required_ruby_version: !ruby/object:Gem::Requirement
216
+ requirements:
217
+ - - ">="
218
+ - !ruby/object:Gem::Version
219
+ version: '0'
220
+ required_rubygems_version: !ruby/object:Gem::Requirement
221
+ requirements:
222
+ - - ">="
223
+ - !ruby/object:Gem::Version
224
+ version: '0'
225
+ requirements: []
226
+ rubyforge_project:
227
+ rubygems_version: 2.4.8
228
+ signing_key:
229
+ specification_version: 4
230
+ summary: Send notifications easier
231
+ test_files: []