<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="https://www.w3.org/2005/Atom">

 <title>Jon McCartie</title>
 <link href="https://www.mccartie.com/feed.xml" rel="self"/>
 <link href="https://www.mccartie.com"/>
 <updated>2026-02-23T18:28:25+00:00</updated>
 <id>https://www.mccartie.com/</id>
 <author>
   <name>Jon McCartie</name>
 </author>

 
 <entry>
   <title>How To Delete All Your Tweets</title>
   <link href="https://www.mccartie.com/tech/2019/01/21/how-to-delete-all-your-tweets.html"/>
   <updated>2019-01-21T10:32:26+00:00</updated>
   <id>https://www.mccartie.com/tech/2019/01/21/how-to-delete-all-your-tweets</id>
   <content type="html">&lt;p&gt;There may come a time in your Twitter existence when you realize that you’ve said a lot of dumb stuff in your past. Facebook is especially good at reminding you of how immature and annoying you were 10 years ago. So while the reasons may differ from person to person, it’s important to know how to flush your Twitter account.&lt;/p&gt;

&lt;p&gt;Unfortunately, Twitter doesn’t make this easy. And the 3rd party online tools have some severe limitations: 1) the Twitter API only exposes a limited number (few thousand) of your most recent tweets, and 2) you’re giving a 3rd party tool all permissions to your account.&lt;/p&gt;

&lt;p&gt;Well, if you have a few minutes and have Ruby installed, you can do this whole process by yourself.&lt;/p&gt;

&lt;h2 id=&quot;create-an-oauth-app&quot;&gt;Create an Oauth App&lt;/h2&gt;

&lt;p&gt;Head over to: &lt;a href=&quot;https://developer.twitter.com/en/apps&quot;&gt;https://developer.twitter.com/en/apps&lt;/a&gt; If you already have an existing Oauth app, you’re good to go. If you need to create one, you’ll need to sign up for a Twitter developer account.&lt;/p&gt;

&lt;p&gt;Once you create (or have) an Oauth app handy, view the app details and click on “Keys and Tokens”. You’ll need 4 pieces of data:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Consumer Key&lt;/li&gt;
  &lt;li&gt;Consumer Secret&lt;/li&gt;
  &lt;li&gt;Access Token&lt;/li&gt;
  &lt;li&gt;Access Secret&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You may need to generate the Access Token. You can do this by clicking “Create” under “Access token &amp;amp; access token secret”.&lt;/p&gt;

&lt;p&gt;Hang onto this info – you’ll need it in the Ruby script later.&lt;/p&gt;

&lt;h2 id=&quot;download-your-data&quot;&gt;Download Your Data&lt;/h2&gt;

&lt;p&gt;Because Twitter doesn’t expose &lt;em&gt;all&lt;/em&gt; of your tweets in the API, you’ll need to get all of the IDs yourself. Luckily, Twitter allows you to download all of your Twitter data.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Head to: &lt;a href=&quot;https://twitter.com/settings/your_twitter_data&quot;&gt;https://twitter.com/settings/your_twitter_data&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Under “Download your Twitter data”, click “Request Data”&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Give it a few minutes and you’ll get a link to download a ZIP file. Inside this, you’ll want to grab two specific files:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tweets.js&lt;/code&gt; (this will give you all your tweet ID’s), and&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;like.js&lt;/code&gt; (this will give you the tweet ID’s of your likes)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Do two things to these files:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Remove &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;window.YTD.like.part0 = &lt;/code&gt; from the first line. Now it should only be &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[ {&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Save the file to your desktop (or somewhere you can find it easily) and &lt;strong&gt;rename the file extension&lt;/strong&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.json&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;delete-your-tweets&quot;&gt;Delete Your Tweets&lt;/h2&gt;

&lt;p&gt;Ok. You ready? No turning back, my friend.&lt;/p&gt;

&lt;p&gt;Create a file in the same directory as your two JSON files. Call it … &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;twitter.rb&lt;/code&gt; ?&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;twitter&quot;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;json&quot;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;client&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Twitter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Streaming&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;consumer_key&lt;/span&gt;        &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;YOUR_CONSUMER_KEY&quot;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;consumer_secret&lt;/span&gt;     &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;YOUR_CONSUMER_SECRET&quot;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;access_token&lt;/span&gt;        &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;YOUR_ACCESS_TOKEN&quot;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;access_token_secret&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;YOUR_ACCESS_SECRET&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;tweets&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;tweets.json&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;tweets&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tweet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;begin&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;destroy_tweet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tweet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;print&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;.&quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;rescue&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;exception&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;exception&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;To run this, simply open a terminal window and run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ruby twitter.rb&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Common issues:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Unauthorized. Your keys are wrong. (I accidentally had a space after my access token)&lt;/li&gt;
  &lt;li&gt;Random other twitter messages exposed in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;exception.message&lt;/code&gt;. (I have about 40 @-replies directed at suspended accounts. Twitter won’t show them, but they also won’t let me delete them)&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;delete-your-likes&quot;&gt;Delete Your Likes&lt;/h2&gt;

&lt;p&gt;Done with your tweets? Let’s get rid of those likes too. Change your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;twitter.rb&lt;/code&gt; file to this:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;twitter&quot;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;json&quot;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;client&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Twitter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Streaming&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;consumer_key&lt;/span&gt;        &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;YOUR_CONSUMER_KEY&quot;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;consumer_secret&lt;/span&gt;     &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;YOUR_CONSUMER_SECRET&quot;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;access_token&lt;/span&gt;        &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;YOUR_ACCESS_TOKEN&quot;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;access_token_secret&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;YOUR_ACCESS_SECRET&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;likes&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;like.json&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;likes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;like&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;begin&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;unfavorite&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;like&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;like&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;tweetId&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;print&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;.&quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;rescue&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;exception&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;exception&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Same running process: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ruby twitter.rb&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;The hardest part of this is creating the Oauth app. I had some old apps from back in the day that I was able to repurpose. Just remember: you can’t put these tweets back. So give it a second thought before you run these scripts.&lt;/p&gt;

&lt;p&gt;Happy Twittering!&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>How Teams Grow, Thrive, and Fail</title>
   <link href="https://www.mccartie.com/blog/2018/03/30/how-teams-grow-thrive-and-fail.html"/>
   <updated>2018-03-30T09:49:57+00:00</updated>
   <id>https://www.mccartie.com/blog/2018/03/30/how-teams-grow-thrive-and-fail</id>
   <content type="html">&lt;p&gt;As your team or business grows, things change – they have to! A $10 billion business cannot operate the same as a 1-2 person startup. And since no businesses start at that scale, it’s important to understand what helps a team grow without breaking. How can your team continue to innovate, take on new customers, provide quality service, and maintain a healthy work/life balance for your staff?&lt;/p&gt;

&lt;p&gt;A key difference between large and small teams is their amount of process and organization. As new problems arise from growth, successful teams adapt. And they do so without losing what made them great to begin with. But as you’ll see, a team can easily cross an imaginary line into bureaucracy. Their processes can become cumbersome, and begin to suck the life out of them. They lose themselves as they keep adding layers of process in order to accomodate for their growth.&lt;/p&gt;

&lt;p&gt;The goal of this discussion is to help you understand the different stages of growth, identify where your team is at, and learn how to take on the next stage with confidence. But first we have to talk about … Problems.&lt;/p&gt;

&lt;h2 id=&quot;problems&quot;&gt;Problems&lt;/h2&gt;

&lt;p&gt;If your team is lucky enough to grow, you’re bound to experience growing pains. What worked for your team when it was small is no longer working. Communication is breaking down, quality has declined.&lt;/p&gt;

&lt;p&gt;But guess what? If you can solve the problems that you’re currently facing, you’ll be handsomely rewarded with … new problems! And they won’t be the same ones – they’ll be larger and more complex. So if dealing with growth problems is a constant, what can we do to prepare for them?&lt;/p&gt;

&lt;p&gt;Your role is not to prevent problems. Nor is it to slow the pace of change. Instead, your focus should be on increasing your team’s ability to both recognize and resolve problems. One of these is not enough. High recognition with low resolution is chaos. Low recognition with high resolution is a team with its head in the sand. But if your team can do both – quickly recognize and then resolve problems – you have a distinct advantage.&lt;/p&gt;

&lt;p&gt;But how can you possibly foresee the future like this? Thankfully, most of the issues you will face are common to other teams. Understanding the different stages of growth will allow you to see the road ahead and prepare for what’s next.&lt;/p&gt;

&lt;p&gt;So let’s take a look at what these different stages of growth look like.&lt;/p&gt;

&lt;h2 id=&quot;lifecycles&quot;&gt;LifeCycles&lt;/h2&gt;

&lt;p&gt;I was first introduced to the idea of “Lifecycles” through the work of Dr. Izach Adizes. His book “Corporate Lifecycles” is well researched and is a wonderful, detailed exploration. His model starts with the “Courtship” phase, progresses through the apex of “Prime”, then descends to “Bureaucracy” and finally “Death”.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/adizes-lifecycle.jpg&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The Adizes model is more detailed than the other model we’ll look at and provides several “short-circuit” scenarios like “Affair” and “Founder’s Trap” that explain certain ways where teams can cut their lives short through negative behaviors.&lt;/p&gt;

&lt;p&gt;The second model was created by Les McKeown in his book “Predictable Success”. It takes the highly academic work of Adizes and simplifies it greatly. We’ll explore this one in more depth because it provides a good entry-level overview of the concepts.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/predictable_success_model.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;early-struggle&quot;&gt;Early Struggle&lt;/h3&gt;

&lt;p&gt;“Early Struggle” is your team or business’ first steps into the world. It’s an exciting, yet terrifying time for your team. For a business, the stakeholders are simply focused on two things: cash flow and establishing a market. For a team within an organization, you might have been newly tasked with creating and building a team and feel the need to prove yourself or even prove that your team should exist! Unfortunately, most new businesses never make it out of this stage. But if you’re lucky enough to do so, it’s time for “Fun”!&lt;/p&gt;

&lt;h3 id=&quot;fun&quot;&gt;Fun&lt;/h3&gt;
&lt;p&gt;Congrats! The frustration and difficulty of “Early Struggle” is behind you. You’ve established a market, you have a few customers, and you’re not so terrified about revenue. Your team has proven itself and you’re quickly transitioning from cash to sales. Things are growing and you’re having a huge impact because you’ve got your hands in everything. You may not how why you’re growing, but it sure is fun!&lt;/p&gt;

&lt;p&gt;“Fun” is also where you get company myths. If your team is past this stage, you’ll hear about the “old timers” that came before you. At Heroku, people fondly reminisce of when we crossed $10 million or that major incident where people were all-hands-on-deck. The people on the team that came before me were gods. These myths are built because at the time, these people were part of small team that did incredible things!&lt;/p&gt;

&lt;h3 id=&quot;whitewater&quot;&gt;Whitewater&lt;/h3&gt;

&lt;p&gt;Mo’ customers, mo’ problems. In “Whitewater”, you start to notice that the structure and minimal processes you built during “Early Struggle” and “Fun” aren’t fully adept at handling the growth of your business. If you’re on a support team, you’re finding yourself unable to keep up with all the tickets. So you start to hire. But hiring requires training. What about those feature requests and bug reports? How should we organize them and report them back to product or engineering?&lt;/p&gt;

&lt;p&gt;You may be able to come up with ways to change things, but are they the right decisions? And you may have good ideas, but getting these new policies to actually stick is an entirely other issue. Your team is having an identity crisis (and you probably are too).&lt;/p&gt;

&lt;p&gt;The focus used to be on cash, then sales, but now it’s on profitability. And profitability requires new systems and policies to bring your team towards “Predictable Success”.&lt;/p&gt;

&lt;h3 id=&quot;predictable-success&quot;&gt;Predictable Success&lt;/h3&gt;

&lt;p&gt;“Predictable Success” is the perfect balance between process and innovation. Predictable Success is healthy and sustainable. Your team is structured and adaptive, but your team is also given the freedom to work efficiently without being bogged down by process. Your team is able to consistently set and achieve their goals.&lt;/p&gt;

&lt;p&gt;The big difference between “Fun” and “Predictable Success” is that you actually know how and why your team is growing and are able to adapt to changes in the business.&lt;/p&gt;

&lt;h3 id=&quot;treadmill&quot;&gt;Treadmill&lt;/h3&gt;

&lt;p&gt;You might consider “Predictable Success” a perfect balance between your cowboys and your bureaucrats; between your highly innovative and highly organized types. If that’s the case, “Treadmill” is what happens when your bureaucrats begin to have too much say in things. Policies begin to be too burdensome. Your team is spending more time trying to cross every T and dot every I, and as a result, they’re not getting much done.&lt;/p&gt;

&lt;p&gt;The metaphor of a treadmill is perfect for this stage because while a lot of energy is being applied, there’s little to no forward momentum. Risk-taking, creative thinking, and initiative are in decline. We’re now in a world where we second guess everything. Why bother taking a risk?&lt;/p&gt;

&lt;p&gt;You see where this is going…&lt;/p&gt;

&lt;h3 id=&quot;the-big-rut&quot;&gt;The Big Rut&lt;/h3&gt;

&lt;p&gt;If “Treadmill” is what happens when process begins to squeeze out innovation, “The Big Rut” is what happens when this environment begins to solidify. “Treadmill” can be reversed – processes can be trimmed back and innovation can be restored. But left unchecked, “The Big Rut” takes over. The team now firmly values process and systems over action and results. The team has also lost its self-awareness and can no longer diagnose problems as they come up. “A problem?! Well, is there a playbook to follow?”&lt;/p&gt;

&lt;p&gt;Sadly, companies can stay here for a very long while, but eventually they’ll decay.&lt;/p&gt;

&lt;h3 id=&quot;death-rattle&quot;&gt;Death Rattle&lt;/h3&gt;

&lt;p&gt;If you love bankruptcies and corporate sell-offs, then you’ll love “Death Rattle”! This is the end of the line. People are jumping ship left and right and things are falling apart. If you were lucky enough, you got out of the burning building with only 2nd degree burns. This is a sad stage for any company, but far too common.&lt;/p&gt;

&lt;h2 id=&quot;application&quot;&gt;Application&lt;/h2&gt;

&lt;h3 id=&quot;where-is-your-team&quot;&gt;Where is your team?&lt;/h3&gt;

&lt;p&gt;But the good news is, this doesn’t have to be your story! If you’re reading this, chances are your team isn’t in one of the last two stages (by that point, those organizations have lost the self-awareness to read an article like this!). So how can we apply this understanding of these stages to our teams and businesses?&lt;/p&gt;

&lt;p&gt;First, it’s crucial to identify your team along this arc. Is your team small and struggling? Mid-size and cruising? Growing beyond your capacity? Second, once you identify what stage you’re in, you can see what it will take to move closer to the next stage and what will be waiting for you when you get there.&lt;/p&gt;

&lt;h3 id=&quot;implementing-process&quot;&gt;Implementing Process&lt;/h3&gt;

&lt;p&gt;If there is only one thing you remember from this discussion, it should be this: how you decide on and implement systems and processes is crucial to your team’s success. Create too much process and you’ll snuff out innovation and start your team on the path to The Big Rut. Create too little process and your team will buckle under the weight of its own growth and new responsibilities.&lt;/p&gt;

&lt;p&gt;When deciding to implement process, remember two things: 1) only implement new processes when necessary. Don’t create a new policy because it’s a Monday and you feel like you haven’t done much work as a manager lately. And similarly 2) don’t create policies for one-off issues. Wait to see if a pattern arises. Nothing is worse as an innovator on your team than to have Mike from accounting screw up everything for the rest of us because the team manager felt like every decision now has to have multiple layers of approval and now things are stupid and suck and I’m going to start interviewing somewhere else tomorrow.&lt;/p&gt;

&lt;h2 id=&quot;finally&quot;&gt;Finally…&lt;/h2&gt;

&lt;p&gt;We’ve quickly covered the ideas of a team lifecycle. Hopefully, you’re able to identify where your team is at and you’re now able to see what’s behind and ahead of you. And you’re now able to see where this is all headed – towards a “prime” position. Balancing on the tip of this arc, your team has found middle ground between too much and too little process. Innovation is fostered, but is given healthy boundaries that encourage growth. And you’ll be able to see what it looks like when your team begins to tip too far into systems and administration.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“Your people are doing their best, but their efforts cannot compensate
for your inadequate, dysfunctional system.”
&lt;cite&gt;Peter Scholtes, The Leader’s Handbook&lt;/cite&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Take some time to self-reflect on where your team is and where it’s headed; what challenges await you around the next corner. Doing so will enable your team to attack the problems – instead of each other.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>How Not To Squash Your Team With Red Tape</title>
   <link href="https://www.mccartie.com/blog/2018/02/09/how-not-to-squash-your-team-with-red-tape.html"/>
   <updated>2018-02-09T16:18:30+00:00</updated>
   <id>https://www.mccartie.com/blog/2018/02/09/how-not-to-squash-your-team-with-red-tape</id>
   <content type="html">&lt;p&gt;One of the common tenets of growth theories is that companies require process and structure as they grow. And if they’re lucky, they’ll be around long enough to reach a critical tipping point where too much structure will begin to squash innovation. It’s easy to find examples of companies that have crossed this threshold — they’re slow, bureaucratic, behemoths. How did they get there? At some point in time, someone decided they needed a new process or policy without considering the cost.&lt;/p&gt;

&lt;p&gt;I’ve been thinking a lot about this on the Heroku Support team. We’re at a critical point in our evolution where we’re growing fast and trying to keep up. New policies and procedures are probably necessary to streamline our operations. However, we’re also dangerously close to that important tipping point of flexibility versus bureaucracy.&lt;/p&gt;

&lt;p&gt;It’s also very tempting as a manager to want to try to fix everything for your staff. You’ll spot an inconsistency or ineffective workflow and immediately want to throw on a cape and get to work. But too often we place even greater burdens on the team that we were trying to help in the first place.&lt;/p&gt;

