twilio-rails 1.0.1 → 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.
Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +20 -14
  3. data/app/controllers/twilio/rails/phone_controller.rb +6 -2
  4. data/app/controllers/twilio/rails/sms_controller.rb +6 -5
  5. data/app/jobs/twilio/rails/phone/attach_recording_job.rb +1 -0
  6. data/app/jobs/twilio/rails/phone/finished_call_job.rb +1 -0
  7. data/app/jobs/twilio/rails/phone/unanswered_call_job.rb +1 -0
  8. data/app/operations/twilio/rails/application_operation.rb +2 -1
  9. data/app/operations/twilio/rails/find_or_create_phone_caller_operation.rb +2 -1
  10. data/app/operations/twilio/rails/phone/attach_recording_operation.rb +3 -2
  11. data/app/operations/twilio/rails/phone/base_operation.rb +1 -0
  12. data/app/operations/twilio/rails/phone/create_operation.rb +11 -8
  13. data/app/operations/twilio/rails/phone/find_operation.rb +1 -0
  14. data/app/operations/twilio/rails/phone/finished_call_operation.rb +2 -1
  15. data/app/operations/twilio/rails/phone/receive_recording_operation.rb +3 -2
  16. data/app/operations/twilio/rails/phone/start_call_operation.rb +15 -13
  17. data/app/operations/twilio/rails/phone/twiml/after_operation.rb +2 -1
  18. data/app/operations/twilio/rails/phone/twiml/base_operation.rb +11 -5
  19. data/app/operations/twilio/rails/phone/twiml/error_operation.rb +2 -1
  20. data/app/operations/twilio/rails/phone/twiml/greeting_operation.rb +3 -2
  21. data/app/operations/twilio/rails/phone/twiml/invalid_phone_number_operation.rb +25 -0
  22. data/app/operations/twilio/rails/phone/twiml/prompt_operation.rb +6 -5
  23. data/app/operations/twilio/rails/phone/twiml/prompt_response_operation.rb +2 -1
  24. data/app/operations/twilio/rails/phone/twiml/request_validation_failure_operation.rb +1 -0
  25. data/app/operations/twilio/rails/phone/twiml/timeout_operation.rb +5 -4
  26. data/app/operations/twilio/rails/phone/unanswered_call_operation.rb +2 -1
  27. data/app/operations/twilio/rails/phone/update_operation.rb +1 -0
  28. data/app/operations/twilio/rails/phone/update_response_operation.rb +1 -0
  29. data/app/operations/twilio/rails/sms/base_operation.rb +1 -0
  30. data/app/operations/twilio/rails/sms/create_operation.rb +2 -1
  31. data/app/operations/twilio/rails/sms/find_message_operation.rb +1 -0
  32. data/app/operations/twilio/rails/sms/find_operation.rb +1 -0
  33. data/app/operations/twilio/rails/sms/send_operation.rb +12 -12
  34. data/app/operations/twilio/rails/sms/twiml/base_operation.rb +1 -0
  35. data/app/operations/twilio/rails/sms/twiml/error_operation.rb +1 -0
  36. data/app/operations/twilio/rails/sms/twiml/message_operation.rb +4 -3
  37. data/app/operations/twilio/rails/sms/update_message_operation.rb +1 -0
  38. data/lib/generators/twilio/rails/install/install_generator.rb +1 -0
  39. data/lib/generators/twilio/rails/install/templates/initializer.rb +5 -8
  40. data/lib/generators/twilio/rails/install/templates/message.rb +1 -0
  41. data/lib/generators/twilio/rails/install/templates/phone_call.rb +1 -0
  42. data/lib/generators/twilio/rails/install/templates/phone_caller.rb +1 -0
  43. data/lib/generators/twilio/rails/install/templates/recording.rb +1 -0
  44. data/lib/generators/twilio/rails/install/templates/response.rb +1 -0
  45. data/lib/generators/twilio/rails/install/templates/sms_conversation.rb +1 -0
  46. data/lib/generators/twilio/rails/phone_tree/phone_tree_generator.rb +1 -0
  47. data/lib/generators/twilio/rails/sms_responder/sms_responder_generator.rb +1 -0
  48. data/lib/generators/twilio/rails/sms_responder/templates/responder.rb.erb +2 -2
  49. data/lib/tasks/rails_tasks.rake +6 -5
  50. data/lib/twilio/rails/client.rb +9 -8
  51. data/lib/twilio/rails/concerns/has_direction.rb +2 -1
  52. data/lib/twilio/rails/concerns/has_phone_number.rb +13 -4
  53. data/lib/twilio/rails/concerns/has_time_scopes.rb +1 -0
  54. data/lib/twilio/rails/configuration.rb +42 -40
  55. data/lib/twilio/rails/formatter.rb +35 -29
  56. data/lib/twilio/rails/models/message.rb +1 -0
  57. data/lib/twilio/rails/models/phone_call.rb +1 -0
  58. data/lib/twilio/rails/models/phone_caller.rb +4 -3
  59. data/lib/twilio/rails/models/recording.rb +1 -0
  60. data/lib/twilio/rails/models/response.rb +7 -6
  61. data/lib/twilio/rails/models/sms_conversation.rb +1 -0
  62. data/lib/twilio/rails/phone/base_tree.rb +8 -7
  63. data/lib/twilio/rails/phone/tree.rb +8 -7
  64. data/lib/twilio/rails/phone/tree_macros.rb +9 -9
  65. data/lib/twilio/rails/phone.rb +3 -2
  66. data/lib/twilio/rails/phone_number.rb +6 -5
  67. data/lib/twilio/rails/phone_number_formatter/north_america.rb +52 -0
  68. data/lib/twilio/rails/phone_number_formatter.rb +20 -0
  69. data/lib/twilio/rails/railtie.rb +6 -1
  70. data/lib/twilio/rails/sms/delegated_responder.rb +6 -5
  71. data/lib/twilio/rails/sms/responder.rb +3 -2
  72. data/lib/twilio/rails/sms.rb +3 -2
  73. data/lib/twilio/rails/version.rb +1 -1
  74. data/lib/twilio/rails.rb +12 -26
  75. metadata +20 -6
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Twilio
3
4
  module Rails
