twilio-rails 1.0.0 → 1.1.0
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 +4 -4
- data/README.md +23 -17
- data/app/controllers/twilio/rails/phone_controller.rb +6 -2
- data/app/controllers/twilio/rails/sms_controller.rb +6 -5
- data/app/jobs/twilio/rails/phone/attach_recording_job.rb +1 -0
- data/app/jobs/twilio/rails/phone/finished_call_job.rb +1 -0
- data/app/jobs/twilio/rails/phone/unanswered_call_job.rb +1 -0
- data/app/operations/twilio/rails/application_operation.rb +2 -1
- data/app/operations/twilio/rails/find_or_create_phone_caller_operation.rb +2 -1
- data/app/operations/twilio/rails/phone/attach_recording_operation.rb +3 -2
- data/app/operations/twilio/rails/phone/base_operation.rb +1 -0
- data/app/operations/twilio/rails/phone/create_operation.rb +11 -8
- data/app/operations/twilio/rails/phone/find_operation.rb +1 -0
- data/app/operations/twilio/rails/phone/finished_call_operation.rb +2 -1
- data/app/operations/twilio/rails/phone/receive_recording_operation.rb +3 -2
- data/app/operations/twilio/rails/phone/start_call_operation.rb +15 -13
- data/app/operations/twilio/rails/phone/twiml/after_operation.rb +2 -1
- data/app/operations/twilio/rails/phone/twiml/base_operation.rb +11 -5
- data/app/operations/twilio/rails/phone/twiml/error_operation.rb +2 -1
- data/app/operations/twilio/rails/phone/twiml/greeting_operation.rb +3 -2
- data/app/operations/twilio/rails/phone/twiml/invalid_phone_number_operation.rb +25 -0
- data/app/operations/twilio/rails/phone/twiml/prompt_operation.rb +6 -6
- data/app/operations/twilio/rails/phone/twiml/prompt_response_operation.rb +2 -1
- data/app/operations/twilio/rails/phone/twiml/request_validation_failure_operation.rb +1 -0
- data/app/operations/twilio/rails/phone/twiml/timeout_operation.rb +5 -4
- data/app/operations/twilio/rails/phone/unanswered_call_operation.rb +2 -1
- data/app/operations/twilio/rails/phone/update_operation.rb +1 -0
- data/app/operations/twilio/rails/phone/update_response_operation.rb +1 -0
- data/app/operations/twilio/rails/sms/base_operation.rb +1 -0
- data/app/operations/twilio/rails/sms/create_operation.rb +2 -1
- data/app/operations/twilio/rails/sms/find_message_operation.rb +1 -0
- data/app/operations/twilio/rails/sms/find_operation.rb +1 -0
- data/app/operations/twilio/rails/sms/send_operation.rb +12 -12
- data/app/operations/twilio/rails/sms/twiml/base_operation.rb +1 -0
- data/app/operations/twilio/rails/sms/twiml/error_operation.rb +1 -0
- data/app/operations/twilio/rails/sms/twiml/message_operation.rb +4 -3
- data/app/operations/twilio/rails/sms/update_message_operation.rb +1 -0
- data/lib/generators/twilio/rails/install/install_generator.rb +1 -0
- data/lib/generators/twilio/rails/install/templates/initializer.rb +5 -8
- data/lib/generators/twilio/rails/install/templates/message.rb +1 -0
- data/lib/generators/twilio/rails/install/templates/phone_call.rb +1 -0
- data/lib/generators/twilio/rails/install/templates/phone_caller.rb +1 -0
- data/lib/generators/twilio/rails/install/templates/recording.rb +1 -0
- data/lib/generators/twilio/rails/install/templates/response.rb +1 -0
- data/lib/generators/twilio/rails/install/templates/sms_conversation.rb +1 -0
- data/lib/generators/twilio/rails/phone_tree/phone_tree_generator.rb +1 -0
- data/lib/generators/twilio/rails/sms_responder/sms_responder_generator.rb +1 -0
- data/lib/generators/twilio/rails/sms_responder/templates/responder.rb.erb +2 -2
- data/lib/tasks/rails_tasks.rake +6 -5
- data/lib/twilio/rails/client.rb +9 -8
- data/lib/twilio/rails/concerns/has_direction.rb +2 -1
- data/lib/twilio/rails/concerns/has_phone_number.rb +13 -4
- data/lib/twilio/rails/concerns/has_time_scopes.rb +1 -0
- data/lib/twilio/rails/configuration.rb +46 -42
- data/lib/twilio/rails/formatter.rb +35 -29
- data/lib/twilio/rails/models/message.rb +1 -0
- data/lib/twilio/rails/models/phone_call.rb +1 -0
- data/lib/twilio/rails/models/phone_caller.rb +4 -3
- data/lib/twilio/rails/models/recording.rb +1 -0
- data/lib/twilio/rails/models/response.rb +7 -6
- data/lib/twilio/rails/models/sms_conversation.rb +1 -0
- data/lib/twilio/rails/phone/base_tree.rb +8 -8
- data/lib/twilio/rails/phone/tree.rb +8 -8
- data/lib/twilio/rails/phone/tree_macros.rb +27 -8
- data/lib/twilio/rails/phone.rb +3 -2
- data/lib/twilio/rails/phone_number.rb +6 -5
- data/lib/twilio/rails/phone_number_formatter/north_america.rb +52 -0
- data/lib/twilio/rails/phone_number_formatter.rb +20 -0
- data/lib/twilio/rails/railtie.rb +6 -1
- data/lib/twilio/rails/sms/delegated_responder.rb +6 -5
- data/lib/twilio/rails/sms/responder.rb +3 -2
- data/lib/twilio/rails/sms.rb +3 -2
- data/lib/twilio/rails/version.rb +1 -1
- data/lib/twilio/rails.rb +12 -26
- metadata +22 -6
data/lib/twilio/rails/phone.rb
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module Twilio
|
3
4
|
module Rails
|
4
5
|
module Phone
|
5
6
|
# Base error class for errors relating to Twilio phone interactions.
|
6
|
-
class Error < ::Twilio::Rails::Error
|
7
|
+
class Error < ::Twilio::Rails::Error; end
|
7
8
|
|
8
9
|
# Error raised when attempting to build a phone tree.
|
9
|
-
class InvalidTreeError < Error
|
10
|
+
class InvalidTreeError < Error; end
|
10
11
|
end
|
11
12
|
end
|
12
13
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module Twilio
|
3
4
|
module Rails
|
4
5
|
# A phone number object that includes the country and some optional metadata.
|
@@ -10,8 +11,8 @@ module Twilio
|
|
10
11
|
# @param label [String, nil] an optional label for the phone number, such as its source or purpose.
|
11
12
|
# @param project [String, nil] an optional project identifier for grouping phone numbers.
|
12
13
|
def initialize(number:, country:, label: nil, project: nil)
|
13
|
-
@number = Twilio::Rails::
|
14
|
-
raise Twilio::Rails::Phone::Error, "Invalid phone number '#{
|
14
|
+
@number = Twilio::Rails::PhoneNumberFormatter.coerce(number)
|
15
|
+
raise Twilio::Rails::Phone::Error, "Invalid phone number '#{number}'" unless @number
|
15
16
|
@country = country&.upcase
|
16
17
|
@label = label
|
17
18
|
@project = project.presence&.to_s
|
@@ -19,9 +20,9 @@ module Twilio
|
|
19
20
|
|
20
21
|
# @return [String] a human readable string representation of the phone number and its metadata.
|
21
22
|
def to_s
|
22
|
-
s = "Phone number #{
|
23
|
-
s = "#{
|
24
|
-
s = "#{
|
23
|
+
s = "Phone number #{number} (#{country})"
|
24
|
+
s = "#{s} #{label}" if label.present?
|
25
|
+
s = "#{s} for #{project}" if project.present?
|
25
26
|
s
|
26
27
|
end
|
27
28
|
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Twilio
|
4
|
+
module Rails
|
5
|
+
module PhoneNumberFormatter
|
6
|
+
# Formats phone numbers as North American 10 digit numbers only, and treats any other number as invalid.
|
7
|
+
# This is the legacy behavior from 1.0 which will be the default still in 1.1 as an upgrade path.
|
8
|
+
class NorthAmerica
|
9
|
+
PHONE_NUMBER_REGEX = /\A\+1[0-9]{10}\Z/
|
10
|
+
PHONE_NUMBER_SEGMENTS_REGEX = /\A\+1([0-9]{3})([0-9]{3})([0-9]{4})\Z/
|
11
|
+
|
12
|
+
def coerce(string)
|
13
|
+
string = string.number if string.is_a?(Twilio::Rails::PhoneNumber)
|
14
|
+
string = string.to_s.presence
|
15
|
+
|
16
|
+
if string
|
17
|
+
string = string.gsub(/[^0-9]/, "")
|
18
|
+
string = "1#{string}" unless string.starts_with?("1")
|
19
|
+
string = "+#{string}"
|
20
|
+
string = nil unless valid?(string)
|
21
|
+
end
|
22
|
+
|
23
|
+
string
|
24
|
+
end
|
25
|
+
|
26
|
+
def valid?(string)
|
27
|
+
string = string.number if string.is_a?(Twilio::Rails::PhoneNumber)
|
28
|
+
!!string&.match?(PHONE_NUMBER_REGEX)
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_param(string)
|
32
|
+
string = coerce(string)
|
33
|
+
return "" unless string
|
34
|
+
matches = string.match(PHONE_NUMBER_SEGMENTS_REGEX)
|
35
|
+
raise Twilio::Rails::Error, "[to_param] Phone number marked as valid but could not capture. I made a bad regex: #{string}" unless matches
|
36
|
+
matches.captures.join("-")
|
37
|
+
end
|
38
|
+
|
39
|
+
def display(string)
|
40
|
+
coerced_phone_number = coerce(string)
|
41
|
+
if coerced_phone_number
|
42
|
+
matches = coerced_phone_number.match(PHONE_NUMBER_SEGMENTS_REGEX)
|
43
|
+
raise Twilio::Rails::Error, "[display] Phone number marked as valid but could not capture. I made a bad regex: #{string}" unless matches
|
44
|
+
"(#{matches.captures[0]}) #{matches.captures[1]} #{matches.captures[2]}"
|
45
|
+
else
|
46
|
+
string
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Twilio
|
4
|
+
module Rails
|
5
|
+
# The interface for the phone number validator and formatter that is defined in the `Twilio::Rails.config.phone_number_formatter`
|
6
|
+
# configuration. This delegates the methods to that instance and is used both internally to the gem and by the gem consumer.
|
7
|
+
module PhoneNumberFormatter
|
8
|
+
extend self
|
9
|
+
extend Forwardable
|
10
|
+
|
11
|
+
def_delegators :formatter, :coerce, :valid?, :to_param, :display
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def formatter
|
16
|
+
Twilio::Rails.config.phone_number_formatter
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/twilio/rails/railtie.rb
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module Twilio
|
3
4
|
module Rails
|
4
5
|
class Railtie < ::Rails::Railtie
|
5
6
|
config.before_initialize do
|
6
7
|
ActiveSupport::Inflector.inflections(:en) do |inflect|
|
7
|
-
inflect.acronym
|
8
|
+
inflect.acronym "SMS"
|
8
9
|
end
|
9
10
|
end
|
10
11
|
|
@@ -12,6 +13,10 @@ module Twilio
|
|
12
13
|
# TODO: This should work but it does not. I think maybe it happens too late? The same line works if you add it directly to the `application.rb` of the app. It is needed for dev mode.
|
13
14
|
# application.config.hosts << Twilio::Rails.config.host_domain
|
14
15
|
end
|
16
|
+
|
17
|
+
initializer "twilio_rails.deprecator" do |app|
|
18
|
+
app.deprecators[:twilio_rails] = Twilio::Rails.deprecator
|
19
|
+
end
|
15
20
|
end
|
16
21
|
end
|
17
22
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module Twilio
|
3
4
|
module Rails
|
4
5
|
module SMS
|
@@ -34,7 +35,7 @@ module Twilio
|
|
34
35
|
#
|
35
36
|
# @return [String] the name of the responder.
|
36
37
|
def responder_name
|
37
|
-
|
38
|
+
name.demodulize.underscore.gsub(/_responder\Z/, "")
|
38
39
|
end
|
39
40
|
end
|
40
41
|
|
@@ -43,22 +44,22 @@ module Twilio
|
|
43
44
|
@sms_conversation = message.sms_conversation
|
44
45
|
end
|
45
46
|
|
46
|
-
# Must be implemented by the subclass otherwise will raise a `
|
47
|
+
# Must be implemented by the subclass otherwise will raise a `NoMethodError`. Returns true if this
|
47
48
|
# responder should handle the given message. If true then the {#reply} method will be called to generate the
|
48
49
|
# body of the response. It has access to the message and the conversation.
|
49
50
|
#
|
50
51
|
# @return [true, false] true if this responder should handle the given message.
|
51
52
|
def handle?
|
52
|
-
raise
|
53
|
+
raise NoMethodError, "#{self.class}#handle? must be implemented."
|
53
54
|
end
|
54
55
|
|
55
|
-
# Must be implemented by the subclass otherwise will raise a `
|
56
|
+
# Must be implemented by the subclass otherwise will raise a `NoMethodError`. Returns the body of the
|
56
57
|
# message to be sent in response. Will only be called if {#handle?} returns true. It has access to the message
|
57
58
|
# and the conversation.
|
58
59
|
#
|
59
60
|
# @return [String, nil] the body of the response to be sent as SMS, or `nil` if no message should be sent.
|
60
61
|
def reply
|
61
|
-
raise
|
62
|
+
raise NoMethodError, "#{self.class}#reply must be implemented."
|
62
63
|
end
|
63
64
|
|
64
65
|
protected
|
@@ -1,5 +1,5 @@
|
|
1
|
-
|
2
1
|
# frozen_string_literal: true
|
2
|
+
|
3
3
|
module Twilio
|
4
4
|
module Rails
|
5
5
|
module SMS
|
@@ -25,7 +25,8 @@ module Twilio
|
|
25
25
|
return responder.reply if responder.handle?
|
26
26
|
end
|
27
27
|
|
28
|
-
raise Twilio::Rails::SMS::InvalidResponderError, "No responder found for message_id=#{
|
28
|
+
raise Twilio::Rails::SMS::InvalidResponderError, "No responder found for SMS. message_id=#{message.id} " \
|
29
|
+
"phone_caller_id=#{sms_conversation.phone_caller&.id} from_number=\"#{sms_conversation.from_number}\" body=\"#{message.body}\""
|
29
30
|
end
|
30
31
|
end
|
31
32
|
end
|
data/lib/twilio/rails/sms.rb
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module Twilio
|
3
4
|
module Rails
|
4
5
|
module SMS
|
5
6
|
# Base error class for errors relating to Twilio phone interactions.
|
6
|
-
class Error < ::Twilio::Rails::Error
|
7
|
+
class Error < ::Twilio::Rails::Error; end
|
7
8
|
|
8
9
|
# Error raised when a responder is unable to handle an SMS message.
|
9
|
-
class InvalidResponderError < Error
|
10
|
+
class InvalidResponderError < Error; end
|
10
11
|
end
|
11
12
|
end
|
12
13
|
end
|
data/lib/twilio/rails/version.rb
CHANGED
data/lib/twilio/rails.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require "forwardable"
|
1
2
|
require "active_operation"
|
2
3
|
require "twilio-ruby"
|
3
4
|
require "faraday"
|
@@ -8,13 +9,15 @@ require "twilio/rails/engine"
|
|
8
9
|
module Twilio
|
9
10
|
module Rails
|
10
11
|
# Base error class for all errors raised by the Twilio::Rails gem. Every error is a subclass of this one.
|
11
|
-
class Error < StandardError
|
12
|
+
class Error < StandardError; end
|
12
13
|
end
|
13
14
|
end
|
14
15
|
|
15
16
|
require "twilio/rails/railtie"
|
16
17
|
require "twilio/rails/configuration"
|
17
18
|
require "twilio/rails/formatter"
|
19
|
+
require "twilio/rails/phone_number_formatter"
|
20
|
+
require "twilio/rails/phone_number_formatter/north_america"
|
18
21
|
require "twilio/rails/phone_number"
|
19
22
|
require "twilio/rails/client"
|
20
23
|
|
@@ -40,7 +43,6 @@ require "twilio/rails/models/message"
|
|
40
43
|
module Twilio
|
41
44
|
module Rails
|
42
45
|
class << self
|
43
|
-
|
44
46
|
# Read and write accessible configuration object. In most cases this should only be read after the app has been
|
45
47
|
# initialized. See {Twilio::Rails::Configuration} for more information.
|
46
48
|
#
|
@@ -49,6 +51,14 @@ module Twilio
|
|
49
51
|
@config ||= ::Twilio::Rails::Configuration.new
|
50
52
|
end
|
51
53
|
|
54
|
+
# See the ActiveSupport::Deprecation documentation:
|
55
|
+
# https://api.rubyonrails.org/classes/ActiveSupport/Deprecation.html
|
56
|
+
#
|
57
|
+
# @return [ActiveSupport::Deprecation] the deprecator for the engine.
|
58
|
+
def deprecator
|
59
|
+
@deprecator ||= ActiveSupport::Deprecation.new("2.0", "Twilio::Rails")
|
60
|
+
end
|
61
|
+
|
52
62
|
# Called in the `config/initializers/twilio_rails.rb` file to configure the engine. This yields the {.config}
|
53
63
|
# object above and then calls {Twilio::Rails::Configuration#validate!} to ensure the configuration is valid.
|
54
64
|
#
|
@@ -60,30 +70,6 @@ module Twilio
|
|
60
70
|
config.validate!
|
61
71
|
nil
|
62
72
|
end
|
63
|
-
|
64
|
-
# Abstraction for the framework to notify of an important exception that has occurred. This safely calls the
|
65
|
-
# configured `config.exception_notifier` or does nothing if it is set to `nil`. This does not catch, handle, or
|
66
|
-
# prevent the exception from raising.
|
67
|
-
#
|
68
|
-
# @param exception [Exception] the exception that has occurred.
|
69
|
-
# @param message [String] a description of the exception, defaults to `exception.message` if blank.
|
70
|
-
# @param context [Hash] a hash of arbitrary additional context to include in the notification.
|
71
|
-
# @param exception_binding [Binding] the binding of where the exception is being notified.
|
72
|
-
# @return [true, false] if an exception has been successfully notified.
|
73
|
-
def notify_exception(exception, message: nil, context: {}, exception_binding: nil)
|
74
|
-
if config.exception_notifier
|
75
|
-
begin
|
76
|
-
message = message.presence || exception.message
|
77
|
-
config.exception_notifier.call(exception, message, context, exception_binding)
|
78
|
-
true
|
79
|
-
rescue => e
|
80
|
-
config.logger.tagged(self.class) { |l| l.error("ExceptionNotifier failed to notify of exception=#{ exception.inspect } message=#{ message.inspect } context=#{ context.inspect }") }
|
81
|
-
false
|
82
|
-
end
|
83
|
-
else
|
84
|
-
false
|
85
|
-
end
|
86
|
-
end
|
87
73
|
end
|
88
74
|
end
|
89
75
|
end
|
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: twilio-rails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kevin McPhillips
|
8
|
-
autorequire:
|
9
8
|
bindir: bin
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 2025-02-21 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
13
12
|
- !ruby/object:Gem::Dependency
|
14
13
|
name: rails
|
@@ -164,6 +163,20 @@ dependencies:
|
|
164
163
|
- - ">="
|
165
164
|
- !ruby/object:Gem::Version
|
166
165
|
version: '0'
|
166
|
+
- !ruby/object:Gem::Dependency
|
167
|
+
name: standardrb
|
168
|
+
requirement: !ruby/object:Gem::Requirement
|
169
|
+
requirements:
|
170
|
+
- - ">="
|
171
|
+
- !ruby/object:Gem::Version
|
172
|
+
version: '0'
|
173
|
+
type: :development
|
174
|
+
prerelease: false
|
175
|
+
version_requirements: !ruby/object:Gem::Requirement
|
176
|
+
requirements:
|
177
|
+
- - ">="
|
178
|
+
- !ruby/object:Gem::Version
|
179
|
+
version: '0'
|
167
180
|
description: A Rails engine that provides the framework to build complex phone interactions
|
168
181
|
using the Twilio API.
|
169
182
|
email:
|
@@ -201,6 +214,7 @@ files:
|
|
201
214
|
- app/operations/twilio/rails/phone/twiml/base_operation.rb
|
202
215
|
- app/operations/twilio/rails/phone/twiml/error_operation.rb
|
203
216
|
- app/operations/twilio/rails/phone/twiml/greeting_operation.rb
|
217
|
+
- app/operations/twilio/rails/phone/twiml/invalid_phone_number_operation.rb
|
204
218
|
- app/operations/twilio/rails/phone/twiml/prompt_operation.rb
|
205
219
|
- app/operations/twilio/rails/phone/twiml/prompt_response_operation.rb
|
206
220
|
- app/operations/twilio/rails/phone/twiml/request_validation_failure_operation.rb
|
@@ -255,6 +269,8 @@ files:
|
|
255
269
|
- lib/twilio/rails/phone/tree.rb
|
256
270
|
- lib/twilio/rails/phone/tree_macros.rb
|
257
271
|
- lib/twilio/rails/phone_number.rb
|
272
|
+
- lib/twilio/rails/phone_number_formatter.rb
|
273
|
+
- lib/twilio/rails/phone_number_formatter/north_america.rb
|
258
274
|
- lib/twilio/rails/railtie.rb
|
259
275
|
- lib/twilio/rails/sms.rb
|
260
276
|
- lib/twilio/rails/sms/delegated_responder.rb
|
@@ -267,7 +283,8 @@ metadata:
|
|
267
283
|
homepage_uri: https://github.com/kmcphillips/twilio-rails
|
268
284
|
source_code_uri: https://github.com/kmcphillips/twilio-rails
|
269
285
|
changelog_uri: https://github.com/kmcphillips/twilio-rails/blob/main/CHANGELOG.md
|
270
|
-
|
286
|
+
documentation_uri: https://rubydoc.info/gems/twilio-rails
|
287
|
+
rubygems_mfa_required: 'true'
|
271
288
|
rdoc_options: []
|
272
289
|
require_paths:
|
273
290
|
- lib
|
@@ -282,8 +299,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
282
299
|
- !ruby/object:Gem::Version
|
283
300
|
version: '0'
|
284
301
|
requirements: []
|
285
|
-
rubygems_version: 3.
|
286
|
-
signing_key:
|
302
|
+
rubygems_version: 3.6.2
|
287
303
|
specification_version: 4
|
288
304
|
summary: A framework for building rich phone interactions in Rails using Twilio.
|
289
305
|
test_files: []
|