&lt;p&gt;For example, your team and ticket load is growing and you’re seeing more and more examples of unhappy customers demanding refunds. Your gut instinct may be to implement a hard-and-fast policy: “All requests for account credits much be approved by 2 managers!” Instead, trust your staff and give them guidance on how to deal with these situations. If you’re worried about it, place a soft limit and have them consult you if the amount is more. Then continually train and guide your staff to be more self-sufficient.&lt;/p&gt;

&lt;p&gt;Over-bearing policies can also cast too wide of a net and affect the wrong people. It’s all too easy to try to affect the behavior of a single team member by creating a policy that inadvertently affects the entire team. One agent handles a ticket poorly and suddenly an entire new process is enacted to fix one isolated incident. As a result, the entire team suffers.&lt;/p&gt;

&lt;p&gt;Instead, I’d encourage you (both manager and team member) to think long and hard as you add additional processes and structures to your team. Before you do so, ask yourself these important questions:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;What am I trying to solve here? Do I have a clear understanding of the specific problem?&lt;/li&gt;
  &lt;li&gt;What negative side effects might this change trigger? Could other positive behaviors be negatively affected?&lt;/li&gt;
  &lt;li&gt;Have I run this idea by other members of the team? Do they agree on both the problem and the solution?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Implementing new processes is far easier than removing them. And often times, many issues can be resolved at the individual level. So before you decide to change out your support tool, change up a playbook, or require a new process in handling tickets, stop and consider the full cost and effects of a new policy. Your team will thank you!&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;If you’re interested in hearing more about this: I’m really excited to be speaking at &lt;a href=&quot;https://elevatesummit.co/&quot;&gt;Elevate Philadelphia&lt;/a&gt; this spring! My talk is called “How Teams Grow, Thrive, and Fail” and we’ll be mostly talking about &lt;a href=&quot;https://amzn.to/2DwEYl4&quot;&gt;corporate growth theories&lt;/a&gt; and how they can apply to our teams.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Building Healthy Teams - Elevate Summit, Denver</title>
   <link href="https://www.mccartie.com/blog/2017/10/07/building-healthy-teams-elevate-summit-denver.html"/>
   <updated>2017-10-07T18:21:55+00:00</updated>
   <id>https://www.mccartie.com/blog/2017/10/07/building-healthy-teams-elevate-summit-denver</id>
   <content type="html">&lt;p&gt;In preparation for speaking at Elevate Summit in Denver, I prepared this script for my talk. I wasn’t able to memorize it fully, so wanted to share it in text since there were a few key points that may be easier to digest in this format. The video of the talk is embedded at the end.  👍&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;intro&quot;&gt;Intro&lt;/h2&gt;

&lt;p&gt;After working in corporate-land right out of college, an opportunity came up to join a hot new startup in San Francisco. I just bought an outrageously overpriced first home (thanks housing bubble) and was very excited for my massive startup payday. So we packed up the moving truck and headed to the Bay.&lt;/p&gt;

&lt;p&gt;The first few months were very exciting. Our small team was a powerhouse and we were getting tons of interest and new customers. Fast forward a year and things … were different. We had raised a few rounds of capital and we had grown to fill our needs. But our team was no longer … a team.&lt;/p&gt;

&lt;p&gt;Here’s an example of what I mean:&lt;/p&gt;

&lt;p&gt;Our CEO had demanded a 9am start time from everyone, so each of us filed in on time. Those that were late made sure to stop by the CEO’s office (near the front door) to give him our excuse for our tardiness that day. We mumbled our “hello”’s to each other at the coffee pot. The person who arrived the latest that day could usually be found grumbling at the lack of coffee when they arrived.&lt;/p&gt;

&lt;p&gt;We all worked in relative silence in our open office. Then – around 11:45am – came the day’s moment of truth. Your stomach would start to grumble, announcing that it was time to make a decision: do I really want to eat lunch with anyone in here? The answer, of course, was “no”. But you couldn’t just throw your coat on and head to the elevators. Someone would know what’s up. And the only thing worse than a group lunch was a one-on-one lunch with someone you didn’t like.&lt;/p&gt;

&lt;p&gt;So, the trick was to not take your coat. Stand up. Stretch. Maybe add in a yawn. Then slowly walk towards the front of the office. If anyone stopped to ask you if you were headed to lunch, you quickly respond: “Me? No. Just headed to the bathroom.” If caught in this lie, make sure to hang a left towards the bathroom. Maybe stand in there for a minute to really sell it. Stare at yourself in the mirror and wonder how the hell you ended up working somewhere like this. Then slowly re-open the door and sneak down the hallway to the elevator. Head to some place along Market St where you know no one else will eat. Finish your lunch, then stare at your watch and watch the minutes tick by until it was time to return to your desk.&lt;/p&gt;

&lt;p&gt;This was my life in San Francisco. It’s no wonder I left after only a year. But sadly, this is what happens when teams are not cultivated: when growth consumes the culture of a team and focus is not given to a team’s health. And sadly, this is an all too common experience.&lt;/p&gt;

&lt;p&gt;According to a 2015 Gallup survey, only 32% of people say they are “engaged” at work. Aside from these toxic work environments being a drain on the individual, they have serious business impacts as well. Disengaged individuals tend to do the bare minimum at work. Low morale breeds low morale and it spreads across entire organizations. It costs the U.S. an estimated $450 billion to $550 billion per year of lost productivity, stolen goods, and missed days of work.&lt;/p&gt;

&lt;p&gt;So if working somewhere like this sucks so bad, why aren’t we doing more? Why aren’t we actively engaged in building healthy teams instead?&lt;/p&gt;

&lt;p&gt;Frankly, nobody taught us how.&lt;/p&gt;

&lt;p&gt;Now, there are a litany of managerial and leadership books you can pick up. Each has their own unique perspective on this topic. Each one may help a different team in a different stage in its growth. But for the teams I’ve had the pleasure of leading, I’ve found 3 strategies most effective.&lt;/p&gt;

&lt;h2 id=&quot;clarify-the-why&quot;&gt;Clarify the Why&lt;/h2&gt;

&lt;p&gt;First, we’re going to clarify the “why.” Pat McMillan said: “A clear common compelling task that is important to the individual team members is the single biggest factor in team success.”&lt;/p&gt;

&lt;p&gt;If you’ve ever worked on a crappy team, odds are that there was no “clear, common, or compelling task”. Was it “make a bunch of money”? Sell the most ads? For lots of people, business metrics are hard to get excited about. So what’s the goal here? What is your team doing that’s exciting or compelling?&lt;/p&gt;

&lt;p&gt;But before you call for a team offsite to layout your corporate mission statement, your best bet is to start with some introspection. Find a quiet space and begin to really think through some questions. What drew you to this company? Why are you working there? What makes you get up each morning and go to work? What’s the problem your team is solving, or the opportunity it has before it?&lt;/p&gt;

&lt;p&gt;As a manager on the Heroku Support team, I love helping people. Heroku is a platform that runs over 6 millions apps, receiving over 13 Billion requests per day. Our customers are unique in that most are programmers. I’ve been programming for almost 20 years and so I feel a unique connection to our customers. Sure, we get the usual “how do I reset my password?” tickets, but most of the time we’re there to help fellow programmers solve a difficult problem and get on with their day. This is incredibly rewarding for me and everyone on my team (who are also programmers).&lt;/p&gt;

&lt;p&gt;So for me, I tell my team that I love helping fellow programmers get un-stuck. Programming is hard and I want every customer that opens a ticket to be amazed at how great our team is in answering even the toughest of questions. And as a manager, this is an easy idea to rally my team behind. It elevates the cause of our team and gives us a clear goal. In addition, when we have a clear goal ahead of us, it often helps solve many other problems the team faces. “Should we invest more time in better documentation?”  Well, does it help our fellow programmers get un-stuck? Yes. Then do it. “We’ve seen a lot of tickets about this new feature.” It looks like we’re causing unnecessary issues for our fellow programmers. Fix the damn bug.&lt;/p&gt;

&lt;p&gt;So find the common goal for your team. Don’t start with “what” your team does – it’s important, sure. But instead, start with “why”. Why are we all here? And once you have that, tell your team. Then repeat it. And repeat it again. Focus has a tendency to drift, but if you continually remind each other about your “why”, you have a better chance at building a healthy team.&lt;/p&gt;

&lt;h2 id=&quot;find-and-develop-individual-strengths&quot;&gt;Find and Develop Individual Strengths&lt;/h2&gt;

&lt;p&gt;Have you ever heard of Rudy? If you’re too young to remember, the movie “Rudy” was about a kid who grew up near Notre Dame. All his childhood he dreamed of playing football. When we grew up, he was accepted to Notre Dame, but not on the football team. But he worked hard, practiced, and helped out on the team. And one day … at the final game of his senior year, the crowd begins to chant: “RU-DEE! RU-DEE!”  Finally, the coach turns to him in the final few minutes of the game and sends him out on the field. He makes a few plays and when the game was over, his teammates carried him out of the stadium on their shoulders.&lt;/p&gt;

&lt;p&gt;Wow!&lt;/p&gt;

&lt;p&gt;But here’s the thing. Rudy sucked at football. But we love the underdog. It’s a great story to watch, but at some point you have to wonder: what was Rudy really good at? How would his younger years have looked different if someone told him the truth and helped him discover his natural talents.&lt;/p&gt;

&lt;p&gt;On my teams, we work to find and develop strengths. We don’t ignore weaknesses, but we certainly don’t dwell on them for too long. Instead, we work together to find the things that come naturally to you and focus on developing those.&lt;/p&gt;

&lt;p&gt;During one-on-one’s, I’m constantly looking for a glimmer in someone’s eye – that look of excitement when they’ve finished a certain ticket or task. The look or tone of voice that tells me that this thing is something they loved doing. Or I look for times when a task is done in half the time as expected. “Was this hard for you?”  Nope, it came easy.&lt;/p&gt;

&lt;p&gt;Bingo. Now we have a conversation starter. Why was it exciting to do that type of ticket? What about that task was easy for you? If it makes sense, we now have a path towards growth. Over and over on our teams at Heroku, we’ve grown people out of the Support team. Retention is not a metric for me. I am actively working with my team to grow them. “What?! What if you lose them?” I’m ok with that. I’d rather grow them and lose them, then force them into stagnation and keep them around.&lt;/p&gt;

&lt;p&gt;Chances are, you have some incredibly bright and talented people on your team, but no one has told them not to play football. Sit with them, invest in them, and see what incredible things they’re capable of.&lt;/p&gt;

&lt;h2 id=&quot;create-a-high-feedback-culture&quot;&gt;Create a High-Feedback Culture&lt;/h2&gt;

&lt;p&gt;Finally, we get to talk about one of the hardest things about leading a team: feedback.&lt;/p&gt;

&lt;p&gt;There are many long and leather-bound books about this topic, so I don’t want to stay here too long. But I think there’s an unhealthy focus on this topic around critical feedback. Don’t get me wrong – providing critical or negative feedback is absolutely necessary and it’s something that’s a learned skill.&lt;/p&gt;

&lt;p&gt;But we far too often ignore the fact that feedback must come from all directions. A healthy team not only extends feedback up and down the org chart, but members accept feedback. No, they crave it.&lt;/p&gt;

&lt;p&gt;Two jobs ago, the CEO of our company popped his head into my team’s office. He scanned the room, and called out to me: “Can I borrow you for 30 minutes?”  Oh crap. I headed to his office, where he had assembled people from all over the company.&lt;/p&gt;

&lt;p&gt;“I’m giving this presentation next week and want your honest feedback. Don’t sugar-coat it. Tell me what I’m missing or what things I need to take out.”&lt;/p&gt;

&lt;p&gt;It was terrifying to tell your CEO what was wrong with his presentation. But it was incredible. Not only did this guy preach “high feedback”, but he lived it, too!&lt;/p&gt;

&lt;p&gt;And when your team isn’t worried about how you’ll take it, you’ll be amazed at how helpful that feedback can be. Ask for it! Don’t shy away. And learn to take it well – even the crappy ideas. Thank them, digest the feedback, then pick out anything you want to keep.  But in doing so, you’re creating a valuable level of trust amongst your team.&lt;/p&gt;

&lt;p&gt;Let’s talk about positive feedback. It’s the easiest type of feedback to give, but we rarely do it. Why is that? Why is it so hard for us to acknowledge someone for doing a good job?&lt;/p&gt;

&lt;p&gt;According to a American Psychological Association survey, 93% of employees who reported feeling valued, said that they are motivated do their best work. And yet, we seem to only dole out praise when people leave the company.&lt;/p&gt;

&lt;p&gt;Let’s start using the Homeland Security procedure: “see something, say something.” If you see something good on your team, just say it! Call it out! Some people don’t like public praise – that’s fine. But encourage people when they’re doing the right thing.&lt;/p&gt;

&lt;p&gt;And when we do express praise, we tend to only focus on the work – not the person. We need to move from individual accomplishments to wholistic praise and gratitude. We need to move from “Nice work on that ticket” to “I’m grateful for you and your work on this team.”  Do you hear the difference?&lt;/p&gt;

&lt;p&gt;We don’t have workers on our team – we have people.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Dr. Henry Cloud said: “As a leader, you are always going to get a combination of two things: What you create and What you allow.”&lt;/p&gt;

&lt;p&gt;Building a healthy team is an act of intentionality. You cannot turn it on and let it run by itself with no guidance, and expect a positive result.&lt;/p&gt;

&lt;p&gt;And we all have an obligation to create positive, healthy work environments. This isn’t just a management thing. Think about the work you’re doing. If we’re honest, a lot of the work we’re doing won’t be around in 15 or 20 years. But the relationships, the friendships, the people’s lives on your team who you choose to pour into … those will last.&lt;/p&gt;

&lt;p&gt;If you’re a manager, you’ve been entrusted with a few short years of someone’s life. It may one day end up as a bullet note on a resume. Or it could be one of the happiest and life-giving moments they’ll ever have. Choose to invest in the people around you.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;div align=&quot;center&quot;&gt;
  &lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/u_quhi0vJDI?rel=0&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;
</content>
 </entry>
 
 <entry>
   <title>Rails 5.1: Default Primary Keys</title>
   <link href="https://www.mccartie.com/tech/2016/12/05/rails-5.1.html"/>
   <updated>2016-12-05T13:34:31+00:00</updated>
   <id>https://www.mccartie.com/tech/2016/12/05/rails-5.1</id>
   <content type="html">&lt;blockquote class=&quot;twitter-tweet&quot; data-lang=&quot;en&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;Friends don&amp;#39;t let friends use INT as a primary key. &lt;/p&gt;&amp;mdash; Richard Schneeman (@schneems) &lt;a href=&quot;https://twitter.com/schneems/status/731167572096253952&quot;&gt;May 13, 2016&lt;/a&gt;&lt;/blockquote&gt;

&lt;h2 id=&quot;a-bit-of-history&quot;&gt;A Bit of History…&lt;/h2&gt;

&lt;p&gt;Remember the &lt;a href=&quot;https://techcrunch.com/2009/06/12/all-hell-may-break-loose-on-twitter-in-2-hours/&quot;&gt;Twitpocalypse&lt;/a&gt;? Back in 2009, Twitter ran out of Integers. Like most of us, they set up the primary key in their &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tweets&lt;/code&gt; table to be a 32-bit signed integer. Once they hit the magical number (2,147,483,647), servers were all but guaranteed to catch fire in the most spectacular fashion.&lt;/p&gt;

&lt;p&gt;Just in the nick of time, however, they managed to migrate to using 64-bit integers and bought themselves a nice chunk of time (the max is now 9,223,372,036,854,775,807).&lt;/p&gt;

&lt;p&gt;Now, I’m sure you’re thinking to yourself: “Self, should I be worried about this?” And some days, it may feel kind of silly to worry about ever having more than 2.1B rows in a single table. But Twitter was like you once and didn’t worry about it … until it was too late!&lt;/p&gt;

&lt;p&gt;Well, worry yourself no more. &lt;a href=&quot;https://github.com/rails/rails/pull/26266&quot;&gt;Primary keys in Rails 5.1 are now big integers by default!&lt;/a&gt; 🎉&lt;/p&gt;

&lt;h2 id=&quot;but-why-not-uuid&quot;&gt;But why not UUID?&lt;/h2&gt;

&lt;p&gt;Hey, I love me some UUID’s. But their usage as primary keys is still debated in the community. So for this change, we opted for making a more sane default that was more widely accepted.&lt;/p&gt;

&lt;p&gt;But don’t give up hope! You can default to using UUID’s for primary keys in generators if you want. I submitted that patch last year: &lt;a href=&quot;https://www.mccartie.com/2015/10/20/default-uuid&apos;s-in-rails.html&quot;&gt;https://www.mccartie.com/2015/10/20/default-uuid’s-in-rails.html&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;migrate&quot;&gt;Migrate&lt;/h2&gt;

&lt;p&gt;Already have a table with 32-bit integer primary keys? It’s easy enough to migrate them:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;rails g migration change_primary_key_to_bigint_on_users&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Then…&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;change_column&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:users&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:bigint&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;&lt;a href=&quot;https://twitter.com/rpbaptist&quot;&gt;Richard Baptist&lt;/a&gt; mentioned to me that he needed to run the following migration for MySQL 5.7:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;change_column&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:users&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:bigint&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;unique: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;null: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;auto_increment: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The usual warnings apply when doing this to a large table running in prouduction…consult your local DBA for side effects.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NOTE: If you perform this upgrade, make sure to update foreign keys as well. If your primary key and foreign keys don’t match, Postgres won’t give you any warnings – &lt;a href=&quot;https://github.com/rails/rails/pull/26266/files#diff-98ce5dc0cd01f8b0435ff6feac10d8a8R748&quot;&gt;MySQL will break&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;h2 id=&quot;rails-core-team-ftw&quot;&gt;Rails Core Team FTW&lt;/h2&gt;

