twilio-rails 1.0.1 → 1.1.0

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