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 +7 -0
- data/.gitignore +13 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +106 -0
- data/Rakefile +6 -0
- data/app/jobs/voltron/sms_job.rb +7 -0
- data/app/mailers/voltron/notification_mailer.rb +13 -0
- data/app/models/voltron/notification/email_notification.rb +102 -0
- data/app/models/voltron/notification/sms_notification/attachment.rb +5 -0
- data/app/models/voltron/notification/sms_notification.rb +132 -0
- data/app/models/voltron/notification.rb +79 -0
- data/app/views/voltron/notification_mailer/notify.html.erb +1 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/generators/templates/db/migrate/create_voltron_notification_email_notifications.rb +14 -0
- data/lib/generators/templates/db/migrate/create_voltron_notification_sms_notification_attachments.rb +10 -0
- data/lib/generators/templates/db/migrate/create_voltron_notification_sms_notifications.rb +16 -0
- data/lib/generators/templates/db/migrate/create_voltron_notifications.rb +10 -0
- data/lib/generators/voltron/notify/install_generator.rb +94 -0
- data/lib/voltron/config/notify.rb +26 -0
- data/lib/voltron/notify/engine.rb +12 -0
- data/lib/voltron/notify/version.rb +5 -0
- data/lib/voltron/notify.rb +47 -0
- data/voltron-notify.gemspec +33 -0
- metadata +231 -0
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
data/.rspec
ADDED
data/.travis.yml
ADDED
data/CODE_OF_CONDUCT.md
ADDED
@@ -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
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
|
+
[](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,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,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,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
|
data/lib/generators/templates/db/migrate/create_voltron_notification_sms_notification_attachments.rb
ADDED
@@ -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,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,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: []
|