<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Cody Norman</title>
    <description>The latest articles on DEV Community by Cody Norman (@cnorm35).</description>
    <link>https://dev.to/cnorm35</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F874887%2Fd8ee80a6-d562-47a1-bd52-b6b92f591a48.jpeg</url>
      <title>DEV Community: Cody Norman</title>
      <link>https://dev.to/cnorm35</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/cnorm35"/>
    <language>en</language>
    <item>
      <title>Advanced Route Constraints with Rails</title>
      <dc:creator>Cody Norman</dc:creator>
      <pubDate>Fri, 08 Jul 2022 14:51:02 +0000</pubDate>
      <link>https://dev.to/cnorm35/advanced-route-constraints-with-rails-1ff5</link>
      <guid>https://dev.to/cnorm35/advanced-route-constraints-with-rails-1ff5</guid>
      <description>&lt;p&gt;Recently, I was working on a feature sending sms messages with appointment links. The links ended up taking up most of the message body so I wanted to look into some options to shorten the link to conserve space in the body of the&lt;br&gt;
message.&lt;/p&gt;

&lt;p&gt;Instead of using something like Bitly, I wanted to see if there were any simple options for something that would remain within the app. I did some googling for link shortener gems and tried a couple of the first ones&lt;br&gt;
that popped up.&lt;/p&gt;

&lt;p&gt;I was able to take my links and create a shortened version that gets sent out in the sms confirmation pretty easily. Everything seemed to be going fine until I noticed some test failures. Some of my static pages were returning 404. After a bit of digging, I noticed that all my static pages, like &lt;code&gt;/about&lt;/code&gt; and &lt;code&gt;/contact&lt;/code&gt; were being intercepted by the shortener.&lt;/p&gt;

&lt;p&gt;When it didn't find a shortened link with an id of &lt;code&gt;about&lt;/code&gt; it was returning a 404 and breaking my static pages.&lt;/p&gt;

&lt;p&gt;The easiest solution would have been to just mount the shorteners rails engine under a prefix, something like &lt;code&gt;/srt&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is what I ended up with but wanted to attempt to solve the issue using route constraints.&lt;/p&gt;

&lt;p&gt;Route constraints are something I've heard of, but have never had cause to use. I thought this would be a good chance to take a stab at routing constraints and get some more experience with them.&lt;/p&gt;

&lt;p&gt;There are a few different options for route constraints.  Some of the advanced options are using a lambda in the route definition and one that's a little more involved that uses a class for the route constraint.&lt;/p&gt;

&lt;p&gt;My first approach was using a lambda within the route definition but eventually moved to using a class.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://guides.rubyonrails.org/routing.html#advanced-constraints"&gt;Rails Guides on Advanced Route Constraints&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;lambda route constraint example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;  &lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="s1"&gt;'*path'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;to: &lt;/span&gt;&lt;span class="s1"&gt;'restricted_list#index'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;constraints: &lt;/span&gt;&lt;span class="nb"&gt;lambda&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="no"&gt;RestrictedList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;retrieve_ips&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;include?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remote_ip&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;class route constrain example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ShortenerRouteConstraint&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;matches?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# if request.path is not in static routes it should be a short link&lt;/span&gt;
    &lt;span class="n"&gt;static_page_routes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exclude?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="kp"&gt;private&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;static_page_routes&lt;/span&gt;
    &lt;span class="c1"&gt;# dynamically pull all static pages + admin, 404, and errors&lt;/span&gt;
    &lt;span class="n"&gt;static_page_paths&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Dir&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"app/views/static"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;children&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="s2"&gt;"/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;basename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;".html.erb"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="n"&gt;static_page_paths&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"/admin"&lt;/span&gt;
    &lt;span class="n"&gt;static_page_paths&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"/404"&lt;/span&gt;
    &lt;span class="n"&gt;static_page_paths&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"/500"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;My first pass had me adding the routes of my static pages to an array, and checking the &lt;code&gt;request.path&lt;/code&gt; to see if it was included in the request.&lt;/p&gt;