4
5
  module Phone
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Twilio
3
4
  module Rails
4
5
  module SMS
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Twilio
3
4
  module Rails
4
5
  module SMS
@@ -12,7 +13,7 @@ module Twilio
12
13
  from_number: params["From"].presence,
13
14
  from_city: params["FromCity"].presence,
14
15
  from_province: params["FromState"].presence,
15
- from_country: params["FromCountry"].presence,
16
+ from_country: params["FromCountry"].presence
16
17
  )
17
18
  conversation.save!
18
19
  conversation
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Twilio
3
4
  module Rails
4
5
  module SMS
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Twilio
3
4
  module Rails
4
5
  module SMS
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Twilio
3
4
  module Rails
4
5
  module SMS
@@ -19,7 +20,7 @@ module Twilio
19
20
  input :messages, accepts: Array, type: :keyword, required: true
20
21
  input :from_number, accepts: [String, Twilio::Rails::PhoneNumber], type: :keyword, required: false
21
22
 
22
- TWILIO_UNSUBSCRIBED_ERROR_CODES = [ 21610 ].freeze
23
+ TWILIO_UNSUBSCRIBED_ERROR_CODES = [21610].freeze
23
24
 
24
25
  # @param phone_caller_id [Integer] the id of the phone caller to send the message to.
25
26
  # @param messages [Array<String>] the messages to send to the phone caller. It may be empty.
@@ -29,14 +30,14 @@ module Twilio
29
30
  # @return [Twilio::Rails::Models::SMSConversation] the SMS conversation that was created and sent.
30
31
  def execute
31
32
  return nil if messages.blank?
32
- raise Twilio::Rails::SMS::Error, "from_number=#{ from_number } is not a valid phone number" if from_number.present? && !Twilio::Rails::Formatter.coerce_to_valid_phone_number(from_number)
33
+ raise Twilio::Rails::SMS::Error, "from_number=#{from_number} is not a valid phone number" if from_number.present? && !Twilio::Rails::PhoneNumberFormatter.coerce(from_number)
33
34
 
34
35
  conversation = ::Twilio::Rails.config.sms_conversation_class.new(
35
36
  number: calculated_from_number,
36
37
  from_number: calculated_to_number,
37
38
  from_city: phone_call&.from_city,
38
39
  from_province: phone_call&.from_province,
39
- from_country: phone_call&.from_country,
40
+ from_country: phone_call&.from_country
40
41
  )
41
42
  conversation.save!
42
43
 
@@ -46,21 +47,20 @@ module Twilio
46
47
  sid = Twilio::Rails::Client.send_message(
47
48
  message: body,
48
49
  to: calculated_to_number,
49
- from: calculated_from_number,
50
+ from: calculated_from_number
50
51
  )
51
52
  rescue Twilio::REST::RestError => e