&lt;p&gt;This was a big change to make. A fellow Heroku co-worker (“Herokai” &lt;a href=&quot;https://github.com/rwz&quot;&gt;Pavel Pravosud&lt;/a&gt; ) kicked this off back in May with a change for Postgresql. &lt;a href=&quot;https://github.com/rails/rails/pull/26266#issuecomment-241932341&quot;&gt;Unbenknowst to me&lt;/a&gt; that Pavel had started with this work, I made my own change to include both MySQL and Postgresql (sorry, SQLite doesn’t do bigint primary keys). I was able to merge Pavel’s work into my own. Then began the difficult (yet satisfying) task of hashing out the finer points with the Rails core team. But looking back on it all and seeing the quality of the tweaks that were suggested, I’m really grateful for the back and forth.&lt;/p&gt;

&lt;p&gt;So find yourself a Core Team member today and give them a hug. Tell them thanks for their tireless efforts. And stop worrying about whether or not your primary keys are going to run out.&lt;/p&gt;

&lt;p&gt;Cheers!&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Token-Based API Authentication</title>
   <link href="https://www.mccartie.com/tech/2016/11/03/token-based-api-authentication.html"/>
   <updated>2016-11-03T11:02:09+00:00</updated>
   <id>https://www.mccartie.com/tech/2016/11/03/token-based-api-authentication</id>
   <content type="html">&lt;p&gt;A friend recently came to me wondering how he could add token-based authentication to his API.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;I used Devise for my app, but it looks like they removed token auth. I’ve found a few gems, but they all look to do more than I need.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I’ve been critical of Devise for a long time. I used it exclusively on my earliest Rails sites because of how popular it was and how powerful it was out of the box. But trying to customize Devise is its biggest downside. In this instance, Devise is percevied as your authentication gatekeeper – and homage must be paid.&lt;/p&gt;

&lt;p&gt;The good news is: feel free to keep Devise around, but you don’t need it for your API. We can use some relatively unknown methods built right into Ruby and Rails.  Here’s how…&lt;/p&gt;

&lt;h2 id=&quot;migrations&quot;&gt;Migrations&lt;/h2&gt;

&lt;p&gt;We’re going to assign a token to each user. Once the client signs in, they’ll receive a token that can then be used for all authenticated API calls.  Let’s get that onto the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;users&lt;/code&gt; table.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;rails g migration add_auth_token_to_users auth_token&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This creates the following migration for us:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AddAuthTokenToUsers&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveRecord&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Migration&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;change&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;add_column&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:users&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:auth_token&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:string&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;add_index&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:users&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:auth_token&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;We’re going to be looking up users based on that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;auth_token&lt;/code&gt;, so that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;add_index&lt;/code&gt; method is very important here.&lt;/p&gt;

&lt;p&gt;Run the migration:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;rake db:migrate&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;the-base-controller&quot;&gt;The Base Controller&lt;/h2&gt;

&lt;p&gt;I’ve built plenty of Rails-based API’s in my time. The first step for me is usually to make sure I have a good “base” controller for all API controllers. This allows me to set things up apart from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ApplicationController&lt;/code&gt;, which typically is heavily bloated with methods that I don’t need in my API. In this case, it will allow me to set custom authentication methods outside of Devise.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Api::ApiController&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActionController&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Base&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;There’s a lot to digest here, so let’s break it down:&lt;/p&gt;

&lt;p&gt;First, we need a way to protect our controllers:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;require_login!&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;authenticate_token&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;render&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;json: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;errors: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;detail: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Access denied&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;status: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;401&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;We’re stubbing out a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;authenticate_token&lt;/code&gt; method here for now – we want to encapsulate that logic in a separte method. But if it returns a user, we’re all set.  Otherwise, we return an error message and a 401 status.&lt;/p&gt;

&lt;p&gt;We know that the vast majority of our controllers are not accessible without authentication, so we can run the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;require_login!&lt;/code&gt; method as a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;before_action&lt;/code&gt; in this controller.  If we need to skip it for certain endpoints (like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sign-in&lt;/code&gt;), we can use a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;skip_before_action&lt;/code&gt; there.&lt;/p&gt;

&lt;p&gt;So now we can move on to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;authenticate_token&lt;/code&gt; method:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;authenticate_token&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;authenticate_with_http_token&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;token&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find_by&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;auth_token: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;token&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Rails has the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;authenticate_with_http_token&lt;/code&gt; method &lt;a href=&quot;https://api.rubyonrails.org/classes/ActionController/HttpAuthentication/Token/ControllerMethods.html#method-i-authenticate_with_http_token&quot;&gt;built-in&lt;/a&gt;. It’ll handle all the details for us here – we just need to know how to lookup the user with the token that’s passed in as a header.&lt;/p&gt;

&lt;p&gt;Next, we’ll finish this up with some helper methods like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;current_user&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;user_signed_in?&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The finished controller:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# app/controllers/api/base_controller.rb&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Api::BaseController&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActionController&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Base&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;before_action&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:require_login!&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;helper_method&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:person_signed_in?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:current_user&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;user_signed_in?&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;current_person&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;present?&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;require_login!&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;authenticate_token&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;render&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;json: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;errors: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;detail: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Access denied&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;status: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;401&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;current_user&lt;/span&gt;
    &lt;span class=&quot;vi&quot;&gt;@_current_user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;authenticate_token&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;kp&quot;&gt;private&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;authenticate_token&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;authenticate_with_http_token&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;token&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
        &lt;span class=&quot;no&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find_by&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;auth_token: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;token&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;signing-in&quot;&gt;Signing In&lt;/h2&gt;

&lt;p&gt;So how do we get the token for the user?  Just like a normal sessions controller – but we’ll return the token instead of handling setting session info and performing redirects.&lt;/p&gt;

&lt;p&gt;First, we’ll add some routes:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# config/routes.rb&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:api&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:defaults&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:format&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:json&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:user&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;post&lt;/span&gt;   &lt;span class=&quot;s2&quot;&gt;&quot;/sign-in&quot;&lt;/span&gt;       &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;sessions#create&quot;&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;delete&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;/sign-out&quot;&lt;/span&gt;      &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;sessions#destroy&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;And the controller:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Api::SessionsController&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Api&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;BaseController&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;skip_before_action&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:require_login!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;only: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;create&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;resource&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find_for_database_authentication&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:email&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:user_login&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:email&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;resource&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;valid_password?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:user_login&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:password&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;auth_token&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;generate_auth_token&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;render&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;json: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;auth_token: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;auth_token&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;invalid_login_attempt&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;destroy&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;resource&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;current_person&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;resource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;invalidate_auth_token&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;head&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:ok&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;kp&quot;&gt;private&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;invalid_login_attempt&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;render&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;json: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;errors: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;detail&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:&quot;Error with your login or password&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}]},&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;status: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;401&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;We’re using two Devise methods in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;create&lt;/code&gt; method here: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;find_for_database_authentication&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;valid_password?&lt;/code&gt;.  If you’re not using Devise, you can easily replace these with your own authentication system. The key here is to load and verify the user.  Once this is done, we’ll ask the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;User&lt;/code&gt; model to give us a token. &lt;strong&gt;Note that we’re delegating this responsibilty to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;User&lt;/code&gt; model – this is not the job of the controller!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Likewise for destroying this token, we provide a sign out method.  We’ve stubbed out &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;invalidate_auth_token&lt;/code&gt; on the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;User&lt;/code&gt; which we can build next.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Please also note that we don’t immediately error if the user is not found – we’ll load &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;User.new&lt;/code&gt; and still check the password even if it’s blank. This prevents attackers from timing our response times to determine if an email is valid or no.&lt;/em&gt;&lt;/p&gt;

&lt;h2 id=&quot;user-model&quot;&gt;User Model&lt;/h2&gt;

&lt;p&gt;In the sessions controller, we stubbed out two methods on the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;User&lt;/code&gt; model – one to generate a token and one to invalidate this token. Let’s fill those in:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# app/models/user.rb&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;generate_auth_token&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;token&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;SecureRandom&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;hex&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;update_columns&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;auth_token: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;token&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;token&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;invalidate_auth_token&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;update_columns&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;auth_token: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;We’re using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SecureRandom&lt;/code&gt; to &lt;a href=&quot;https://ruby-doc.org/stdlib-2.3.0/libdoc/securerandom/rdoc/SecureRandom.html&quot;&gt;generate a random hexadecimal string&lt;/a&gt;. This will generate a 32 character string. For example:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;irb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;&lt;span class=&quot;mo&quot;&gt;001&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;times&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;SecureRandom&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;hex&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;s2&quot;&gt;&quot;c46890f205b82c1b74c750c3dce43223&quot;&lt;/span&gt;
&lt;span class=&quot;s2&quot;&gt;&quot;0cda5c4fd3b4e9536b823696fbd8874b&quot;&lt;/span&gt;
&lt;span class=&quot;s2&quot;&gt;&quot;8d8d96a8dc575b7d3659acf8e37aee64&quot;&lt;/span&gt;
&lt;span class=&quot;s2&quot;&gt;&quot;60d5f9a3586da5290bdbfab88ec6e47a&quot;&lt;/span&gt;
&lt;span class=&quot;s2&quot;&gt;&quot;25a6c19009805f89c71b4a9079a3585f&quot;&lt;/span&gt;
&lt;span class=&quot;s2&quot;&gt;&quot;336b75f35e60f987b477b852c29989f3&quot;&lt;/span&gt;
&lt;span class=&quot;s2&quot;&gt;&quot;849dcecdf685712517d9f5d0a7793138&quot;&lt;/span&gt;
&lt;span class=&quot;s2&quot;&gt;&quot;554992f0b9cabb68b9aa8a90070da259&quot;&lt;/span&gt;
&lt;span class=&quot;s2&quot;&gt;&quot;e5159948971bf835deb59df0f5ac3efe&quot;&lt;/span&gt;
&lt;span class=&quot;s2&quot;&gt;&quot;5dd10aca5fea289027228f422898f48f&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;generate_auth_token&lt;/code&gt; will generate a new token, update the database, and return the token to be used by our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SessionsController&lt;/code&gt;.  &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;invalidate_auth_token&lt;/code&gt; will simply set this value to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nil&lt;/code&gt; and disallow the User to be authenticated with this token in the future.&lt;/p&gt;

&lt;h2 id=&quot;api-usage&quot;&gt;API Usage&lt;/h2&gt;

&lt;p&gt;Now that we’ve got all the moving parts, let’s test things out with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;curl&lt;/code&gt;.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;c&quot;&gt;# Initial Authorization&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;curl &lt;span class=&quot;nt&quot;&gt;-X&lt;/span&gt; POST &lt;span class=&quot;nt&quot;&gt;--data&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;user_login%5Bemail%5D=jon%40mccartie.com&amp;amp;user_login%5Bpassword%5D=please1234&quot;&lt;/span&gt; https://localhost:5000/api/sign-in.json
&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;auth_token&quot;&lt;/span&gt;:&lt;span class=&quot;s2&quot;&gt;&quot;a88601e054ba3df53bf9c9ff2d0d24f9&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Protected Calls&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;curl &lt;span class=&quot;nt&quot;&gt;-H&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Authorization: Token token=a88601e054ba3df53bf9c9ff2d0d24f9&quot;&lt;/span&gt; https://localhost:5000/api/people.json
&lt;span class=&quot;o&quot;&gt;[{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;id&quot;&lt;/span&gt;:3,&lt;span class=&quot;s2&quot;&gt;&quot;name&quot;&lt;/span&gt;:&lt;span class=&quot;s2&quot;&gt;&quot;Lonzo McDermott&quot;&lt;/span&gt;,&lt;span class=&quot;s2&quot;&gt;&quot;profile&quot;&lt;/span&gt;:...

&lt;span class=&quot;c&quot;&gt;# Example Failed Authentication&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;curl &lt;span class=&quot;nt&quot;&gt;-H&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Authorization: Token token=abc&quot;&lt;/span&gt; https://localhost:5000/api/people.json
&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;errors&quot;&lt;/span&gt;:[&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;detail&quot;&lt;/span&gt;:&lt;span class=&quot;s2&quot;&gt;&quot;Access denied&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;}]}&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Sign out (returns blank 200 response)&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;curl &lt;span class=&quot;nt&quot;&gt;-X&lt;/span&gt; DELETE &lt;span class=&quot;nt&quot;&gt;-H&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Authorization: Token token=a88601e054ba3df53bf9c9ff2d0d24f9&quot;&lt;/span&gt; https://localhost:5000/api/sign-out.json&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;expiration&quot;&gt;Expiration&lt;/h2&gt;

&lt;p&gt;Maybe you don’t like the idea of these auth tokens living on indefinitely.  We can easily add some logic to expire these tokens.&lt;/p&gt;

&lt;p&gt;First, we need to know when the token was generated at.  So we add a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;token_created_at&lt;/code&gt; field to User:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;rails g migration add_token_created_at_to_users token_created_at:datetime&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Note: now we’ll be looking up users now not just by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;auth_token&lt;/code&gt;, but by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;auth_token&lt;/code&gt; &lt;em&gt;and&lt;/em&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;token_created_at&lt;/code&gt;. Let’s make sure to add a compound index:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AddTokenCreatedAtToUsers&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveRecord&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Migration&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;change&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;add_column&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:users&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:token_created_at&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:datetime&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;remove_index&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:users&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:auth_token&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;add_index&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:users&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:auth_token&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:token_created_at&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Next, let’s make sure we touch this attribute when we create and destroy tokens:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# app/models/user.rb&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;generate_auth_token&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;token&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;SecureRandom&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;hex&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;update_columns&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;auth_token: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;token&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;token_created_at: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;zone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;token&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;invalidate_auth_token&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;update_columns&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;auth_token: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;token_created_at: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Now in our base controller, we can check to make sure the token is still “fresh”. Should we validate the user, then check the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;token_created_at&lt;/code&gt; value? Gadzooks, no! Let’s make our database do that.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# app/controllers/api/base_controller&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;authenticate_token&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;authenticate_with_http_token&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;token&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;auth_token: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;token&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;token_created_at &amp;gt;= ?&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;month&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ago&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;first&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The API client will get the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;unauthorized&lt;/code&gt; response and can attempt to sign in again to fetch a fresh token.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;As Ruby developers, we have a littany of incredible Gems available to us. And when faced with the question “Should I build this myself? Or use a gem?”, there’s usually incredible value to not re-inventing the wheel.  But make sure your reliance on other libraries never blinds you to simpler solutions that can be fully customized to your needs. Sometimes “rolling your own” can actually save you time.&lt;/p&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Rails and Slack: Creating a Slash Command App</title>
   <link href="https://www.mccartie.com/tech/2016/09/15/rails-and-slack-creating-a-slash-command-app.html"/>
   <updated>2016-09-15T13:49:52+00:00</updated>
   <id>https://www.mccartie.com/tech/2016/09/15/rails-and-slack-creating-a-slash-command-app</id>
   <content type="html">&lt;p&gt;Back in my Campfire days, I remember being blown away when Github released &lt;a href=&quot;https://hubot.github.com/&quot;&gt;Hubot&lt;/a&gt;. Our team tinkered around with our install for days – hoping to make Hubot do our bidding. It was a great little tool (and still is!), but I’ve recently fallen in love with Slack’s “&lt;a href=&quot;https://api.slack.com/slash-commands&quot;&gt;Slash Commands&lt;/a&gt;”. They’re a handy way to send commands to a service and get a response. And the best part? You can write one in Ruby!&lt;/p&gt;

&lt;h2 id=&quot;the-slack-webhook&quot;&gt;The Slack Webhook&lt;/h2&gt;

&lt;p&gt;Slash commands work in a simple manner: you start a command with a forward slash, then the command name. For this basic example, we’ll ask for weather for a particular zip code:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;/weather 83864&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The entire command, along with any additional text, is sent as a webhook to your application.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;token=gIkuvaNzQIHg97ATvDxqgjtO
team_id=T0001
team_domain=example
channel_id=C2147483705
channel_name=test
user_id=U2147483697
user_name=Steve
command=/weather
text=83864
response_url=https://hooks.slack.com/commands/1234/5678&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;token&lt;/code&gt; is a secret setup when you configure the command inside your Slack instance. You’ll be checking this to ensure the command is &lt;em&gt;actually&lt;/em&gt; from Slack.&lt;/p&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;response_url&lt;/code&gt; is where we’ll send our data once it’s ready.&lt;/p&gt;

&lt;h2 id=&quot;receiving-the-command&quot;&gt;Receiving the Command&lt;/h2&gt;

&lt;p&gt;The webhook from Slack comes as a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;POST&lt;/code&gt; request, so we’ll create a Rails controller with a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;create&lt;/code&gt; method:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CommandsController&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ApplicationController&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;create&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;render&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;json: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{},&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;status: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;403&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;unless&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;valid_slack_token?&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;CommandWorker&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;perform_async&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;command_params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_h&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;render&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;json: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;response_type: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;in_channel&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;status: :created&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

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

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;valid_slack_token?&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:token&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;SLACK_SLASH_COMMAND_TOKEN&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# Only allow a trusted parameter &quot;white list&quot; through.&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;command_params&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;permit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:token&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:user_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:response_url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;We’re doing a few simple things here:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;We immediately return a 403 (forbidden) unless the Slack token matches. We do that with the private method &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;valid_slack_token?&lt;/code&gt; which compares the token in the params with the one Slack gave us (and is stored in an ENV var).&lt;/li&gt;
  &lt;li&gt;Next, we’ll send off our entire hash of params to a Sidekiq background job. (I had some trouble serializing the params hash in Rails 5, so I’m using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.to_h&lt;/code&gt; to do this for me) We’re using a background job here since we’ll be communicating with a 3rd party API. That way, we can return a response quickly to Slack and avoid a timeout.&lt;/li&gt;
  &lt;li&gt;Finally, we return a 201 (created) and send back some JSON.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{ response_type: &quot;in_channel&quot; }&lt;/code&gt; JSON tells slack to leave the original command (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/weather 83864&lt;/code&gt;) in the Slack chat history. If you’re sending something back that only the user will see privately, you’ll want to return &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{ response_type: &quot;ephemeral&quot; }&lt;/code&gt; at this step.&lt;/p&gt;

&lt;h2 id=&quot;returning-data&quot;&gt;Returning Data&lt;/h2&gt;

&lt;p&gt;Next, we’ll have our Sidekiq worker fetch the data and send it all back using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;response_url&lt;/code&gt; that Slack sent us in the params.&lt;/p&gt;

&lt;p&gt;To fetch the Weather data, I’m using &lt;a href=&quot;https://www.wunderground.com/weather/api/&quot;&gt;Weather Underground&lt;/a&gt;. I’m going to create a basic class to encapsulate some of this behavior.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Weather&lt;/span&gt;

  &lt;span class=&quot;nb&quot;&gt;attr_reader&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:zip&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;initialize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;zip&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;vi&quot;&gt;@zip&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;zip&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;city&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;full&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;temperature&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;temperature_string&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;icon_url&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;icon_url&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;


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

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;data&lt;/span&gt;
      &lt;span class=&quot;vi&quot;&gt;@data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;HTTParty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;https://api.wunderground.com/api/&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;WUNDERGROUND_KEY&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/conditions/q/&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;zip&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;.json&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;current_observation&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;display_location&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;And then, the Sidekiq worker which uses this class:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CommandWorker&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Sidekiq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Worker&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;sidekiq_options&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:retry&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;perform&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;weather&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Weather&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;message&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;ss&quot;&gt;text: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;The temperature in &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;weather&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;city&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; is &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;weather&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;temperature&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;ss&quot;&gt;response_type: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;in_channel&quot;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;no&quot;&gt;HTTParty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:response_url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;body: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_json&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;headers: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;s2&quot;&gt;&quot;Content-Type&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;application/json&quot;&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Here’s what’s happening:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Our worker starts up with the params it was sent by Slack.&lt;/li&gt;
  &lt;li&gt;We initialize a Weather object and pass in the text from the message (zip code)&lt;/li&gt;
  &lt;li&gt;The Weather instance fetches the data from the API, then exposes it back to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CommandWorker&lt;/code&gt; to be used in generating the text.&lt;/li&gt;
  &lt;li&gt;We post back our text string to Slack, using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;response_url&lt;/code&gt; value it sent us in the first step.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&quot;https://www.dropbox.com/s/et6zzjntvgpdh2k/Screenshot%202016-09-15%2013.37.05.png?dl=1&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;attachments&quot;&gt;Attachments&lt;/h2&gt;

&lt;p&gt;To make our message a little prettier, we can send along the image URL as an “attachment” to Slack. This requires simply modifying our JSON a bit like this:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;attachments&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;title&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Weather for Sandpoint, ID&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;title_link&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;https://www.wunderground.com/US/ID/Sandpoint.html&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;text&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Optional text that appears within the attachment&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;fields&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;title&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Temp&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;68F&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;short&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;title&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Wind&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;16mph&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;short&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;image_url&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;https://icons.wxug.com/i/c/k/clear.gif&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;&lt;img src=&quot;https://www.dropbox.com/s/exmij0skdr657i7/Screenshot%202016-09-15%2013.43.00.png?dl=1&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Slack provides a nice &lt;a href=&quot;https://api.slack.com/docs/messages/builder&quot;&gt;demo tool of message formatting&lt;/a&gt;. It’s very handy when getting your messages to look &lt;em&gt;just right&lt;/em&gt;.&lt;/p&gt;

&lt;h2 id=&quot;extending&quot;&gt;Extending&lt;/h2&gt;

&lt;p&gt;Next, we may want to send different types of commands to the same app. For instance, I could specify what I want from the weather app, like this:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;/weather forecast:tomorrow 83864
/weather temp:f 83864&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;To do so, you may choose to separate out pieces of the app into separate modules or sub-classes, then dispatch them from one place.  In an app I’m working on at Heroku, we pass commands like this:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;/slack-app-name task:subtask command&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;…where &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;slack-app-name&lt;/code&gt; is one of a few different Slash Commands apps. In order to properly parse the task, subtask, and command, we have a Processor module:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Commands&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Processor&lt;/span&gt;
    &lt;span class=&quot;kp&quot;&gt;extend&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;task&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;titleize&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&quot;Commands::&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;constantize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# Given `task:sub message`, return task&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;scan&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/\w+/&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;first&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# Given `task:sub message`, return subtask&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;subtask&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;scan&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/\w+/&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# Given `task:sub message`, return message&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/\w+:\w+\s(.*)/&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;We use this to initialize the correct class to handle the command:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CommandWorker&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Sidekiq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Worker&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;sidekiq_options&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:retry&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# job will be discarded immediately if failed&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;perform&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;Commands&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Processor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;run&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Now we can send something like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/weather forecast:tomorrow 83864&lt;/code&gt; and have a class &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Commands::Forecast&lt;/code&gt; process it by looking at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;subtask&lt;/code&gt; (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tomorrow&lt;/code&gt;) and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;message&lt;/code&gt; (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;83864&lt;/code&gt;). By doing so we can extend and expand our app to process multiple different tasks.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;I hope this gives you a glimpse into what’s possible with Slash Commands! Happy coding, Rubyist!&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Added "not_in?" to Rails</title>
   <link href="https://www.mccartie.com/tech/2016/07/21/added-to-rails-not_in.html"/>
   <updated>2016-07-21T18:01:59+00:00</updated>
   <id>https://www.mccartie.com/tech/2016/07/21/added-to-rails:-not_in?</id>
   <content type="html">&lt;p&gt;&lt;strong&gt;UPDATE&lt;/strong&gt;: DHH &lt;a href=&quot;https://github.com/rails/rails/pull/25914#issuecomment-236301504&quot;&gt;yanked this patch&lt;/a&gt;. Ah well. Open source giveth, and open source taketh away. I sure wish we could restart this discussion and get this change made, but I’ll probably pass on fighting over this one.&lt;/p&gt;

&lt;p&gt;Here’s the (now defunct) celebratory post…&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;While helping a friend today with their Rails project, I found I wanted to add a simple helper method to an object. In the existing view I found something like this:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@item&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;tag&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;free&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@item&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;tag&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;extras&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This is a code smell and I could solve it in two ways: either write a view helper or add a helper method to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Item&lt;/code&gt; class. In this case, I want to ask the object a question, so this makes sense to me on the object itself.&lt;/p&gt;

&lt;p&gt;So first we change the view:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@item&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;for_sale?&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;And we implement on the model:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Item&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveRecord&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Base&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;for_sale?&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;tag&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;free&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;tag&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;extras&quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Ew. We’re checking a string attribute agaisnt two other strings. Let’s clean that up.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;for_sale?&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;free&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;extras&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;include?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tag&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;We’re basically saying “this item is for sale if the tag isn’t either ‘free’ or ‘extras’”. To shave off some cognitive load, we use Rails’ &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;in?&lt;/code&gt; method and flip things around. Since we’re really talking about the tag (and not the array up front), this makes it easier to read.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;for_sale?&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tag&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;in?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;free&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;extras&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;That’s all fine and good, but that bang (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;!&lt;/code&gt;) out front is still making my mind do some gymnastics. &lt;em&gt;Surely&lt;/em&gt;, if Rails has &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;in?&lt;/code&gt; it has an opposite. But Google gave me nothing, and some other Rails friends I asked hadn’t heard of such a thing.&lt;/p&gt;

&lt;p&gt;Turns out, Rails &lt;em&gt;doesn’t&lt;/em&gt; have an opposite for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;in?&lt;/code&gt;. Ruby has &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;include?&lt;/code&gt; and Rails implements &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;exclude?&lt;/code&gt;, which is nice. But Rails implements &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;in?&lt;/code&gt; without an opposite.&lt;/p&gt;

&lt;p&gt;Putting aside the fact that I think these methods (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;exclude?&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;in?&lt;/code&gt;) &lt;a href=&quot;https://github.com/rails/rails/pull/25914#issuecomment-234398116&quot;&gt;should be added to Ruby core&lt;/a&gt;, I would love to see Rails go that extra step and have a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;not_in?&lt;/code&gt; method.&lt;/p&gt;

&lt;p&gt;… and now it does!&lt;/p&gt;

&lt;p&gt;As of &lt;a href=&quot;https://github.com/rails/rails/commit/0020f1a4c96731a17ac4f84fa67058b5bc459fe2&quot;&gt;this commit&lt;/a&gt;, Rails now has a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;not_in?&lt;/code&gt; method. It works like this:&lt;/p&gt;

&lt;p&gt;Replace this:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;exclude?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;…into this:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;user_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;not_in?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;So in our previous example, we can now lead with the object in question. So this:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;for_sale?&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tag&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;in?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;free&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;extras&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;… turns into this:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;for_sale?&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;tag&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;not_in?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;free&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;extras&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Nice and clean! Enjoy!&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Heroku National Park Name Generator</title>
   <link href="https://www.mccartie.com/tech/2016/07/05/heroku-national-park-name-generator.html"/>
   <updated>2016-07-05T13:08:54+00:00</updated>
   <id>https://www.mccartie.com/tech/2016/07/05/heroku-national-park-name-generator</id>
   <content type="html">&lt;p&gt;In the past, the Heroku API has done some fun stuff on certain holidays. Whenever you run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;heroku create&lt;/code&gt; to make a new app, the API usually generates a random name for your app. But on these holidays (like Halloween), we’ve changed up the API to return some fun names specific to the day.&lt;/p&gt;

&lt;p&gt;In celebtration of the National Park Service’s 100th anniversary, the Heroku API did something special on July 4th (American Independence Day) this year. Whenever you ran &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;heroku create&lt;/code&gt;, we populated your app’s name with National Park names.&lt;/p&gt;

&lt;p&gt;Well, July 4th has come and gone, but the National Park centenenial continues! So here’s a quick script for you to run locally whenever you want to add a new app.  Just download and run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ruby script.rb&lt;/code&gt; and it’ll create a new Heroku app for you.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;no&quot;&gt;NATIONAL_PARK_ADJECTIVES&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sx&quot;&gt;%w[rugged beautiful scenic peaceful pristine majestic
  tranquil historic serene lovely dazzling alluring marvelous stunning gorgeous]&lt;/span&gt;

&lt;span class=&quot;no&quot;&gt;NATIONAL_PARK_NOUNS&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sx&quot;&gt;%w[acadia arches badlands big-bend biscayne
  gunnison bryce-canyon canyonlands capitol-reef carlsbad-caverns
  channel-islands congaree crater-lake cuyahoga-valley death-valley
  denali-preserve dry-tortugas everglades gates-of-the-arctic glacier-bay
  glacier grand-canyon grand-teton great-basin great-sand-dunes
  great-smoky-mountains guadalupe-mountains haleakala hawaii-volcanoes
  hot-springs isle-royale joshua-tree kenai-fjords kings-canyon kobuk-valley
  lake-clark lassen-volcanic mammoth-cave mesa-verde mount-rainier north-cascades
  olympic petrified-forest redwood rocky-mountain saguaro sequoia shenandoah
  theodore-roosevelt virgin-islands voyageurs wind-cave yellowstone yosemite zion]&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;system&lt;/span&gt; &lt;span class=&quot;sb&quot;&gt;`heroku create &quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;NATIONAL_PARK_ADJECTIVES&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sample&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;sb&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;NATIONAL_PARK_NOUNS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sample&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;sb&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;rand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10000&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;99999&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;sb&quot;&gt;&quot;`&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;A few sample names:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;historic-lake-clark-26105
rugged-joshua-tree-84943
alluring-grand-canyon-67671
majestic-badlands-30198
tranquil-joshua-tree-92886&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Enjoy!!&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Letting Users Add Custom Domains On Heroku</title>
   <link href="https://www.mccartie.com/tech/2016/04/04/letting-users-add-custom-domains-on-heroku.html"/>
   <updated>2016-04-04T11:50:52+00:00</updated>
   <id>https://www.mccartie.com/tech/2016/04/04/letting-users-add-custom-domains-on-heroku</id>
   <content type="html">&lt;p&gt;Consider the following use case: you’d like to run a blogging service on Heroku that allows users to sign up and bring their own custom domain. We’ll call your new idea “Me-dium” (that’s probably not taken).&lt;/p&gt;

&lt;p&gt;So you’ve got &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;www.me-dium.com&lt;/code&gt; setup on Heroku. And your users automatically get a subdomain like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;username.me-dium.com&lt;/code&gt;. But they also want to bring their &lt;em&gt;own&lt;/em&gt; domain and CNAME it to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;username.me-dium.com&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The Heroku Platform requires that any custom domains be registered with their corresponding app. That way, when a request comes into your custom domain, the Heroku router knows where to send the request. This is a pretty simple process when you have 1 or 2 domains, but when you want to give users the ability to add their own domain, you need a way to tell Heroku about these new domains.&lt;/p&gt;

&lt;p&gt;We’ll be adding a simple background worker to interact with the Heroku API. We add a column and form field to the user’s profile for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;domain&lt;/code&gt;. Then, when they change this value, we update the Heroku API with the new value.&lt;/p&gt;

&lt;p&gt;Let’s first start by adding the Heroku API gem to our project.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;gem &lt;span class=&quot;s1&quot;&gt;&apos;platform-api&apos;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Next, we need an API token to use with our app.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;heroku plugins:install git@github.com:heroku/heroku-oauth.git
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;heroku authorizations:create &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Platform API example token&quot;&lt;/span&gt;
Created OAuth authorization.
  ID:          2f01aac0-e9d3-4773-af4e-3e510aa006ca
  Description: Platform API example token
  Scope:       global
  Token:       e7dd6ad7-3c6a-411e-a2be-c9fe52ac7ed2&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Add the token to your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.env&lt;/code&gt; file:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;HEROKU_API_KEY&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;e7dd6ad7-3c6a-411e-a2be-c9fe52ac7ed2&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Let’s set up the worker first. Since this is an external API call, we’ll send this work off to a ActiveJob worker.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;platform-api&apos;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;HerokuDomainJob&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveJob&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Base&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;queue_as&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:default&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;perform&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;domain&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;action&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;heroku&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;PlatformAPI&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;connect_oauth&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;HEROKU_API_KEY&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;begin&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;action&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;add&quot;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;heroku&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;domain&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;MY_HEROKU_APP_NAME&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;hostname&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;domain&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;remove&quot;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;heroku&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;domain&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;delete&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;MY_HEROKU_APP_NAME&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;domain&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;rescue&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Heroku&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;API&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Errors&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;RequestFailed&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;
      &lt;span class=&quot;no&quot;&gt;Rails&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;error&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;[Heroku Domain Worker] ERROR: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;So this one worker will take two params: the domain in question, and what to do with it (add or remove it). We’ll trigger this worker using an ActiveRecord callback. This method utilizes &lt;a href=&quot;https://apidock.com/rails/ActiveRecord/AttributeMethods/Dirty&quot;&gt;dirty tracking&lt;/a&gt; and we expect a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;domain&lt;/code&gt; column on the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;users&lt;/code&gt; table.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;c1&quot;&gt;# app/models/user.rb&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;after_save&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:update_heroku_domains&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;if: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;domain.present?&quot;&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;

&lt;span class=&quot;kp&quot;&gt;private&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;update_heroku_domains&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;domain_changed?&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;domain_was&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;present?&lt;/span&gt;
      &lt;span class=&quot;no&quot;&gt;HerokuDomainJob&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;perform_later&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;domain_was&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;remove&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;HerokuDomainJob&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;perform_later&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;domain&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;add&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;First, we check to see if there was an existing value and that it has been changed (line 9). If so, we send off a worker job to remove the old value (line 10).  Then, we add the new value (line 12).&lt;/p&gt;

&lt;p&gt;That’s it! Your app is now setup to interact with the Heroku API and can easily add/remove domains as needed. Good luck on your new blogging platform!&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Copy Heroku Database To Local</title>
   <link href="https://www.mccartie.com/tech/2016/03/22/copy-heroku-database-to-local.html"/>
   <updated>2016-03-22T14:49:30+00:00</updated>
   <id>https://www.mccartie.com/tech/2016/03/22/copy-heroku-database-to-local</id>
   <content type="html">&lt;p&gt;I’ve had a few Heroku projects where it’s been really nice to copy production data to my local database for development. Obviously, there are a few security concerns with this method (make sure the data in your project &lt;em&gt;should&lt;/em&gt; be on your local machine – don’t do this if you’ve got any sensitive data!).&lt;/p&gt;

&lt;p&gt;If you’re ok with having that data locally, you can add this script at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lib/tasks/database.rake&lt;/code&gt;&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:db&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;desc&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Copy production database to local&quot;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;task&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:copy_production&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:environment&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# Download latest dump&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;system&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;wget -O tmp/latest.dump `heroku pg:backups public-url -q`&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# get user and database name&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;   &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Rails&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;configuration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;database_configuration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;development&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;database&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;database&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;username&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# import&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;system&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;pg_restore --verbose --clean --no-acl --no-owner -h localhost -d &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;database&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Rails&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;root&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/tmp/latest.dump&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;With that in place, you can run the task with&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;rake db:copy_production&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This will grab your latest Postgres backup and save it locally to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tmp/latest.dump&lt;/code&gt;. It then grabs your local DB credentials and performs a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pg_restore&lt;/code&gt; to restore the dump file to your local database.&lt;/p&gt;

&lt;p&gt;Enjoy!&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Modify Your Ruby App's DNS Lookup</title>
   <link href="https://www.mccartie.com/tech/2016/03/14/modify-your-ruby-app's-dns-lookup.html"/>
   <updated>2016-03-14T08:34:50+00:00</updated>
   <id>https://www.mccartie.com/tech/2016/03/14/modify-your-ruby-app's-dns-lookup</id>
   <content type="html">&lt;p&gt;We recently had a Heroku support ticket where a user needed to modify their DNS lookup inside their app:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;We’re connecting to an external API that requires a custom DNS entry. Locally, we modify our /etc/hosts file. How can we do this inside our app?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Modifying your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/hosts&lt;/code&gt; file is a common process for tweaking your local DNS lookup. Your machine will first check this file for any custom entries before proceeding to find the host address on your remote DNS server (usually provided by your ISP).&lt;/p&gt;

&lt;p&gt;However, while modifying the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/hosts&lt;/code&gt; file on a remote server will work as well, this decreases the ability for your app to remain portable. In addition, this may create confusion for other team members that aren’t aware that you’ve modifed this file. Instead, let’s make the change &lt;em&gt;inside&lt;/em&gt; our app using the Ruby standard library.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;resolv.rb&lt;/code&gt; is a pure-Ruby DNS implementation, and it’s actually much better performance-wise when app concurrency grows. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;resolv-replace.rb&lt;/code&gt; part replaces the default Ruby DNS resolution (e.g. system DNS) with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;resolv.rb&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To implement, we first require “resolv-replace”, then create a Resolv::Hosts resolver and a regular DNS resolver. That way, the app will will check the hosts first, then fallback to real DNS.&lt;/p&gt;

&lt;p&gt;Let’s start by adding our entry to a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;custom_hosts&lt;/code&gt; file within our app:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;$ echo 127.0.0.1 my.customserver.com &amp;gt; custom_hosts&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;You can now test this out in an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;irb&lt;/code&gt; session:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;resolv-replace&apos;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;hosts_resolver&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Resolv&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Hosts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;custom_hosts&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;dns_resolver&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Resolv&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;DNS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;

&lt;span class=&quot;no&quot;&gt;Resolv&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;DefaultResolver&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;replace_resolvers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hosts_resolver&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dns_resolver&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;net/http&quot;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;uri&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;You can now test the URL’s and ensure the DNS lookup behaves as necessary:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Net&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;HTTP&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get_response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;URI&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;https://google.com/&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#&amp;lt;Net::HTTPFound 302 Found readbody=true&amp;gt;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Net&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;HTTP&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get_response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;URI&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;https://my.customserver.com/&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;Errno&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ECONNREFUSED&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Failed&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;open&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;TCP&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;connection&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;my&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;customserver&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;com&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;80&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Connection&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;refused&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;connect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;127.0.0.1&quot;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;port&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;80&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;As you can see above, the DNS for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;my.customserver.com&lt;/code&gt; resolves correctly as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;127.0.0.1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We now have custom DNS information within our app, instead of needing to modify your system’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/hosts&lt;/code&gt; file. This allows you to keep this vital information contained within your app and under source control.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;NOTE: using resolv.rb can cause some unwanted side effects like &lt;a href=&quot;https://github.com/mperham/sidekiq/issues/1258&quot;&gt;this one&lt;/a&gt;. Please make sure to test your implementation thoroughly!&lt;/em&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Rails' Tagged Logging + Heroku Dyno Metadata</title>
   <link href="https://www.mccartie.com/tech/2016/01/23/tagged-logging.html"/>
   <updated>2016-01-23T15:33:27+00:00</updated>
   <id>https://www.mccartie.com/tech/2016/01/23/tagged-logging</id>
   <content type="html">&lt;p&gt;Rails added “&lt;a href=&quot;https://gist.github.com/qrush/2513183&quot;&gt;tagged&lt;/a&gt; &lt;a href=&quot;https://wearestac.com/blog/log-tagging-in-rails&quot;&gt;logging&lt;/a&gt;” awhile back, but it’s never seemed like a particularly useful thing to me. I care far more about the log output than a request ID or other data.But the more and more I debug odd edge-cases in other people’s Rails apps, the more I see the need the potential benefits.&lt;/p&gt;

&lt;p&gt;“request_id” may be what most people think of with Rails’ tagged logging.  Adding this to your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;production.rb&lt;/code&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;log_tags&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:uuid&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Gives you a unique ID for each request:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;d50fcd66-97c9-4f9e-9a76-d828e1485a8d]
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;d50fcd66-97c9-4f9e-9a76-d828e1485a8d]
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;d50fcd66-97c9-4f9e-9a76-d828e1485a8d] Started GET &lt;span class=&quot;s2&quot;&gt;&quot;/&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; ::1 at 2016-01-13 09:22:54 &lt;span class=&quot;nt&quot;&gt;-0800&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;d50fcd66-97c9-4f9e-9a76-d828e1485a8d]   ActiveRecord::SchemaMigration Load &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;0.5ms&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;  SELECT &lt;span class=&quot;s2&quot;&gt;&quot;schema_migrations&quot;&lt;/span&gt;.&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; FROM &lt;span class=&quot;s2&quot;&gt;&quot;schema_migrations&quot;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;d50fcd66-97c9-4f9e-9a76-d828e1485a8d] Processing by PostsController#index as HTML
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;d50fcd66-97c9-4f9e-9a76-d828e1485a8d]   Post Load &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;0.6ms&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;  SELECT &lt;span class=&quot;s2&quot;&gt;&quot;posts&quot;&lt;/span&gt;.&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; FROM &lt;span class=&quot;s2&quot;&gt;&quot;posts&quot;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;d50fcd66-97c9-4f9e-9a76-d828e1485a8d]   Rendered posts/index.html.erb within layouts/application &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;20.0ms&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;d50fcd66-97c9-4f9e-9a76-d828e1485a8d] Completed 200 OK &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;283ms &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;Views: 276.8ms | ActiveRecord: 4.4ms&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This can be very handy to use when filtering a noisy log stream. (FYI: logging request ID’s is about to become a &lt;a href=&quot;https://github.com/rails/rails/pull/22949&quot;&gt;default&lt;/a&gt; in Rails)&lt;/p&gt;

&lt;p&gt;But the full power of this feature is that you can shove whatever data you want in there. UUID, server name, or even a Git SHA.&lt;/p&gt;

&lt;p&gt;In a recent &lt;a href=&quot;https://heroku.com&quot;&gt;Heroku&lt;/a&gt; support ticket, a customer mentioned he was using Heroku’s Preboot feature. With &lt;a href=&quot;https://devcenter.heroku.com/articles/preboot&quot;&gt;Preboot&lt;/a&gt;, Heroku will deploy your code to a new dyno, then cut over the requests to it, thus avoiding any request queueing while your app restarts.&lt;/p&gt;

&lt;p&gt;The customer wanted to be able to see which version of his code was serving incoming requests at a particular time. This is a great use-case for Rails’ tagged logging, and Heroku’s new “dyno metadata”.&lt;/p&gt;

&lt;p&gt;“&lt;a href=&quot;https://devcenter.heroku.com/articles/dyno-metadata&quot;&gt;Dyno metadata&lt;/a&gt;” adds a ton of information on your running dyno like: app id, dyno id, release version, and Git SHA. This information is saved as an ENV var for easy reference. Here’s a sampling:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;HEROKU_APP_ID:                   9daa2797-e49b-4624-932f-ec3f9688e3da
HEROKU_APP_NAME:                 example-app
HEROKU_DYNO_ID:                  1vac4117-c29f-4312-521e-ba4d8638c1ac
HEROKU_RELEASE_VERSION:          v42
HEROKU_SLUG_COMMIT:              2c3a0b24069af49b3de35b8e8c26765c1dba9ff0
HEROKU_SLUG_DESCRIPTION:         Deploy 2c3a0b2&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This feature is currently as “Labs” feature, so add it to your app with this command:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;heroku labs:enable runtime-dyno-metadata &lt;span class=&quot;nt&quot;&gt;-a&lt;/span&gt; &amp;lt;app_name&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;You can then pass the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ENV[&quot;HEROKU_SLUG_COMMIT&quot;]&lt;/code&gt; into your Rails’ logging:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;log_tags&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;HEROKU_SLUG_COMMIT&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Now you’ll know exactly which Git version your app is running. Very handy for Preboot or otherwise! (Alternatively, your might choose to use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ENV[&quot;HEROKU_RELEASE_VERSION&quot;]&lt;/code&gt; instead, since the Git SHA length might be bothersome)&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;2016-01-13T17:41:16.857263+00:00 app[web.1]: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;f8ce293494e4fa6634cdb30995bade8817047a7f] Started GET &lt;span class=&quot;s2&quot;&gt;&quot;/&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;70.209.200.115 at 2016-01-13 17:41:16 +0000
2016-01-13T17:41:17.084326+00:00 app[web.1]: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;f8ce293494e4fa6634cdb30995bade8817047a7f]   Post Load &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;6.7ms&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;  SELECT &lt;span class=&quot;s2&quot;&gt;&quot;posts&quot;&lt;/span&gt;.&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; FROM &lt;span class=&quot;s2&quot;&gt;&quot;posts&quot;&lt;/span&gt;
2016-01-13T17:41:16.984597+00:00 app[web.1]: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;f8ce293494e4fa6634cdb30995bade8817047a7f] Processing by PostsController#index as HTML
2016-01-13T17:41:17.119678+00:00 app[web.1]: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;f8ce293494e4fa6634cdb30995bade8817047a7f]   Rendered posts/index.html.erb within layouts/application &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;108.8ms&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
2016-01-13T17:41:17.146112+00:00 app[web.1]: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;f8ce293494e4fa6634cdb30995bade8817047a7f] Completed 200 OK &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;161ms &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;Views: 140.3ms | ActiveRecord: 16.5ms&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Here’s an example of one of my Heroku apps cutting over from one version of my code to another.  Note the change from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;adc997c&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;7be3dfd&lt;/code&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;2016-01-13T17:53:42.704148+00:00 app[web.1]: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;adc997cad5e8941b1a085ae3875e2370824cffb6] Started HEAD &lt;span class=&quot;s2&quot;&gt;&quot;/&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;70.209.200.115 at 2016-01-13 17:53:42 +0000
2016-01-13T17:53:42.711699+00:00 app[web.1]: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;adc997cad5e8941b1a085ae3875e2370824cffb6]   Post Load &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;0.9ms&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;  SELECT &lt;span class=&quot;s2&quot;&gt;&quot;posts&quot;&lt;/span&gt;.&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; FROM &lt;span class=&quot;s2&quot;&gt;&quot;posts&quot;&lt;/span&gt;
2016-01-13T17:53:42.713734+00:00 app[web.1]: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;adc997cad5e8941b1a085ae3875e2370824cffb6] Completed 200 OK &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;5ms &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;Views: 3.0ms | ActiveRecord: 0.9ms&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
2016-01-13T17:53:42.708938+00:00 app[web.1]: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;adc997cad5e8941b1a085ae3875e2370824cffb6] Processing by PostsController#index as &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;/&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;
2016-01-13T17:53:42.712343+00:00 app[web.1]: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;adc997cad5e8941b1a085ae3875e2370824cffb6]   Rendered posts/index.html.erb within layouts/application &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;2.1ms&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
2016-01-13T17:53:45.379444+00:00 app[web.1]: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;7be3dfd2f62e3a876d74212be6a7c9dab8128f1d] Started HEAD &lt;span class=&quot;s2&quot;&gt;&quot;/&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;70.209.200.115 at 2016-01-13 17:53:45 +0000
2016-01-13T17:53:45.477517+00:00 app[web.1]: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;7be3dfd2f62e3a876d74212be6a7c9dab8128f1d]   Post Load &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;3.0ms&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;  SELECT &lt;span class=&quot;s2&quot;&gt;&quot;posts&quot;&lt;/span&gt;.&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; FROM &lt;span class=&quot;s2&quot;&gt;&quot;posts&quot;&lt;/span&gt;
2016-01-13T17:53:45.495110+00:00 app[web.1]: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;7be3dfd2f62e3a876d74212be6a7c9dab8128f1d] Completed 200 OK &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;43ms &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;Views: 29.6ms | ActiveRecord: 7.3ms&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
2016-01-13T17:53:45.498012+00:00 heroku[router]: &lt;span class=&quot;nv&quot;&gt;at&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;info &lt;span class=&quot;nv&quot;&gt;method&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;HEAD &lt;span class=&quot;nv&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;/&quot;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;host&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;whispering-eyrie-6721.herokuapp.com &lt;span class=&quot;nv&quot;&gt;request_id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;11b40ae8-2cf4-4915-89af-0cecad83005a &lt;span class=&quot;nv&quot;&gt;fwd&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;70.209.200.115&quot;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;dyno&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;web.1 &lt;span class=&quot;nv&quot;&gt;connect&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1ms &lt;span class=&quot;nv&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;123ms &lt;span class=&quot;nv&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;200 &lt;span class=&quot;nv&quot;&gt;bytes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;684
2016-01-13T17:53:45.452038+00:00 app[web.1]: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;7be3dfd2f62e3a876d74212be6a7c9dab8128f1d] Processing by PostsController#index as &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;/&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;
2016-01-13T17:53:45.492969+00:00 app[web.1]: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;7be3dfd2f62e3a876d74212be6a7c9dab8128f1d]   Rendered posts/index.html.erb within layouts/application &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;31.9ms&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
2016-01-13T17:53:48.316089+00:00 heroku[router]: &lt;span class=&quot;nv&quot;&gt;at&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;info &lt;span class=&quot;nv&quot;&gt;method&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;HEAD &lt;span class=&quot;nv&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;/&quot;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;host&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;whispering-eyrie-6721.herokuapp.com &lt;span class=&quot;nv&quot;&gt;request_id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;3f08fd7d-4b40-4c3f-8f25-3f245a1e3188 &lt;span class=&quot;nv&quot;&gt;fwd&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;70.209.200.115&quot;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;dyno&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;web.1 &lt;span class=&quot;nv&quot;&gt;connect&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;3ms &lt;span class=&quot;nv&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;9ms &lt;span class=&quot;nv&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;200 &lt;span class=&quot;nv&quot;&gt;bytes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;684
2016-01-13T17:53:48.309340+00:00 app[web.1]: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;7be3dfd2f62e3a876d74212be6a7c9dab8128f1d] Started HEAD &lt;span class=&quot;s2&quot;&gt;&quot;/&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;70.209.200.115 at 2016-01-13 17:53:48 +0000
2016-01-13T17:53:48.315024+00:00 app[web.1]: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;7be3dfd2f62e3a876d74212be6a7c9dab8128f1d]   Post Load &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;1.7ms&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;  SELECT &lt;span class=&quot;s2&quot;&gt;&quot;posts&quot;&lt;/span&gt;.&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; FROM &lt;span class=&quot;s2&quot;&gt;&quot;posts&quot;&lt;/span&gt;
2016-01-13T17:53:48.316491+00:00 app[web.1]: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;7be3dfd2f62e3a876d74212be6a7c9dab8128f1d] Completed 200 OK &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;4ms &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;Views: 2.2ms | ActiveRecord: 1.7ms&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
2016-01-13T17:53:48.312092+00:00 app[web.1]: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;7be3dfd2f62e3a876d74212be6a7c9dab8128f1d] Processing by PostsController#index as &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;/&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;
2016-01-13T17:53:48.315465+00:00 app[web.1]: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;7be3dfd2f62e3a876d74212be6a7c9dab8128f1d]   Rendered posts/index.html.erb within layouts/application &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;2.7ms&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Tagged Logging is an incredibly powerful feature in Rails that gets very little attention. Here’s a few other articles I found that give it a more in-depth explanation.  Enjoy!&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.justinweiss.com/articles/keeping-your-logs-from-becoming-an-unreadable-mess/&quot;&gt;https://www.justinweiss.com/articles/keeping-your-logs-from-becoming-an-unreadable-mess/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://gist.github.com/qrush/2513183&quot;&gt;https://gist.github.com/qrush/2513183&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://wearestac.com/blog/log-tagging-in-rails&quot;&gt;https://wearestac.com/blog/log-tagging-in-rails&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
 </entry>
 
 <entry>
   <title>Removing Distractions For Your Team</title>
   <link href="https://www.mccartie.com/blog/2015/11/24/removing-distractions-for-your-team.html"/>
   <updated>2015-11-24T10:48:11+00:00</updated>
   <id>https://www.mccartie.com/blog/2015/11/24/removing-distractions-for-your-team</id>
   <content type="html">&lt;p&gt;I’ve known far too many people who reach the coveted level of “middle management” and believe they’ve finally reached Easy Street. “Time to sit back and let the minions do my bidding!” Ok, I’m sure that’s not exactly verbalized, but delegation is easily abused by middle managers who think their primary job is to ensure everyone is typing away happily all day. (these are all the same managers you find tapping their watch when you come in too late or leave too early)&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/office-space-lumbergh.jpg&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;On the contrary, great managers accept the responsibility of what it is to lead. And great leaders don’t bark out orders and head out for a long lunch break. Great managers help their team by &lt;em&gt;serving&lt;/em&gt; them.&lt;/p&gt;