&lt;p&gt;That worked just fine, but after I added a new static page, I got another test fail for 404 errors (this happens when the shortener can't find an object).&lt;/p&gt;

&lt;p&gt;I wanted to see if there was a way to make that check dynamic and keep from updating manually, usually after seeing a spec fail and having to refresh my memory on what's happening.&lt;/p&gt;

&lt;p&gt;I created a route constrain class matching the one above. After having the route constraint class, the next step was to update the routes to use that constraint class.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/routes.rb&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'app/services/ShortenerRouteConstraint.rb'&lt;/span&gt;

&lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="s2"&gt;"/:id"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"shortener/shortened_urls#show"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;constraints: &lt;/span&gt;&lt;span class="no"&gt;ShortenerRouteConstraint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Note: I really didn't know the best place to put that class, so just defaulted to putting it in my &lt;code&gt;app/services&lt;/code&gt; directory and requiring that file at the top of my &lt;code&gt;config/routes.rb&lt;/code&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Checking the routes against a dynamic list of view files is absolutely overkill, &lt;br&gt;
but wanted to see if I could find a way to set and forget.&lt;/p&gt;

&lt;p&gt;After a few minutes skimming through the Ruby docs and found what I needed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;  &lt;span class="c1"&gt;# dynamically pull all static pages&lt;/span&gt;
  &lt;span class="n"&gt;static_page_paths&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Dir&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"app/views/static"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;children&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="s2"&gt;"/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;basename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;".html.erb"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a new &lt;a href="https://ruby-doc.org/core-3.1.2/Dir.html"&gt;Dir&lt;/a&gt; object for the &lt;code&gt;app/views/static&lt;/code&gt; directory.  We access its children, and use &lt;code&gt;map&lt;/code&gt; to return a new&lt;br&gt;
array with a format that matches our path for the route constraint.&lt;/p&gt;

&lt;p&gt;This will pull the name of any view ending in &lt;code&gt;.html.erb&lt;/code&gt;, including partials, in the &lt;code&gt;app/views/static&lt;/code&gt; directory.&lt;/p&gt;

&lt;p&gt;The output looks something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"/index"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s2"&gt;"/contact"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s2"&gt;"/success"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s2"&gt;"/privacy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s2"&gt;"/pricing"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s2"&gt;"/about"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s2"&gt;"/terms"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After checking all the pages in the static directory, I add &lt;code&gt;/admin&lt;/code&gt;, &lt;code&gt;/404&lt;/code&gt;, and &lt;code&gt;/500&lt;/code&gt; to the constraint for the other routes outside of my static pages it was having conflicts with.&lt;/p&gt;

&lt;p&gt;Now, whenever I add a new page to my static controller, that route will be added to the whitelist of static pages that should not be checked against shortened routes.&lt;/p&gt;

&lt;p&gt;Like I mentioned before, I ended up going a different route (see what I did there?) but thought it was a good exercise in over-engineering a solution for the fun of it.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Automating Updates to Twilio Webhook URLs</title>
      <dc:creator>Cody Norman</dc:creator>
      <pubDate>Thu, 16 Jun 2022 16:57:50 +0000</pubDate>
      <link>https://dev.to/cnorm35/automating-updates-to-twilio-webhook-urls-4ikh</link>
      <guid>https://dev.to/cnorm35/automating-updates-to-twilio-webhook-urls-4ikh</guid>
      <description>&lt;p&gt;This was an interesting challenge I ran into recently and has been the &lt;br&gt;
first time in a while that I've felt that &lt;em&gt;itch&lt;/em&gt; to solve a problem  just&lt;br&gt;
because I wanted to see if I could do it.&lt;/p&gt;

&lt;p&gt;It's been (&lt;em&gt;checks notes&lt;/em&gt;) about &lt;em&gt;7&lt;br&gt;
years&lt;/em&gt; since the last time I wrote anything related to ruby and probably 5 years&lt;br&gt;
since I've given any talks or presentations.&lt;/p&gt;

&lt;p&gt;I don't think I realized how burnt out I was after a few tumultuous years both&lt;br&gt;
personally and professionally. It was great to feel like I &lt;em&gt;had&lt;/em&gt; to solve a&lt;br&gt;
problem just for fun and was even compelled to write about it.&lt;/p&gt;

&lt;p&gt;That being said, ya boy is &lt;em&gt;rusty&lt;/em&gt;, so bear with me.&lt;/p&gt;

&lt;p&gt;Recently, I've been doing some work on a side project that uses Twilio for sending and receiving text messages.&lt;br&gt;
I've always thought of SMS as a really powerful tool to add to any app's arsenal since it leverages something&lt;br&gt;
users already use multiple times per day.  In a recent report, Twilio even&lt;br&gt;
reported that &lt;em&gt;&lt;strong&gt;96% of messages are read within three minutes of receipt, and 90% within three seconds!&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This post won't go into detail on sending text messages.  If you'd like some&lt;br&gt;
more information on how to send text messages, the&lt;br&gt;
&lt;a href="https://github.com/twilio/twilio-ruby#send-an-sms"&gt;twilio-ruby&lt;/a&gt; gem has some examples.&lt;/p&gt;

&lt;p&gt;Twilio phone numbers have webhooks for both Voice and SMS. These webhooks are&lt;br&gt;
called whenever your Twilio phone number gets an inbound text or phone call.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.twilio.com/docs/usage/webhooks/sms-webhooks#type-1-incoming-message"&gt;Incoming message example&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Both channels have a fallback URL if the first request to the webhook fails.&lt;/p&gt;
&lt;h3&gt;
  
  
  Sending Webhook Requests To Your Local Environment
&lt;/h3&gt;

&lt;p&gt;If you've ever developed features from a third-party webhook or service with your local&lt;br&gt;
environment, you probably did it by exposing your local environment to the&lt;br&gt;
internet with something like ngrok.&lt;/p&gt;

&lt;p&gt;If you haven't heard of ngrok, it's one of my favorite tools that I've been&lt;br&gt;
integrating into more workflow more and more.  You can read more about ngrok&lt;br&gt;
&lt;a href="https://ngrok.com/"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Running Ngrok In Your Procfile
&lt;/h3&gt;

&lt;p&gt;With Rails 7 embracing using foreman and Procfiles for starting and running&lt;br&gt;
services, I've added ngrok as its own process so it's always available.&lt;/p&gt;

&lt;p&gt;example &lt;code&gt;Procfile.dev&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
# Procfile.dev
web: bin/rails server -p $PORT
css: yarn build:css --watch
js: yarn build --reload
worker: bundle exec sidekiq
stripe: stripe listen --forward-to $PORT/webhooks/stripe
ngrok: ngrok http --log=stdout $PORT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Updating Twilio Webhook URLs
&lt;/h3&gt;

&lt;p&gt;With ngrok running, there's now a URL to point your webhooks to.&lt;br&gt;
One option for updating your webhook URLs is grabbing your ngrok URL, going into&lt;br&gt;
the Twilio console and update the webhook URLs for voice and sms.&lt;/p&gt;

&lt;p&gt;That was fine for me...for a while.&lt;/p&gt;

&lt;p&gt;My main issue with this setup is whenever I restart my services, the ngrok URL&lt;br&gt;
changes and needs to be updated again.  That can make for a lot of updates when&lt;br&gt;
starting and stopping your services and errors when forgetting to update the&lt;br&gt;
URLs each time.&lt;/p&gt;

&lt;p&gt;Also, if that's something you're not doing very often, it's easy&lt;br&gt;
to forget how to find the section to update the webhook URLs.&lt;/p&gt;

&lt;p&gt;That's when I felt the urge to find a way to automate that whenever I needed it.&lt;/p&gt;

&lt;p&gt;After some digging in the Twilio documentation, I found some options to set the&lt;br&gt;
webhooks URLs through their &lt;a href="https://github.com/twilio/twilio-ruby"&gt;ruby gem&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A little more digging helped me find an endpoint for ngrok you can query to get&lt;br&gt;
it's public url.&lt;/p&gt;

&lt;p&gt;Now we're cooking with gas.  With the URL of our local ngrok instance and a way&lt;br&gt;
to programmatically update the webhook URLs, we have the 2 pieces we need.&lt;/p&gt;

&lt;p&gt;The next step is to decide how to execute that code when we need updates.&lt;br&gt;
For me, that was a rake task.  That gives me a nice repeatable way to update the&lt;br&gt;
URLs whenever I need to.&lt;/p&gt;
&lt;h3&gt;
  
  
  Rake Task For Updating URLs
&lt;/h3&gt;

&lt;p&gt;Before I start going into more detail, here's the code for the rake task. The &lt;br&gt;
dependencies for the rake task are a running ngrok instance, the &lt;code&gt;twilio-ruby&lt;/code&gt; &lt;br&gt;
gem (I'm using v5.67.1), &lt;br&gt;
and if you'd like a pop of color in your terminal to make things easier to spot, the &lt;a href="https://github.com/fazibear/colorize"&gt;colorize&lt;/a&gt; gem&lt;/p&gt;

&lt;p&gt;Here is an example of the rake task&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;namespace&lt;/span&gt; &lt;span class="ss"&gt;:development&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;desc&lt;/span&gt; &lt;span class="s2"&gt;"updates twilio webhook urls to ngrok url"&lt;/span&gt;
  &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="ss"&gt;set_twilio_webhooks: :environment&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;development?&lt;/span&gt;
    &lt;span class="k"&gt;begin&lt;/span&gt;
      &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Net&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HTTP&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;URI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:4040/api/tunnels"&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
      &lt;span class="n"&gt;ngrok_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"tunnels"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"public_url"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;Errno&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ECONNREFUSED&lt;/span&gt;
      &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"*******NGROK not running*******"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;yellow&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ngrok_url&lt;/span&gt;
      &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;ngrok_url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;green&lt;/span&gt;
      &lt;span class="n"&gt;account_sid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:twilio&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="ss"&gt;:account_sid&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="n"&gt;auth_token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:twilio&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="ss"&gt;:auth_token&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="n"&gt;development_incoming_phone_number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:twilio&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="ss"&gt;:auth_token&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="n"&gt;twilio_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Twilio&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;account_sid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;auth_token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;twilio_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;v2010&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;accounts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;account_sid&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;incoming_phone_numbers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;developemnt_incoming_phone_number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;sms_url: &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;ngrok_url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/webhooks/twilio"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="ss"&gt;sms_fallback_url: &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;ngrok_url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/webhooks/twilio_fallback"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="ss"&gt;voice_url: &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;ngrok_url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/webhooks/twilio_voice"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="ss"&gt;voice_fallback_url: &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;ngrok_url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/webhooks/twilio_voice_fallback"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Webhook URLs updated!"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;green&lt;/span&gt;
    &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;Twilio&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;REST&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;RestError&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
      &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Deets
&lt;/h3&gt;

&lt;p&gt;Now, let's dig a little deeper into what's going on.&lt;/p&gt;

&lt;p&gt;Inside the rake task, the first thing I'm doing is making sure we're in the&lt;br&gt;
&lt;code&gt;development&lt;/code&gt; environment and returning from the task if we're not.  Probably not&lt;br&gt;
required, but I thought it was a nice check to keep from accidentally updating&lt;br&gt;
information outside of our &lt;code&gt;development&lt;/code&gt; environment.&lt;/p&gt;

&lt;p&gt;After ensuring we're in our dev environment, we need to do is find the URL of the&lt;br&gt;
running ngrok instance.  Ngrok has an API endpoint running at port &lt;code&gt;4040&lt;/code&gt; for the&lt;br&gt;
current tunnels.  This endpoint returns an XML response with information about&lt;br&gt;
the current tunnels.  There are 2 tunnels returned, and I grab the one with the&lt;br&gt;
name &lt;code&gt;command_line (http)&lt;/code&gt; which is the second one returned.&lt;/p&gt;

&lt;p&gt;Usually, my ruby HTTP library of choice is &lt;a href="https://github.com/jnunemaker/httparty"&gt;HTTParty&lt;/a&gt; but I wanted to set this up using&lt;br&gt;
the ruby Net::HTTP lib to keep from introducing another dependency.&lt;/p&gt;

&lt;p&gt;After ensuring we're in the &lt;code&gt;development&lt;/code&gt; environment, Net::HTTP then makes a &lt;br&gt;
request to the ngrok endpoint and parses the XML response.&lt;/p&gt;

&lt;p&gt;Here are some examples of the response it sends back.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Truncated Ngrok response&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;tunnelListResource&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;Tunnels&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/Tunnels&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;Tunnels&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/Tunnels&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;URI&amp;gt;&lt;/span&gt;/api/tunnels&lt;span class="nt"&gt;&amp;lt;/URI&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/tunnelListResource&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Single Tunnel Object&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;Tunnels&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;Name&amp;gt;&lt;/span&gt;command_line&lt;span class="nt"&gt;&amp;lt;/Name&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;URI&amp;gt;&lt;/span&gt;/api/tunnels/command_line&lt;span class="nt"&gt;&amp;lt;/URI&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;PublicURL&amp;gt;&lt;/span&gt;https://7be0-72-42-102-194.ngrok.io&lt;span class="nt"&gt;&amp;lt;/PublicURL&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;Proto&amp;gt;&lt;/span&gt;https&lt;span class="nt"&gt;&amp;lt;/Proto&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;Config&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;Addr&amp;gt;&lt;/span&gt;http://localhost:80&lt;span class="nt"&gt;&amp;lt;/Addr&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;Inspect&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/Inspect&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/Config&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;Metrics&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;Conns&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/Conns&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;HTTP&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;Count&amp;gt;&lt;/span&gt;0&lt;span class="nt"&gt;&amp;lt;/Count&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;Rate1&amp;gt;&lt;/span&gt;0&lt;span class="nt"&gt;&amp;lt;/Rate1&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;Rate5&amp;gt;&lt;/span&gt;0&lt;span class="nt"&gt;&amp;lt;/Rate5&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;Rate15&amp;gt;&lt;/span&gt;0&lt;span class="nt"&gt;&amp;lt;/Rate15&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;P50&amp;gt;&lt;/span&gt;0&lt;span class="nt"&gt;&amp;lt;/P50&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;P90&amp;gt;&lt;/span&gt;0&lt;span class="nt"&gt;&amp;lt;/P90&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;P95&amp;gt;&lt;/span&gt;0&lt;span class="nt"&gt;&amp;lt;/P95&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;P99&amp;gt;&lt;/span&gt;0&lt;span class="nt"&gt;&amp;lt;/P99&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/HTTP&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/Metrics&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Tunnels&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rake task takes the parsed XML and grabs the 2nd Tunnel returned.  Why you&lt;br&gt;
ask?&lt;/p&gt;

&lt;p&gt;Honestly, it was most likely the first thing that worked from StackOverflow and I moved&lt;br&gt;
on.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;&amp;lt;PublicURL&amp;gt;&lt;/code&gt; for both tunnels seem to always be the same so it's&lt;br&gt;
possible either would work.&lt;/p&gt;

&lt;p&gt;If ngrok is not running, we'll get an &lt;code&gt;Errno::ECONNREFUSED&lt;/code&gt; error so we can&lt;br&gt;
rescue that error, and print some output in yellow to make it easy to know when&lt;br&gt;
something went wrong&lt;/p&gt;
&lt;h4&gt;
  
  
  Grab Ngrok Public Url
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;  &lt;span class="k"&gt;begin&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Net&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HTTP&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;URI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:4040/api/tunnels"&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
    &lt;span class="n"&gt;ngrok_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"tunnels"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"public_url"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;Errno&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ECONNREFUSED&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"*******NGROK not running*******"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;yellow&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Now we have a repeatable and reliable way to grab our ngrok URL whenever it is&lt;br&gt;
running.  We also get some colored output to give us a quick visual indicator&lt;br&gt;
that everything looks a-ok.&lt;/p&gt;

&lt;p&gt;The next part is to programmatically update the Twilio webhook endpoints with our&lt;br&gt;
public URL for ngrok.&lt;/p&gt;

&lt;p&gt;It took quite a bit of digging to find the endpoints needed in the twilio-ruby&lt;br&gt;
gem, but once I found them, it was smooth sailing from there.&lt;/p&gt;
&lt;h4&gt;
  
  
  Updating with the Twilio API
&lt;/h4&gt;

&lt;p&gt;There are a few things we'll need to update the webhook URLs for Twilio.  The&lt;br&gt;
first thing we need is a client with our keys.&lt;/p&gt;

&lt;p&gt;To create the Twilio client, you'll need to get your &lt;code&gt;account_sid&lt;/code&gt; and&lt;br&gt;
&lt;code&gt;auth_token&lt;/code&gt;.  You can find both of those by logging into Twilio.  While you're&lt;br&gt;
there, be sure to find the service id of your phone number. That's required to&lt;br&gt;
target the correct phone number for the updates.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Twilio Updates&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;  &lt;span class="n"&gt;account_sid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:twilio&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="ss"&gt;:account_sid&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="n"&gt;auth_token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:twilio&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="ss"&gt;:auth_token&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="n"&gt;development_incoming_phone_number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:twilio&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="ss"&gt;:auth_token&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="n"&gt;twilio_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Twilio&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;account_sid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;auth_token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;twilio_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;v2010&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;accounts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;account_sid&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;incoming_phone_numbers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;developemnt_incoming_phone_number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;sms_url: &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;ngrok_url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/webhooks/twilio"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;sms_fallback_url: &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;ngrok_url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/webhooks/twilio_fallback"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;voice_url: &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;ngrok_url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/webhooks/twilio_voice"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;voice_fallback_url: &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;ngrok_url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/webhooks/twilio_voice_fallback"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Webhook URLs updated!"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;green&lt;/span&gt;
&lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;Twilio&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;REST&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;RestError&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I included the Twilio client setup in the rake task, but that was mostly to have&lt;br&gt;
an example that would work as-is without additional configuration.  I typically&lt;br&gt;
configure my Twilio client in an initializer to keep duplication to a minimum.&lt;/p&gt;

&lt;p&gt;example &lt;code&gt;config/initializers/twilio.rb&lt;/code&gt; file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Twilio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;account_sid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:twilio&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="ss"&gt;:account_sid&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;auth_token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:twilio&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="ss"&gt;:auth_token&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the client created, we can use the &lt;code&gt;ngrok_url&lt;/code&gt; combined with the endpoint&lt;br&gt;
that points to your webhook handler and update our Twilio webhook URLs.&lt;/p&gt;

&lt;p&gt;If everything goes as planned, we'll output a success message in green.&lt;/p&gt;

&lt;p&gt;If things &lt;em&gt;don't&lt;/em&gt; go as planned, we're rescuing &lt;code&gt;Twilio::REST::RestError&lt;/code&gt; and&lt;br&gt;
outputting the error.&lt;/p&gt;

&lt;p&gt;Now, whenever we need to update our Twilio webhook URLs (or&lt;br&gt;
any other 3rd party service in the future) we can run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;bin/rails development:set_twilio_webhooks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finding a way to automatically run ngrok in my Procfile and have a way to&lt;br&gt;
programmatically grab its public URL was a great find for my workflow. This is &lt;br&gt;
something that can&lt;br&gt;
be expanded to tons of other services you may like to work with in your development&lt;br&gt;
environment.&lt;/p&gt;

&lt;p&gt;Let me know any other services you automate!&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>twilio</category>
    </item>
  </channel>
</rss>