52
53
  if TWILIO_UNSUBSCRIBED_ERROR_CODES.include?(e.code)
53
- Twilio::Rails.config.logger.tagged(self.class) { |l| l.warn("tried to send to unsubscribed and got Twilio::REST::RestError code=21610 phone_caller_id=#{ phone_caller.id } phone_number=#{ calculated_to_number } message=#{ body }") }
54
+ Twilio::Rails.config.logger.tagged(self.class) { |l| l.warn("tried to send to unsubscribed and got Twilio::REST::RestError code=21610 phone_caller_id=#{phone_caller.id} phone_number=#{calculated_to_number} message=#{body}") }
54
55
  else
55
- Twilio::Rails.notify_exception(e,
56
- message: "Failed to send Twilio message. Got REST error response.",
56
+ ::Rails.error.report(e,
57
+ handled: false,
57
58
  context: {
59
+ message: "Failed to send Twilio message. Got REST error response.",
58
60
  to: calculated_to_number,
59
61
  from: calculated_from_number,
60
- phone_call_id: phone_call&.id,
61
- },
62
- exception_binding: binding
63
- )
62
+ phone_call_id: phone_call&.id
63
+ })
64
64
  raise
65
65
  end
66
66
  end
@@ -85,7 +85,7 @@ module Twilio
85
85
 
86
86
  def calculated_from_number
87
87
  if from_number.present?
88
- Twilio::Rails::Formatter.coerce_to_valid_phone_number(from_number)
88
+ Twilio::Rails::PhoneNumberFormatter.coerce(from_number)
89
89
  elsif phone_call
90
90
  phone_call.number
91
91
  else
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Twilio
3
4
  module Rails
4
5
  module SMS
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Twilio
3
4
  module Rails
4
5
  module SMS
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Twilio
3
4
  module Rails
4
5
  module SMS
@@ -20,7 +21,7 @@ module Twilio
20
21
  if body.present?
21
22
  message = conversation.messages.build(
22
23
  direction: "outbound",
23
- body: body,
24
+ body: body
24
25
  )
25
26
 
26
27
  message.save!
@@ -32,11 +33,11 @@ module Twilio
32
33
  )
33
34
  end
34
35
 
35
- Twilio::Rails.config.logger.info("message_twiml: #{twiml_response.to_s}")
36
+ Twilio::Rails.config.logger.info("message_twiml: #{twiml_response}")
36
37
  twiml_response.to_s
37
38
  else
38
39
  Twilio::Rails.config.logger.info("resply is blank, not sending message in response")
39
- Twilio::Rails.config.logger.info("message_twiml: #{twiml_response.to_s}")
40
+ Twilio::Rails.config.logger.info("message_twiml: #{twiml_response}")
40
41
 
41
42
  twiml = Twilio::TwiML::MessagingResponse.new
42
43
  twiml.to_s
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Twilio
3
4
  module Rails
4
5
  module SMS
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  class Twilio::Rails::InstallGenerator < Rails::Generators::Base
3
4
  source_root File.expand_path("templates", __dir__)
4
5
 
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  Twilio::Rails.setup do |config|
3
4
  # These are the Twilio account credentials used to access the Twilio API. These should likely be configured in the
4
5
  # encrypted Rails credentials or loaded from an ENV variable.
@@ -45,14 +46,6 @@ Twilio::Rails.setup do |config|
45
46
  # [ "Bad text" ].any? { |regex| regex.match?(params["Body"]) }
46
47
  # }
47
48
 
48
- # A proc that will be called when an exception is raised in certain key points in the framework. This will never
49
- # capture the exception, it will raise regardless, but it is a good spot to send an email or notify in chat
50
- # if desired. The proc needs to accept `(exception, message, context, exception_binding)` as arguments. The
51
- # default is `nil`, which means no action will be taken.
52
- # config.exception_notifier = ->(exception, message, context, exception_binding) {
53
- # MyChatClient.send_message("Error: #{ message } #{ exception.message } #{ context }")
54
- # }
55
-
56
49
  # Controls if recordings will be downloaded and attached to the `Recording` model in an ActiveStorage attachment.
57
50
  # This is `true` by default, but can be set to `false` to disable all downloads. It can also be set to a `Proc` or
58
51
  # callable that will receive the `Recording` instance and return a boolean for this specific instance. if reordings will be downloaded.
@@ -66,6 +59,10 @@ Twilio::Rails.setup do |config|
66
59
  # the phone macros. Defaults to a list of common responses.
67
60
  # config.no_responses = ["no"]
68
61
 