&lt;p&gt;The thing to remember here is that moving into management usually means you’re no longer a direct producer. Your coding/designing/selling skills are usually taking a back seat to your newly found management skills. So you must realize that your &lt;em&gt;team&lt;/em&gt; is now the only ones capable of producing a win. The prevailing thought cannot be “Who can I get to do this work for me?” but instead should be “My job is to make sure my team members can succeed.”&lt;/p&gt;

&lt;p&gt;The best way to make sure your team succeeds is to ensure they’re productive. So you must keep an eye out for things that destroy productivity: useless meetings, directional ambiguity, and interruptions.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Useless Meetings.&lt;/strong&gt; For some reason, middle managers seem to think that creating and attending meetings is their gift to the team. And yet, there are countless studies that prove that meetings destroy productivity. Yes, there are some meetings that &lt;em&gt;must&lt;/em&gt; be had, but please stop thinking that everything must be done synchronously. Most times, the discussion you’re wanting to have can be done over email.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Directional Ambiguity.&lt;/strong&gt; When the team doesn’t know what’s expected or where they’re headed, they will attempt to fill in the gap. This leads to team members going “off the reservation”, building features that aren’t needed, and needing to &lt;em&gt;re-build&lt;/em&gt; things because someone up the chain has changed their mind (again). Make a plan. Work the plan. Remind your team what the plan is and do it frequently.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Interruptions.&lt;/strong&gt; There’s no bigger productivity killer than a distracting work environment. Most people think music in the office is the main offender, but sometimes the worst distraction is you. You walk around the office during peak productivity times, stopping by desks to smile and shake hands … to “see what you’re working on”. And your team will stop what they’re doing, take off their headphones, and engage. But you’ve just cost them their focus. Instead, leave “check-ins” for stand-ups or 1-on-1’s. Say hello in the morning while getting coffee, but fight the urge to constantly interrupt your team throughout the day.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Like a good Scrum Master, a great manager excels at removing obstacles for their team. Cut through the red tape, get the answers your team needs, and do your very best to provide a distraction-free work environment. Your team will thank you.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Measure, Release, Measure Again</title>
   <link href="https://www.mccartie.com/tech/2015/11/03/measure-release-measure-again.html"/>
   <updated>2015-11-03T13:16:15+00:00</updated>
   <id>https://www.mccartie.com/tech/2015/11/03/measure-release-measure-again</id>
   <content type="html">&lt;p&gt;In the world of product development, there is far too little mentioned about the need and use of data in decision making. We’re easily pleased with ideas that &lt;em&gt;seem&lt;/em&gt; logical to us, but may be terrible for the users of our product. We add new features, but we never check to see if the investment we made (in resources and hours spent) have actually paid off.&lt;/p&gt;

