<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Nick Holden</title>
    <description>The latest articles on DEV Community by Nick Holden (@nholden).</description>
    <link>https://dev.to/nholden</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F53506%2Ff76b39c8-3e32-43f3-9f4a-a84784337afc.jpg</url>
      <title>DEV Community: Nick Holden</title>
      <link>https://dev.to/nholden</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nholden"/>
    <language>en</language>
    <item>
      <title>Is $170,000 top 10% pay for software developers in San Francisco?</title>
      <dc:creator>Nick Holden</dc:creator>
      <pubDate>Wed, 13 Mar 2024 17:08:06 +0000</pubDate>
      <link>https://dev.to/nholden/is-170000-top-10-pay-for-software-developers-in-san-francisco-34k6</link>
      <guid>https://dev.to/nholden/is-170000-top-10-pay-for-software-developers-in-san-francisco-34k6</guid>
      <description>&lt;p&gt;The short answer: no. &lt;a href="https://www.bls.gov/oes/current/oes151252.htm" rel="noopener noreferrer"&gt;According to the U.S. Bureau of Labor Statistics&lt;/a&gt;, the 90th percentile annual wage for software developers nationwide is $198,100.&lt;/p&gt;

&lt;p&gt;Developer salaries are much higher in San Francisco. &lt;a href="https://gusto.com/resources/research/salary/software-engineer/ca/san-francisco" rel="noopener noreferrer"&gt;According to Gusto&lt;/a&gt;, the 90th percentile annual salary for software engineers in San Francisco is $215,000. They note that "offering a total compensation package that includes employee benefits and bonus pay to incentivize performance, in addition to competitive base pay, can help attract and retain top talent."&lt;/p&gt;

&lt;p&gt;37signals claims they pay &lt;a href="https://apply.workable.com/37signals/j/9E33A5269C/" rel="noopener noreferrer"&gt;"in the top 10% of the industry based on San Francisco rates"&lt;/a&gt; and that the salary for Programmers is $170,000. Can that be accurate?&lt;/p&gt;

&lt;p&gt;It's not clear what "industry" 37signals refers to in their statement. We know that software developer salaries at non-tech companies can be lower than software developer salaries at all companies in general and much lower than software developer salaries at tech companies. Gergely Orosz covers this well in &lt;a href="https://blog.pragmaticengineer.com/software-engineering-salaries-in-the-netherlands-and-europe/" rel="noopener noreferrer"&gt;The Trimodal Nature of Software Engineering Salaries in the Netherlands and Europe&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Is 37signals a tech company? Their business is building and selling software. When they accepted investment from Jeff Bezos in 2006, &lt;a href="https://signalvnoise.com/archives2/bezos_expeditions_invests_in_37signals" rel="noopener noreferrer"&gt;they wrote about this him&lt;/a&gt;: "What we’ve been looking for is the wisdom of a very special entrepreneur who’s been through what we’re going through." 37signals looks like a tech company.&lt;/p&gt;

&lt;p&gt;It's also not clear what 37signals means by "San Francisco rates" in their statement. One portion of pay that San Francisco software developers receive is salary, the 90th percentile of which is $215,000 according to Gusto. Many San Francisco software developers also receive equity compensation, and it's not uncommon for the equity portion to be larger than the salary portion. In all public companies and even some private companies, workers can sell that equity portion for dollars.&lt;/p&gt;

&lt;p&gt;37signals does not offer traditional equity compensation. They do offer &lt;a href="https://basecamp.com/handbook/benefits-and-perks#employee-profit-sharing" rel="noopener noreferrer"&gt;profit sharing&lt;/a&gt;, in which employees with more than two years at the company split 10% of the profits based on tenure, and &lt;a href="https://basecamp.com/handbook/benefits-and-perks#employee-liquidity-pool" rel="noopener noreferrer"&gt;a liquidity pool&lt;/a&gt; that they never intend to use.&lt;/p&gt;

&lt;p&gt;So can it be true that 37signals pays in the top 10% of the industry based on San Francisco rates, which includes paying programmers $170,000? Possibly. We'd need to know their definition of "industry" and "rates." At best, the statement is unhelpful and misleading.&lt;/p&gt;

&lt;p&gt;I love that 37signals is transparent about salaries. It allows applicants to self-select and stops workers from wondering if they're being paid unfairly.&lt;/p&gt;

&lt;p&gt;37signals is a proudly independent company, and they can pay any rate that the law allows. At $170,000, they'll receive many highly qualified applicants for the position. I wonder why they don't remove the unhelpful and misleading "top 10%" statement.&lt;/p&gt;

</description>
      <category>salary</category>
    </item>
    <item>
      <title>Hacking Code Review: Share a resource</title>
      <dc:creator>Nick Holden</dc:creator>
      <pubDate>Tue, 07 May 2019 13:41:07 +0000</pubDate>
      <link>https://dev.to/nholden/hacking-code-review-share-a-resource-5d87</link>
      <guid>https://dev.to/nholden/hacking-code-review-share-a-resource-5d87</guid>
      <description>&lt;p&gt;&lt;em&gt;This is part of &lt;a href="https://www.hackingcodereview.com" rel="noopener noreferrer"&gt;Hacking Code Review&lt;/a&gt;, a series of bite-sized hacks you can start using today to make your code reviews more impactful. &lt;a href="https://www.hackingcodereview.com/#subscribe" rel="noopener noreferrer"&gt;Subscribe&lt;/a&gt; to be the first to know about new hacks.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Reviewing code requires some original thought. When I read through proposed changes, I think about how the code makes me feel and what my future self might think when I come across it. I spend time collecting my thoughts and feelings and putting them into words that will be helpful and persuasive.&lt;/p&gt;

