sms_carrier 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 8856860fad36aa1fa2eef28c277106e3c3e8a361
4
+ data.tar.gz: 261bcc32724a7d9c75eae9217208800a1117881f
5
+ SHA512:
6
+ metadata.gz: 0f5dccb9d50b80a849099fb1df1dc732ae78a6ca50c4d7e750e725472ef1debe2b149845b8cd46e78d881b017473df20445e8d9979d35fe4910e017d81ec39d5
7
+ data.tar.gz: 0a144ff33491e6ae9bd781010eacb4cbf76e347b670a886b5c9102e5dd755b8c31a3365ccef590b1f925378bae702129e3970ae112fb669350afdfcb8da85363
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
@@ -0,0 +1,9 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.2
4
+ before_install: gem install bundler
5
+ branches:
6
+ only:
7
+ - master
8
+ env:
9
+ - COVERALLS=1
@@ -0,0 +1,3 @@
1
+ # v0.1.0 / 2015-10-23
2
+
3
+ Create project
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in sms_carrier.gemspec
4
+ gemspec
5
+
6
+ gem 'simplecov', :require => false
7
+ gem 'coveralls', :require => false
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Chen Yi-Cyuan
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.
@@ -0,0 +1,86 @@
1
+ # SMS Carrier
2
+
3
+ [![Build Status](https://api.travis-ci.org/emn178/sms_carrier.png)](https://travis-ci.org/emn178/sms_carrier)
4
+ [![Coverage Status](https://coveralls.io/repos/emn178/sms_carrier/badge.svg?branch=master)](https://coveralls.io/r/emn178/sms_carrier?branch=master)
5
+
6
+ SMS Carrier is a framework for designing SMS service layers. These layers are used to consolidate code for sending out confirmation token, and any other use case that requires a written notification to either a person or another system.
7
+
8
+ SMS Carrier is in essence a wrapper around Action Controller. It provides a way to make SMSes using templates in the same way that Action Controller renders views using templates.
9
+
10
+ ## Installation
11
+
12
+ Add this line to your application's Gemfile:
13
+
14
+ ```ruby
15
+ gem 'sms_carrier'
16
+ ```
17
+
18
+ And then execute:
19
+
20
+ bundle
21
+
22
+ Or install it yourself as:
23
+
24
+ gem install sms_carrier
25
+
26
+ ## Usage
27
+ ### Sending SMSes
28
+ The framework works by initializing any instance variables you want to be available in the SMS template, followed by a call to sms to deliver the SMS.
29
+
30
+ This can be as simple as:
31
+ ```Ruby
32
+ class Notifier < SmsCarrier::Base
33
+ default from: '+886987654321'
34
+
35
+ def welcome(recipient, token)
36
+ @token = token
37
+ sms(to: recipient)
38
+ end
39
+ end
40
+ ```
41
+ The body of the SMS is created by using an Action View template (regular ERB) that has the instance variables that are declared in the carrier action.
42
+
43
+ So the corresponding body template for the method above could look like this:
44
+ ```
45
+ Your token is <%= @token %>, please confirm your phone number
46
+ ```
47
+ If the token was given as “1234”, the SMS generated would look like this:
48
+ ```
49
+ Your token is 1234, please confirm your phone number
50
+ ```
51
+ In order to send SMSes, you simply call the method and then call deliver_now on the return value.
52
+
53
+ Calling the method returns a Sms object:
54
+ ```Ruby
55
+ message = Notifier.welcome("1234") # => Returns a SmsCarrier::Sms object
56
+ message.deliver_now # => delivers the SMS
57
+ ```
58
+ Or you can just chain the methods together like:
59
+ ```Ruby
60
+ Notifier.welcome("1234").deliver_now # Creates the SMS and sends it immediately
61
+ ```
62
+ Or you can send SMS without carrier and template:
63
+ ```Ruby
64
+ SmsCarrier::Base.sms(from: "+886987654321", to: "+886912345678", body: "Your token is #{@token}").deliver_now
65
+ ```
66
+
67
+ ### Setting defaults
68
+ It is possible to set default values that will be used in every method in your SMS Carrier class. To implement this functionality, you just call the public class method default which you get for free from SmsCarrier::Base. This method accepts a Hash as the parameter. You can use any of the options, SMS messages have, like :from as the key. You can also pass in a string as the key, like “Content-Type”, but SMS Carrier does this out of the box for you, so you won't need to worry about that. Finally, it is also possible to pass in a Proc that will get evaluated when it is needed.
69
+
70
+ Note that every value you set with this method will get overwritten if you use the same key in your carrier method.
71
+
72
+ Example:
73
+ ```Ruby
74
+ class AuthenticationCarrier < SmsCarrier::Base
75
+ default from: "+886987654321", body: Proc.new { "SMS was generated at #{Time.now}" }
76
+ .....
77
+ end
78
+ ```
79
+
80
+ ## License
81
+
82
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
83
+
84
+ ## Contact
85
+ The project's website is located at https://github.com/emn178/sms_carrier
86
+ Author: emn178@gmail.com
@@ -0,0 +1,23 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require 'rake/testtask'
4
+
5
+ desc "Default Task"
6
+ task default: [ :test ]
7
+
8
+ # Run the unit tests
9
+ Rake::TestTask.new { |t|
10
+ t.libs << "test"
11
+ t.pattern = 'test/**/*_test.rb'
12
+ t.warning = true
13
+ t.verbose = true
14
+ t.ruby_opts = ["--dev"] if defined?(JRUBY_VERSION)
15
+ }
16
+
17
+ namespace :test do
18
+ task :isolated do
19
+ Dir.glob("test/**/*_test.rb").all? do |file|
20
+ sh(Gem.ruby, '-w', '-Ilib:test', file)
21
+ end or raise "Failures"
22
+ end
23
+ end
@@ -0,0 +1,22 @@
1
+ require 'abstract_controller'
2
+ require 'sms_carrier/version'
3
+
4
+ # Common Active Support usage in SmsCarrier
5
+ require 'active_support/rails'
6
+ require 'active_support/core_ext/class'
7
+ require 'active_support/core_ext/module/attr_internal'
8
+ require 'active_support/core_ext/string/inflections'
9
+ require 'active_support/lazy_load_hooks'
10
+
11
+ module SmsCarrier
12
+ extend ::ActiveSupport::Autoload
13
+
14
+ autoload :Base
15
+ autoload :DeliveryMethods
16
+ autoload :TestCase
17
+ autoload :TestHelper
18
+ autoload :MessageDelivery
19
+ autoload :DeliveryJob
20
+ end
21
+
22
+ require "sms_carrier/railtie" if defined? ::Rails
@@ -0,0 +1,292 @@
1
+ require 'active_support/core_ext/string/inflections'
2
+ require 'active_support/core_ext/hash/except'
3
+ require 'active_support/core_ext/module/anonymous'
4
+
5
+ require 'sms_carrier/sms'
6
+ require 'sms_carrier/log_subscriber'
7
+
8
+ module SmsCarrier
9
+ class Base < AbstractController::Base
10
+ include DeliveryMethods
11
+
12
+ abstract!
13
+
14
+ include AbstractController::Rendering
15
+
16
+ include AbstractController::Logger
17
+ include AbstractController::Helpers
18
+ include AbstractController::Translation
19
+ include AbstractController::AssetPaths
20
+ include AbstractController::Callbacks
21
+
22
+ include ActionView::Layouts
23
+
24
+ PROTECTED_IVARS = AbstractController::Rendering::DEFAULT_PROTECTED_INSTANCE_VARIABLES + [:@_action_has_layout]
25
+
26
+ def _protected_ivars # :nodoc:
27
+ PROTECTED_IVARS
28
+ end
29
+
30
+ private_class_method :new #:nodoc:
31
+
32
+ class_attribute :default_params
33
+ self.default_params = {}.freeze
34
+
35
+ class << self
36
+ # Register one or more Observers which will be notified when SMS is delivered.
37
+ def register_observers(*observers)
38
+ observers.flatten.compact.each { |observer| register_observer(observer) }
39
+ end
40
+
41
+ # Register one or more Interceptors which will be called before SMS is sent.
42
+ def register_interceptors(*interceptors)
43
+ interceptors.flatten.compact.each { |interceptor| register_interceptor(interceptor) }
44
+ end
45
+
46
+ # Register an Observer which will be notified when SMS is delivered.
47
+ # Either a class, string or symbol can be passed in as the Observer.
48
+ # If a string or symbol is passed in it will be camelized and constantized.
49
+ def register_observer(observer)
50
+ delivery_observer = case observer
51
+ when String, Symbol
52
+ observer.to_s.camelize.constantize
53
+ else
54
+ observer
55
+ end
56
+
57
+ Sms.register_observer(delivery_observer)
58
+ end
59
+
60
+ # Register an Interceptor which will be called before SMS is sent.
61
+ # Either a class, string or symbol can be passed in as the Interceptor.
62
+ # If a string or symbol is passed in it will be camelized and constantized.
63
+ def register_interceptor(interceptor)
64
+ delivery_interceptor = case interceptor
65
+ when String, Symbol
66
+ interceptor.to_s.camelize.constantize
67
+ else
68
+ interceptor
69
+ end
70
+
71
+ Sms.register_interceptor(delivery_interceptor)
72
+ end
73
+
74
+ # Returns the name of current carrier.
75
+ # If this is an anonymous carrier, this method will return +anonymous+ instead.
76
+ def carrier_name
77
+ @carrier_name ||= anonymous? ? "anonymous" : name.underscore
78
+ end
79
+ # Allows to set the name of current carrier.
80
+ attr_writer :carrier_name
81
+ alias :controller_path :carrier_name
82
+
83
+ # Sets the defaults through app configuration:
84
+ #
85
+ # config.sms_carrier.default(from: "+886987654321")
86
+ #
87
+ # Aliased by ::default_options=
88
+ def default(value = nil)
89
+ self.default_params = default_params.merge(value).freeze if value
90
+ default_params
91
+ end
92
+ # Allows to set defaults through app configuration:
93
+ #
94
+ # config.sms_carrier.default_options = { from: "+886987654321" }
95
+ alias :default_options= :default
96
+
97
+ # Wraps an SMS delivery inside of <tt>ActiveSupport::Notifications</tt> instrumentation.
98
+ #
99
+ # This method is actually called by the <tt>Sms</tt> object itself
100
+ # through a callback when you call <tt>:deliver</tt> on the <tt>Sms</tt>,
101
+ # calling +deliver_sms+ directly and passing a <tt>Sms</tt> will do
102
+ # nothing except tell the logger you sent the SMS.
103
+ def deliver_sms(sms) #:nodoc:
104
+ ActiveSupport::Notifications.instrument("deliver.sms_carrier") do |payload|
105
+ set_payload_for_sms(payload, sms)
106
+ yield # Let Sms do the delivery actions
107
+ end
108
+ end
109
+
110
+ def respond_to?(method, include_private = false) #:nodoc:
111
+ super || action_methods.include?(method.to_s)
112
+ end
113
+
114
+ protected
115
+
116
+ def set_payload_for_sms(payload, sms) #:nodoc:
117
+ payload[:carrier] = name
118
+ payload[:to] = sms.to
119
+ payload[:from] = sms.from
120
+ payload[:sms] = sms.body
121
+ end
122
+
123
+ def method_missing(method_name, *args) #:nodoc:
124
+ if action_methods.include?(method_name.to_s)
125
+ MessageDelivery.new(self, method_name, *args)
126
+ else
127
+ super
128
+ end
129
+ end
130
+ end
131
+
132
+ attr_internal :message
133
+
134
+ # Instantiate a new carrier object. If +method_name+ is not +nil+, the carrier
135
+ # will be initialized according to the named method. If not, the carrier will
136
+ # remain uninitialized (useful when you only need to invoke the "receive"
137
+ # method, for instance).
138
+ def initialize(method_name=nil, *args)
139
+ super()
140
+ @_sms_was_called = false
141
+ @_message = Sms.new
142
+ process(method_name, *args) if method_name
143
+ end
144
+
145
+ def process(method_name, *args) #:nodoc:
146
+ payload = {
147
+ carrier: self.class.name,
148
+ action: method_name
149
+ }
150
+
151
+ ActiveSupport::Notifications.instrument("process.sms_carrier", payload) do
152
+ super
153
+ @_message = NullMessage.new unless @_sms_was_called
154
+ end
155
+ end
156
+
157
+ class NullMessage #:nodoc:
158
+ def body; '' end
159
+
160
+ def respond_to?(string, include_all=false)
161
+ true
162
+ end
163
+
164
+ def method_missing(*args)
165
+ nil
166
+ end
167
+ end
168
+
169
+ # Returns the name of the carrier object.
170
+ def carrier_name
171
+ self.class.carrier_name
172
+ end
173
+
174
+ # Allows you to pass random and unusual options to the new <tt>SmsCarrier::Sms</tt>
175
+ # object which will add them to itself.
176
+ #
177
+ # options['X-Special-Domain-Specific-Option'] = "SecretValue"
178
+ #
179
+ # The resulting <tt>SmsCarrier::Sms</tt> will have the following in its option:
180
+ #
181
+ # X-Special-Domain-Specific-Option: SecretValue
182
+ # def options
183
+ # @_message.options
184
+ # end
185
+
186
+ def options(args = nil)
187
+ if args
188
+ @_message.options.merge!(args)
189
+ else
190
+ @_message
191
+ end
192
+ end
193
+
194
+ # The main method that creates the message and renders the SMS templates. There are
195
+ # two ways to call this method, with a block, or without a block.
196
+ #
197
+ # It accepts a headers hash. This hash allows you to specify
198
+ # the most used headers in an SMS message, these are:
199
+ #
200
+ # * +:to+ - Who the message is destined for, can be a string of addresses, or an array
201
+ # of addresses.
202
+ # * +:from+ - Who the message is from
203
+ #
204
+ # You can set default values for any of the above headers (except +:date+)
205
+ # by using the ::default class method:
206
+ #
207
+ # class Notifier < SmsCarrier::Base
208
+ # default from: '+886987654321'
209
+ # end
210
+ #
211
+ # If you do not pass a block to the +sms+ method, it will find all
212
+ # templates in the view paths using by default the carrier name and the
213
+ # method name that it is being called from, it will then create parts for
214
+ # each of these templates intelligently, making educated guesses on correct
215
+ # content type and sequence, and return a fully prepared <tt>Sms</tt>
216
+ # ready to call <tt>:deliver</tt> on to send.
217
+ #
218
+ # For example:
219
+ #
220
+ # class Notifier < SmsCarrier::Base
221
+ # default from: 'no-reply@test.lindsaar.net'
222
+ #
223
+ # def welcome
224
+ # sms(to: 'mikel@test.lindsaar.net')
225
+ # end
226
+ # end
227
+ #
228
+ # Will look for all templates at "app/views/notifier" with name "welcome".
229
+ # If no welcome template exists, it will raise an ActionView::MissingTemplate error.
230
+ #
231
+ # However, those can be customized:
232
+ #
233
+ # sms(template_path: 'notifications', template_name: 'another')
234
+ #
235
+ # And now it will look for all templates at "app/views/notifications" with name "another".
236
+ #
237
+ # You can even render plain text directly without using a template:
238
+ #
239
+ # sms(to: '+886987654321', body: 'Hello Mikel!')
240
+ #
241
+ def sms(options = {})
242
+ return @_message if @_sms_was_called && options.blank?
243
+
244
+ m = @_message
245
+
246
+ # Call all the procs (if any)
247
+ default_values = {}
248
+ self.class.default.each do |k,v|
249
+ default_values[k] = v.is_a?(Proc) ? instance_eval(&v) : v
250
+ end
251
+
252
+ # Handle defaults
253
+ options = options.reverse_merge(default_values)
254
+
255
+ # Set configure delivery behavior
256
+ wrap_delivery_behavior!(options.delete(:delivery_method), options.delete(:delivery_method_options))
257
+
258
+ # Assign all options except body, template_name, and template_path
259
+ assignable = options.except(:body, :template_name, :template_path)
260
+ assignable.each { |k, v| m[k] = v }
261
+
262
+ # Render the templates and blocks
263
+ m.body = response(options)
264
+ @_sms_was_called = true
265
+
266
+ m
267
+ end
268
+
269
+ def response(options) #:nodoc:
270
+ if options[:body]
271
+ return options.delete(:body)
272
+ else
273
+ templates_path = options.delete(:template_path) || self.class.carrier_name
274
+ templates_name = options.delete(:template_name) || action_name
275
+
276
+ template = lookup_context.find(templates_name, templates_path)
277
+ if template.nil?
278
+ raise ActionView::MissingTemplate.new(templates_path, templates_name, templates_path, false, 'carrier')
279
+ else
280
+ return render(template: template)
281
+ end
282
+ end
283
+ end
284
+
285
+ # SMS do not support relative path links.
286
+ def self.supports_path?
287
+ false
288
+ end
289
+
290
+ ActiveSupport.run_load_hooks(:sms_carrier, self)
291
+ end
292
+ end
@@ -0,0 +1,13 @@
1
+ require 'active_job'
2
+
3
+ module SmsCarrier
4
+ # The <tt>SmsCarrier::DeliveryJob</tt> class is used when you
5
+ # want to send SMSes outside of the request-response cycle.
6
+ class DeliveryJob < ActiveJob::Base # :nodoc:
7
+ queue_as { SmsCarrier::Base.deliver_later_queue_name }
8
+
9
+ def perform(sms, sms_method, delivery_method, *args) #:nodoc:
10
+ sms.constantize.public_send(sms_method, *args).send(delivery_method)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,67 @@
1
+ require 'sms_carrier/test_carrier'
2
+
3
+ module SmsCarrier
4
+ # This module handles everything related to SMS delivery, from registering
5
+ # new delivery methods to configuring the SMS object to be sent.
6
+ module DeliveryMethods
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ class_attribute :delivery_methods, :delivery_method
11
+
12
+ # Do not make this inheritable, because we always want it to propagate
13
+ cattr_accessor :raise_delivery_errors
14
+ self.raise_delivery_errors = true
15
+
16
+ cattr_accessor :perform_deliveries
17
+ self.perform_deliveries = true
18
+
19
+ cattr_accessor :deliver_later_queue_name
20
+ self.deliver_later_queue_name = :carriers
21
+
22
+ self.delivery_methods = {}.freeze
23
+ self.delivery_method = :test
24
+
25
+ add_delivery_method :test, TestCarrier
26
+ end
27
+
28
+ # Helpers for creating and wrapping delivery behavior, used by DeliveryMethods.
29
+ module ClassMethods
30
+ # Provides a list of SMSes that have been delivered by TestCarrier
31
+ delegate :deliveries, :deliveries=, to: TestCarrier
32
+
33
+ # Adds a new delivery method through the given class using the given
34
+ # symbol as alias and the default options supplied.
35
+ def add_delivery_method(symbol, klass, default_options = {})
36
+ class_attribute(:"#{symbol}_settings") unless respond_to?(:"#{symbol}_settings")
37
+ send(:"#{symbol}_settings=", default_options)
38
+ self.delivery_methods = delivery_methods.merge(symbol.to_sym => klass).freeze
39
+ end
40
+
41
+ def wrap_delivery_behavior(sms, method = nil, options = nil) # :nodoc:
42
+ method ||= delivery_method
43
+ sms.delivery_handler = self
44
+
45
+ case method
46
+ when NilClass
47
+ raise "Delivery method cannot be nil"
48
+ when Symbol
49
+ if klass = delivery_methods[method]
50
+ sms.delivery_method(klass, (send(:"#{method}_settings") || {}).merge(options || {}))
51
+ else
52
+ raise "Invalid delivery method #{method.inspect}"
53
+ end
54
+ else
55
+ sms.delivery_method(method)
56
+ end
57
+
58
+ sms.perform_deliveries = perform_deliveries
59
+ sms.raise_delivery_errors = raise_delivery_errors
60
+ end
61
+ end
62
+
63
+ def wrap_delivery_behavior!(*args) # :nodoc:
64
+ self.class.wrap_delivery_behavior(message, *args)
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,33 @@
1
+ require 'active_support/log_subscriber'
2
+
3
+ module SmsCarrier
4
+ # Implements the ActiveSupport::LogSubscriber for logging notifications when
5
+ # sms is delivered or received.
6
+ class LogSubscriber < ActiveSupport::LogSubscriber
7
+ # An SMS was delivered.
8
+ def deliver(event)
9
+ info do
10
+ recipients = Array(event.payload[:to]).join(', ')
11
+ "\nSent SMS to #{recipients} (#{event.duration.round(1)}ms)"
12
+ end
13
+
14
+ debug { event.payload[:sms] }
15
+ end
16
+
17
+ # An SMS was generated.
18
+ def process(event)
19
+ debug do
20
+ carrier = event.payload[:carrier]
21
+ action = event.payload[:action]
22
+ "\n#{carrier}##{action}: processed outbound SMS in #{event.duration.round(1)}ms"
23
+ end
24
+ end
25
+
26
+ # Use the logger configured for SmsCarrier::Base.
27
+ def logger
28
+ SmsCarrier::Base.logger
29
+ end
30
+ end
31
+ end
32
+
33
+ SmsCarrier::LogSubscriber.attach_to :sms_carrier
@@ -0,0 +1,93 @@
1
+ require 'delegate'
2
+
3
+ module SmsCarrier
4
+ # The <tt>SmsCarrier::MessageDelivery</tt> class is used by
5
+ # <tt>SmsCarrier::Base</tt> when creating a new carrier.
6
+ # <tt>MessageDelivery</tt> is a wrapper (+Delegator+ subclass) around a lazy
7
+ # created <tt>Sms</tt>. You can get direct access to the
8
+ # <tt>Sms</tt>, deliver the SMS or schedule the SMS to be sent
9
+ # through Active Job.
10
+ #
11
+ # Notifier.welcome(User.first) # an SmsCarrier::MessageDelivery object
12
+ # Notifier.welcome(User.first).deliver_now # sends the email
13
+ # Notifier.welcome(User.first).deliver_later # enqueue email delivery as a job through Active Job
14
+ # Notifier.welcome(User.first).message # a Sms object
15
+ class MessageDelivery < Delegator
16
+ def initialize(carrier, sms_method, *args) #:nodoc:
17
+ @carrier = carrier
18
+ @sms_method = sms_method
19
+ @args = args
20
+ end
21
+
22
+ def __getobj__ #:nodoc:
23
+ @obj ||= @carrier.send(:new, @sms_method, *@args).message
24
+ end
25
+
26
+ def __setobj__(obj) #:nodoc:
27
+ @obj = obj
28
+ end
29
+
30
+ # Returns the Message object
31
+ def message
32
+ __getobj__
33
+ end
34
+
35
+ # Enqueues the SMS to be delivered through Active Job. When the
36
+ # job runs it will send the SMS using +deliver_now!+. That means
37
+ # that the message will be sent bypassing checking +perform_deliveries+
38
+ # and +raise_delivery_errors+, so use with caution.
39
+ #
40
+ # Notifier.welcome(User.first).deliver_later!
41
+ # Notifier.welcome(User.first).deliver_later!(wait: 1.hour)
42
+ # Notifier.welcome(User.first).deliver_later!(wait_until: 10.hours.from_now)
43
+ #
44
+ # Options:
45
+ #
46
+ # * <tt>:wait</tt> - Enqueue the SMS to be delivered with a delay
47
+ # * <tt>:wait_until</tt> - Enqueue the SMS to be delivered at (after) a specific date / time
48
+ # * <tt>:queue</tt> - Enqueue the SMS on the specified queue
49
+ def deliver_later!(options={})
50
+ enqueue_delivery :deliver_now!, options
51
+ end
52
+
53
+ # Enqueues the SMS to be delivered through Active Job. When the
54
+ # job runs it will send the SMS using +deliver_now+.
55
+ #
56
+ # Notifier.welcome(User.first).deliver_later
57
+ # Notifier.welcome(User.first).deliver_later(wait: 1.hour)
58
+ # Notifier.welcome(User.first).deliver_later(wait_until: 10.hours.from_now)
59
+ #
60
+ # Options:
61
+ #
62
+ # * <tt>:wait</tt> - Enqueue the SMS to be delivered with a delay.
63
+ # * <tt>:wait_until</tt> - Enqueue the SMS to be delivered at (after) a specific date / time.
64
+ # * <tt>:queue</tt> - Enqueue the SMS on the specified queue.
65
+ def deliver_later(options={})
66
+ enqueue_delivery :deliver_now, options
67
+ end
68
+
69
+ # Delivers an SMS without checking +perform_deliveries+ and +raise_delivery_errors+,
70
+ # so use with caution.
71
+ #
72
+ # Notifier.welcome(User.first).deliver_now!
73
+ #
74
+ def deliver_now!
75
+ message.deliver!
76
+ end
77
+
78
+ # Delivers an SMS:
79
+ #
80
+ # Notifier.welcome(User.first).deliver_now
81
+ #
82
+ def deliver_now
83
+ message.deliver
84
+ end
85
+
86
+ private
87
+
88
+ def enqueue_delivery(delivery_method, options={})
89
+ args = @carrier.name, @sms_method.to_s, delivery_method.to_s, *@args
90
+ SmsCarrier::DeliveryJob.set(options).perform_later(*args)
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,45 @@
1
+ require 'active_job/railtie'
2
+ require "sms_carrier"
3
+ require "rails"
4
+ require "abstract_controller/railties/routes_helpers"
5
+
6
+ module SmsCarrier
7
+ class Railtie < Rails::Railtie # :nodoc:
8
+ config.sms_carrier = ActiveSupport::OrderedOptions.new
9
+ config.eager_load_namespaces << SmsCarrier
10
+
11
+ initializer "sms_carrier.logger" do
12
+ ActiveSupport.on_load(:sms_carrier) { self.logger ||= Rails.logger }
13
+ end
14
+
15
+ initializer "sms_carrier.set_configs" do |app|
16
+ paths = app.config.paths
17
+ options = app.config.sms_carrier
18
+
19
+ options.assets_dir ||= paths["public"].first
20
+ options.javascripts_dir ||= paths["public/javascripts"].first
21
+ options.stylesheets_dir ||= paths["public/stylesheets"].first
22
+
23
+ # make sure readers methods get compiled
24
+ options.asset_host ||= app.config.asset_host
25
+ options.relative_url_root ||= app.config.relative_url_root
26
+
27
+ ActiveSupport.on_load(:sms_carrier) do
28
+ include AbstractController::UrlFor
29
+ extend ::AbstractController::Railties::RoutesHelpers.with(app.routes, false)
30
+ include app.routes.mounted_helpers
31
+
32
+ register_interceptors(options.delete(:interceptors))
33
+ register_observers(options.delete(:observers))
34
+
35
+ options.each { |k,v| send("#{k}=", v) }
36
+ end
37
+ end
38
+
39
+ initializer "sms_carrier.compile_config_methods" do
40
+ ActiveSupport.on_load(:sms_carrier) do
41
+ config.compile_methods! if config.respond_to?(:compile_methods!)
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,126 @@
1
+ module SmsCarrier
2
+ class Sms
3
+ attr_accessor :body, :from, :to, :options, :perform_deliveries, :raise_delivery_errors, :delivery_handler
4
+
5
+ def initialize
6
+ @options = {}
7
+ @perform_deliveries = true
8
+ @raise_delivery_errors = true
9
+ @to = []
10
+ end
11
+
12
+ def [](name)
13
+ options[name]
14
+ end
15
+
16
+ def []=(name, value)
17
+ if name.to_s == 'body'
18
+ self.body = value
19
+ elsif name.to_s == 'from'
20
+ self.from = value
21
+ elsif name.to_s == 'to'
22
+ self.to = value
23
+ else
24
+ options[name] = value
25
+ end
26
+ end
27
+
28
+ def to( val = nil )
29
+ if val.nil?
30
+ @to
31
+ elsif val.is_a? Array
32
+ @to = @to + val
33
+ elsif !@to.include? val
34
+ @to << val
35
+ end
36
+ end
37
+
38
+ def to=( val )
39
+ to(val)
40
+ end
41
+
42
+ def inform_observers
43
+ Sms.inform_observers(self)
44
+ end
45
+
46
+ def inform_interceptors
47
+ Sms.inform_interceptors(self)
48
+ end
49
+
50
+ def deliver
51
+ inform_interceptors
52
+ if delivery_handler
53
+ delivery_handler.deliver_sms(self) { do_delivery }
54
+ else
55
+ do_delivery
56
+ end
57
+ inform_observers
58
+ self
59
+ end
60
+
61
+ def deliver!
62
+ inform_interceptors
63
+ response = delivery_method.deliver!(self)
64
+ inform_observers
65
+ delivery_method.settings[:return_response] ? response : self
66
+ end
67
+
68
+ def delivery_method(method = nil, settings = {})
69
+ unless method
70
+ @delivery_method
71
+ else
72
+ @delivery_method = method.new(settings)
73
+ end
74
+ end
75
+
76
+ def to_s
77
+ buffer = ''
78
+ options.each do |key, value|
79
+ buffer += "#{key}: #{value}\n"
80
+ end
81
+ buffer += "From: #{from}\n"
82
+ buffer += "To: #{to}\n"
83
+ buffer += "Body: #{body}\n"
84
+ buffer
85
+ end
86
+
87
+ @@delivery_notification_observers = []
88
+ @@delivery_interceptors = []
89
+
90
+ def self.register_observer(observer)
91
+ unless @@delivery_notification_observers.include?(observer)
92
+ @@delivery_notification_observers << observer
93
+ end
94
+ end
95
+
96
+ def self.register_interceptor(interceptor)
97
+ unless @@delivery_interceptors.include?(interceptor)
98
+ @@delivery_interceptors << interceptor
99
+ end
100
+ end
101
+
102
+ def self.inform_observers(sms)
103
+ @@delivery_notification_observers.each do |observer|
104
+ observer.delivered_sms(sms)
105
+ end
106
+ end
107
+
108
+ def self.inform_interceptors(sms)
109
+ @@delivery_interceptors.each do |interceptor|
110
+ interceptor.delivering_sms(sms)
111
+ end
112
+ end
113
+
114
+ private
115
+
116
+ def do_delivery
117
+ begin
118
+ if perform_deliveries
119
+ delivery_method.deliver!(self)
120
+ end
121
+ rescue => e
122
+ raise e if raise_delivery_errors
123
+ end
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,21 @@
1
+ module SmsCarrier
2
+ class TestCarrier
3
+ attr_accessor :settings
4
+
5
+ def initialize(settings)
6
+ self.settings = settings
7
+ end
8
+
9
+ def deliver!(sms)
10
+ TestCarrier.deliveries << sms
11
+ end
12
+
13
+ def self.deliveries
14
+ @@deliveries ||= []
15
+ end
16
+
17
+ def self.deliveries=(val)
18
+ @@deliveries = val
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,85 @@
1
+ require 'active_support/test_case'
2
+
3
+ module SmsCarrier
4
+ class NonInferrableCarrierError < ::StandardError
5
+ def initialize(name)
6
+ super "Unable to determine the carrier to test from #{name}. " +
7
+ "You'll need to specify it using tests YourCarrier in your " +
8
+ "test case definition"
9
+ end
10
+ end
11
+
12
+ class TestCase < ActiveSupport::TestCase
13
+ module Behavior
14
+ extend ActiveSupport::Concern
15
+
16
+ include ActiveSupport::Testing::ConstantLookup
17
+ include TestHelper
18
+
19
+ included do
20
+ class_attribute :_carrier_class
21
+ setup :initialize_test_deliveries
22
+ setup :set_expected_sms
23
+ teardown :restore_test_deliveries
24
+ end
25
+
26
+ module ClassMethods
27
+ def tests(carrier)
28
+ case carrier
29
+ when String, Symbol
30
+ self._carrier_class = carrier.to_s.camelize.constantize
31
+ when Module
32
+ self._carrier_class = carrier
33
+ else
34
+ raise NonInferrableCarrierError.new(carrier)
35
+ end
36
+ end
37
+
38
+ def carrier_class
39
+ if carrier = self._carrier_class
40
+ carrier
41
+ else
42
+ tests determine_default_carrier(name)
43
+ end
44
+ end
45
+
46
+ def determine_default_carrier(name)
47
+ carrier = determine_constant_from_test_name(name) do |constant|
48
+ Class === constant && constant < SmsCarrier::Base
49
+ end
50
+ raise NonInferrableCarrierError.new(name) if carrier.nil?
51
+ carrier
52
+ end
53
+ end
54
+
55
+ protected
56
+
57
+ def initialize_test_deliveries # :nodoc:
58
+ set_delivery_method :test
59
+ @old_perform_deliveries = SmsCarrier::Base.perform_deliveries
60
+ SmsCarrier::Base.perform_deliveries = true
61
+ end
62
+
63
+ def restore_test_deliveries # :nodoc:
64
+ restore_delivery_method
65
+ SmsCarrier::Base.perform_deliveries = @old_perform_deliveries
66
+ SmsCarrier::Base.deliveries.clear
67
+ end
68
+
69
+ def set_delivery_method(method) # :nodoc:
70
+ @old_delivery_method = SmsCarrier::Base.delivery_method
71
+ SmsCarrier::Base.delivery_method = method
72
+ end
73
+
74
+ def restore_delivery_method # :nodoc:
75
+ SmsCarrier::Base.delivery_method = @old_delivery_method
76
+ end
77
+
78
+ def set_expected_sms # :nodoc:
79
+ @expected = Sms.new
80
+ end
81
+ end
82
+
83
+ include Behavior
84
+ end
85
+ end
@@ -0,0 +1,66 @@
1
+ require 'active_job'
2
+
3
+ module SmsCarrier
4
+ # Provides helper methods for testing SmsCarrier, including #assert_smses
5
+ # and #assert_no_smses.
6
+ module TestHelper
7
+ include ActiveJob::TestHelper
8
+
9
+ # Asserts that the number of SMSes sent matches the given number.
10
+ #
11
+ # def test_smses
12
+ # assert_smses 0
13
+ # ContactCarrier.welcome.deliver_now
14
+ # assert_smses 1
15
+ # ContactCarrier.welcome.deliver_now
16
+ # assert_smses 2
17
+ # end
18
+ #
19
+ # If a block is passed, that block should cause the specified number of
20
+ # SMSes to be sent.
21
+ #
22
+ # def test_smses_again
23
+ # assert_smses 1 do
24
+ # ContactCarrier.welcome.deliver_now
25
+ # end
26
+ #
27
+ # assert_smses 2 do
28
+ # ContactCarrier.welcome.deliver_now
29
+ # ContactCarrier.welcome.deliver_now
30
+ # end
31
+ # end
32
+ def assert_smses(number)
33
+ if block_given?
34
+ original_count = SmsCarrier::Base.deliveries.size
35
+ yield
36
+ new_count = SmsCarrier::Base.deliveries.size
37
+ assert_equal number, new_count - original_count, "#{number} SMSes expected, but #{new_count - original_count} were sent"
38
+ else
39
+ assert_equal number, SmsCarrier::Base.deliveries.size
40
+ end
41
+ end
42
+
43
+ # Assert that no SMSes have been sent.
44
+ #
45
+ # def test_smses
46
+ # assert_no_smses
47
+ # ContactCarrier.welcome.deliver_now
48
+ # assert_smses 1
49
+ # end
50
+ #
51
+ # If a block is passed, that block should not cause any SMSes to be sent.
52
+ #
53
+ # def test_smses_again
54
+ # assert_no_smses do
55
+ # # No SMSes should be sent from this block
56
+ # end
57
+ # end
58
+ #
59
+ # Note: This assertion is simply a shortcut for:
60
+ #
61
+ # assert_smses 0
62
+ def assert_no_smses(&block)
63
+ assert_smses 0, &block
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,3 @@
1
+ module SmsCarrier
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'sms_carrier/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "sms_carrier"
8
+ spec.version = SmsCarrier::VERSION
9
+ spec.authors = ["Chen Yi-Cyuan"]
10
+ spec.email = ["emn178@gmail.com"]
11
+
12
+ spec.summary = %q{SMS composition and delivery framework.}
13
+ spec.description = %q{SMS on Rails. Compose, deliver and test SMSes using the familiar controller/view pattern.}
14
+ spec.homepage = "https://github.com/emn178/sms_carrier"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_dependency "actionpack", '>= 4.2.0'
21
+ spec.add_dependency 'actionview', '>= 4.2.0'
22
+ spec.add_dependency "activejob", '>= 4.2.0'
23
+
24
+ spec.add_development_dependency "bundler", "~> 1.10"
25
+ spec.add_development_dependency "rake", "~> 10.0"
26
+ spec.add_development_dependency "mocha"
27
+ end
metadata ADDED
@@ -0,0 +1,150 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sms_carrier
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Chen Yi-Cyuan
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-10-23 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: actionpack
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 4.2.0
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.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: actionview
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 4.2.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 4.2.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: activejob
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: 4.2.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: 4.2.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.10'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.10'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '10.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '10.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: mocha
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: SMS on Rails. Compose, deliver and test SMSes using the familiar controller/view
98
+ pattern.
99
+ email:
100
+ - emn178@gmail.com
101
+ executables: []
102
+ extensions: []
103
+ extra_rdoc_files: []
104
+ files:
105
+ - ".gitignore"
106
+ - ".rspec"
107
+ - ".travis.yml"
108
+ - CHANGELOG.md
109
+ - Gemfile
110
+ - LICENSE.txt
111
+ - README.md
112
+ - Rakefile
113
+ - lib/sms_carrier.rb
114
+ - lib/sms_carrier/base.rb
115
+ - lib/sms_carrier/delivery_job.rb
116
+ - lib/sms_carrier/delivery_methods.rb
117
+ - lib/sms_carrier/log_subscriber.rb
118
+ - lib/sms_carrier/message_delivery.rb
119
+ - lib/sms_carrier/railtie.rb
120
+ - lib/sms_carrier/sms.rb
121
+ - lib/sms_carrier/test_carrier.rb
122
+ - lib/sms_carrier/test_case.rb
123
+ - lib/sms_carrier/test_helper.rb
124
+ - lib/sms_carrier/version.rb
125
+ - sms_carrier.gemspec
126
+ homepage: https://github.com/emn178/sms_carrier
127
+ licenses:
128
+ - MIT
129
+ metadata: {}
130
+ post_install_message:
131
+ rdoc_options: []
132
+ require_paths:
133
+ - lib
134
+ required_ruby_version: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ required_rubygems_version: !ruby/object:Gem::Requirement
140
+ requirements:
141
+ - - ">="
142
+ - !ruby/object:Gem::Version
143
+ version: '0'
144
+ requirements: []
145
+ rubyforge_project:
146
+ rubygems_version: 2.4.8
147
+ signing_key:
148
+ specification_version: 4
149
+ summary: SMS composition and delivery framework.
150
+ test_files: []