&lt;p&gt;Instead of assuming, let’s take a look at how data can be easily gathered and utilized to inform our decision making process.&lt;/p&gt;

&lt;h2 id=&quot;the-problem&quot;&gt;The Problem&lt;/h2&gt;

&lt;p&gt;“Why aren’t we gaining more users?” someone asked at our monthly meeting with management. “Do we need to spend more money on marketing?”&lt;/p&gt;

&lt;p&gt;A simple “yeah, sure” would have been easier for me. But blowing tons of money on marketing usually masks other issues. Sure, it has its place, but why invest that kind of money if another issue is the root of the problem.&lt;/p&gt;

&lt;p&gt;“I don’t know,” I said. (&lt;a href=&quot;https://freakonomics.com/2014/05/15/the-three-hardest-words-in-the-english-language-a-new-freakonomics-radio-podcast/&quot;&gt;the 3 hardest words in the English language&lt;/a&gt;) But I said it with confidence. I honestly had not yet considered this question and knew I didn’t have data one way or the other. But I assured management I would spend the week trying to see if we had a leaky funnel somewhere – were users not coming to the site? Or were they coming but not signing up?&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.google.com/analytics/&quot;&gt;Google Analytics&lt;/a&gt; was an easy tool to reach for here. I spent some time digging through user patterns – seeing where they went after hitting the homepage. After only about an hour, I found the problem: a 65% conversion rate on our signup page. I knew we could do better.&lt;/p&gt;

&lt;h2 id=&quot;hypothesis&quot;&gt;Hypothesis&lt;/h2&gt;

&lt;p&gt;Now that we had a baseline set of data, we could begin to experiment. The first thought from our team was that the form was too long. We had our entire terms of service document as an iframe that was visible before clicking the submit button.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/signup-control.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;“Let’s just chop off that entire section and place a link to our TOS instead.” But we had another issue at hand – we wanted to capture more information about the user on sign up. So for our first test, we decided to split the difference: we added a few more fields, but we completely removed the TOS iframe.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/signup-no-terms.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I certainly had my doubts about this theory. But that’s just it, they were &lt;em&gt;my&lt;/em&gt; doubts. It’s easy to sit in a meeting and argue about subjectivity. Making product decisions from the gut can be a great move, but you have to remember that it’s impossible for you to fully and accurately represent your entire user base.&lt;/p&gt;

&lt;p&gt;After a week, we checked the numbers. Our conversion rate had dropped from 65% to 57%. The addition of the extra fields were scaring off more people than the TOS iframe had been. So we implemented another change: a simpler form to validate the theory that our original form was too intimidating. Our form needed to say: “Just give us the bare mimimum and get in here!” So we pulled it all back and had the form only ask the bare minimum to use the site successfully.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/signup-final.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Lo and behold, our numbers jumped to 71% the very next week. We continued for 2 more weeks to make changes that eeked out another 4% to get us to a respectable 75% conversion. The need for the extra user info was still there – we still wanted to capture that data somehow. But we all agreed that increasing our conversion was more critical than getting that extra data at this step.&lt;/p&gt;

&lt;h2 id=&quot;next-steps&quot;&gt;Next Steps&lt;/h2&gt;

&lt;p&gt;After seeing how easy it was to perform these tests, we feel in love with &lt;a href=&quot;https://www.optimizely.com/ab-testing/&quot;&gt;Optimizely&lt;/a&gt; – a great tool made for A/B testing. We looked for other ways to make small but meaningful changes to our marketing sites: smaller headers, larger headers, different copy in the navbar, different colored buttons. All of these were small changes to make, but we saw huge improvements in our metrics.&lt;/p&gt;

&lt;p&gt;For you, it may not be a marketing homepage. Acquisition may not be the target – maybe it’s retention. So where’s the drop-off? Why are customers leaving? What pain points might your users be having that they’re not telling you about, but your data can reveal to you?  Use the data you have – or create more through logging – but make your data work for you. Invest your time and money in features that you know will pay off; not just ones that “feel” right, but ones backed by solid data.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Delegation in Rails</title>
   <link href="https://www.mccartie.com/tech/2015/10/27/delegation-in-rails.html"/>
   <updated>2015-10-27T16:22:19+00:00</updated>
   <id>https://www.mccartie.com/tech/2015/10/27/delegation-in-rails</id>
   <content type="html">&lt;p&gt;After spending a year or two in Rails, I started to plateau a bit. I knew my code wasn’t clean, but didn’t know how to make it better. I began to ask more-senior Rails programmers about thoughts and ideas that helped them improve.&lt;/p&gt;

&lt;p&gt;One of the more influential lessons I learned was about the Law of Demeter (and loose coupling). Let’s start with defining what it is, then anti-patterns, and an example on how Rails makes it easier to avoid some costly technical debt.&lt;/p&gt;

&lt;h2 id=&quot;law-of-demeter&quot;&gt;Law of Demeter&lt;/h2&gt;

&lt;p&gt;Here’s the best definition I’ve found for the Law of Demeter: “Principle of Least Knowledge: Only talk to your friends”&lt;/p&gt;

&lt;p&gt;Basically, we want our models to have very little knowledge of the models it interacts with. “Loose coupling” means we can swap out models and/or make changes without requiring a complete re-write. You’ll see a bit later what can happen when your models are tightly coupled, but for now, let’s look at this &lt;em&gt;bad&lt;/em&gt; example.&lt;/p&gt;

&lt;h2 id=&quot;example-anti-pattern&quot;&gt;Example Anti-Pattern&lt;/h2&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;vi&quot;&gt;@post&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Post&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
&lt;span class=&quot;vi&quot;&gt;@post_date&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@post&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;posted_on&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;In this (contrived) example, we’re wanting to get the date for a blog post. We have a relationship between post and a body, like this:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Post&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveRecord&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Base&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;has_one&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:body&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Body&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveRecord&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Base&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;belongs_to&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:post&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;So the Post’s body has the date we want, but we have to hop through Body to get there. This isn’t bad &lt;em&gt;now&lt;/em&gt;, but will get bad once we try to refactor this. This is an example of tight coupling.&lt;/p&gt;

&lt;p&gt;Ever seen (or written) something like this?&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;vi&quot;&gt;@address&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@invoice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;preferences&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;billing_address&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;We’re taking the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@invoice&lt;/code&gt; we have, but we’re storing an address on the client, perhaps in a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;preferences&lt;/code&gt; hash. If we ever want to tweak anything in the middle, we’re gonna have a bad time.&lt;/p&gt;

&lt;h2 id=&quot;delegate&quot;&gt;Delegate&lt;/h2&gt;

&lt;p&gt;Rails (via ActiveSupport) has a &lt;a href=&quot;https://apidock.com/rails/Module/delegate&quot;&gt;great little helper to solve our problem&lt;/a&gt;: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;delegate&lt;/code&gt;.  Let’s see how we fix the “post” example above:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Post&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveRecord&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Base&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;has_one&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:body&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;delegate&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:posted_on&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;to: :body&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;That’s it! Now we can call &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;posted_on&lt;/code&gt; directly on our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@post&lt;/code&gt; instance. Sprinkle &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@post.posted_on&lt;/code&gt; wherever you need it, and if ever the delegation changes, just change the implementation in one spot. Much better than a massive find/replace on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@post.body.posted_on&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Here’s how we solve the other example:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Invoice&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveRecord&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Base&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;belongs_to&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:client&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;delegate&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:billing_address&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;to: :client&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Client&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveRecord&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Base&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;has_many&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:invoices&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;billing_address&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;preferences&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:billing_address&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Now we can call &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@invoice.billing_address&lt;/code&gt; and Rails will handle the details.&lt;/p&gt;

&lt;h2 id=&quot;thinking-ahead&quot;&gt;Thinking Ahead&lt;/h2&gt;

&lt;p&gt;So how do you know when to implement &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;delegate&lt;/code&gt; in your own project? What’s the “code smell”?&lt;/p&gt;

&lt;p&gt;The best way to catch it in the act it to be on the look out for multiple “dots”. If your object needs to reach across more than one other object to get its data, you’re probably violating the Law of Demeter. Instead, start by writing the method as you want it to be without worrying about implementation first.&lt;/p&gt;

&lt;p&gt;For instance, if you wrote &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@post.body.posted_on&lt;/code&gt;, an alarm should go off in your head: “Why am I asking another object for data that makes perfect sense for me to know myself?” Instead, write &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@post.posted_on&lt;/code&gt;, then move down to the model layer to implement.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@invoice.client.preferences...&lt;/code&gt; “OH LAWD! No good.” &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@invoice.billing_address&lt;/code&gt; – “Ahhhh. Much better.”&lt;/p&gt;

&lt;p&gt;The more you get in the habbit of smelling out bad code like this, the faster you’ll get at implementing them right the first time. Staying ahead of technical debt like this will save you hours (sometimes days) of headache in the future.&lt;/p&gt;

&lt;p&gt;Happy delegating!&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Default UUID's In Rails</title>
   <link href="https://www.mccartie.com/tech/2015/10/20/default-uuid's-in-rails.html"/>
   <updated>2015-10-20T17:27:56+00:00</updated>
   <id>https://www.mccartie.com/tech/2015/10/20/default-uuid's-in-rails</id>
   <content type="html">&lt;p&gt;🎉 I added a new feature to Rails: Default to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uuid&lt;/code&gt; as primary key when generating database migrations. Enjoy!&lt;/p&gt;

&lt;h2 id=&quot;uuids&quot;&gt;UUID’s&lt;/h2&gt;

&lt;p&gt;I love UUID’s. &lt;a href=&quot;https://blog.joevandyk.com/2013/08/14/uuids-as-keys/&quot;&gt;You&lt;/a&gt; &lt;a href=&quot;https://blog.starkandwayne.com/2015/05/23/uuid-primary-keys-in-postgresql/&quot;&gt;should&lt;/a&gt; &lt;a href=&quot;https://www.percona.com/blog/2007/03/13/to-uuid-or-not-to-uuid/&quot;&gt;use&lt;/a&gt; &lt;a href=&quot;https://krow.livejournal.com/497839.html&quot;&gt;them&lt;/a&gt;. Rails 4 makes it simple to get setup and use UUID’s throughout your project.&lt;/p&gt;

&lt;p&gt;First, we enable UUID’s:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;rails g migration enable_uuid_extension&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This creates the following migration:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;EnableUuidExtension&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveRecord&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Migration&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;change&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;enable_extension&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;pgcrypto&apos;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Next, create a model&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;rails g model Book title&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;the-problem&quot;&gt;The Problem&lt;/h2&gt;

&lt;p&gt;Unfortunately, this creates an integer-based primary key migration:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CreateBooks&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveRecord&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Migration&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;change&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;create_table&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:books&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:title&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;timestamps&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;To move forward, you must fiddle with that migration to add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;id: :uuid&lt;/code&gt; to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;create_table&lt;/code&gt; method:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CreateBooks&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveRecord&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Migration&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;change&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;create_table&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:books&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;id: :uuid&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:title&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;timestamps&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;After spending a few weeks on a new project doing this, I figured we could make a change to Rails to allow this to happen.  Here’s how.&lt;/p&gt;

&lt;h2 id=&quot;the-solution&quot;&gt;The Solution&lt;/h2&gt;

&lt;p&gt;I recently had a change merged into Rails you should know about. Here’s a look at &lt;a href=&quot;https://github.com/rails/rails/commit/fb42c492a7eff58f45867ea50440d938648cdb48&quot;&gt;the original commit&lt;/a&gt;, then a &lt;a href=&quot;https://github.com/rails/rails/compare/f94e328cf801...5a47639aac45&quot;&gt;follow-up&lt;/a&gt; to make a few minor modifications.&lt;/p&gt;

&lt;p&gt;In &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;application.rb&lt;/code&gt;, simply make the following addition:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;generators&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;g&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;g&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;orm&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:active_record&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;primary_key_type: :uuid&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Now, whenever you generate a migration, we’ll tag on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;id: :uuid&lt;/code&gt; to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;create_table&lt;/code&gt; method.&lt;/p&gt;

&lt;h2 id=&quot;warning&quot;&gt;Warning&lt;/h2&gt;

&lt;p&gt;You &lt;em&gt;must&lt;/em&gt; have already added a UUID extension to your database. If not, running this migration will fail. So don’t forget the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rails g migration enable_uuid_extension&lt;/code&gt; migration up front.&lt;/p&gt;

&lt;p&gt;Enjoy!&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>SMS Verification With Rails</title>
   <link href="https://www.mccartie.com/tech/2015/10/20/sms-verification.html"/>
   <updated>2015-10-20T14:27:56+00:00</updated>
   <id>https://www.mccartie.com/tech/2015/10/20/sms-verification</id>
   <content type="html">&lt;p&gt;While working recently on a side project, I came across the task of “SMS validation”. The project allows users to sign up with their mobile phone number, and have certain text messages sent to their phone on a schedule they determine. Here’s how the feature request came in:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;As a user, I need my phone number verified before being allowed to send out SMS messages to myself&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;database&quot;&gt;Database&lt;/h2&gt;

&lt;p&gt;First off, we need to figure out what part in our sign-up process this shows up in. We don’t want the user to be able to create any SMS jobs without making sure their phone number is a) valid, and b) their own. So let’s do two things:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Make sure the user is marked as “unverified” until they confirm their number&lt;/li&gt;
  &lt;li&gt;Ensure they are not able to leave the sign up process and return to subvert this security check&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So we start with the basics: the database. We know we’ll want the “verified” boolean, but we’ll also want two other bits of data: storing the pin andwhen was the pin sent at (so we disallow old pins from being used).&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;rails generate migration add_verified_to_users pin:integer pin_sent_at:datetime verified:boolean&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Then we modify the resulting migration to set the default for “verified”:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AddPinAndVerifiedToUsers&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveRecord&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Migration&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;change&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;add_column&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:users&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:pin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:integer&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;add_column&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:users&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:pin_sent_at&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:datetime&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;add_column&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:users&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:verified&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:boolean&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;default: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;controller&quot;&gt;Controller&lt;/h2&gt;