62
+ # An instance of class that will be used to format phone numbers. Defaults to `Twilio::Rails::PhoneNumberFormatter::NorthAmerica`
63
+ # for now but in the next major version it will be set to a formatter that can handle all countries.
64
+ # config.phone_number_formatter = Twilio::Rails::PhoneNumberFormatter::NorthAmerica.new
65
+
69
66
  # The name of the model classes, as strings, that this application uses to represent the concepts stored in the DB.
70
67
  # The generators will generate the models with the default names below, but they can be changed as the application
71
68
  # may need.
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  class Message < ApplicationRecord
3
4
  include Twilio::Rails::Models::Message
4
5
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  class PhoneCall < ApplicationRecord
3
4
  include Twilio::Rails::Models::PhoneCall
4
5
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  class PhoneCaller < ApplicationRecord
3
4
  include Twilio::Rails::Models::PhoneCaller
4
5
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  class Recording < ApplicationRecord
3
4
  include Twilio::Rails::Models::Recording
4
5
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  class Response < ApplicationRecord
3
4
  include Twilio::Rails::Models::Response
4
5
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  class SMSConversation < ApplicationRecord
3
4
  include Twilio::Rails::Models::SMSConversation
4
5
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  class Twilio::Rails::PhoneTreeGenerator < Rails::Generators::NamedBase
3
4
  source_root File.expand_path("templates", __dir__)
4
5
 
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  class Twilio::Rails::SmsResponderGenerator < Rails::Generators::NamedBase
3
4
  source_root File.expand_path("templates", __dir__)
4
5
 
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
  class <%= class_name %>Responder < Twilio::Rails::SMS::DelegatedResponder
3
3
  def handle?
4
- raise NotImplementedError, "Implement #{ self.class }#handle?"
4
+ raise NoMethodError, "#{ self.class }#handle? must be implemented."
5
5
  end
6
6
 
7
7
  def reply
8
- raise NotImplementedError, "Implement #{ self.class }#reply"
8
+ raise NoMethodError, "#{ self.class }#reply must be implemented."
9
9
  end
10
10
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  namespace :twilio do
3
4
  namespace :rails do
4
5
  desc "Show the available values to config in Twilio"
@@ -9,7 +10,7 @@ namespace :twilio do
9
10
  puts "Twilio::Rails.config.host is set to a test value. Set it in the `config/initializers/twilio_rails.rb` file."
10
11
  else
11
12
  http_methods = if Twilio::Rails.config.controller_http_methods.length == 1
12
- "HTTP #{ Twilio::Rails.config.controller_http_methods.first.to_s.upcase }"
13
+ "HTTP #{Twilio::Rails.config.controller_http_methods.first.to_s.upcase}"
13
14
  else
14
15
  "HTTP POST or HTTP GET"
15
16
  end
@@ -22,18 +23,18 @@ namespace :twilio do
22
23
  puts "You cannot yet configure `Voice & Fax' because There are no phone trees registered in this application."
23
24
  puts "Register them in the `config/initializers/twilio_rails.rb` file if you want to handle phone calls, and run this task again to help configure Twilio."
24
25
  else
25
- puts "Under 'Voice & Fax' set 'A CALL COMES IN' to 'Webhook' with #{ http_methods } and one of the following URLs:"
26
+ puts "Under 'Voice & Fax' set 'A CALL COMES IN' to 'Webhook' with #{http_methods} and one of the following URLs:"
26
27
  Twilio::Rails.config.phone_trees.all.each do |name, tree|
27
28
  puts " #{tree.inbound_url}"
28
29
  end
29
30
  puts ""
30
31
  puts "Under 'Voice & Fax' set 'CALL STATUS CHANGES' to following URL:"
31
- puts " #{ ::Twilio::Rails.config.host }#{ ::Twilio::Rails::Engine.routes.url_helpers.phone_status_path(format: :xml) }"
32
+ puts " #{::Twilio::Rails.config.host}#{::Twilio::Rails::Engine.routes.url_helpers.phone_status_path(format: :xml)}"
32
33
  end
33
34
 
34
35
  puts ""
35
- puts "Under 'Messaging' set 'A MESSAGE COMES IN' to 'Webhook' with #{ http_methods } and the following URL:"
36
- puts " #{ ::Twilio::Rails.config.host }#{ ::Twilio::Rails::Engine.routes.url_helpers.sms_message_path(format: :xml) }"
36
+ puts "Under 'Messaging' set 'A MESSAGE COMES IN' to 'Webhook' with #{http_methods} and the following URL:"
37
+ puts " #{::Twilio::Rails.config.host}#{::Twilio::Rails::Engine.routes.url_helpers.sms_message_path(format: :xml)}"
37
38
 
