Service Objects for API Interactions with Twilio

Episode #154 by Teacher's Avatar David Kimura

Summary

In this episode, learn how to extract the interactions with an external API into a service object so that code is isolated and interchangeable.
rails ruby api service objects 15:55

Resources

Summary

# Terminal bundle init

# Gemfile # frozen_string_literal: true source "https://rubygems.org" git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } gem 'twilio-ruby' gem 'dotenv'

# main.rb require 'dotenv/load' require_relative 'services/send_sms' puts "Enter Phone Number" number = gets.chomp! puts "Enter Message" message = gets.chomp! request = Services::SendSms.call(number, message) if request.success? puts 'Message sent successfully' elsif request.failure? puts request.errors end puts request.success? puts request.result puts request.result.result.class

# services/object.rb require_relative 'common/errors' module Services class Object class << self def call(*arg) new(*arg).constructor end end attr_reader :result def constructor @result = call self end def success? !failure? end def failure? errors.any? end def errors @errors ||= Services::Common::Errors.new end def call fail NotImplementedError unless defined?(super) end end end

# services/send_sms.rb require_relative 'object' require_relative 'clients/twilio' module Services class SendSms < Services::Object def initialize(recipient, message) @recipient = recipient @message = message end def call errors.add :validation, "Missing recipient's number." if @recipient.empty? errors.add :validation, "Missing message to recipient." if @message.empty? send_message unless errors.any? end private def send_message request = Services::Clients::Twilio.call(@recipient, @message) errors.add_multiple_errors(request.errors) if request.failure? request end end end

# services/clients/twilio.rb require 'twilio-ruby' module Services module Clients class Twilio < Services::Object PHONE_NUMBER = '+18124322807' def initialize(recipient, message) @recipient = recipient @message = message end def call send_message end private def send_message account_id = ENV['TWILIO_ACCOUNT_ID'] auth_token = ENV['TWILIO_AUTH_TOKEN'] @client = ::Twilio::REST::Client.new account_id, auth_token @client.api.account.messages.create( from: PHONE_NUMBER, to: @recipient, body: @message ) @client rescue ::Twilio::REST::RestError => error errors.add :sms, error.message end end end end

# services/common/errors.rb module Services module Common class Errors < Hash def add(key, value, _opts = {}) self[key] ||= [] self[key] << value self[key].uniq! end def add_multiple_errors(errors_hash) errors_hash.each do |key, values| errors_hash[key].each { |value| add key, value } end end def each each_key do |field| self[field].each { |message| yield field, message } end end end end end