&lt;p&gt;Now that we have our data fields, let’s make sure our controllers route the user to the correct place based on whether or not they’ve been verified. First, let’s look at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;applciation_controller.rb&lt;/code&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;n&quot;&gt;before_filter&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:redirect_if_unverified&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;redirect_if_unverified&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logged_in?&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;current_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;verified?&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;redirect_to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;new_verify_url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;notice: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Please verify your phone number&quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;For boolean fields, Rails provides a handy &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;?&lt;/code&gt; method on the column name. So if you’re boolean was &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;awesome&lt;/code&gt;, you now have &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;user.awesome?&lt;/code&gt; availabe to you.  So we tap into that and check to see if the user has yet been verified. If not, we redirect them to our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;new_verify_url&lt;/code&gt;.  Here’s the routing for that:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;/verify&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;verify#new&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;as: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;new_verify&apos;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;put&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;/verify&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;verify#update&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;as: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;verify&apos;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;post&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;/verify&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;verify#create&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;as: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;resend_verify&apos;&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;&lt;em&gt;NOTE: you can create similar routing with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;resource&lt;/code&gt;, but I wanted to name the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;POST&lt;/code&gt; method as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;resend_verify&lt;/code&gt;. We’ll see that next.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We’re still missing two things at the controller level:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Making sure we visit &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;new_verify_url&lt;/code&gt; after sign up, and&lt;/li&gt;
  &lt;li&gt;The verification form&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here’s the basics of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;users_controller.rb&lt;/code&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;create&lt;/span&gt;
  &lt;span class=&quot;vi&quot;&gt;@user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user_params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;save&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;auto_login&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;vi&quot;&gt;@user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;redirect_to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;new_verify_url&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;render&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:new&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;We’re simply changing the redirect after user creation from something like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;redirect_to root_url&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;redirect_to new_verify_url&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Next, we create a separate controller for verification. It’s temping here to shove all of this logic into &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;users_controller.rb&lt;/code&gt;, but resist the urge. The behavior of verification is its own thing, and as such, belongs in its own controller (single responsibility principle).&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;VerifyController&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ApplicationController&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;skip_before_filter&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:redirect_if_unverified&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# GET /verify&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# PUT /verify&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;update&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;now&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;current_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;pin_sent_at&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;advance&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;minutes: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;60&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;flash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:alert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Your pin has expired. Please request another.&quot;&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;render&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;elsif&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:pin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:to_i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;current_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;pin&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;current_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;update_attribute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:verified&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;redirect_to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;root_url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;notice: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Your phone number has been verified!&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;flash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:alert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;The code you entered is invalid.&quot;&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;render&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:new&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# POST /verify&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;create&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;current_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;send_pin!&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;redirect_to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;new_verify_url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;notice: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;A PIN number has been sent to your phone&quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;First off, we make sure we skip our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;redirect_if_unverified&lt;/code&gt; filter or else we’ll end up in a redirect loop. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;def new&lt;/code&gt; is the basics of our form. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;def update&lt;/code&gt; is the business.&lt;/p&gt;

&lt;p&gt;In this method, we’re checking to make sure that the user’s pin was sent less than an hour ago. We return early from this method since this is a non-starter for us. Next, we check to see if the pin exists (using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;try&lt;/code&gt;) and that it matches the user’s pin. If not, we return an error immediately.&lt;/p&gt;

&lt;p&gt;You could probably rework this method a few different ways, but I’m happy with how readable it is. Having the last &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;else&lt;/code&gt; block also allows for a clean “fall-through” in case we add other validation checks above it.&lt;/p&gt;

&lt;p&gt;Finally, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;def create&lt;/code&gt; will be used for a “Send me another PIN” link in the view:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;sx&quot;&gt;%= link_to &apos;Resend code&apos;, resend_verify_path, method: :post %&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;model&quot;&gt;Model&lt;/h2&gt;