38
39
  if Twilio::Rails.config.sms_responders.all.length == 0
39
40
  puts "There are no SMS responders registered so they will not be handled."
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Twilio
3
4
  module Rails
4
5
  # An abstraction over top of the `Twilio::REST` API client. Used to send SMS messages and start calls, as well as
@@ -10,7 +11,7 @@ module Twilio
10
11
  def client
11
12
  @twilio_client ||= Twilio::REST::Client.new(
12
13
  Twilio::Rails.config.account_sid,
13
- Twilio::Rails.config.auth_token,
14
+ Twilio::Rails.config.auth_token
14
15
  )
15
16
  end
16
17
 
@@ -23,12 +24,12 @@ module Twilio
23
24
  # @param from [String] the phone number to send the message from.
24
25
  # @return [String] the SID returned from Twilio for the sent SMS message.
25
26
  def send_message(message:, to:, from:)
26
- Twilio::Rails.config.logger.tagged(self) { |l| l.info("[send_message] to=#{ to } from=#{ from } body='#{ message }'") }
27
+ Twilio::Rails.config.logger.tagged(self) { |l| l.info("[send_message] to=#{to} from=#{from} body='#{message}'") }
27
28
  client.messages.create(
28
29
  from: from,
29
30
  to: to,
30
31
  body: message,
31
- status_callback: "#{ Twilio::Rails.config.host }#{ ::Twilio::Rails::Engine.routes.url_helpers.sms_status_path(format: :xml) }",
32
+ status_callback: "#{Twilio::Rails.config.host}#{::Twilio::Rails::Engine.routes.url_helpers.sms_status_path(format: :xml)}"
32
33
  ).sid
33
34
  end
34
35
 
@@ -55,18 +56,18 @@ module Twilio
55
56
  # @param answering_machine_detection [true, false] whether or not to enable answering machine detection.
56
57
  # @return [String] the SID returned from Twilio for the started call.
57
58
  def start_call(url:, to:, from:, answering_machine_detection: true)
58
- Twilio::Rails.config.logger.tagged(self) { |l| l.info("[start_call] to=#{ to } from=#{ from } url=#{ url } answering_machine_detection=#{ !!answering_machine_detection }") }
59
+ Twilio::Rails.config.logger.tagged(self) { |l| l.info("[start_call] to=#{to} from=#{from} url=#{url} answering_machine_detection=#{!!answering_machine_detection}") }
59
60
  client.calls.create(
60
61
  from: from,
61
62
  to: to,
62
63
  url: url,
63
- machine_detection: ( answering_machine_detection ? "Enable" : "Disable" ),
64
+ machine_detection: (answering_machine_detection ? "Enable" : "Disable"),
64
65
  async_amd: true,
65
- async_amd_status_callback: "#{ Twilio::Rails.config.host }#{ ::Twilio::Rails::Engine.routes.url_helpers.phone_status_path(format: :xml, async_amd: "true") }",
66
+ async_amd_status_callback: "#{Twilio::Rails.config.host}#{::Twilio::Rails::Engine.routes.url_helpers.phone_status_path(format: :xml, async_amd: "true")}",
66
67
  async_amd_status_callback_method: "POST",
67
- status_callback: "#{ Twilio::Rails.config.host }#{ ::Twilio::Rails::Engine.routes.url_helpers.phone_status_path(format: :xml) }",
68
+ status_callback: "#{Twilio::Rails.config.host}#{::Twilio::Rails::Engine.routes.url_helpers.phone_status_path(format: :xml)}",
68
69
  status_callback_method: "POST",
69
- status_callback_event: ["completed", "no-answer"],
70
+ status_callback_event: ["completed", "no-answer"]
70
71
  # timeout: 30,
71
72
  ).sid
72
73
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Twilio
3
4
  module Rails
4
5
  # Provides scopes, validations, and convenience methods for a model that has an attribute `direction` with
@@ -7,7 +8,7 @@ module Twilio
7
8
  extend ActiveSupport::Concern
8
9
 
9
10
  included do
10
- validates :direction, inclusion: { in: ["outbound", "inbound"] }
11
+ validates :direction, inclusion: {in: ["outbound", "inbound"]}
11
12
 
12
13
  scope :outbound, -> { where(direction: "outbound") }
13
14
  scope :inbound, -> { where(direction: "inbound") }
@@ -1,26 +1,35 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Twilio
3
4
  module Rails