&lt;p&gt;Often though, somebody else has already said what I’m thinking in a better or more comprehensive way. Developers are fortunate that the internet is chock-full of ideas and prior art that we can borrow from. Sharing resources in code review is a great way to make your reviews more impactful without much extra effort.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to share
&lt;/h2&gt;

&lt;p&gt;Sharing API documentation is great for making sure that the reviewer and author are on the same page. It’s easy to develop assumptions about an API’s functionality that may or may not be true, especially as developers become more comfortable with the syntax and conventions. Linking off to documentation ensures that the reviewer and the author have a shared understanding of the APIs they use.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Is &lt;a href="https://api.rubyonrails.org/v5.1/classes/ActiveRecord/QueryMethods.html#method-i-distinct" rel="noopener noreferrer"&gt;this&lt;/a&gt; the &lt;code&gt;distinct&lt;/code&gt; we're using here? Since we want distinct records, I don't think we need an argument.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Blog posts are another helpful resource to share in code review. There are a handful of blog posts that I always refer back to when I bump into a specific kind of problem, like &lt;a href="https://thoughtbot.com/blog/its-about-time-zones" rel="noopener noreferrer"&gt;timezones&lt;/a&gt;, &lt;a href="https://www.joelonsoftware.com/2003/10/08/the-absolute-minimum-every-software-developer-absolutely-positively-must-know-about-unicode-and-character-sets-no-excuses/" rel="noopener noreferrer"&gt;character encoding&lt;/a&gt;, or &lt;a href="https://blog.prototypr.io/7-rules-of-using-radio-buttons-vs-drop-down-menus-fddf50d312d1" rel="noopener noreferrer"&gt;form UI&lt;/a&gt;. Start building up a library of helpful blog posts, and drop them in code reviews when they’re relevant so that you can develop some common knowledge with authors.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I learned in &lt;a href="https://www.speedshop.co/2019/01/10/three-activerecord-mistakes.html" rel="noopener noreferrer"&gt;this awesome blog post&lt;/a&gt; that &lt;code&gt;exists?&lt;/code&gt; always executes a SQL query. Should we memoize this or consider using &lt;code&gt;present?&lt;/code&gt; to avoid making multiple queries?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Teams make all sorts of decisions that aren’t formally documented. To uncover some of those in code review, I share links to old issues, merged pull requests, and existing code. Through discussions, previous code reviews, and commit messages, I can help piece together the story of why we solved a problem in a particular way and if it makes sense to take that approach again.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I really like the user experience for publishing (#105160), which disables the form and includes an explanatory note for users marked as spammy. What do you think about taking a similar approach here?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  How to share
&lt;/h2&gt;

&lt;p&gt;Provide context when you share a resource. Dropping a link by itself in a review is cryptic, and it can leave the author guessing what you’re suggesting they do. Authors are more likely to appreciate the resource if you make it clear that your intent is to share something that could be helpful rather than to shame them for not knowing something that you knew already.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;VCR has some &lt;a href="https://relishapp.com/vcr/vcr/v/1-6-0/docs/cassettes/request-matching" rel="noopener noreferrer"&gt;request matching&lt;/a&gt; options that might mean we don't need to get these URLs exactly right to reuse the desired cassettes. I'm wondering if it might be worthwhile to explore that so we can decouple the cassettes from the tests a bit.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Combining a resource with &lt;a href="https://dev.to/nholden/hacking-code-review-give-a-compliment-534m"&gt;a compliment&lt;/a&gt; can be particularly effective. If you appreciate one of the author’s approaches and you’ve seen it executed well somewhere else, share it. It’s a great way to give the author some extra affirmation, and it gives them something to think about the next time they encounter a similar problem.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I love how reusable this new &lt;code&gt;.text-center&lt;/code&gt; class is. Have you heard of &lt;a href="https://tailwindcss.com" rel="noopener noreferrer"&gt;Tailwind CSS&lt;/a&gt;? It’s a whole framework of utility classes like this. Maybe we could consider using it in the future if we like this style.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In code review, a link is worth a thousand words. Save yourself some keystrokes and look for opportunities to share helpful resources.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Cover image by Michael D Beckwith on &lt;a href="https://flic.kr/p/r3zodL" rel="noopener noreferrer"&gt;Flickr&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>codereview</category>
      <category>codequality</category>
      <category>devtips</category>
      <category>github</category>
    </item>
    <item>
      <title>Hacking Code Review: Give a compliment</title>
      <dc:creator>Nick Holden</dc:creator>
      <pubDate>Tue, 23 Apr 2019 15:10:59 +0000</pubDate>
      <link>https://dev.to/nholden/hacking-code-review-give-a-compliment-534m</link>
      <guid>https://dev.to/nholden/hacking-code-review-give-a-compliment-534m</guid>
      <description>&lt;p&gt;&lt;em&gt;This is part of &lt;a href="https://www.hackingcodereview.com" rel="noopener noreferrer"&gt;Hacking Code Review&lt;/a&gt;, a series of bite-sized hacks you can start using today to make your code reviews more impactful. &lt;a href="https://www.hackingcodereview.com/#subscribe" rel="noopener noreferrer"&gt;Subscribe&lt;/a&gt; to be the first to know about new hacks.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It’s easy to think of code review as an opportunity to find all the mistakes in a pull request. When I first look at proposed changes, I can be drawn to the code that’s most surprising or confusing, and it’s natural to begin thinking about what could be handled in a different way.&lt;/p&gt;

&lt;p&gt;But if all your code reviews are exclusively critical, you could come off as hostile. Other developers may become frustrated and seek your feedback less often.&lt;/p&gt;

&lt;p&gt;In your next code review, give the author a compliment about something you thought they did well.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to give a compliment
&lt;/h2&gt;

&lt;p&gt;One of the best times to give a compliment in code review is when you notice a novel approach. Authors tend to feel less confident about changes that don’t reuse a well-established pattern in the codebase, so when you see a positive change that looks different from what you’re used to, show your support. By giving a compliment, you send a signal that you’d like to see more changes like it in the future.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I know we usually stub this out, but your approach of calling the actual method in this test is 💯. I feel much more confident that we’ll catch changes to the API in the future. 😍&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Another great time to give a compliment is when the author is a new contributor. This can be someone who is making their first pull request to a repository, but it could also be an author who is proposing changes outside their area of expertise. Giving a compliment to new contributors makes authors feel appreciated, and they’ll be more likely to contribute in the future. Having more developers comfortable contributing to more parts of the codebase leads to more resilient teams and software.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Love how you made this its own PR. I can see the temptation to group it with the rest of the changes that are coming to this model, but keeping this PR focused made it easy to follow and a joy to review. ✨&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;One other opportunity to give a compliment is when most of the rest of your review is critical. Don’t force compliments or try to maintain a certain ratio of positive to negative feedback—authors will notice when you’re not being authentic. Instead, compliment one specific thing about the change that you genuinely appreciate. A thoughtful compliment could help the author begin to think of ways to improve the rest of their proposed changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to give a compliment
&lt;/h2&gt;

&lt;p&gt;An impactful compliment is specific. If the compliment about a particular section of code, explain why you like it. Specific compliments help authors understand what they did well and what they may want to replicate in the future.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Woah, I had no idea that the old version of this scope wouldn't include records where &lt;code&gt;type&lt;/code&gt; is &lt;code&gt;nil&lt;/code&gt;. Great job thinking through edge cases!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Compliments of the change on the whole can be impactful, too. More meta feedback on things like impact, scope, or timing can show your appreciation for the author’s overall approach.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This refactor is awesome! I’ve been feeling pain working with this class lately, and this feels like the perfect time to make the arguments more straightforward. ❤️&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Code reviews aren’t all about being critical, so next time, call out something you like about the author’s work. Everyone will feel great, and your review will be more impactful.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Cover image by Epic Fireworks on &lt;a href="https://flic.kr/p/dh8TH4" rel="noopener noreferrer"&gt;Flickr&lt;/a&gt;&lt;/em&gt;.&lt;/p&gt;

</description>
      <category>codereview</category>
      <category>codequality</category>
      <category>devtips</category>
      <category>github</category>
    </item>
    <item>
      <title>How I replaced a Rails app with a few dozen lines of Ruby</title>
      <dc:creator>Nick Holden</dc:creator>
      <pubDate>Mon, 25 Feb 2019 16:29:35 +0000</pubDate>
      <link>https://dev.to/nholden/how-i-replaced-a-rails-app-with-a-few-dozen-lines-of-ruby-3l1m</link>
      <guid>https://dev.to/nholden/how-i-replaced-a-rails-app-with-a-few-dozen-lines-of-ruby-3l1m</guid>
      <description>&lt;p&gt;A couple years ago, I broke a dashboard at work.&lt;/p&gt;

&lt;p&gt;As part of building a new feature, I changed our database schema. I carefully laid out a plan to add some new columns and migrate existing data. I made sure the changes to the database would work with both new and old application code and the feature could be deployed without any downtime.&lt;/p&gt;

&lt;p&gt;I forgot to consider that our application wasn’t the only consumer of our data. Our data team maintained dashboards that the company used to inform product decisions and make financial projections. Those dashboards expected our database schema to look a certain way, and when I made the changes without talking to the data team, one of those dashboards broke.&lt;/p&gt;

&lt;p&gt;After we fixed the dashboard, I thought about how I could prevent myself from making the same mistake in the future. I decided needed a reminder each time I made a change to our database schema. To get that reminder, I reached for the tool I was most comfortable with and spun up a new Rails app.&lt;/p&gt;

&lt;h2&gt;
  
  
  There’s a lot to a Rails app
&lt;/h2&gt;

&lt;p&gt;My goal with the app, &lt;a href="https://github.com/nholden/diffalert" rel="noopener noreferrer"&gt;DiffAlert&lt;/a&gt;, was to send an alert to Slack each time someone made a change to &lt;code&gt;db/structure.sql&lt;/code&gt; on the &lt;code&gt;master&lt;/code&gt; branch of our company’s application. I saw that GitHub had &lt;a href="https://developer.github.com/webhooks/" rel="noopener noreferrer"&gt;webhooks&lt;/a&gt; and that the &lt;a href="https://developer.github.com/v3/activity/events/types/#pushevent" rel="noopener noreferrer"&gt;push event&lt;/a&gt; sent along data about commits, including which files were changed.&lt;/p&gt;

&lt;p&gt;In a few hours, I spun up the app, created an endpoint for the GitHub webhook events, and wrote the code to tell whether or not &lt;code&gt;db/structure.sql&lt;/code&gt; changed in a given push. DiffAlert’s core logic was complete. Then the real work started.&lt;/p&gt;

&lt;p&gt;Next, I had to figure out how to send an alert to Slack. We were already using &lt;a href="https://nickholden.slack.com/apps/A0F81496D-email" rel="noopener noreferrer"&gt;Slack’s Email app&lt;/a&gt;, so I signed up for a new &lt;a href="https://www.mailgun.com/" rel="noopener noreferrer"&gt;Mailgun&lt;/a&gt; account and created an email template with &lt;a href="https://guides.rubyonrails.org/action_mailer_basics.html" rel="noopener noreferrer"&gt;Action Mailer&lt;/a&gt;. Then I had to deploy DiffAlert somewhere, so I created a new &lt;a href="https://www.heroku.com" rel="noopener noreferrer"&gt;Heroku&lt;/a&gt; application and did some tweaking to get everything properly configured with my workflow.&lt;/p&gt;

&lt;p&gt;Besides &lt;code&gt;db/structure.sql&lt;/code&gt;, we’d also want to monitor a few other files in our codebase, so I started building a UI to configure alert settings. I’d need authentication, so I designed the data model and created login and sign up forms. Then I needed another view to show all the alerts, and I needed forms to create new alerts and edit existing ones. I needed background jobs so that longer processes like webhook parsing and email sending wouldn’t hold up regular web requests.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwfzxz68qyhpp4d3y67hk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwfzxz68qyhpp4d3y67hk.png" alt="DiffAlert UI" width="800" height="501"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;DiffAlert chugged along for about a year and a half, reminding my team in Slack when we made changes to our database schema. Each time, we reached out to the data team and didn’t break any more dashboards.&lt;/p&gt;

&lt;p&gt;DiffAlert was a side project, so when I left the company, they needed to decide if they would continue sending a former employee their GitHub metadata, maintain their own instance of DiffAlert, or stop receiving alerts. They understandably decided to stop receiving alerts.&lt;/p&gt;

&lt;h2&gt;
  
  
  GitHub Actions helped me focus on the problem
&lt;/h2&gt;

&lt;p&gt;I work for GitHub, and when I learned about &lt;a href="https://github.com/features/actions" rel="noopener noreferrer"&gt;GitHub Actions&lt;/a&gt;, I wondered if I could replace DiffAlert with a single Action. A GitHub Action is code, written in any language, that runs in a Docker container when a specified event happens in a repository. I saw that &lt;a href="https://developer.github.com/actions/creating-workflows/workflow-configuration-options/#events-supported-in-workflow-files" rel="noopener noreferrer"&gt;push events&lt;/a&gt; could trigger GitHub Action workflows. I also saw that there was &lt;a href="https://github.com/marketplace/actions/github-action-for-slack" rel="noopener noreferrer"&gt;a GitHub Action&lt;/a&gt; that could post messages to Slack. I started writing &lt;a href="https://github.com/nholden/modified-file-filter-action" rel="noopener noreferrer"&gt;Modified File Filter&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;First, I needed a &lt;code&gt;Dockerfile&lt;/code&gt;. I wanted to write my Action in Ruby, so I used the &lt;code&gt;FROM&lt;/code&gt; instruction to create a Docker container from &lt;a href="https://hub.docker.com/_/ruby" rel="noopener noreferrer"&gt;an official Ruby base image&lt;/a&gt;. I wrote &lt;a href="https://developer.github.com/actions/creating-github-actions/creating-a-docker-container/#label" rel="noopener noreferrer"&gt;some &lt;code&gt;LABEL&lt;/code&gt; instructions&lt;/a&gt; so that my Action would show up correctly in the GitHub Actions visual workflow editor. Finally, I specified which folders the Docker container would need access to and pointed the &lt;code&gt;ENTRYPOINT&lt;/code&gt; at an executable Ruby script.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Dockerfile&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; ruby:2.6.0&lt;/span&gt;

&lt;span class="k"&gt;LABEL&lt;/span&gt;&lt;span class="s"&gt; "com.github.actions.name"="Modified File Filter"&lt;/span&gt;
&lt;span class="k"&gt;LABEL&lt;/span&gt;&lt;span class="s"&gt; "com.github.actions.description"="Stops a workflow unless a specified file has been modified."&lt;/span&gt;
&lt;span class="k"&gt;LABEL&lt;/span&gt;&lt;span class="s"&gt; "com.github.actions.icon"="filter"&lt;/span&gt;
&lt;span class="k"&gt;LABEL&lt;/span&gt;&lt;span class="s"&gt; "com.github.actions.color"="orange"&lt;/span&gt;

&lt;span class="k"&gt;ADD&lt;/span&gt;&lt;span class="s"&gt; bin /bin&lt;/span&gt;
&lt;span class="k"&gt;ADD&lt;/span&gt;&lt;span class="s"&gt; lib /lib&lt;/span&gt;

&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["entrypoint"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The executable Ruby script delegates most of the work to a plain old Ruby class, &lt;code&gt;PushEvent&lt;/code&gt;, which parses the event data from GitHub and answers whether a file at a specific path is modified. When a push event modifies the file, the Action exits with a &lt;code&gt;0&lt;/code&gt; status to trigger the next Action in a workflow. When a push event doesn’t modify the file, the Action exits &lt;code&gt;1&lt;/code&gt; to halt the workflow.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# bin/entrypoint&lt;/span&gt;

&lt;span class="nb"&gt;require_relative&lt;/span&gt; &lt;span class="s2"&gt;"../lib/push_event"&lt;/span&gt;

&lt;span class="n"&gt;file_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;ARGV&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;
&lt;span class="n"&gt;push_event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;PushEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"GITHUB_EVENT_PATH"&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;push_event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;modified?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; was modified"&lt;/span&gt;
  &lt;span class="nb"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; was not modified"&lt;/span&gt;
  &lt;span class="nb"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I didn’t need to configure emails or figure out how to integrate with Slack since I could lean on the existing GitHub Action for Slack. I didn’t need to design any UI or authentication because that was all handled by GitHub. I didn’t need to configure databases or background jobs. I didn’t need to spin up a Heroku application and set up deployments.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl06tckzo3cl6wic1d3qn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl06tckzo3cl6wic1d3qn.png" alt="Modified File Filter in GitHub Actions’ workflow editor" width="800" height="567"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Starting smaller
&lt;/h2&gt;

&lt;p&gt;Rails has a reputation for being a great framework for validating ideas. You can use Rails to &lt;a href="https://www.youtube.com/watch?v=Gzj723LkRJY" rel="noopener noreferrer"&gt;build a blog in 15 minutes&lt;/a&gt;! Because I love working with Rails, I often reach for it first when I have an idea.&lt;/p&gt;

&lt;p&gt;But even with all the magic that Rails provides, most apps need a whole bunch of things — like authentication, UI, background jobs, email sending, deployment — that aren’t unique to my idea. Next time I have an idea, I’ll look for ways to write less code and maintain less infrastructure, at least to get started.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Cover image by Iain Farrell on &lt;a href="https://flic.kr/p/mg8ZsL" rel="noopener noreferrer"&gt;Flickr&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>github</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Crawl Vote: Helping groups on the move pick a next spot</title>
      <dc:creator>Nick Holden</dc:creator>
      <pubDate>Thu, 24 May 2018 15:37:16 +0000</pubDate>
      <link>https://dev.to/nholden/crawl-vote-helping-groups-on-the-move-pick-a-next-spot-11o9</link>
      <guid>https://dev.to/nholden/crawl-vote-helping-groups-on-the-move-pick-a-next-spot-11o9</guid>
      <description>&lt;h1&gt;
  
  
  What I built
&lt;/h1&gt;

&lt;p&gt;Crawl Vote helps groups on the move pick a next spot. Whether they’re looking for poutine in Montreal or tacos in Tijuana, Crawl Vote will return some suggestions for everyone to vote on together.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo link
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://crawlvote.com" rel="noopener noreferrer"&gt;https://crawlvote.com&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Link to code
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/nholden" rel="noopener noreferrer"&gt;
        nholden
      &lt;/a&gt; / &lt;a href="https://github.com/nholden/crawl_vote" rel="noopener noreferrer"&gt;
        crawl_vote
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Helps groups on the move choose a next spot
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Crawl Vote&lt;/h1&gt;

&lt;/div&gt;
&lt;p&gt;Helps groups on the move choose a next spot&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Installation&lt;/h2&gt;

&lt;/div&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;git clone git://github.com/nholden/crawl_vote
cd crawl_vote
brew install redis postgresql heroku
bundle install
yarn
cp .env.example .env
bundle exec rake db:reset
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Getting started&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;Sign up for a Pusher Channels app at  &lt;a href="https://pusher.com/signup" rel="nofollow noopener noreferrer"&gt;https://pusher.com/signup&lt;/a&gt; and a Yelp API key at &lt;a href="https://www.yelp.com/developers/documentation/v3" rel="nofollow noopener noreferrer"&gt;https://www.yelp.com/developers/documentation/v3&lt;/a&gt; and update &lt;code&gt;.env&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Start these long-running processes in separate terminal windows:&lt;/p&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;heroku local
webpack-dev-server
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Crawl Vote should be accessible at &lt;a href="http://localhost:3345" rel="nofollow noopener noreferrer"&gt;http://localhost:3345&lt;/a&gt; (or whichever port you specify in &lt;code&gt;.env&lt;/code&gt;).&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Testing&lt;/h2&gt;

&lt;/div&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;bundle exec rspec
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Contributing&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;Contributions are welcome from everyone! Feel free to make a pull request or use GitHub issues for help getting started, to report bugs, or to make feature requests.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;License&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;This project was created by &lt;a href="https://www.nickholden.io" rel="nofollow noopener noreferrer"&gt;Nick Holden&lt;/a&gt; and is licensed under the terms of the MIT license.&lt;/p&gt;
&lt;/div&gt;



&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/nholden/crawl_vote" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;h1&gt;
  
  
  How I built it
&lt;/h1&gt;

&lt;p&gt;Crawl Vote uses &lt;a href="https://rubyonrails.org/" rel="noopener noreferrer"&gt;Ruby on Rails&lt;/a&gt; on the back end &lt;a href="https://vuejs.org/" rel="noopener noreferrer"&gt;Vue.js&lt;/a&gt; on the front end. I used &lt;a href="https://github.com/rails/webpacker" rel="noopener noreferrer"&gt;Webpacker&lt;/a&gt;, which comes with a &lt;a href="https://github.com/rails/webpacker#vue" rel="noopener noreferrer"&gt;Vue integration&lt;/a&gt;, to compile and bundle my JavaScript, CSS, and images. I used &lt;a href="https://tailwindcss.com/" rel="noopener noreferrer"&gt;Tailwind&lt;/a&gt; to quickly prototype my design without writing any CSS.&lt;/p&gt;

&lt;p&gt;I used &lt;a href="https://pusher.com/channels" rel="noopener noreferrer"&gt;Pusher Channels&lt;/a&gt; in a couple ways. When a user first submits where they’re going and what they’re looking for, Crawl Vote creates a new “crawl”, gives it a unique name, and subscribes the user to a Pusher channel with that name. Then, the application kicks off a &lt;a href="https://sidekiq.org/" rel="noopener noreferrer"&gt;Sidekiq&lt;/a&gt; job to query &lt;a href="https://www.yelp.com/fusion" rel="noopener noreferrer"&gt;Yelp’s Fusion API&lt;/a&gt; in the background. Once the job has fetched relevant businesses and persisted them to the &lt;a href="https://www.postgresql.org/" rel="noopener noreferrer"&gt;PostgreSQL&lt;/a&gt; database, it triggers an event to the Pusher channel. When the user’s browser receives the event, a Vue component uses the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API" rel="noopener noreferrer"&gt;Fetch API&lt;/a&gt; to make a request to grab all of the relevant data and display it on the screen.&lt;/p&gt;

&lt;p&gt;Once the user has successfully created a crawl and invited some friends, Pusher Channels allow everyone to see the voting in real time without refreshing their browsers. When a user clicks the vote button next to a spot, it makes a request to the application that persists the vote and triggers an event to the Pusher channel. When all the users’ browsers receive the event, the Vue component makes another request to refresh the data on the screen.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8e0teoapci2zgj7yq6y7.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8e0teoapci2zgj7yq6y7.gif" alt="Crawl Vote demo" width="800" height="379"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I had never used Vue’s &lt;a href="https://vuejs.org/v2/guide/transitions.html#List-Move-Transitions" rel="noopener noreferrer"&gt;List Move Transitions&lt;/a&gt; before, but I was really impressed how, with very little CSS, Vue added silky smooth animations as spots moved up and down in the rankings. Magic! ✨&lt;/p&gt;

&lt;p&gt;To identify users, Crawl Vote assigns each person a UUID and stores it in the user’s session. When a user first visits a crawl or refreshes the page, the applications identifies the user directly from the session and renders the data server side. When the user’s browser receives a Pusher event letting it know that it’s time to fetch more data, the UUID is sent along as a token in the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization" rel="noopener noreferrer"&gt;authorization request header&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Crawl Vote is continuously deployed to &lt;a href="https://www.heroku.com/" rel="noopener noreferrer"&gt;Heroku&lt;/a&gt; by &lt;a href="https://travis-ci.com/" rel="noopener noreferrer"&gt;Travis CI&lt;/a&gt; and is served by &lt;a href="https://www.cloudflare.com/" rel="noopener noreferrer"&gt;Cloudflare&lt;/a&gt;. I’m using &lt;a href="https://rollbar.com/" rel="noopener noreferrer"&gt;Rollbar&lt;/a&gt; for exception tracking and &lt;a href="https://papertrailapp.com/" rel="noopener noreferrer"&gt;Papertrail&lt;/a&gt; for logging. The only thing I paid for was the domain name, which came from &lt;a href="https://www.namecheap.com/" rel="noopener noreferrer"&gt;Namecheap&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  What’s next
&lt;/h1&gt;

&lt;p&gt;I’m happy with how things turned out for this MVP, but I have a whole bunch of things I’d love to try with more time. I’d like to increase test coverage, including at least one or two &lt;a href="http://guides.rubyonrails.org/testing.html#implementing-a-system-test" rel="noopener noreferrer"&gt;Rails system tests&lt;/a&gt; that click through my Vue components using &lt;a href="https://developers.google.com/web/updates/2017/04/headless-chrome" rel="noopener noreferrer"&gt;Headless Chrome&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I’d also like to continue improving the UX/UI, including adding autocomplete to the form fields. The categories that Yelp’s &lt;a href="https://www.yelp.com/developers/documentation/v3/autocomplete" rel="noopener noreferrer"&gt;autocomplete endpoint&lt;/a&gt; returns look like they could be useful for the “Find” field, and I’ve been happy working with Google’s &lt;a href="https://developers.google.com/places/web-service/autocomplete" rel="noopener noreferrer"&gt;Place Autocomplete&lt;/a&gt; in the past. The design overall could use a little sprucing up, and I think some thoughtful use of color could help the look and feel better reflect the spirit of the app.&lt;/p&gt;

&lt;p&gt;Finally, it would be great to do some more tweaking with the parameters Crawl Vote sends along to Yelp’s API. There are &lt;a href="https://www.yelp.com/developers/documentation/v3/business_search" rel="noopener noreferrer"&gt;a lot of options&lt;/a&gt;, and I’m not sure I’d want to expose too many to the user since the complexity could turn people away, but I’d love to do some optimizing to make sure Crawl Vote returns a great selection of spots for every query.&lt;/p&gt;

</description>
      <category>pushercontest</category>
      <category>rails</category>
      <category>vue</category>
    </item>
    <item>
      <title>Resist the ‘quick fix’</title>
      <dc:creator>Nick Holden</dc:creator>
      <pubDate>Mon, 12 Mar 2018 15:17:10 +0000</pubDate>
      <link>https://dev.to/nholden/resist-the-quick-fix--4fpl</link>
      <guid>https://dev.to/nholden/resist-the-quick-fix--4fpl</guid>
      <description>&lt;p&gt;I often come across problems that, on the surface, seem like they’d be “quick fixes.” Maybe it’s a bug that could be resolved by tweaking one variable or a new feature that could be added with a change to one branch of a conditional.&lt;/p&gt;

&lt;p&gt;When I notice opportunities for quick fixes, I naturally start thinking through the implementation. Popping open my text editor and making the change is tempting. I like coding; it’s a big part of why I’m a developer. Shipping code makes me feel productive. And because I’m almost always working by myself or on a small team with lots of trust and autonomy, nothing’s stopping me from creating a new branch and hacking away.&lt;/p&gt;

&lt;p&gt;Most of the time though, resisting the temptation to code the quick fix, and writing a public to-do instead, results in better outcomes.&lt;/p&gt;

&lt;p&gt;To-dos take more time upfront but can ultimately lead to less work. A number of times, I’ve shared a to-do and learned that someone else on my team was thinking about a similar problem and addressing both of them at once would be faster and result in a better solution. Other times, I’ve created a to-do about something I think is a problem, but through discussing the to-do, I’ve learned that a change wasn’t necessary after all.&lt;/p&gt;

&lt;p&gt;The visibility that creating a to-do brings to a problem extends beyond getting out of extra work. Getting trapped in a cycle of working on quick fixes in a silo can leave me feeling disorganized and others wondering how I’m spending my time. Making a public to-do shows my team where I’m focusing my energy and allows them to contribute when it makes sense.&lt;/p&gt;

&lt;p&gt;To-dos move the context of a problem about out of my brain and into a centralized place where others can consider it for their own work. A thoughtful explanation of a problem in prose is more discoverable than a commit message, a comment, or “self-documenting” code. It can also bring more perspectives into the conversation by making the problem accessible to those who otherwise wouldn’t be reviewing a code solution.&lt;/p&gt;

&lt;p&gt;Often a “quick” fix isn’t. A few times, I’ve started coding a fix, and as time ticked away, I’ve found myself so deep down a thread in the codebase that I forget about the original problem I set out to solve. The exercise of unpacking a problem in English can shake out nuances about the problem that can prevent journeys down rabbit holes.&lt;/p&gt;

&lt;p&gt;To-dos lessen cognitive load, too. While working on one problem, it’s easy to find a handful of other problems, and defaulting to quick fixes would force me to keep all of those in my head. Noting those problems in to-dos along the way and moving back to the original one keeps me focused.&lt;/p&gt;

&lt;p&gt;All that said, sometimes, issues pop up that seem like clear candidates for quick fixes. Even though I default to to-dos, sometimes I’ll use my discretion and start by poking at some code. In those cases, timeboxing is helpful. If after 20 to 30 minutes I’m not feeling very close to some sort of resolution, I’ll stop and create a to-do, even if I intend to go right back to working on the same solution.&lt;/p&gt;

&lt;p&gt;But most of the time, starting with a public to-do instead of a quick fix turns out better for my code and for the people I work with.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Cover image by Trilok Rangan on &lt;a href="https://flic.kr/p/9zFdsk" rel="noopener noreferrer"&gt;Flickr&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>programming</category>
      <category>productivity</category>
      <category>writing</category>
      <category>culture</category>
    </item>
    <item>
      <title>Continuously deploy custom Heroku error and maintenance pages from your app’s repository</title>
      <dc:creator>Nick Holden</dc:creator>
      <pubDate>Mon, 29 Jan 2018 16:44:34 +0000</pubDate>
      <link>https://dev.to/nholden/continuously-deploy-custom-heroku-error-and-maintenance-pages-from-your-apps-repository-13kn</link>
      <guid>https://dev.to/nholden/continuously-deploy-custom-heroku-error-and-maintenance-pages-from-your-apps-repository-13kn</guid>
      <description>&lt;p&gt;If you’ve accidentally deployed some bad code to Heroku, you may be familiar with &lt;a href="https://www.herokucdn.com/error-pages/application-error.html" rel="noopener noreferrer"&gt;Heroku’s application error page&lt;/a&gt;. Or maybe you’ve needed to put your application into maintenance mode and you’ve seen &lt;a href="https://www.herokucdn.com/error-pages/maintenance-mode.html" rel="noopener noreferrer"&gt;Heroku's maintenance mode page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Both of these pages are &lt;a href="https://devcenter.heroku.com/articles/error-pages#customize-pages" rel="noopener noreferrer"&gt;customizable via config vars&lt;/a&gt;. All you need are publicly accessible, static HTML pages living somewhere outside of your application. Heroku recommends hosting them on Amazon S3, but it can be easy to upload pages there, forget about them, and have them fall out of sync with the rest of your application.&lt;/p&gt;

&lt;p&gt;One way to avoid losing track of custom error and maintenance pages is to have them continuously deployed from your application’s repository. Here’s one way to do that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pick a directory to serve the pages from
&lt;/h2&gt;

&lt;p&gt;This could be a new directory in your repository or one that already exists. Be aware that for whichever directory you pick, all of its files will be publicly accessible.&lt;/p&gt;

&lt;p&gt;My application, &lt;a href="https://github.com/nholden/monday-miles" rel="noopener noreferrer"&gt;Monday Miles&lt;/a&gt;, is a Rails application, which means that the &lt;code&gt;/public&lt;/code&gt; directory is already publicly accessible. Serving a mirror of it didn’t seem like a big deal in my case, so I chose to store my error and maintenance pages in &lt;code&gt;/public&lt;/code&gt;. Other options include a separate &lt;code&gt;/pages&lt;/code&gt; directory or perhaps a directory named after the domain from which you plan to serve these pages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add your pages to the directory
&lt;/h2&gt;

&lt;p&gt;The pages should be static HTML pages. While you won’t be able to access assets from elsewhere in your application (like from the Asset Pipeline for Rails), you can upload images and CSS into the same directory and reference them with relative paths.&lt;/p&gt;

&lt;p&gt;I already had a &lt;a href="https://github.com/nholden/monday-miles/blob/master/public/500.html" rel="noopener noreferrer"&gt;&lt;code&gt;/public/500.html&lt;/code&gt;&lt;/a&gt; page for Monday Miles, so I reused that for my error page. I tweaked the copy to create my &lt;a href="https://github.com/nholden/monday-miles/blob/master/public/maintenance.html" rel="noopener noreferrer"&gt;&lt;code&gt;/public/maintenance.html&lt;/code&gt;&lt;/a&gt; page.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create a new Netlify app
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.netlify.com/" rel="noopener noreferrer"&gt;Netlify&lt;/a&gt; is an awesome service for hosting static sites. It can do some fancy stuff if you’re using a static site generator like Jekyll or Middleman, but it’s also great for hosting plain old directories of static HTML files. My favorite advantage of Netlify over GitHub Pages is that it provides free, one-click HTTPS for custom domains via Let’s Encrypt.&lt;/p&gt;

&lt;p&gt;Once you’re signed up, click “New site from Git.” Choose your Git provider (as of writing, GitHub, GitLab, and Bitbucket are supported), select your repository, and choose the branch to deploy from (probably &lt;code&gt;master&lt;/code&gt;). To ensure that you’re only exposing the desired directory, you can either enter that directory in to the “Publish directory” field or add a super simple &lt;a href="https://www.netlify.com/docs/continuous-deployment/#deploy-contexts" rel="noopener noreferrer"&gt;netlify.toml&lt;/a&gt; configuration file to the root of your repository (&lt;a href="https://github.com/nholden/monday-miles/blob/master/netlify.toml" rel="noopener noreferrer"&gt;here's mine&lt;/a&gt; for Monday Miles). Since you’re not using a static site generator, you don’t need to set a build command. Click “Deploy site,” and verify that your pages are live at the provided *.netlify.com domain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Set up your custom domain (optional)
&lt;/h2&gt;

&lt;p&gt;Heroku will serve your error and maintenance pages within an iFrame, so it’s unlikely that your users will notice if the pages are served from *.netlify.com. However, Netlify makes it so easy to get started with a custom domain that I think it’s worth the couple extra minutes.&lt;/p&gt;

&lt;p&gt;On the Overview page for your new Netlify app, click “Set up a custom domain” and follow the instructions to add a custom domain (I chose public.mondaymiles.com). If your domain lives with a provider that provides a CDN like Cloudflare, be sure to set the new CNAME record for “DNS only,” since Netlify has its own CDN. Follow the instructions to provision a Let’s Encrypt certificate and enable HTTPS.&lt;/p&gt;

&lt;p&gt;If you want any requests for non-existent files at your Netlify domain to redirect users to your main application, you’ll need to add a &lt;a href="https://www.netlify.com/docs/redirects/" rel="noopener noreferrer"&gt;&lt;code&gt;_redirects&lt;/code&gt; file&lt;/a&gt; to your Netlify publish directory. &lt;a href="https://github.com/nholden/monday-miles/blob/master/public/_redirects" rel="noopener noreferrer"&gt;Here's what mine looks like&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configure Heroku
&lt;/h2&gt;

&lt;p&gt;Set the &lt;code&gt;ERROR_PAGE_URL&lt;/code&gt; and &lt;code&gt;MAINTENANCE_PAGE_URL&lt;/code&gt; config vars to point at your error and maintenance pages on Netlify. You an do this through the &lt;a href="https://devcenter.heroku.com/articles/error-pages#configure-your-application" rel="noopener noreferrer"&gt;Heroku CLI&lt;/a&gt; or through your application’s dashboard on the “Settings” page. Remember to do this for all of your applications if you’re using &lt;a href="https://devcenter.heroku.com/articles/pipelines" rel="noopener noreferrer"&gt;Heroku Pipelines&lt;/a&gt;. Follow &lt;a href="https://devcenter.heroku.com/articles/error-pages#testing" rel="noopener noreferrer"&gt;Heroku’s instructions to test your error and maintenance pages&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;And that’s it! Anytime you update those pages in your application’s repository, they’ll be updated on Netlify. Instead of seeing the generic Heroku error and maintenance mode pages, your users will see the custom pages you created (hopefully not too often!).&lt;/p&gt;

</description>
      <category>heroku</category>
      <category>deployment</category>
      <category>github</category>
      <category>devops</category>
    </item>
    <item>
      <title>Try ending today with a failing test for a great start tomorrow</title>
      <dc:creator>Nick Holden</dc:creator>
      <pubDate>Mon, 15 Jan 2018 15:45:26 +0000</pubDate>
      <link>https://dev.to/nholden/try-ending-today-with-a-failing-test-for-a-great-start-tomorrow-3i8j</link>
      <guid>https://dev.to/nholden/try-ending-today-with-a-failing-test-for-a-great-start-tomorrow-3i8j</guid>
      <description>&lt;p&gt;Tying up loose ends at the end of the day feels nice. Completing a task, committing my work, and shutting my laptop leaves me with a satisfying sense of accomplishment that sticks with me through the evening. And on those days when I grapple with a problem for a while and don’t come to any resolution, I can feel a little defeated.&lt;/p&gt;

&lt;p&gt;While it’s nice to watch my test suite pass and call it a day, I’ve found that some of my most productive mornings come when I’ve left myself a failing test the night before.&lt;/p&gt;

&lt;p&gt;Mornings are a great time to get caught up and plan. But it can be challenging to transition from organizing and digesting to opening a text editor and getting into a flow state. When I get some momentum working on a tricky problem later in the day, I want to bottle it up for morning. I can get close with one failing test.&lt;/p&gt;

&lt;p&gt;Starting with a failing test means I know exactly what to work on: making it pass. Compared to an item on a to-do list, a failing test is better at returning my mind to the state it was in when I was focused deeply on the task. Maybe I even spent a bit of time the night before thinking about how I might approach it, so the implementation is a quick win.&lt;/p&gt;

&lt;p&gt;It doesn’t work all the time. Sometimes, priorities change overnight, and I have to put the test aside and focus on something else. But often, I find that a little time spent writing a failing test at the end of the day lays the foundation for a great morning.&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>testing</category>
      <category>programming</category>
      <category>motivation</category>
    </item>
  </channel>
</rss>