&lt;p&gt;For the User model, we want to make sure we cover the following:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Set the PIN upon user creation&lt;/li&gt;
  &lt;li&gt;Create a method where the PIN can be reset&lt;/li&gt;
  &lt;li&gt;Fire off a background job to send out the SMS to the user&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For number 1, let’s use ActiveRecor’s callbacks to fire a method when we create a user:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;n&quot;&gt;after_save&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:send_pin!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;if: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;phone_number_changed?&quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Next, let’s create two helper methods and the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;send_pin!&lt;/code&gt; method.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;reset_pin!&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;update_column&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:pin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;rand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;9999&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;unverify!&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;update_column&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:verified&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;send_pin!&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;reset_pin!&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;unverify!&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;SendPinJob&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;perform_later&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Whenever we send the PIN, we need to make sure we update the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pin&lt;/code&gt; column and reset the user to be unverified. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;send_pin!&lt;/code&gt; relies the other two helper methods to do these tasks, then enqueues a background job to send out the SMS message.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;“You really shouldn’t enqueue a background job with an object. Use an ID.”&lt;/em&gt;  Meh. Rails has a great new feature called “Global ID” which allows you to enqueue jobs with the ActiveRecord object itself.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;“But what about &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pin_sent_at&lt;/code&gt;?”&lt;/em&gt;  Well, let’s let our upcoming background job handle this since we don’t want the model setting this in case there’s a delay in &lt;em&gt;actually&lt;/em&gt; sending out the job.&lt;/p&gt;

&lt;h2 id=&quot;background-job&quot;&gt;Background Job&lt;/h2&gt;

&lt;p&gt;Easy peasy.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SendPinJob&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveJob&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Base&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;perform&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;nexmo&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Nexmo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;key: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;NEXMO_KEY&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;secret: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;NEXMO_SECRET&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;resp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nexmo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;send_2fa_message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;to: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;phone_number&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;pin: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;pin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;touch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:pin_sent_at&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;We’re using &lt;a href=&quot;https://nexmo.com&quot;&gt;Nexmo&lt;/a&gt; here, but regardless of the SMS provider, the basics are the same: send the message and update &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pin_sent_at&lt;/code&gt;. Rails’ &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;touch&lt;/code&gt; method is a good fit here.&lt;/p&gt;

&lt;p&gt;Note that I’m assiging the response from the Nexmo API as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;resp&lt;/code&gt; but not doing anything with it yet. One of my next TODO’s is to only touch &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pin_sent_at&lt;/code&gt; if the response is successful.&lt;/p&gt;

&lt;h2 id=&quot;re-send-sms&quot;&gt;Re-Send SMS&lt;/h2&gt;

&lt;p&gt;So what happens if the user doesn’t get their PIN or if they’re too late in entering it? No problem! As seen earlier, the “Resend Code” link makes a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;POST&lt;/code&gt; to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;verify_controller.rb&lt;/code&gt;, which re-uses our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;send_pin!&lt;/code&gt; method:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;create&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;current_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;send_pin!&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;redirect_to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;new_verify_url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;notice: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;A PIN number has been sent to your phone&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This will then ensure the user is unverified and is sent a new pin.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Overall, I really enjoyed working on this task. It took awhile to make sure we covered all our use-cases (user leaving mid-verification, expired PIN, re-send, etc), but the process is seamless now. From a security standpoint, creating a 4-digit pin is not the most secure thing here. However, the user must already be logged in to verify their phone, so we’re relying on our session security already. Furthermore, we’re just checking to make sure the phone number was entered correctly so that we’re not sending out messages to bad numbers.&lt;/p&gt;

&lt;p&gt;I hope you enjoyed this exercise. Have you ever implemented something similar? See any changes I could make in the samples above? Let me know!&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>User Time Zone On Signup</title>
   <link href="https://www.mccartie.com/tech/2014/10/22/user-time-zone-on-signup.html"/>
   <updated>2014-10-22T00:00:00+00:00</updated>
   <id>https://www.mccartie.com/tech/2014/10/22/user-time-zone-on-signup</id>
   <content type="html">&lt;p&gt;Knowing a user’s time zone can be incredibly important to your application. At &lt;a href=&quot;https://www.sproutmark.com&quot;&gt;SproutMark&lt;/a&gt; we utilize this information in numerous places to enhance the user’s experience.&lt;/p&gt;

&lt;p&gt;But it’s difficult to know when to ask for it. If you ask during sign up, you expand the signup form an can reduce your signup’s because of the increased barrier to entry. If you wait until after sign up, you create an experience for the user that’s less than ideal.&lt;/p&gt;

&lt;p&gt;Instead, let’s determine the user’s time zone based on their IP. We can set a sane default for the user and then allow them to change it.  Let’s take a look at how we can do that with rails.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(I’ll be taking you through a pure Ruby implementation first … then the final JS-only version at the end)&lt;/em&gt;&lt;/p&gt;

&lt;h2 id=&quot;the-guts&quot;&gt;The Guts&lt;/h2&gt;

&lt;p&gt;We’re going to use a combo of two gem’s to determine a user’s time zone.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# Gemfile&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;gem&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;geocoder&apos;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;gem&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;timezone&apos;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;We’ll use the &lt;a href=&quot;https://github.com/alexreisner/geocoder&quot;&gt;Geocoder&lt;/a&gt; gem to turn our user’s IP into coordinates. Now Geocoder is awesome, but it doesn’t give us any time zone data.  So we’ll then need to pass these lat/long coordinates to something else to get the time zone.  Enter the &lt;a href=&quot;https://github.com/panthomakos/timezone&quot;&gt;Time Zone&lt;/a&gt; gem. (Clever name, eh?)&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;SignupHelper&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;time_zone_from_ip&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;begin&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;ip_address&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Rails&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;production?&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ip&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;108.245.160.41&quot;&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# In development, our IP will be 127.0.0.1 ... not useful&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;geocode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Geocoder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;search&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ip_address&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;first&lt;/span&gt;
      &lt;span class=&quot;no&quot;&gt;Timezone&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Zone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;latlon: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;geocode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;latitude&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;geocode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;longitude&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;active_support_time_zone&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;rescue&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;
      &lt;span class=&quot;no&quot;&gt;Rails&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;error&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;ERROR WITH GEOCODING: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&quot;UTC&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;…and the view…&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-haml&quot; data-lang=&quot;haml&quot;&gt;&lt;span class=&quot;nc&quot;&gt;.form-group&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;label&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:time_zone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Time Zone&quot;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;collection_select&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:time_zone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveSupport&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;TimeZone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;all&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:to_s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;selected: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;time_zone&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;time_zone_from_ip&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;class: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;chosen-select&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;In short, we’re defaulting to the object’s value (nil by default, or set in case this form was submitted with other errors). If the value is nil, we’re calling our helper to determine the user’s time zone.&lt;/p&gt;

&lt;h2 id=&quot;hide-the-dropdown&quot;&gt;Hide The Dropdown&lt;/h2&gt;

&lt;p&gt;This is pretty slick, but I’m not too happy with how this form appears to the user. Even though we’re setting their default and the user can easily skip this select box, it’s mentally taxing to the user to see a form field and process whether or not they need to change the value.  How can we show the default, but still allow the user to change it?  Let’s make it a link that toggles the select box.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-haml&quot; data-lang=&quot;haml&quot;&gt;&lt;span class=&quot;nc&quot;&gt;.form-group&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;label&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:time_zone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Time Zone&quot;&lt;/span&gt;

  p= link_to f.object.time_zone || &quot;UTC&quot;, &quot;javascript:;&quot;, data: { &quot;time-zone&quot; =&amp;gt; &quot;toggle&quot; }

  &lt;span class=&quot;nc&quot;&gt;.hide&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;collection_select&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:time_zone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveSupport&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;TimeZone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;all&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:to_s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;selected: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;time_zone&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;class: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;chosen-select&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Now for the JS. When a user clicks the link, we’ll hide it and then display the select box.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-coffeescript&quot; data-lang=&quot;coffeescript&quot;&gt;&lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;a[data-time-zone=&apos;toggle&apos;]&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;click&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;hide&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;parent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;.hide&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;removeClass&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;hide&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;.chosen-container&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;css&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;width&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;340px&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;non-blocking&quot;&gt;Non-Blocking&lt;/h2&gt;

&lt;p&gt;One more problem we’ve got: a blocking HTTP request inside our signup form. In order to have the best chance at converting a visitor, we need this page to load FAST. And waiting for this IP check is hurting us.&lt;/p&gt;

&lt;p&gt;Instead of making the form wait on this request, we can background it by loading the form with sane defaults, then making another AJAX request back to the server for the time zone info.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# SignupController.rb&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;determine_time_zone&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;render&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;json: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;time_zone: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;time_zone_from_ip&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;kp&quot;&gt;private&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;time_zone_from_ip&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;begin&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;ip_address&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Rails&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;production?&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ip&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;108.245.160.41&quot;&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;geocode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Geocoder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;search&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ip_address&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;first&lt;/span&gt;
      &lt;span class=&quot;no&quot;&gt;Timezone&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Zone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;latlon: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;geocode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;latitude&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;geocode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;longitude&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;active_support_time_zone&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;geocode&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;rescue&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;
      &lt;span class=&quot;no&quot;&gt;Rails&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;error&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;ERROR WITH GEOCODING: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&quot;UTC&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;And now the JS. If our new div ID is on the page, let’s use jQuery’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;getJSON&lt;/code&gt; to fetch this user’s time zone as JSON. We’ll then update the default link text, show the link, update our select box, and reset Chosen.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-coffeescript&quot; data-lang=&quot;coffeescript&quot;&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;#signup_time_zone_group&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getJSON&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;/determine_time_zone&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;#signup_time_zone_group&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;p a&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;time_zone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;#signup_time_zone_group&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;removeClass&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;hide&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;#signup_time_zone&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;val&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;time_zone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;#signup_time_zone&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;trigger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;chosen:updated&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;theres-got-to-be-a-better-way&quot;&gt;There’s Got To Be A Better Way&lt;/h2&gt;

&lt;p&gt;After having this implementation in production for a few days, I wrote up this post touting how simple this Ruby implementation was, and how terribly awful trying to do this in pure JS was.  Then, magically, a simpler solution showed itself.&lt;/p&gt;

&lt;p&gt;While researching a JS version, I ran across &lt;a href=&quot;https://bitbucket.org/pellepim/jstimezonedetect/src&quot;&gt;jstz&lt;/a&gt; – a little library that grabs a user’s time zone from their browser. The problem with this was the fact that the library returns a &lt;a href=&quot;https://www.iana.org/time-zones&quot;&gt;IANA zone&lt;/a&gt; info key (aka the Olson time zone database). This is not the same key that Rails uses. I scrapped this route based on this information.&lt;/p&gt;

&lt;p&gt;However, today I managed to find a neat little piece of JS that converts from IANA to the Rails time zones: &lt;a href=&quot;https://github.com/davidwood/rails-timezone-js&quot;&gt;rails-timezone-js&lt;/a&gt;. I dropped in this converter along with jstz, and we have a much simpler solution:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-coffeescript&quot; data-lang=&quot;coffeescript&quot;&gt;  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;#signup_time_zone_group&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;rails_timezone&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;RailsTimeZone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;jstz&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;determine&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;#signup_time_zone_group&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;p a&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;rails_timezone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;#signup_time_zone_group&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;removeClass&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;hide&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;#signup_time_zone&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;val&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;rails_timezone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;#signup_time_zone&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;trigger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;chosen:updated&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;You’re notice there’s now no AJAX call back to the server. We use jstz to grab the time zone name, then pass it off to the converter helper method: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rails_timezone = window.RailsTimeZone.to(jstz.determine().name())&lt;/code&gt;.  BOOM!&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;After getting stuck on a pure JS version, I went down the path of using Ruby and a little AJAX… but then managed to find a solution to the JS version. Regardless of how the time zone was determined, the more interesting part of this process for me was the UX.&lt;/p&gt;

&lt;p&gt;My first version didn’t solve the main problem of reducing cognitive load. So I hid the dropdown. This solved the user-facing issue, but the process of determining the user’s lat/lng was creating slow load times.  We fixed this by “backgrounding” this part of the process, but that left us with a lot of Ruby for a problem that I was sure &lt;em&gt;had&lt;/em&gt; to have been solved before. Sure enough, rails-timezone-js to the rescue!&lt;/p&gt;

&lt;p&gt;To see this in action, view SproutMark’s &lt;a href=&quot;https://www.sproutmark.com/signup&quot;&gt;signup form&lt;/a&gt;.  If you have any tips, let me know in the comments!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Much thanks to &lt;a href=&quot;https://twitter.com/theberg&quot;&gt;Jeff Berg&lt;/a&gt; for helping me with this. He and his team at Planning Center use a &lt;a href=&quot;https://accounts.planningcenteronline.com/sign_up/Services?package_id=21&quot;&gt;similar implementation&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Digital Ocean: Ubuntu, Nginx, Unicorn, Rails</title>
   <link href="https://www.mccartie.com/tech/2014/08/28/digital-ocean.html"/>
   <updated>2014-08-28T00:00:00+00:00</updated>
   <id>https://www.mccartie.com/tech/2014/08/28/digital-ocean</id>
   <content type="html">&lt;p&gt;&lt;strong&gt;UPDATE&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I ran this setup from August 2014 to December 2014.  It served me pretty well.  However, I’ve since moved it back to Heroku.&lt;/p&gt;

&lt;p&gt;Honestly, the setup was fun and I certainly enjoyed the smaller bill, but keeping up with security patches, the cost of server monitoring, worrying about firewalls, etc. just got to be a pain.&lt;/p&gt;

&lt;p&gt;That being said, if you’re into the Ops side of things, I hope this post serves you well.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;I recently decided to move &lt;a href=&quot;https://www.sproutmark.com&quot;&gt;SproutMark&lt;/a&gt; off to a VPS. I’d heard great things about &lt;a href=&quot;https://www.digitalocean.com/?refcode=ae9b7bce6932&quot;&gt;Digital Ocean&lt;/a&gt;, so decided to make the switch. Although there are a handful of tutorials out there (&lt;a href=&quot;https://voiceofchunk.com/2014/06/09/deploying-rails-apps-using-passenger-rbenv-postgresql-and-mina/&quot;&gt;this one&lt;/a&gt; was VERY helpful) on how to setup the Nginx/Unicorn/Rails stack, none had everything I needed. So here are my learnings and experience on how I got set up on Digital Ocean.&lt;/p&gt;

&lt;p&gt;Here’s what we’ll be covering&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Ubuntu&lt;/li&gt;
  &lt;li&gt;Nginx&lt;/li&gt;
  &lt;li&gt;Unicorn&lt;/li&gt;
  &lt;li&gt;Rbenv&lt;/li&gt;
  &lt;li&gt;Rails 4.1&lt;/li&gt;
  &lt;li&gt;PostgreSQL&lt;/li&gt;
  &lt;li&gt;Redis&lt;/li&gt;
  &lt;li&gt;Memcache&lt;/li&gt;
  &lt;li&gt;Mina (for deployments)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;get-that-vps-built&quot;&gt;Get That VPS Built&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.digitalocean.com/?refcode=ae9b7bce6932&quot;&gt;Sign Up&lt;/a&gt; for Digital Ocean&lt;/li&gt;
  &lt;li&gt;Click “Create Droplet”&lt;/li&gt;
  &lt;li&gt;Choose your size. A typical Rails app can run about 150-200mb per process. Multiply that by how many Unicorn processes you want to run to figure out how much RAM you need. For my little app, I’m running 2 unicorns + Sidekiq and am hovering around 800mb so the 1GB plan works fine for me.&lt;/li&gt;
  &lt;li&gt;Under “Select Image”, choose “Ubuntu 14.04 x64”&lt;/li&gt;
  &lt;li&gt;Click “Create Droplet”&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That’s it? Yes. Well, yeah, so far.&lt;/p&gt;

&lt;h2 id=&quot;set-up-a-user-for-deployments&quot;&gt;Set Up A User For Deployments&lt;/h2&gt;

&lt;p&gt;It’s usually not good practice to use your root user to handle deployments, so let’s first start by adding a “deployer” user.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;ssh root@YOUR_NEW_DROPLET
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;adduser deployer
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;su deployer&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;ssh-keys&quot;&gt;SSH Keys&lt;/h2&gt;

&lt;p&gt;Stop using that root password.  Setup and upload an SSH key.&lt;/p&gt;

&lt;p&gt;Digital Ocean has a &lt;a href=&quot;https://www.digitalocean.com/community/tutorials/how-to-set-up-ssh-keys--2&quot;&gt;fine document&lt;/a&gt; on creating a SSH key and getting it installed.  The only part I’d never seen before was &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ssh-copy-id&lt;/code&gt;.  It’s quite handy and easy to install with Homebrew.&lt;/p&gt;

&lt;h2 id=&quot;rbenv&quot;&gt;Rbenv&lt;/h2&gt;

&lt;p&gt;We all have fond memories of RVM. Well, I have some &lt;em&gt;fond&lt;/em&gt; memories, but mostly memories or RVM acting crazy and putting crap all over my system. “Is there a better way?”, you ask. Yes. Yes, there is.  Please use &lt;a href=&quot;https://github.com/sstephenson/rbenv&quot;&gt;Rbenv&lt;/a&gt;. Let’s get it installed for your deployer user.&lt;/p&gt;

&lt;p&gt;First, some dependencies:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt-get update
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt-get &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;curl git-core build-essential zlib1g-dev libssl-dev libreadline-dev libyaml-dev libsqlite3-dev sqlite3 libcurl4-openssl-dev libxml2-dev libxslt1-dev python-software-properties&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Now, we’ll install rbenv into your home directory and add some commands to your .bashrc for completution and shims.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;git clone https://github.com/sstephenson/rbenv.git ~/.rbenv
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;export PATH=&quot;$HOME/.rbenv/bin:$PATH&quot;&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; ~/.bashrc
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;eval &quot;$(rbenv init -)&quot;&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; ~/.bashrc&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Now, let’s restart the shell and make sure Rbenv is install:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;exec&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$SHELL&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;type &lt;/span&gt;rbenv
&lt;span class=&quot;c&quot;&gt;#=&amp;gt; &quot;rbenv is a function&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Time to install Ruby!&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;git clone https://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;rbenv &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;2.1.2
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;rbenv global 2.1.2&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This part may take awhile. Go grab an apple.&lt;/p&gt;