4
5
  # Provides validations and reformatting on validation for a model that has an attribute `phone_number` that is
5
- # that is unique and that matches {Twilio::Rails::Formatter.coerce_to_valid_phone_number}.
6
+ # that is unique and that matches {Twilio::Rails::PhoneNumberFormatter.coerce}.
6
7
  module HasPhoneNumber
7
8
  extend ActiveSupport::Concern
8
9
 
9
10
  included do
10
- validates :phone_number, uniqueness: { allow_blank: true, message: "already exists" }
11
+ validates :phone_number, uniqueness: {allow_blank: true, message: "already exists"}
11
12
 
12
13
  before_validation :reformat_phone_number
13
14
  end
14
15
 
15
16
  def reformat_phone_number
16
- current = Twilio::Rails::Formatter.coerce_to_valid_phone_number(phone_number)
17
+ current = Twilio::Rails::PhoneNumberFormatter.coerce(phone_number)
17
18
  self.phone_number = current if current
18
19
 
19
20
  true
20
21
  end
21
22
 
22
23
  def valid_north_american_phone_number?
23
- Twilio::Rails::Formatter.valid_north_american_phone_number?(phone_number)
24
+ Twilio::Rails.deprecator.warn(<<~DEPRECATION.strip)
25
+ valid_north_american_phone_number? is deprecated and will be removed in the next major version.
26
+ Use valid_phone_number? instead. The configured phone_number_formatter can manage the region of the phone number.
27
+ DEPRECATION
28
+ Twilio::Rails::PhoneNumberFormatter.valid?(phone_number)
29
+ end
30
+
31
+ def valid_phone_number?
32
+ Twilio::Rails::PhoneNumberFormatter.valid?(phone_number)
24
33
  end
25
34
  end
26
35
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Twilio
3
4
  module Rails
4
5
  # Provides convenience scopes for a model that has a `created_at` attribute.
@@ -1,9 +1,10 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Twilio
3
4
  module Rails
4
5
  class Configuration
5
6
  # Raised in initialization if the configuration is invalid.
6
- class Error < StandardError ; end
7
+ class Error < StandardError; end
7
8
 
8
9
  def initialize
9
10
  @finalized = false
@@ -14,10 +15,9 @@ module Twilio
14
15
  @account_sid = nil
15
16
  @auth_token = nil
16
17
  @spam_filter = nil
17
- @exception_notifier = nil
18
18
  @attach_recordings = true
19
- @yes_responses = [ "yes", "accept", "ya", "yeah", "true", "ok", "okay", "yep", "yup", "yes please" ]
20
- @no_responses = [ "no", "naw", "nah", "reject", "decline", "negative", "not", "false", "nope", "no thank you", "know" ]
19
+ @yes_responses = ["yes", "accept", "ya", "yeah", "true", "ok", "okay", "yep", "yup", "yes please"]
20
+ @no_responses = ["no", "naw", "nah", "reject", "decline", "negative", "not", "false", "nope", "no thank you", "know"]
21
21
  @message_class_name = "Message"
22
22
  @message_class = nil
23
23
  @phone_call_class_name = "PhoneCall"
@@ -33,12 +33,11 @@ module Twilio
33
33
  @phone_trees = PhoneTreeRegistry.new
34
34
  @sms_responders = SMSResponderRegistry.new
35
35
  @host = if ::Rails.configuration&.action_controller&.default_url_options
36
- "#{ ::Rails.configuration.action_controller.default_url_options[:protocol] }://#{ ::Rails.configuration.action_controller.default_url_options[:host] }"
37
- else
38
- nil
36
+ "#{::Rails.configuration.action_controller.default_url_options[:protocol]}://#{::Rails.configuration.action_controller.default_url_options[:host]}"
39
37
  end
40
38
  @controller_http_methods = [:get, :post]
41
39
  @include_phone_macros = []
40
+ @phone_number_formatter = Twilio::Rails::PhoneNumberFormatter::NorthAmerica.new
42
41
  end
43
42
 
44
43
  # This is the phone number that will be used to send SMS messages or start Phone Calls. It must be first configured
@@ -75,14 +74,6 @@ module Twilio
75
74
  # @return [Proc] a proc that will be called to filter messages, or `nil` if no filter is set.
76
75
  attr_accessor :spam_filter
77
76
 
