voltron-notify 0.1.2

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