&lt;p&gt;Ok, done?&lt;/p&gt;

&lt;p&gt;Let’s make sure all is well…&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;ruby &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt;
ruby 2.1.2p95 &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;2014-05-08 revision 45877&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;x86_64-linux]&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;If it’s not working, you’ll probably see this:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;ruby &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt;
The program &lt;span class=&quot;s1&quot;&gt;&apos;ruby&apos;&lt;/span&gt; can be found &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;the following packages:
 &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; ruby
 &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; ruby1.8
Try: apt-get &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &amp;lt;selected package&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;^ That’s not good. Re-visit the instructions above and don’t proceed until you get Ubuntu figuring out where Ruby is.&lt;/p&gt;

&lt;p&gt;One last step that always trips people up: install Bundler real quick.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;gem &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;bundler&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;You’ll thank me later…&lt;/p&gt;

&lt;h2 id=&quot;postgresql&quot;&gt;PostgreSQL&lt;/h2&gt;

&lt;p&gt;Use MySQL if you must, but I ain’t helping you there. Let’s set up PostgreSQL.&lt;/p&gt;

&lt;p&gt;Don’t try and get fancy with other DB users. Just use the built-in ‘postgres’ user.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt-get &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;postgresql postgresql-contrib
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;createuser &lt;span class=&quot;nt&quot;&gt;--pwprompt&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Create your database:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;su postgres
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;psql
psql &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;9.3.5&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
Type &lt;span class=&quot;s2&quot;&gt;&quot;help&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;help.

&lt;span class=&quot;nv&quot;&gt;postgres&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# CREATE DATABASE yourapp_production;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;memcache-and-redis&quot;&gt;Memcache, and Redis&lt;/h2&gt;

&lt;p&gt;These two are a piece of cake. Thanks, ‘apt-get’&lt;/p&gt;

&lt;p&gt;Memcache&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt-get &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;memcached&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;And Redis (for Sidekiq and other fun stuff)&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;apt-get &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;redis-server&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Boot ‘er up and make sure you can get to the Redis console:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;redis-server /etc/redis/redis.conf
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;redis-cli
127.0.0.1:6379&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;If you’re just using Redis for Sidekiq, you’re done. However, Redis is a pretty powerful datastore. For more, check out &lt;a href=&quot;https://www.amazon.com/gp/product/1617290858/ref=as_li_tl?ie=UTF8&amp;amp;camp=1789&amp;amp;creative=9325&amp;amp;creativeASIN=1617290858&amp;amp;linkCode=as2&amp;amp;tag=mccblo-20&amp;amp;linkId=TQKIMJDHQC5UANZL&quot;&gt;Redis in Action&lt;/a&gt;&lt;img src=&quot;https://ir-na.amazon-adsystem.com/e/ir?t=mccblo-20&amp;amp;l=as2&amp;amp;o=1&amp;amp;a=1617290858&quot; width=&quot;1&quot; height=&quot;1&quot; border=&quot;0&quot; alt=&quot;&quot; style=&quot;border:none !important; margin:0px !important;&quot; /&gt;.&lt;/p&gt;

&lt;h2 id=&quot;nginx&quot;&gt;Nginx&lt;/h2&gt;

&lt;p&gt;Here’s the part where I started getting scared. I don’t know Nginx well. There. I said it. I’m not ashamed. I got a big head start using &lt;a href=&quot;https://voiceofchunk.com/2014/06/09/deploying-rails-apps-using-passenger-rbenv-postgresql-and-mina/&quot;&gt;Esther Hazzard-Strong’s post&lt;/a&gt;, but still had some problems. Here’s the config I ended up with:&lt;/p&gt;

&lt;p&gt;Install Nginx:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt-get &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;nginx
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;service nginx start&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;You should now be able to view Nginx’s welcome page by visiting your Droplet’s IP in your browser.&lt;/p&gt;

&lt;p&gt;You can also check to make sure Nginx is running:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;ps ax | &lt;span class=&quot;nb&quot;&gt;grep &lt;/span&gt;nginx&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Now we can get Nginx set up to point to our (upcoming) Rails install. Let’s open up the nginx.conf file:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;nano /etc/nginx/nginx.conf&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Here are my settings:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;user www-data&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
worker_processes 4&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
pid /run/nginx.pid&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

events &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  worker_connections 768&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

http &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

  &lt;span class=&quot;c&quot;&gt;##&lt;/span&gt;
  &lt;span class=&quot;c&quot;&gt;# Basic Settings&lt;/span&gt;
  &lt;span class=&quot;c&quot;&gt;##&lt;/span&gt;

  sendfile on&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  tcp_nopush on&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  tcp_nodelay on&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  keepalive_timeout 65&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  types_hash_max_size 2048&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

  server_name_in_redirect off&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

  include /etc/nginx/mime.types&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  default_type application/octet-stream&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;c&quot;&gt;##&lt;/span&gt;
  &lt;span class=&quot;c&quot;&gt;# Logging Settings&lt;/span&gt;
  &lt;span class=&quot;c&quot;&gt;##&lt;/span&gt;

  access_log /var/log/nginx/access.log&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  error_log /var/log/nginx/error.log&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;c&quot;&gt;##&lt;/span&gt;
  &lt;span class=&quot;c&quot;&gt;# Gzip Settings&lt;/span&gt;
  &lt;span class=&quot;c&quot;&gt;##&lt;/span&gt;

  &lt;span class=&quot;nb&quot;&gt;gzip &lt;/span&gt;on&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  gzip_disable &lt;span class=&quot;s2&quot;&gt;&quot;msie6&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;c&quot;&gt;##&lt;/span&gt;
  &lt;span class=&quot;c&quot;&gt;# Virtual Host Configs&lt;/span&gt;
  &lt;span class=&quot;c&quot;&gt;##&lt;/span&gt;

  include /etc/nginx/conf.d/&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;.conf&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  include /etc/nginx/sites-enabled/&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;So that’s the overall Nginx setup.  Now let’s set up the conf file for our Rails site. The cheater way to do this is place a file directly into /etc/nginx/sites-enabled. The better way is to place the file into sites-available and then symlink into sites-enabled.&lt;/p&gt;

&lt;p&gt;But I’m a cheater…&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;nano /etc/nginx/sites-enabled/default&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Here’s my setup. I’m running SSL, so there are two server blocks. The “listen 80” block takes any non-http request and redirects to the other server block.  The second block is for “listen 443” (SSL). If you don’t want/need SSL, you can remove the first server block and swap out 443 to 80 in the second block. You’ll want to remove the ssl_certificate lines, too.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;upstream app &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;# Path to Unicorn SOCK file, as defined previously&lt;/span&gt;
    server unix:/home/deployer/YOUR_APP_NAME/shared/sockets/unicorn.sock fail_timeo
&lt;span class=&quot;nv&quot;&gt;ut&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

server &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    listen         80&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return &lt;/span&gt;301 https://&lt;span class=&quot;nv&quot;&gt;$host$request_uri&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

server &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    listen 443&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;c&quot;&gt;# Application root, as defined previously&lt;/span&gt;
    root /home/deployer/YOUR_APP_NAME/current/public&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    ssl on&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    ssl_certificate /etc/nginx/ssl/SSL.crt&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    ssl_certificate_key /etc/nginx/ssl/YOUR_APP_NAME.key&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    server_name www.YOUR_APP_NAME.com YOUR_APP_NAME.com&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    try_files &lt;span class=&quot;nv&quot;&gt;$uri&lt;/span&gt;/index.html &lt;span class=&quot;nv&quot;&gt;$uri&lt;/span&gt; @app&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    access_log /var/log/nginx/YOUR_APP_NAME_access.log combined&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    error_log /var/log/nginx/YOUR_APP_NAME_error.log&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    location @app &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        proxy_set_header X-Forwarded-For &lt;span class=&quot;nv&quot;&gt;$remote_addr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        proxy_set_header Host &lt;span class=&quot;nv&quot;&gt;$http_host&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        proxy_redirect off&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        proxy_pass https://app&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        proxy_set_header   X-Forwarded-Proto https&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;  &lt;span class=&quot;c&quot;&gt;# &amp;lt;-- don&apos;t need this if you&apos;re not running SSL&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    error_page 500 502 503 504 /500.html&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    client_max_body_size 4G&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    keepalive_timeout 10&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;I probably spent more time on that one file above anything. Thank you, internet, for providing tons of random tutorials on setting that up.&lt;/p&gt;

&lt;p&gt;Ok, so Nginx is set up. Time to get into the fun stuff.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;
If you’re looking for a deep dive into Nginx, check out &lt;a href=&quot;https://www.amazon.com/gp/product/1849517444/ref=as_li_tl?ie=UTF8&amp;amp;camp=1789&amp;amp;creative=9325&amp;amp;creativeASIN=1849517444&amp;amp;linkCode=as2&amp;amp;tag=mccblo-20&amp;amp;linkId=R4DM5YI4KWFBKXOM&quot;&gt;Mastering Nginx&lt;/a&gt;&lt;img src=&quot;https://ir-na.amazon-adsystem.com/e/ir?t=mccblo-20&amp;amp;l=as2&amp;amp;o=1&amp;amp;a=1849517444&quot; width=&quot;1&quot; height=&quot;1&quot; border=&quot;0&quot; alt=&quot;&quot; style=&quot;border:none !important; margin:0px !important;&quot; /&gt;.
&lt;/em&gt;&lt;/p&gt;

&lt;h2 id=&quot;unicorn&quot;&gt;Unicorn&lt;/h2&gt;

&lt;p&gt;This was another one that took me awhile, so I’m excited to share this setup with someone. I’m going to assume you know what Unicorn is and why you should use it. Here’s my unicorn.rb Rails initializer (config/unicorn.rb)&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# Set your full path to application.&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;app_dir&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;expand_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;../../&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;__FILE__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;shared_dir&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;expand_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;../../../shared/&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;__FILE__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Set unicorn options&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;worker_processes&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;preload_app&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;timeout&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;30&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Fill path to your app&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;working_directory&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;app_dir&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Set up socket location&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;listen&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;shared_dir&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/sockets/unicorn.sock&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:backlog&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;64&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Loging&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;stderr_path&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;shared_dir&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/log/unicorn.stderr.log&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;stdout_path&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;shared_dir&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/log/unicorn.stdout.log&quot;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Set master PID location&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;pid&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;shared_dir&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/pids/unicorn.pid&quot;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;before_fork&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;worker&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;defined?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ActiveRecord&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveRecord&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;connection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;disconnect!&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;old_pid&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:pid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;.oldbin&quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;exists?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;old_pid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;pid&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;old_pid&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;begin&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;sig&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;worker&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;nr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;worker_processes&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:QUIT&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:TTOU&lt;/span&gt;
      &lt;span class=&quot;no&quot;&gt;Process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;kill&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;old_pid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;rescue&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Errno&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ENOENT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Errno&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ESRCH&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;# someone else did our job for us&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;after_fork&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;worker&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;defined?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ActiveRecord&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveRecord&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;establish_connection&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;before_exec&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;BUNDLE_GEMFILE&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;app_dir&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/Gemfile&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;mina&quot;&gt;Mina&lt;/h2&gt;

&lt;p&gt;If you’ve ever struggled with the Capistrano DSL, then you’ll be quite happy when you see &lt;a href=&quot;https://nadarei.co/mina/&quot;&gt;Mina&lt;/a&gt;. It’s quite lovely. And VERY fast.&lt;/p&gt;

&lt;p&gt;Add it to your Gemfile.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;gem&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;mina&apos;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Let’s also add two other gems that will help us manage Unicorn and Sidekiq&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;gem&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;mina-sidekiq&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:require&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;gem&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;mina-unicorn&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:require&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Create the necessary “deploy.rb”:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;mina init
Created config/deploy.rb.&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;&lt;strong&gt;Make sure to add your project’s folder inside ‘/home/deployer’.  For instance, ‘/home/deployer/YOUR_APP’.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let’s tweak that deploy.rb file.  Here’s my setup:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;mina/bundler&apos;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;mina/rails&apos;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;mina/git&apos;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;mina/rbenv&apos;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;mina_sidekiq/tasks&apos;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;mina/unicorn&apos;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Basic settings:&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#   domain       - The hostname to SSH to.&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#   deploy_to    - Path to deploy into.&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#   repository   - Git repo to clone from. (needed by mina/git)&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#   branch       - Branch name to deploy. (needed by mina/git)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:domain&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;YOUR DROPLETS IP&apos;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:deploy_to&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;/home/deployer/YOUR_APP/&apos;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:repository&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;YOUR GIT REPO URL&apos;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:branch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;master&apos;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;deployer&apos;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:forward_agent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:port&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;22&apos;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:unicorn_pid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;deploy_to&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/shared/pids/unicorn.pid&quot;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Manually create these paths in shared/ (eg: shared/config/database.yml) in your server.&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# They will be linked in the &apos;deploy:link_shared_paths&apos; step.&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:shared_paths&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;config/database.yml&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;log&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;config/secrets.yml&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;


&lt;span class=&quot;c1&quot;&gt;# This task is the environment that is loaded for most commands, such as&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# `mina deploy` or `mina rake`.&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;task&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:environment&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;queue&lt;/span&gt; &lt;span class=&quot;sx&quot;&gt;%{
echo &quot;-----&amp;gt; Loading environment&quot;
#{echo_cmd %[source ~/.bashrc]}
}&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;invoke&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:&apos;rbenv:load&apos;&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# If you&apos;re using rbenv, use this to load the rbenv environment.&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# Be sure to commit your .rbenv-version to your repository.&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Put any custom mkdir&apos;s in here for when `mina setup` is ran.&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# For Rails apps, we&apos;ll make some of the shared paths that are shared between&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# all releases.&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;task&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:setup&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:environment&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;queue!&lt;/span&gt; &lt;span class=&quot;sx&quot;&gt;%[mkdir -p &quot;#{deploy_to}/shared/log&quot;]&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;queue!&lt;/span&gt; &lt;span class=&quot;sx&quot;&gt;%[chmod g+rx,u+rwx &quot;#{deploy_to}/shared/log&quot;]&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;queue!&lt;/span&gt; &lt;span class=&quot;sx&quot;&gt;%[mkdir -p &quot;#{deploy_to}/shared/config&quot;]&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;queue!&lt;/span&gt; &lt;span class=&quot;sx&quot;&gt;%[chmod g+rx,u+rwx &quot;#{deploy_to}/shared/config&quot;]&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;queue!&lt;/span&gt; &lt;span class=&quot;sx&quot;&gt;%[touch &quot;#{deploy_to}/shared/config/database.yml&quot;]&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;queue&lt;/span&gt;  &lt;span class=&quot;sx&quot;&gt;%[echo &quot;-----&amp;gt; Be sure to edit &apos;shared/config/database.yml&apos;.&quot;]&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;queue!&lt;/span&gt; &lt;span class=&quot;sx&quot;&gt;%[touch &quot;#{deploy_to}/shared/config/secrets.yml&quot;]&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;queue&lt;/span&gt; &lt;span class=&quot;sx&quot;&gt;%[echo &quot;-----&amp;gt; Be sure to edit &apos;shared/config/secrets.yml&apos;.&quot;]&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# sidekiq needs a place to store its pid file and log file&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;queue!&lt;/span&gt; &lt;span class=&quot;sx&quot;&gt;%[mkdir -p &quot;#{deploy_to}/shared/pids/&quot;]&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;queue!&lt;/span&gt; &lt;span class=&quot;sx&quot;&gt;%[chmod g+rx,u+rwx &quot;#{deploy_to}/shared/pids&quot;]&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;desc&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Deploys the current version to the server.&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;task&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:deploy&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:environment&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;deploy&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# stop accepting new workers&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;invoke&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:&apos;sidekiq:quiet&apos;&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;invoke&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:&apos;git:clone&apos;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;invoke&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:&apos;deploy:link_shared_paths&apos;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;invoke&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:&apos;bundle:install&apos;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;invoke&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:&apos;rails:db_migrate&apos;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;invoke&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:&apos;rails:assets_precompile&apos;&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:launch&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;invoke&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:&apos;sidekiq:restart&apos;&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;invoke&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:&apos;unicorn:restart&apos;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;I absolutely love how easy it is to read that file!&lt;/p&gt;

&lt;p&gt;Ok, let’s run that setup task to create the necessary folders and files on your Droplet. If one doesn’t get created that you need, no worries. Just SSH back in there and create the folder yourself.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;mina setup
&lt;span class=&quot;nt&quot;&gt;-----&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; Creating folders... &lt;span class=&quot;k&quot;&gt;done&lt;/span&gt;.&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;set-up-databaseyml-and-secretsyml&quot;&gt;Set Up database.yml and secrets.yml&lt;/h2&gt;

&lt;p&gt;Let’s get back on your Droplet and set up your database.yml and secrets.yml files:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;ssh deployer@YOUR_IP
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;nano /home/deployer/YOUR_APP/shared/config/database.yml&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Tweak that file:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;na&quot;&gt;production&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;adapter&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;postgresql&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;encoding&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;unicode&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;database&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;APPNAME_production&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;postgres&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;DB_PASSWORD_SET_ABOVE&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;host&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;localhost&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;…and your secrets.yml…&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;nano /home/deployer/YOUR_APP/shared/config/secrets.yml&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;production:
  secret_key_base: RUN &lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;rake secret&lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt; TO GENERATE A KEY&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;deploying&quot;&gt;Deploying&lt;/h2&gt;
&lt;p&gt;And finally, deploy!&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;mina deploy
&lt;span class=&quot;nt&quot;&gt;-----&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; Deploying to 2012-06-12-040248
       ...
       Lots of things happening...
       ...
&lt;span class=&quot;nt&quot;&gt;-----&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; Done.&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;That’s it!  If you have any questions, leave them in the comments. I want to make this document better for the next guy, so let me know if there’s anything I can improve.&lt;/p&gt;
</content>
 </entry>
 

</feed>