78
- # A proc that will be called when an exception is raised in certain key points in the framework. This will never
79
- # capture the exception, it will raise regardless, but it is a good spot to send an email or notify in chat
80
- # if desired. The proc needs to accept `(exception, message, context, exception_binding)` as arguments. The
81
- # default is `nil`, which means no action will be taken.
82
- #
83
- # @return [Proc] a proc that will be called when an exception is raised in certain key points in the framework.
84
- attr_accessor :exception_notifier
85
-
86
77
  # Controls if recordings will be downloaded and attached to the `Recording` model in an ActiveStorage attachment.
87
78
  # This is `true` by default, but can be set to `false` to disable all downloads. It can also be set to a `Proc` or
88
79
  # callable that will receive the `Recording` instance and return a boolean for this specific instance. A typical
@@ -155,8 +146,7 @@ module Twilio
155
146
  def host_domain
156
147
  return nil unless host.present?
157
148
  value = host.gsub(/\Ahttps?:\/\//, "")
158
- value = value.gsub(/:\d+\z/, "")
159
- value
149
+ value.gsub(/:\d+\z/, "")
160
150
  end
161
151
 
162
152
  # The HTTP methods that Twilio will use to call into the app. Defaults to `[:get, :post]` but can be restricted
@@ -195,6 +185,19 @@ module Twilio
195
185
  end
196
186
  end
197
187
 
188
+ # An instance of the class which will validate and format phone numbers. This is used internally to decide what
189
+ # phone numbers are valid, how to format them, how to parse them, how to display them, and what country assumptions
190
+ # to make.
191
+ #
192
+ # This class must implement four methods:
193
+ # * coerce(string)
194
+ # * valid?(string)
195
+ # * to_param(string)
196
+ # * display(string)
197
+ #
198
+ # @return [Object]
199
+ attr_accessor :phone_number_formatter
200
+
198
201
  # Flags that the configuration has been setup and should be validated and finalized.
199
202
  # If this is not called, the framework will not work, but the Railtie will not prevent
200
203
  # the application from starting.
@@ -217,12 +220,12 @@ module Twilio
217
220
  raise Error, "`auth_token` must be set" if @auth_token.blank?
218
221
  raise Error, "`logger` must be set" if @logger.blank?
219
222
  raise Error, "`spam_filter` must be callable" if @spam_filter && !@spam_filter.respond_to?(:call)
220
- raise Error, "`exception_notifier` must be callable" if @exception_notifier && !@exception_notifier.respond_to?(:call)
221
- raise Error, '`yes_responses` must be an array' unless @yes_responses.is_a?(Array)
222
- raise Error, '`no_responses` must be an array' unless @no_responses.is_a?(Array)
223
- raise Error, "`host` #{ @host.inspect } is not a valid URL of the format https://example.com without the trailing slash" unless @host =~ /\Ahttps?:\/\/[a-z0-9\-\.:]+\Z/i
224
- raise Error, "`controller_http_methods` must be an array containing one or both of `:get` and `:post` but was #{ @controller_http_methods.inspect }" unless @controller_http_methods.is_a?(Array) && @controller_http_methods.sort == [:get, :post].sort || @controller_http_methods == [:get] || @controller_http_methods == [:post]
225
- raise Error, "`include_phone_macros` must be a module, but received #{ @include_phone_macros.inspect }" unless @include_phone_macros.all? { |mod| mod.is_a?(Module) }
223
+ raise Error, "`yes_responses` must be an array" unless @yes_responses.is_a?(Array)
224
+ raise Error, "`no_responses` must be an array" unless @no_responses.is_a?(Array)
225
+ raise Error, "`host` #{@host.inspect} is not a valid URL of the format https://example.com without the trailing slash" unless /\Ahttps?:\/\/[a-z0-9\-\.:]+\Z/i.match?(@host)
226
+ raise Error, "`controller_http_methods` must be an array containing one or both of `:get` and `:post` but was #{@controller_http_methods.inspect}" unless [[:get], [:post], [:get, :post], [:post, :get]].any? { |v| @controller_http_methods == v }
227
+ raise Error, "`include_phone_macros` must be a module, but received #{@include_phone_macros.inspect}" unless @include_phone_macros.all? { |mod| mod.is_a?(Module) }
228
+ raise Error, "`phone_number_formatter` must be set" unless @phone_number_formatter
226
229
  nil
227
230
  end
228
231
 
@@ -241,13 +244,13 @@ module Twilio
241
244
  :response_class_name,
242
245
  :sms_conversation_class_name,
243
246
  :message_class_name,
244
- :recording_class_name,
247
+ :recording_class_name
245
248
  ].each do |attribute|
246
- value = self.send(attribute)
249
+ value = send(attribute)
247
250
  raise Error, "`#{attribute}` must be set to a string name" if value.blank? || !value.is_a?(String)
248
251
  begin
249
252
  klass = value.constantize
250
- instance_variable_set("@#{ attribute.to_s.gsub("_name", "") }", klass)
253
+ instance_variable_set("@#{attribute.to_s.gsub("_name", "")}", klass)
251
254
  rescue NameError
252
255
  raise Error, "`#{attribute}` must be a valid class name but could not be found or constantized"
253
256
  end
@@ -290,7 +293,7 @@ module Twilio
290
293
  # @yield [nil] if a block is passed, it will be called and the result will be used as the value.
291
294
  # @yieldreturn [Class, String, Proc] containing the Class to be lazily initialized when {#finalize!} is called.
292
295
  # @return [nil]
293
- def register(klass_or_proc=nil, &block)
296
+ def register(klass_or_proc = nil, &block)
294
297
  raise Error, "Must pass either a param or a block" unless klass_or_proc.present? ^ block.present?
295
298
  value = klass_or_proc || block
296
299
 
@@ -305,7 +308,7 @@ module Twilio
305
308
  # @param [String, Symbol] name of the phone tree or SMS responder to find.
306
309
  # @return [Class] the phone tree or SMS responder class.
307
310
  def for(name)
308
- @registry[name.to_s] || raise(error_class, "No responder registered for '#{ name }'")
311
+ @registry[name.to_s] || raise(error_class, "Name '#{name}' has not been registered and cannot be found.")
309
312
  end
310
313
 
311
314
  # Returns all the phone trees or SMS responders as a read-only hash, keyed by name.
@@ -318,7 +321,7 @@ module Twilio
318
321
  private
319
322
 
320
323
  def add_to_registry(value)
321
- raise NotImplementedError
324
+ raise NoMethodError
322
325
  end
323
326
 
324
327
  def error_class
@@ -335,14 +338,14 @@ module Twilio
335
338
  value = value.call if value.respond_to?(:call)
336
339
  begin
337
340
  value = value.constantize if value.is_a?(String)
338
- rescue NameError => e
339
- raise(error_class, "Responder class '#{ value }' could not be constantized")
341
+ rescue NameError
342
+ raise(error_class, "Responder class '#{value}' could not be constantized")
340
343
  end
341
344
  raise(error_class, "Responder cannot be blank") unless value.present?
342
- raise(error_class, "Responder must be a class but got #{ value.inspect }") unless value.is_a?(Class)
345
+ raise(error_class, "Responder must be a class but got #{value.inspect}") unless value.is_a?(Class)
343
346
  name = value.responder_name
344
347
  raise(error_class, "Responder name cannot be blank") unless name.present?
345
- raise(error_class, "Responder name '#{ name }' is already registered") if @registry[name]
348
+ raise(error_class, "Responder name '#{name}' is already registered") if @registry[name]
346
349
  @registry[name] = value
347
350
  end
348
351
 
@@ -360,16 +363,15 @@ module Twilio
360
363
  value = value.call if value.respond_to?(:call)
361
364
  begin
362
365
  value = value.constantize if value.is_a?(String)
363
- rescue NameError => e
364
- raise(error_class, "Tree class '#{ value }' could not be constantized")
366
+ rescue NameError
367
+ raise(error_class, "Tree class '#{value}' could not be constantized")
365
368
  end
366
- raise(error_class, "Tree cannot be blank #{ value }") unless value.present?
367
- raise(error_class, "Tree is not a Twilio::Rails::Phone::BaseTree class #{ value }") unless value.is_a?(Class)
368
- raise(error_class, "Tree is not a Twilio::Rails::Phone::BaseTree #{ value }") unless value.ancestors.include?(Twilio::Rails::Phone::BaseTree)
369
+ raise(error_class, "Tree cannot be blank #{value}") unless value.present?
370
+ raise(error_class, "Tree is not a Twilio::Rails::Phone::BaseTree class #{value}") unless value.is_a?(Class)
371
+ raise(error_class, "Tree is not a Twilio::Rails::Phone::BaseTree #{value}") unless value.ancestors.include?(Twilio::Rails::Phone::BaseTree)
369
372
  name = value.tree_name
370
373
  raise(error_class, "Tree name cannot be blank") unless name.present?
371
- raise(error_class, "Tree name '#{ name }' is already registered") if @registry[name]
372
- klass = klass.constantize if klass.is_a?(String)
374
+ raise(error_class, "Tree name '#{name}' is already registered") if @registry[name]
373
375
  @registry[name] = value.tree
374
376
  end
375
377