Jordan Elverhttp://jordanelver.co.uk2021-04-30T01:00:00+01:00Jordan ElverRunning custom Ruby with Cypresshttp://jordanelver.co.uk/blog/2021/04/30/running-custom-ruby-with-cypress/2021-04-30T01:00:00+01:002022-10-08T11:23:17+01:00Jordan Elver<p>I've been using <a href="https://www.cypress.io">Cypress</a> on a recent project to do end-to-end testing.
From the brief introduction I've had so far, I'm impressed. The debugging
experience seems like it's going to be considerably better than with tests
written using <a href="https://github.com/teamcapybara/capybara">Capybara</a>. Capybara is great, but Cypress's visual
debugging is hard to compete against.</p>
<h2><a href="#running-custom-ruby" id="running-custom-ruby">Running custom Ruby</a></h2>
<p>This project uses <a href="https://github.com/shakacode/cypress-on-rails">CypressOnRails</a> to build a bridge between
tests running within Cypress and Ruby-land, where the Rails app lives. In one
particular test I wanted to turn a feature switch on and off (using
<a href="https://github.com/jnunemaker/flipper">Flipper</a>). This required me calling some Ruby code from the Cypress
test.</p>
<div class="highlight"><pre class="highlight ruby"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre><span class="no">Flipper</span><span class="p">.</span><span class="nf">enable</span><span class="p">(</span><span class="ss">:new_feature</span><span class="p">)</span>
</pre></td></tr></tbody></table></code></pre></div>
<p>This is possible through <code>cy.appEval</code> added by CypressOnRails.</p>
<div class="highlight"><pre class="highlight javascript"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
</pre></td><td class="rouge-code"><pre><span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">Tests something with a feature switch turned on</span><span class="dl">'</span><span class="p">,</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
<span class="nx">cy</span><span class="p">.</span><span class="nx">appEval</span><span class="p">(</span><span class="dl">"</span><span class="s2">Flipper.enable(:new_feature)</span><span class="dl">"</span><span class="p">).</span><span class="nx">then</span><span class="p">(()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">cy</span><span class="p">.</span><span class="nx">visit</span><span class="p">(</span><span class="dl">'</span><span class="s1">/a/page</span><span class="dl">'</span><span class="p">)</span>
<span class="c1">// select some elements</span>
<span class="c1">// assert some things</span>
<span class="p">})</span>
<span class="p">})</span>
</pre></td></tr></tbody></table></code></pre></div><h2><a href="#making-it-reusable" id="making-it-reusable">Making it reusable</a></h2>
<p>Cypress supports adding functions that can be re-used inside the
<code>spec/cypress/support/commands.js</code> file.</p>
<div class="highlight"><pre class="highlight javascript"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
</pre></td><td class="rouge-code"><pre><span class="nx">Cypress</span><span class="p">.</span><span class="nx">Commands</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="dl">'</span><span class="s1">enableFeature</span><span class="dl">'</span><span class="p">,</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">flag</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">cy</span><span class="p">.</span><span class="nx">appEval</span><span class="p">(</span><span class="dl">'</span><span class="s1">Flipper.enable(:</span><span class="dl">'</span> <span class="o">+</span> <span class="nx">flag</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">)</span><span class="dl">'</span><span class="p">)</span>
<span class="p">});</span>
<span class="nx">Cypress</span><span class="p">.</span><span class="nx">Commands</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="dl">'</span><span class="s1">disableFeature</span><span class="dl">'</span><span class="p">,</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">flag</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">cy</span><span class="p">.</span><span class="nx">appEval</span><span class="p">(</span><span class="dl">'</span><span class="s1">Flipper.disable(:</span><span class="dl">'</span> <span class="o">+</span> <span class="nx">flag</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">)</span><span class="dl">'</span><span class="p">)</span>
<span class="p">);</span>
</pre></td></tr></tbody></table></code></pre></div>
<p>Now we can call these functions throughout all our tests.</p>
<div class="highlight"><pre class="highlight javascript"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
</pre></td><td class="rouge-code"><pre><span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">Tests something with a feature switch turned on</span><span class="dl">'</span><span class="p">,</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
<span class="nx">cy</span><span class="p">.</span><span class="nx">enableFeature</span><span class="p">(</span><span class="dl">'</span><span class="s1">new_feature</span><span class="dl">'</span><span class="p">)</span> <span class="c1">// Turn it on</span>
<span class="nx">cy</span><span class="p">.</span><span class="nx">visit</span><span class="p">(</span><span class="dl">'</span><span class="s1">/a/page</span><span class="dl">'</span><span class="p">)</span>
<span class="c1">// select some elements</span>
<span class="c1">// assert some things</span>
<span class="nx">cy</span><span class="p">.</span><span class="nx">disableFeature</span><span class="p">(</span><span class="dl">'</span><span class="s1">new_feature</span><span class="dl">'</span><span class="p">)</span> <span class="c1">// Turn it off again</span>
<span class="p">})</span>
</pre></td></tr></tbody></table></code></pre></div>TIL about Markdown reference style linkshttp://jordanelver.co.uk/blog/2021/03/13/til-about-markdown-reference-style-links/2021-03-13T00:00:00+00:002021-03-13T13:23:59+00:00Jordan Elver<p>After years of writing Markdown I only recently discovered that you can use any
identifier in a reference style link. I had made the incorrect assumption that
the identifier <em>had</em> to be a number, but no!</p>
<h2><a href="#before" id="before">Before</a></h2>
<div class="markdown"><code><pre>
[I am an example link][1]
[1]: https://example.com
</pre></code></div>
<h2><a href="#after" id="after">After</a></h2>
<div class="markdown"><code><pre>
I am an example link][example]
[example]: https://example.com
</pre></code></div>
<p>I've spent a long time re-ordering/numbering my reference links in the past but
that should no longer be necessary.</p>
Converting ebook formats with ebook-converthttp://jordanelver.co.uk/blog/2021/02/28/converting-ebook-formats-with-ebook-convert/2021-02-28T00:00:00+00:002021-02-28T08:44:09+00:00Jordan Elver<p>I need to convert between ebook formats on a regular basis so that I can read
them on my Kindle, and I prefer to do this on the command line as it's quicker
and easier.</p>
<p>I found a utility called <code>ebook-convert</code> that comes included as part of
<a href="https://calibre-ebook.com">Calibre</a>, the ebook management behemouth, and it suits the task
superbly. It's a shame to have to install such a big application to get access
to a small utility, but I do sometimes use the metadata editing features of
Calibre, so it's a price I'm willing to pay.</p>
<p>It's very easy to use and hasn't failed me up to now.</p>
<div class="highlight"><pre class="highlight shell"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre>ebook-convert <span class="k">*</span>.epub .mobi <span class="c"># from epub to mobi</span>
ebook-convert <span class="k">*</span>.mobi .epub <span class="c"># from mobi to epub</span>
</pre></td></tr></tbody></table></code></pre></div>
<p>Other formats are also supported.</p>
<p>Installable on macOS using <code>brew install calibre</code>.</p>
<p>You might want to link the <code>ebook-convert</code> binary to <code>/usr/local/bin</code> for easier
usage after installation: <code>ln -s /Applications/calibre.app/Contents/MacOS/ebook-convert /usr/local/bin/ebook-convert</code></p>
Combine PDFs on the command line with pdfunitehttp://jordanelver.co.uk/blog/2021/01/30/combine-pdfs-on-the-command-line-with-pdfunite/2021-01-30T00:00:00+00:002021-01-30T15:09:37+00:00Jordan Elver<p>I often need a quick way to combine PDF pages into a single file. I've used
macOS's Preview in the past but found it clunky so I went looking for a command
line alternative.</p>
<p>There seem to be many ways to do this, but I've found the <code>pdfunite</code> utility
to be the least friction method for me.</p>
<div class="highlight"><pre class="highlight shell"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>pdfunite file1.pdf file2.pdf destination.pdf
</pre></td></tr></tbody></table></code></pre></div>
<p>Files are combined in the order you supply them in the arguments.</p>
<p>Thankfully <code>pdfunite</code> is easily installable on macOS, as part of the
<a href="https://poppler.freedesktop.org/">Poppler</a> project, using <code>brew install poppler</code>.</p>
Project specific .gemrc files using the GEMRC environment variablehttp://jordanelver.co.uk/blog/2020/12/06/project-specific-gemrc-files-using-the-gemrc-environment-variable/2020-12-06T00:00:00+00:002020-12-06T16:20:50+00:00Jordan Elver<p>Recently I needed to add a new <a href="https://guides.rubygems.org/command-reference/#gem-sources">source entry</a> to my <code>.gemrc</code> for a
private <a href="https://rubygems.org/">RubyGems</a> server. I <a href="https://github.com/jordelver/dotfiles/blob/master/gemrc">commit my <code>.gemrc</code> to
Git</a>, so this was a problem. Not only because I don't want
project specific changes cluttering up my <code>.gemrc</code> (I don't), but also because
the URL of the source contains secrets that shouldn't be shared.</p>
<p>My default <code>gemrc</code> <code>:sources:</code> entry looked like this.</p>
<div class="highlight"><pre class="highlight yaml"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre><span class="nn">---</span>
<span class="s">:sources:</span>
<span class="pi">-</span> <span class="s">https://rubygems.org</span>
</pre></td></tr></tbody></table></code></pre></div>
<p>There doesn't seem to be a supported way to have per-project <code>.gemrc</code> files but
you <em>can</em> <a href="http://www.soulcutter.com/articles/hiding-gemrc-credentials-in-dotfiles.html">override the file using the <code>GEMRC</code></a>
environment variable. So if you can set a per-project <code>GEMRC</code> environment
variable, you <em>can</em> have a per-project <code>.gemrc</code>.</p>
<p>I use <a href="https://direnv.net/"><code>direnv</code></a> to set environment variables for my projects by
creating a <code>.envrc</code> file in each of my project directories. So I can also use
that here. I just add a line like this.</p>
<div class="highlight"><pre class="highlight shell"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre><span class="nb">export </span><span class="nv">GEMRC</span><span class="o">=</span>path/to/project/specific/gemrc
</pre></td></tr></tbody></table></code></pre></div>
<p>And in that file I add the extra key to <code>:sources:</code></p>
<div class="highlight"><pre class="highlight yaml"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
</pre></td><td class="rouge-code"><pre><span class="nn">---</span>
<span class="s">:sources:</span>
<span class="pi">-</span> <span class="s">https://rubygems.org</span>
<span class="pi">-</span> <span class="s">https://user:pass@custom.gem.server</span>
</pre></td></tr></tbody></table></code></pre></div>
<p>I need to add the original <code>https://rubygems.org</code> source too because although
the <code>~/.gemrc</code> will still be read, the keys in the project specific file will
replace any keys with the same name completely - they are not merged. You can
see what values <code>gem</code> is using by running <code>gem environment</code>. Great for
debugging.</p>
<p>I think using <code>GEMRC</code> for this purpose is a reasonable workaround for
per-project <code>.gemrc</code> files.</p>
Vim plugins don't have to be hardhttp://jordanelver.co.uk/blog/2020/11/20/vim-plugins-dont-have-to-be-hard/2020-11-20T00:00:00+00:002020-11-20T20:26:41+00:00Jordan Elver<p>When pairing on something I will often insert <a href="https://docs.github.com/en/free-pro-team@latest/github/committing-changes-to-your-project/creating-a-commit-with-multiple-authors#creating-co-authored-commits-on-the-command-line">GitHub coauthorship
information</a> into a commit by adding <code>Co-authored-by: name
<name@example.com></code> to the bottom of a commit message. Both parties then get
credit for the work. However, it's a bit tedious to do this manually.</p>
<p>I went looking for a solution that could be triggered from within Vim and <a href="https://github.com/maxjacobson/vim-fzf-coauthorship">found
this nice little plugin</a>. It gives you a command
<code>:Coauthorship</code> which when run will show a list of Git repo contributors from
whom you can choose using the <code>fzf</code> fuzzy finder. It's really neat, and does
exactly what I was looking for.</p>
<p>Here is the Vimscript in its entirety.</p>
<div class="highlight"><pre class="highlight viml"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
</pre></td><td class="rouge-code"><pre><span class="k">function</span><span class="p">!</span> AttributeCoauthorship<span class="p">(</span>nameAndEmail<span class="p">)</span>
<span class="k">let</span> attribution <span class="p">=</span> <span class="s2">"Co-authored-by: "</span> <span class="p">.</span> <span class="nv">a:nameAndEmail</span>
<span class="k">silent</span> <span class="k">put</span> <span class="p">=</span>attribution
<span class="k">endfunction</span>
<span class="k">function</span><span class="p">!</span> Coauthorship<span class="p">()</span>
<span class="k">call</span> fzf#run<span class="p">({</span>
<span class="se"> \</span> <span class="s1">'source'</span><span class="p">:</span> <span class="s1">'git log --pretty="%an <%ae>" | sort | uniq'</span><span class="p">,</span>
<span class="se"> \</span> <span class="s1">'sink'</span><span class="p">:</span> <span class="k">function</span><span class="p">(</span><span class="s1">'AttributeCoauthorship'</span><span class="p">),</span>
<span class="se"> \</span> <span class="s1">'options'</span><span class="p">:</span> <span class="s2">"--preview 'git log -1 --author {} --pretty=\"authored %h %ar:%n%n%B\"'"</span>
<span class="se"> \</span> <span class="p">})</span>
<span class="k">endfunction</span>
command<span class="p">!</span> Coauthorship <span class="k">call</span> Coauthorship<span class="p">()</span>
</pre></td></tr></tbody></table></code></pre></div>
<p>What struck me is just how little code it takes to get something like this
working. Of course, it's relying on external programs (<code>git</code>, <code>sort</code>, <code>uniq</code>)
and libraries (the <code>fzf</code> vim plugin code), but the <em>plumbing</em> to compose this
is small, concise, and easy to build upon. It feels like the unix philosophy in
action.</p>
<p>I've never gone as far as programming my editor for fear of thinking "this
isn't for me, its too hard" but this plugin has got me thinking that it <em>might
just</em> be for me after all.</p>
RSpec-like doc format output in Elixir testshttp://jordanelver.co.uk/blog/2020/10/24/rspec-like-doc-format-output-in-elixir-tests/2020-10-24T01:00:00+01:002020-11-20T18:40:43+00:00Jordan Elver<p>When running RSpec tests <a href="https://relishapp.com/rspec/rspec-core/v/3-9/docs/command-line/format-option">you can pass a <code>--format</code> flag</a> to <code>rspec</code>
to format test output in a more verbose style. The default output looks like this.</p>
<div class="highlight"><pre class="highlight plaintext"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>....F.....*.....
</pre></td></tr></tbody></table></code></pre></div>
<p>If you pass <code>--format documentation</code> it will look more like this.</p>
<div class="highlight"><pre class="highlight plaintext"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
</pre></td><td class="rouge-code"><pre>something
does something that passes
does something that fails (FAILED - 1)
does something that is pending (PENDING: Not Yet Implemented)
</pre></td></tr></tbody></table></code></pre></div>
<p>I find this output really helpful. It helps to visualise the <em>shape</em> of the
tests, and guides naming because you can see the visual hierarchy of how things
fit together.</p>
<h2><a href="#elixir-and-exunit" id="elixir-and-exunit">Elixir and ExUnit</a></h2>
<p>You can achieve a very similar result to RSpec by passing <code>--trace</code> to <code>mix
test</code>.</p>
<div class="highlight"><pre class="highlight plaintext"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
</pre></td><td class="rouge-code"><pre>mix test --trace
TestApp.TimeDateHelper
* test relative_format/1 single hour, multiple minutes (0.00ms)
* test relative_format/1 greater than an hour (0.00ms)
* test relative_format/1 less than a minute (0.00ms)
</pre></td></tr></tbody></table></code></pre></div>
<p>You can make this change across the whole test suite by changing the call to
<code>ExUnit.start/1</code> to include a trace option. This is usually in your
<code>test/test_helper.exs</code> file.</p>
<div class="highlight"><pre class="highlight elixir"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre><span class="no">ExUnit</span><span class="o">.</span><span class="n">start</span><span class="p">(</span><span class="ss">trace:</span> <span class="no">true</span><span class="p">)</span>
</pre></td></tr></tbody></table></code></pre></div>
<p>Be aware though, this might not be what you want. From <a href="https://hexdocs.pm/ex_unit/ExUnit.html#configure/1-options">the ExUnit
docs</a>:</p>
<blockquote style="text-align: left; margin: 0;">
<p style="margin: 0;">
...sets ExUnit into trace mode, <strong>this sets :max_cases to 1</strong>
and prints each test case and test while running...
</p>
</blockquote>
<p>Setting <code>trace: true</code> will also set <code>max_cases: 1</code> which will reduce the amount
of tests that are run in parallel.</p>
<p>So this could slow down your test suite. Be careful!</p>
Using gitattributes to improve git outputhttp://jordanelver.co.uk/blog/2020/09/30/gitattributes/2020-09-30T01:00:00+01:002020-11-20T18:40:43+00:00Jordan Elver<p>From the git man page:</p>
<blockquote>
<p>A gitattributes file is a simple text file that gives attributes to pathnames.</p>
</blockquote>
<p><a href="https://www.git-scm.com/docs/gitattributes"><code>gitattributes</code></a> allows you to tell git that files should be
treated in certain ways. You can use git attributes to apply various attributes
but we're focussing on <code>diff</code>. For example, this tells git that files ending in
<code>*.ex</code> should be treated as Elixir code during diff operations.</p>
<div class="highlight"><pre class="highlight plaintext"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>*.ex diff=elixir
</pre></td></tr></tbody></table></code></pre></div>
<p>I couldn't get the standard <code>~/.gitattributes</code> file location to work for me so I
decided to take this opportunity to standardise all my git config under
<code>~/.config/git</code> instead.</p>
<p><strong>TIP</strong> You can use <a href="https://www.git-scm.com/docs/git-check-attr"><code>git check-attr --all -- path/to/file</code></a> to
check which attributes are applying to a file. This is very useful during setup.</p>
<p>So what effect does this have?</p>
<h2><a href="#hunk-output" id="hunk-output">Hunk output</a></h2>
<p>Here's a simple example. On line 5 we can see a change, but that isn't the
important part. The interesting difference is what's shown on line 1 after the
filename. In this case it's <code>defmodule TestWeb.ListController</code> which in the
Elixir <strong>module</strong> in which the change has been made.</p>
<div class="highlight"><pre class="highlight diff"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
</pre></td><td class="rouge-code"><pre><span class="err">@</span> path/to/file.ex:25 @ defmodule TestWeb.ListController do
{:ok, _list} = Lists.delete_list(list)
conn
<span class="gi">+ |> put_flash(:info, "List deleted successfully.")
</span><span class="gd">- |> put_flash(:info, "List deleted successfully")
</span> |> redirect(to: Routes.list_path(conn, :index))
end
end
</pre></td></tr></tbody></table></code></pre></div>
<p>If we add <code>*.ex diff=elixir</code> to the attributes file we see <code>def delete(conn,
%{"id" => id})</code>, which is the <strong>function</strong> containing the change. Far more
specific.</p>
<div class="highlight"><pre class="highlight diff"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
</pre></td><td class="rouge-code"><pre><span class="err">@</span> path/to/file.ex:25 @ def delete(conn, %{"id" => id}) do
{:ok, _list} = Lists.delete_list(list)
conn
<span class="gi">+ |> put_flash(:info, "List deleted successfully.")
</span><span class="gd">- |> put_flash(:info, "List deleted successfully")
</span> |> redirect(to: Routes.list_path(conn, :index))
end
end
</pre></td></tr></tbody></table></code></pre></div><h2><a href="#function-context" id="function-context">--function-context</a></h2>
<p>Adding this setting also changes the way that
<a href="https://www.git-scm.com/docs/git-diff#Documentation/git-diff.txt---function-context"><code>--function-context</code></a> works. What is <code>--function-context</code>? It
can be passed to <code>git diff</code> (and other subcommands) and will show the full
change in the full context of where the change has been made. By default this
means it will show the <em>full</em> module, which is probably not what you want most
of the time.</p>
<div class="highlight"><pre class="highlight diff"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
</pre></td><td class="rouge-code"><pre><span class="err">@</span> path/to/file.ex:25 @ def delete(conn, %{"id" => id}) do
defmodule TestWeb.ListController do
use TestWeb, :controller
# ...other functions removed for brevity
def delete(conn, %{"id" => id}) do
list = Lists.get_list!(id)
{:ok, _list} = Lists.delete_list(list)
conn
<span class="gi">+ |> put_flash(:info, "List deleted successfully.")
</span><span class="gd">- |> put_flash(:info, "List deleted successfully")
</span> |> redirect(to: Routes.list_path(conn, :index))
end
end
</pre></td></tr></tbody></table></code></pre></div>
<p>However, with the change to <code>gitattributes</code>, the "function context" becomes the
<em>actual</em> function.</p>
<div class="highlight"><pre class="highlight diff"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
</pre></td><td class="rouge-code"><pre><span class="err">@</span> path/to/file.ex:25 @ def delete(conn, %{"id" => id}) do
def delete(conn, %{"id" => id}) do
list = Lists.get_list!(id)
{:ok, _list} = Lists.delete_list(list)
conn
<span class="gi">+ |> put_flash(:info, "List deleted successfully.")
</span><span class="gd">- |> put_flash(:info, "List deleted successfully")
</span> |> redirect(to: Routes.list_path(conn, :index))
end
end
</pre></td></tr></tbody></table></code></pre></div>
<p>It's strange that a lot of the available <code>gitattibutes</code> are not defaults in git.
But with a few simple tweaks you can get more useful git output.</p>
How to get a list of filenames from a Plex playlisthttp://jordanelver.co.uk/blog/2020/08/31/how-to-get-a-list-of-filenames-from-a-plex-playlist/2020-08-31T01:00:00+01:002020-08-31T18:50:45+01:00Jordan Elver<p>I recently wanted to delete some files from my Plex server. Plex has a feature
to allow deleting of files through the web interface, but it makes me feel
slightly uneasy having that feature turned on due to security concerns, so I
chose to keep it off, and for this one-off task I decided to delete the files
manually.</p>
<p>I needed a list of filenames so I started by adding all the files I wanted to
delete to a playlist called "To Delete". Plex uses a SQLite database to store
metadata. This is what I used to get the information from the playlist.</p>
<p>Connect to the SQLite database like this:</p>
<div class="highlight"><pre class="highlight shell"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>sqlite3 <span class="s1">'/var/lib/plexmediaserver/Library/Application Support/Plex Media Server/Plug-in Support/Databases/com.plexapp.plugins.library.db'</span>
</pre></td></tr></tbody></table></code></pre></div>
<p>We can then list all playlists. The <code>15</code> in <code>metadata_type = 15</code> is for
playlists.</p>
<div class="highlight"><pre class="highlight sql"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre><span class="n">sqlite</span><span class="o">></span> <span class="k">select</span> <span class="n">title</span> <span class="k">from</span> <span class="n">metadata_items</span> <span class="k">where</span> <span class="n">metadata_type</span> <span class="o">=</span> <span class="mi">15</span><span class="p">;</span>
</pre></td></tr></tbody></table></code></pre></div>
<p>You can see that the output includes the "To Delete" playlist.</p>
<div class="highlight"><pre class="highlight plaintext"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
</pre></td><td class="rouge-code"><pre>Christmas
Pixar
Comedy classics
Star Wars
To Delete
</pre></td></tr></tbody></table></code></pre></div>
<p>Ideally, we'd like a list of filenames in a text file. This makes it easy to
process later. The <code>sqlite</code> client has a <code>.output</code> command that allows you to
redirect output from queries to an external file.</p>
<div class="highlight"><pre class="highlight sql"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre><span class="n">sqlite</span><span class="o">></span> <span class="p">.</span><span class="k">output</span> <span class="n">files</span><span class="o">-</span><span class="k">to</span><span class="o">-</span><span class="k">delete</span><span class="p">.</span><span class="n">txt</span>
</pre></td></tr></tbody></table></code></pre></div>
<p>When we run subsequent queries, the output will end up in <code>files-to-delete.txt</code>.</p>
<p>Now, we can run a query to get a list of filenames. I won't pretend to
understand the whole structure of this query, but it works for me. Thanks to
<a href="https://www.reddit.com/r/PleX/comments/3cbku8/export_list_of_file_paths_in_a_plex_playlist_for/">this Reddit thread</a>. Note the <code>where metadata_items.title = 'To
Delete'</code> clause where we specify the playlist name.</p>
<div class="highlight"><pre class="highlight sql"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
</pre></td><td class="rouge-code"><pre><span class="n">sqlite</span><span class="o">></span> <span class="k">select</span> <span class="n">file</span> <span class="k">from</span> <span class="n">media_parts</span> <span class="k">left</span> <span class="k">outer</span> <span class="k">join</span> <span class="n">media_items</span> <span class="k">on</span>
<span class="n">media_items</span><span class="p">.</span><span class="n">id</span> <span class="o">=</span> <span class="n">media_parts</span><span class="p">.</span><span class="n">media_item_id</span> <span class="k">left</span> <span class="k">outer</span> <span class="k">join</span>
<span class="n">play_queue_generators</span> <span class="k">on</span> <span class="n">play_queue_generators</span><span class="p">.</span><span class="n">metadata_item_id</span> <span class="o">=</span>
<span class="n">media_items</span><span class="p">.</span><span class="n">metadata_item_id</span> <span class="k">left</span> <span class="k">outer</span> <span class="k">join</span> <span class="n">metadata_items</span> <span class="k">on</span>
<span class="n">metadata_items</span><span class="p">.</span><span class="n">id</span> <span class="o">=</span> <span class="n">play_queue_generators</span><span class="p">.</span><span class="n">playlist_id</span>
<span class="k">where</span> <span class="n">metadata_items</span><span class="p">.</span><span class="n">title</span> <span class="o">=</span> <span class="s1">'To Delete'</span><span class="p">;</span>
</pre></td></tr></tbody></table></code></pre></div>
<p>The <code>files-to-delete.txt</code> file now contains a list of filenames for the movies
on the "To Delete" playlist. We can now use that list to delete the files. In my
case, I used a loop in fish shell.</p>
<div class="highlight"><pre class="highlight shell"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre><span class="k">for </span>f <span class="k">in</span> <span class="o">(</span><span class="nb">cat </span>files-to-delete.txt<span class="o">)</span>
<span class="nb">rm</span> <span class="nt">-i</span> <span class="nt">-v</span> <span class="nv">$f</span>
end
</pre></td></tr></tbody></table></code></pre></div>
<p>There are certainly many ways to do this, but this is how I did it.</p>
Renaming files Vim-stylehttp://jordanelver.co.uk/blog/2020/07/12/renaming-files-vim-style/2020-07-12T01:00:00+01:002020-07-12T15:03:31+01:00Jordan Elver<p>I’ve <a href="https://jordanelver.co.uk/blog/2017/01/31/renaming-files-like-a-pro/">previously written about renaming files</a> with the <code>rename</code>
utility. <code>rename</code> uses sed-like syntax with regular expressions to rename
files, which are very powerful, but can be a bit tricky to get right.</p>
<p>I recently came across <a href="https://github.com/thameera/vimv">vimv</a>, which is a utility (written in bash) to
let you use the full power of Vim to rename files. As a vim user being able to
use the same patterns I would use when writing code is very appealing.</p>
<script id="asciicast-RfVtJfuoplCMZd50vESXbOAFR"
src="https://asciinema.org/a/RfVtJfuoplCMZd50vESXbOAFR.js" async></script>
<p>It's a great idea, and it turns out that this is also not a new idea. Many other
tools have very similar abilities as pointed out by many great <a href="https://twitter.com/MasteringVim/status/1270720407272738818">replies to the
original tweet</a>. Some of tools that offer this ability
include (in order of discovery) the aforementioned vimv; three different file
managers: <a href="https://github.com/ranger/ranger">ranger</a>, <a href="https://github.com/jarun/nnn">nnn</a>, and <a href="https://github.com/vifm/vifm">vifm</a>; <a href="https://github.com/trapd00r/vidir">vidir</a>; and
<a href="http://www.nongnu.org/renameutils/">qmv</a>, from renameutils (around since 2001!).</p>
<script id="asciicast-Scwm0G1Qc70AgaOKhiHdezKSr"
src="https://asciinema.org/a/Scwm0G1Qc70AgaOKhiHdezKSr.js" async></script>
<p>We are spoilt for choice! I already use <code>nnn</code> so I will be making use of it's
"batch rename" feature on my file server. For other cases I think I'll be
reaching for <code>qmv</code> as it's very mature, and simple to use. Try out one that
works for you.</p>
Get video URLs from a YouTube playlisthttp://jordanelver.co.uk/blog/2020/06/11/get-video-urls-from-a-youtube-playlist/2020-06-11T01:00:00+01:002020-06-11T17:58:35+01:00Jordan Elver<p>I wanted to make my own list of YouTube video URLs today, and as far as I can
tell, YouTube doesn't let you do that. The excellent tool
<a href="https://github.com/ytdl-org/youtube-dl"><code>youtube-dl</code></a> came to the rescue, along with a post on
<a href="https://askubuntu.com/questions/1090510/how-to-download-a-playlist-without-the-videos">askubuntu</a> that combines it with <code>jq</code> and <code>sed</code>.</p>
<p>I wanted to output the video URLs <em>and</em> the titles, and it turns out this can be
achieved with <code>jq</code> on it's own. I changed the <code>jq</code> portion and the final
commandline looks like this.</p>
<div class="highlight"><pre class="highlight shell"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre>youtube-dl <span class="nt">--dump-json</span> <span class="nt">--flat-playlist</span> <span class="s2">"https://www.youtube.com/playlist?list=PL46-cKSxMYYCMpzXo6p0Cof8hJInYgohU"</span> <span class="se">\</span>
| jq <span class="nt">-r</span> <span class="s1">'"\(.title)\nhttps://youtu.be/\(.id)\n"'</span>
</pre></td></tr></tbody></table></code></pre></div>
<p>This outputs something like:</p>
<div class="highlight"><pre class="highlight plaintext"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
</pre></td><td class="rouge-code"><pre>Vim Un-Alphabet 01: Teaser
https://youtu.be/7LDlUMMbv6k
Vim Un-Alphabet 02: Help
https://youtu.be/ZTCzWRqR_us
Vim Un-Alphabet 03: Tilde
https://youtu.be/5jMiYtXz2QA
</pre></td></tr></tbody></table></code></pre></div>
<p>Of course, we can pipe the result of this to a file by appending <code>> result.txt</code>
or, in my case, straight to the clipboard with <code>| pbcopy</code>.</p>
Fixing commits with git commit --fixup and git rebase --autosquashhttp://jordanelver.co.uk/blog/2020/06/04/fixing-commits-with-git-commit-fixup-and-git-rebase-autosquash/2020-06-04T01:00:00+01:002020-06-04T14:17:28+01:00Jordan Elver<p>The way I like to make changes in response to a PR review is to make any fixes
to the original commits using a rebase. I don't want commits that "fix typos" in
my history, especially at the feature branch level. There are varying opinions
and approaches to this, but I think rebasing is great tool when used on your <em>own</em>
branches and in moderation.</p>
<p>Standard procedure for this is - 1) Make the code change; 2) Commit the change;
3) Start an interactive rebase; 4) Identify the commit that needs fixing; 5)
Move the new commit underneath it; 6) Change it to "squash". It's quite tedious.</p>
<h2><a href="#fixup-commits" id="fixup-commits">Fixup commits</a></h2>
<p><a href="https://git-scm.com/docs/git-commit#Documentation/git-commit.txt---fixupltcommitgt">Fixup commits</a> are created using <code>git commit --fixup <SHA></code>.
Practically, <code>--fixup</code> associates a new commit with an existing commit so that
when you do an interactive rebase, you don't have to re-order any commits in
order to squash them. And you don't have to change any commit messages. From the
docs:</p>
<blockquote>
<p>Construct a commit message for use with rebase --autosquash.</p>
</blockquote>
<h2><a href="#autosquash" id="autosquash">--autosquash?</a></h2>
<p><a href="https://git-scm.com/docs/git-rebase#Documentation/git-rebase.txt---autosquash"><code>--autosquash</code></a> is a flag to use with <code>rebase</code> and takes everything
a step further. Once you've committed your changes with <code>git commit --fixup
<SHA></code> you can start an interactive rebase as normal, but pass the
<code>--autosquash</code> flag too.</p>
<div class="highlight"><pre class="highlight plaintext"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>git rebase -i --autosquash master
</pre></td></tr></tbody></table></code></pre></div>
<p>Now you won't have to do anything, the rebase will automatically take care of
squashing those commits created with <code>--fixup</code> in the correct order!</p>
<p>You can have this behaviour by default, which seems safe and sensible, by
setting <code>autosquash = true</code> in your <code>~/.gitconfig</code>.</p>
<div class="highlight"><pre class="highlight plaintext"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre>[rebase]
autosquash = true
</pre></td></tr></tbody></table></code></pre></div><h2><a href="#automating-further" id="automating-further">Automating further</a></h2>
<p>I use <code>--fixup</code> so much that I have a helper alias in my <code>~/.gitconfig</code>.</p>
<div class="highlight"><pre class="highlight plaintext"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre>[alias]
fixup = "!git log -n 50 --pretty=format:'%h %s' --no-merges | fzf | cut -c -7 | xargs -o git commit --fixup"
</pre></td></tr></tbody></table></code></pre></div>
<p>This lets me type <code>git fixup</code> and presents a list of my 50 most recent commits
and allows me to search the list using fzf. Once a commit is selected, the SHA
is passed to <code>git commit --fixup</code>.</p>
<p>It works like this:</p>
<script id="asciicast-nyRPY9XaPPtC39ihVDgoN3anH"
src="https://asciinema.org/a/nyRPY9XaPPtC39ihVDgoN3anH.js" async></script>
<p>I hope this makes your commit fixing easier!</p>
History deleting helper for Fish shellhttp://jordanelver.co.uk/blog/2020/05/29/history-deleting-helper-for-fish-shell/2020-05-29T01:00:00+01:002020-06-04T11:27:38+01:00Jordan Elver<p>I <a href="https://jordanelver.co.uk/blog/2020/02/21/deleting-fish-shell-history/">wrote back in February</a> about a neat <code>history</code> command built-in to the Fish
shell that allows you to, amongst other things, delete entries from your
history.</p>
<p>To recap, you use it like this:</p>
<div class="highlight"><pre class="highlight shell"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
</pre></td><td class="rouge-code"><pre><span class="nv">$ </span><span class="nb">history </span>delete <span class="nt">--contains</span> <span class="s2">"mix echo"</span>
<span class="o">[</span>1] <span class="nb">history </span>search <span class="nt">--contains</span> <span class="s2">"mix echo"</span>
<span class="o">[</span>2] mix echo.migrate
<span class="o">[</span>3] mix echo.create
<span class="o">[</span>4] mix echo.reset
<span class="o">[</span>5] mix echo.setup
Enter nothing to cancel the delete, or
Enter one or more of the entry IDs separated by a space, or
Enter <span class="s2">"all"</span> to delete all the matching entries.
<span class="o">[</span>I] Delete which entries? <span class="o">></span>
</pre></td></tr></tbody></table></code></pre></div>
<p>This is really cool, but the only thing missing for me was the ability to
fuzzily find history entries, so I wrote a quick Fish function to combine
<code>history</code> with <a href="https://github.com/junegunn/fzf"><code>fzf</code></a>, my favourite shell program. I also made a small
change to the history command by swapping <code>--contains</code> for <code>--prefix</code> which
makes more sense when combined with <code>fzf</code> as the search is more constrained.</p>
<div class="highlight"><pre class="highlight shell"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre><span class="k">function </span>dh <span class="nt">-d</span> <span class="s2">"Fuzzily delete entries from your history"</span>
<span class="nb">history</span> | fzf | <span class="nb">read</span> <span class="nt">-l</span> item<span class="p">;</span> and <span class="nb">history </span>delete <span class="nt">--prefix</span> <span class="s2">"</span><span class="nv">$item</span><span class="s2">"</span>
end
</pre></td></tr></tbody></table></code></pre></div>
<p>Now you can type <code>dh</code> and fuzzily search for history before being dropped into
the interactive prompt as usual.</p>
<script id="asciicast-hoIxMqnjYe9qjg4uA46EC0rRG"
src="https://asciinema.org/a/hoIxMqnjYe9qjg4uA46EC0rRG.js" async></script>
Protecting Phoenix endpoints with HTTP Basic Auth (on Heroku)http://jordanelver.co.uk/blog/2020/05/01/protecting-phoenix-endpoints-with-http-basic-auth/2020-05-01T01:00:00+01:002020-06-04T11:27:38+01:00Jordan Elver<p><a href="https://github.com/phoenixframework/phoenix_live_dashboard">Phoenix LiveDashboard</a> was recently officially announced
by <a href="https://twitter.com/josevalim/status/1250846714665357315">José Valim</a>, and I was keen try it out so I
installed it on a toy app I've been developing, which is deployed to Heroku.</p>
<p>I didn't want my LiveDashboard to be available to the whole world, so the
quickest and easiest method of protecting it was to use HTTP Basic Authentication,
<a href="https://hexdocs.pm/phoenix_live_dashboard/Phoenix.LiveDashboard.html#module-extra-add-dashboard-access-on-all-environments-including-production">something that is recommended in the LiveDashboard docs</a>.</p>
<p>To that end, I installed and configured the <a href="https://hex.pm/packages/basic_auth"><code>basic_auth</code> plug</a>,
and read the username and password to configure it from environment variables.</p>
<p>These are the steps I took.</p>
<h2><a href="#install-and-configure-code-basic_auth-code" id="install-and-configure-code-basic_auth-code">Install and configure <code>basic_auth</code></a></h2><h3><a href="#1-add-the-code-basic_auth-code-plug" id="1-add-the-code-basic_auth-code-plug">1. Add the <code>basic_auth</code> plug</a></h3>
<p>Add <code>basic_auth</code> to your <code>mix.exs</code> file</p>
<div class="highlight"><pre class="highlight elixir"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre><span class="p">{</span><span class="ss">:basic_auth</span><span class="p">,</span> <span class="s2">"~> 2.2.4"</span><span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div>
<p>Install dependencies</p>
<div class="highlight"><pre class="highlight shell"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>mix deps.get
</pre></td></tr></tbody></table></code></pre></div><h3><a href="#2-configure-code-basic_auth-code-from-environment-variables" id="2-configure-code-basic_auth-code-from-environment-variables">2. Configure <code>basic_auth</code> from environment variables</a></h3>
<p>Edit your <code>prod.secret.exs</code> file and add this.</p>
<div class="highlight"><pre class="highlight elixir"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
</pre></td><td class="rouge-code"><pre><span class="n">http_basic_auth_username</span> <span class="o">=</span>
<span class="no">System</span><span class="o">.</span><span class="n">get_env</span><span class="p">(</span><span class="s2">"HTTP_BASIC_AUTH_USERNAME"</span><span class="p">)</span> <span class="o">||</span>
<span class="k">raise</span> <span class="sd">"""
environment variable HTTP_BASIC_AUTH_USERNAME is missing.
"""</span>
<span class="n">http_basic_auth_password</span> <span class="o">=</span>
<span class="no">System</span><span class="o">.</span><span class="n">get_env</span><span class="p">(</span><span class="s2">"HTTP_BASIC_AUTH_PASSWORD"</span><span class="p">)</span> <span class="o">||</span>
<span class="k">raise</span> <span class="sd">"""
environment variable HTTP_BASIC_AUTH_PASSWORD is missing.
"""</span>
<span class="c1"># Set basic auth from environment variables</span>
<span class="n">config</span> <span class="ss">:example_web</span><span class="p">,</span> <span class="ss">basic_auth:</span> <span class="p">[</span>
<span class="ss">username:</span> <span class="n">http_basic_auth_username</span><span class="p">,</span>
<span class="ss">password:</span> <span class="n">http_basic_auth_password</span><span class="p">,</span>
<span class="p">]</span>
</pre></td></tr></tbody></table></code></pre></div><h3><a href="#3-update-the-router-to-protect-the-livedashboard-routes" id="3-update-the-router-to-protect-the-livedashboard-routes">3. Update the router to protect the LiveDashboard routes</a></h3>
<p>Create a new pipeline that conditionally adds the <code>basic_auth</code> plug if the
<code>HTTP_BASIC_AUTH_USERNAME</code> or <code>HTTP_BASIC_AUTH_PASSWORD</code> are present.</p>
<div class="highlight"><pre class="highlight elixir"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
</pre></td><td class="rouge-code"><pre><span class="n">pipeline</span> <span class="ss">:protected</span> <span class="k">do</span>
<span class="k">if</span> <span class="no">System</span><span class="o">.</span><span class="n">get_env</span><span class="p">(</span><span class="s2">"HTTP_BASIC_AUTH_USERNAME"</span><span class="p">)</span> <span class="o">||</span> <span class="no">System</span><span class="o">.</span><span class="n">get_env</span><span class="p">(</span><span class="s2">"HTTP_BASIC_AUTH_PASSWORD"</span><span class="p">)</span> <span class="k">do</span>
<span class="n">plug</span> <span class="no">BasicAuth</span><span class="p">,</span> <span class="ss">use_config:</span> <span class="p">{</span><span class="ss">:example_web</span><span class="p">,</span> <span class="ss">:basic_auth</span><span class="p">}</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></td></tr></tbody></table></code></pre></div>
<p>Then add <code>:protected</code> to the pipeline.</p>
<div class="highlight"><pre class="highlight elixir"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
</pre></td><td class="rouge-code"><pre><span class="n">scope</span> <span class="s2">"/"</span><span class="p">,</span> <span class="no">ExampleWeb</span> <span class="k">do</span>
<span class="n">pipe_through</span> <span class="p">[</span><span class="ss">:browser</span><span class="p">,</span> <span class="ss">:protected</span><span class="p">]</span>
<span class="n">live_dashboard</span> <span class="s2">"/dashboard"</span>
<span class="k">end</span>
</pre></td></tr></tbody></table></code></pre></div><h3><a href="#4-set-your-environment-variables-on-heroku" id="4-set-your-environment-variables-on-heroku">4. Set your environment variables on Heroku</a></h3><div class="highlight"><pre class="highlight shell"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre>heroku config:set <span class="nv">HTTP_BASIC_AUTH_USERNAME</span><span class="o">=</span>admin <span class="se">\</span>
<span class="nv">HTTP_BASIC_AUTH_PASSWORD</span><span class="o">=</span>P@ssword
</pre></td></tr></tbody></table></code></pre></div>
<p>Now when you visit <code>/dashboard</code> you should be prompted to enter your username
and password.</p>
TIL about the @external_resource module attribute in Elixirhttp://jordanelver.co.uk/blog/2020/04/23/til-about-the-external-resource-module-attribute-in-elixir/2020-04-23T01:00:00+01:002020-06-04T11:27:38+01:00Jordan Elver<p>Today I learnt about the <code>@external_resource</code> module attribute in <a href="https://elixir-lang.org/">Elixir</a>.</p>
<p>From the <a href="https://hexdocs.pm/elixir/Module.html#module-external_resource">Module documentation page</a>:</p>
<blockquote>
<p>Sometimes a module embeds information from an external file. This attribute
allows the module to annotate which external resources have been used.</p>
<p>Tools like Mix may use this information to ensure the module is recompiled in case any of the external resources change.</p>
</blockquote>
<p><code>@external_resource</code> means that you can specify a resource <em>outside</em> of your
module that will trigger recompilation of your module - basically, it sets up a
dependency between your module and another file.</p>
<p><a href="https://blog.oestrich.org/2018/03/elixir-external-resources/">Eric Oestrich blogged about a real-life use-case</a> for this feature -
generating functions based on an external "translations" file. At compile-time
he reads the file and generates functions based on it. His post goes into a lot
more depth and has code examples. I recommend giving it a read.</p>
Handling a custom response body with Faraday middlewarehttp://jordanelver.co.uk/blog/2020/03/27/handling-a-custom-response-body-with-faraday-middleware/2020-03-27T00:00:00+00:002020-06-04T11:27:38+01:00Jordan Elver<p>I've been working with a REST API recently that returns JSON except for one
particular endpoint. This endpoint returns a plain text response with a
non-standard content type header.</p>
<p>It looks like this:</p>
<div class="highlight"><pre class="highlight plaintext"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
</pre></td><td class="rouge-code"><pre>A:1AHGJDSMMPLMPPGNLJBQVLBRSKVDLQRPP:2213,2214,2215
A:201AGBHDRLQHNHPHKKMPKLGPMDRDTDMVL:3134,3135,3136,3137
U:1AHGJDSMMPLMPPGNLJBQVLBRSKVDLQRPP:2212
U:201AGBHDRLQHNHPHKKMPKLGPMDRDTDMVL:3133
X:2AVDSSBSTSQDRDKBHCNTRTHPNTBGQDTMD:2677,2685,2969,2996,3002
X:1AHGJDSMMPLMPPGNLJBQVLBRSKVDLQRPP:3029,3056
</pre></td></tr></tbody></table></code></pre></div>
<p>The output is seat availability for concert venues with colons separating the
fields like so <code><seat-type>:<band-id>:<seats></code>. The problem is that we need to
handle this response explicitly as the default JSON parsing doesn't know what to
do with it.</p>
<p>The desired output in this case would be like this:</p>
<div class="highlight"><pre class="highlight ruby"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
</pre></td><td class="rouge-code"><pre><span class="p">[</span>
<span class="p">{</span>
<span class="ss">:seat_type</span> <span class="o">=></span> <span class="s2">"A"</span><span class="p">,</span>
<span class="ss">:band_id</span> <span class="o">=></span> <span class="s2">"1AHGJDSMMPLMPPGNLJBQVLBRSKVDLQRPP"</span><span class="p">,</span>
<span class="ss">:seats</span> <span class="o">=></span> <span class="p">[</span><span class="s2">"2213"</span><span class="p">,</span> <span class="s2">"2214"</span><span class="p">,</span> <span class="s2">"2215"</span><span class="p">]</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="ss">:seat_type</span> <span class="o">=></span> <span class="s2">"A"</span><span class="p">,</span>
<span class="ss">:band_id</span> <span class="o">=></span> <span class="s2">"201AGBHDRLQHNHPHKKMPKLGPMDRDTDMVL"</span><span class="p">,</span>
<span class="ss">:seats</span> <span class="o">=></span> <span class="p">[</span><span class="s2">"3134"</span><span class="p">,</span> <span class="s2">"3135"</span><span class="p">,</span> <span class="s2">"3136"</span><span class="p">,</span> <span class="s2">"3137"</span><span class="p">]</span>
<span class="p">},</span>
<span class="o">...</span>
<span class="p">]</span>
</pre></td></tr></tbody></table></code></pre></div><h2><a href="#faraday-middleware" id="faraday-middleware">Faraday Middleware</a></h2>
<p>I'm using <a href="https://lostisland.github.io/faraday/">Faraday</a> as the HTTP client for this project and it has the
concept of middleware. Middleware can apply to requests or responses and are
hooked into the lifecycle of a request.</p>
<blockquote>
<p>Faraday is an HTTP client library that provides a common interface over many
adapters (such as Net::HTTP) and <strong>embraces the concept of Rack
middleware when processing the request/response cycle.</strong></p>
</blockquote>
<p>This means that we can write a custom middleware to parse the response for our
custom content type, which for the purpose of illustration, we'll call
<code>venue/seats</code>.</p>
<h2><a href="#custom-response-middleware" id="custom-response-middleware">Custom response middleware</a></h2>
<p>First we make sure we have the correct gems.</p>
<div class="highlight"><pre class="highlight ruby"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre><span class="n">gem</span> <span class="s1">'faraday'</span><span class="p">,</span> <span class="s1">'~> 1.0'</span>
<span class="n">gem</span> <span class="s1">'faraday_middleware'</span><span class="p">,</span> <span class="ss">github: </span><span class="s1">'lostisland/faraday_middleware'</span><span class="p">,</span> <span class="ss">ref: </span><span class="s1">'e1324ca'</span>
</pre></td></tr></tbody></table></code></pre></div>
<p>The latest version of <code>faraday_middleware</code> isn't compatible with Faraday 1.0 as
far as I can tell at the moment, so I pinned the gem to commit <code>e1324c</code>, which
is. There is a <a href="https://github.com/lostisland/faraday_middleware/releases/tag/v1.0.0.rc1">pre-release version available</a> at the time
of writing.</p>
<p>Now we can write a middleware that we will later hook into the request/response
cycle. I'm using the <code>ResponseMiddleware</code> from <a href="https://github.com/lostisland/faraday_middleware/blob/master/lib/faraday_middleware/response_middleware.rb"><code>faraday_middleware</code></a>
as it has a neat <code>define_parser</code> helper which allows us to very easily supply a
parser for <code>venue/seats</code> content type.</p>
<div class="highlight"><pre class="highlight ruby"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
</pre></td><td class="rouge-code"><pre><span class="nb">require</span> <span class="s2">"faraday"</span>
<span class="nb">require</span> <span class="s2">"faraday_middleware"</span>
<span class="nb">require</span> <span class="s2">"faraday_middleware/response_middleware"</span>
<span class="c1"># Custom response middleware</span>
<span class="k">class</span> <span class="nc">SeatAvailabilityResponse</span> <span class="o"><</span> <span class="o">::</span><span class="no">FaradayMiddleware</span><span class="o">::</span><span class="no">ResponseMiddleware</span>
<span class="n">define_parser</span> <span class="k">do</span> <span class="o">|</span><span class="n">body</span><span class="p">,</span> <span class="n">_</span><span class="o">|</span>
<span class="no">SeatAvailability</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="n">body</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="c1"># Register the middleware so we can use it</span>
<span class="no">Faraday</span><span class="o">::</span><span class="no">Response</span><span class="p">.</span><span class="nf">register_middleware</span><span class="p">(</span><span class="ss">seat_availability: </span><span class="no">SeatAvailabilityResponse</span><span class="p">)</span>
</pre></td></tr></tbody></table></code></pre></div>
<p>At the bottom you can see that I register this class as a response middleware
called "seat<em>availability" using `Faraday::Response.register</em>middleware`. This
allows us to refer to this middleware in the future.</p>
<h2><a href="#response-parsing" id="response-parsing">Response parsing</a></h2>
<p>I also delegate parsing responsibility to another class, <code>SeatAvailability</code>.
You could just as easily add it inline, but I like to separate the
responsibilities, and it also makes it easier to test if it's a separate class.</p>
<div class="highlight"><pre class="highlight ruby"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
</pre></td><td class="rouge-code"><pre><span class="k">class</span> <span class="nc">SeatAvailability</span>
<span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">parse</span><span class="p">(</span><span class="n">body</span><span class="p">)</span>
<span class="k">return</span> <span class="k">if</span> <span class="n">body</span><span class="p">.</span><span class="nf">nil?</span>
<span class="n">body</span><span class="p">.</span><span class="nf">split</span><span class="p">.</span><span class="nf">map</span> <span class="k">do</span> <span class="o">|</span><span class="n">section</span><span class="o">|</span>
<span class="n">seat_type</span><span class="p">,</span> <span class="n">band_id</span><span class="p">,</span> <span class="n">seats</span> <span class="o">=</span> <span class="n">section</span><span class="p">.</span><span class="nf">split</span><span class="p">(</span><span class="s2">":"</span><span class="p">)</span>
<span class="p">{</span>
<span class="ss">seat_type: </span><span class="n">seat_type</span><span class="p">,</span>
<span class="ss">band_id: </span><span class="n">band_id</span><span class="p">,</span>
<span class="ss">seats: </span><span class="n">seats</span><span class="p">.</span><span class="nf">split</span><span class="p">(</span><span class="s2">","</span><span class="p">)</span>
<span class="p">}</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></td></tr></tbody></table></code></pre></div><h2><a href="#registering-the-middleware" id="registering-the-middleware">Registering the middleware</a></h2>
<p>And this is how we use the new middleware. We create <code>Faraday</code> connection object
and in the block call <code>conn.response</code> in order to add it.</p>
<div class="highlight"><pre class="highlight ruby"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
</pre></td><td class="rouge-code"><pre><span class="n">conn</span> <span class="o">=</span> <span class="no">Faraday</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="s2">"http://example.com/api"</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">conn</span><span class="o">|</span>
<span class="c1">#...</span>
<span class="c1"># Use the `SeatAvailabilityResponse` middleware for "venue/seats" content types</span>
<span class="n">conn</span><span class="p">.</span><span class="nf">response</span> <span class="ss">:seat_availability</span><span class="p">,</span> <span class="ss">content_type: </span><span class="s2">"venue/seats"</span>
<span class="c1">#...</span>
<span class="k">end</span>
</pre></td></tr></tbody></table></code></pre></div>
<p>Now when we make a call to an endpoint that returns a body with a <code>venue/seats</code>
content type it will automatically be parsed into an array of hashes for us.</p>
<div class="highlight"><pre class="highlight ruby"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
</pre></td><td class="rouge-code"><pre><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="p">(</span><span class="n">pry</span><span class="p">)</span> <span class="ss">main: </span><span class="mi">0</span><span class="o">></span> <span class="n">response</span> <span class="o">=</span> <span class="n">conn</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="s2">"availability"</span><span class="p">)</span>
<span class="o">=></span> <span class="p">[{</span>
<span class="ss">:seat_type</span> <span class="o">=></span> <span class="s2">"A"</span><span class="p">,</span>
<span class="ss">:band_id</span> <span class="o">=></span> <span class="s2">"1AHGJDSMMPLMPPGNLJBQVLBRSKVDLQRPP"</span><span class="p">,</span>
<span class="ss">:seats</span> <span class="o">=></span> <span class="p">[</span><span class="s2">"2213"</span><span class="p">,</span> <span class="s2">"2214"</span><span class="p">,</span> <span class="s2">"2215"</span><span class="p">]</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="ss">:seat_type</span> <span class="o">=></span> <span class="s2">"A"</span><span class="p">,</span>
<span class="ss">:band_id</span> <span class="o">=></span> <span class="s2">"201AGBHDRLQHNHPHKKMPKLGPMDRDTDMVL"</span><span class="p">,</span>
<span class="ss">:seats</span> <span class="o">=></span> <span class="p">[</span><span class="s2">"3134"</span><span class="p">,</span> <span class="s2">"3135"</span><span class="p">,</span> <span class="s2">"3136"</span><span class="p">,</span> <span class="s2">"3137"</span><span class="p">]</span>
<span class="p">}</span>
<span class="p">]</span>
</pre></td></tr></tbody></table></code></pre></div>
<p>I hope this demonstrates how easy it is to handle custom responses in a very
straight-forward and modular way.</p>
Deleting fish shell historyhttp://jordanelver.co.uk/blog/2020/02/21/deleting-fish-shell-history/2020-02-21T00:00:00+00:002020-06-04T11:27:38+01:00Jordan Elver<p>Have you ever made a typo? No, me neither, but in case you ever do...</p>
<p>I run a lot of commands via the shell history using <a href="https://github.com/junegunn/fzf#key-bindings-for-command-line">fzf and CTRL-R</a> to save
re-typing, and ironically, mispellings. However, if you <em>do</em> mispell something
it ends up in the history forevermore, taunting you upon each invocation of
<a href="https://github.com/junegunn/fzf/blob/master/shell/key-bindings.fish#L109">fzf-history-widget</a>.</p>
<p>It turns out that <a href="https://fishshell.com/docs/current/cmds/history.html#cmd-history">fish ships with a neat <code>history</code> command</a> that allows you
to manipulate the history is various ways including searching, deleting, and
completely clearing.</p>
<p>The command I want to remove in this case is <code>mix echo</code> (which should be <code>mix
ecto</code>).</p>
<p>I use the <code>--contains</code> option here which will present a prompt showing the
entries and allowing you to delete individual entries or remove all.</p>
<div class="highlight"><pre class="highlight shell"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
</pre></td><td class="rouge-code"><pre><span class="nv">$ </span><span class="nb">history </span>delete <span class="nt">--contains</span> <span class="s2">"mix echo"</span>
<span class="o">[</span>1] <span class="nb">history </span>search <span class="nt">--contains</span> <span class="s2">"mix echo"</span>
<span class="o">[</span>2] mix echo.migrate
<span class="o">[</span>3] mix echo.create
<span class="o">[</span>4] mix echo.reset
<span class="o">[</span>5] mix echo.setup
Enter nothing to cancel the delete, or
Enter one or more of the entry IDs separated by a space, or
Enter <span class="s2">"all"</span> to delete all the matching entries.
<span class="o">[</span>I] Delete which entries? <span class="o">></span>
</pre></td></tr></tbody></table></code></pre></div>
<p>If you want to search before running <code>delete</code> you can do <code>history search
--contains "<search>"</code> which will return a list of items for you to check.</p>
Routing Docker traffic through a VPN connectionhttp://jordanelver.co.uk/blog/2019/06/03/routing-docker-traffic-through-a-vpn-connection/2019-06-03T01:00:00+01:002020-06-04T11:27:38+01:00Jordan Elver<p>I've recently taken to using Docker to install and run various software
on my home server. Something that so far, it excels at - the people at
<a href="https://linuxserver.io">linuxserver.io</a> are doing great work! I've had a rocky time with Docker in
the past after having had it foisted upon me for development work, which I did
<em>not</em> enjoy, but I can see the benefits for certain situations.</p>
<p>I found myself needing to run the traffic from one particular container
(<a href="https://github.com/linuxserver/docker-jackett">Jackett</a>) over a VPN connection so that it could by-pass country-specific
restrictions. As a noob Docker user, this caused some confusion, but I eventually
stumbled upon the <code>--net</code> parameter to <code>docker create</code> and <code>run</code>.</p>
<p>Using this parameter it's possible to tell a container to use the network of
another. You can run an OpenVPN client container, which will initiate a secure
connection, and configure other containers to use its network. The beauty of
this setup is that you don't need to learn or manage any complicated <code>ip_tables</code>
rules or any other network configuration, you can just point one container at
another and have the traffic secured.</p>
<p>All I needed now was a suitable Docker image. Eventually, I got lucky and
<a href="https://github.com/bubuntux/nordvpn">found an image that supported my exact VPN provider</a>, <a href="https://nordvpn.com/">NordVPN</a>.</p>
<p>This is how I created and started the OpenVPN container. Once this was running,
a secure VPN connection was established to NordVPN.</p>
<div class="highlight"><pre class="highlight shell"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
</pre></td><td class="rouge-code"><pre>docker run <span class="se">\</span>
<span class="nt">--name</span> vpn <span class="se">\</span>
<span class="nt">--cap-add</span><span class="o">=</span>NET_ADMIN <span class="se">\</span>
<span class="nt">--device</span> /dev/net/tun <span class="se">\</span>
<span class="nt">-p</span> 9117:9117 <span class="se">\</span>
<span class="nt">-e</span> <span class="nv">NETWORK</span><span class="o">=</span>192.168.1.0/24 <span class="se">\</span>
<span class="nt">-e</span> <span class="nv">USER</span><span class="o">=</span>username <span class="se">\</span>
<span class="nt">-e</span> <span class="nv">PASS</span><span class="o">=</span><span class="s1">'password'</span> <span class="se">\</span>
bubuntux/nordvpn
</pre></td></tr></tbody></table></code></pre></div>
<p>Most of these options are standard, but the <code>-p 9117:9117</code> parameter on line 5
needs explanation. This is the port mapping that Jackett uses by default. When
we use another container's network it's necessary to expose the port(s) that our
other containers use on the VPN container.</p>
<p>And here is how I setup Jackett to use the VPN container. The relevant line is
<code>--net=container:vpn</code>. Note that I don't have a <code>-p 9117:9117</code> line here like I
would if I was not using <code>--net=container:vpn</code>.</p>
<div class="highlight"><pre class="highlight shell"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
</pre></td><td class="rouge-code"><pre>docker run <span class="se">\</span>
<span class="nt">--name</span><span class="o">=</span>jackett <span class="se">\</span>
<span class="nt">--restart</span> unless-stopped <span class="se">\</span>
<span class="nt">-e</span> <span class="nv">PUID</span><span class="o">=</span>1000 <span class="se">\</span>
<span class="nt">-e</span> <span class="nv">PGID</span><span class="o">=</span>1000 <span class="se">\</span>
<span class="nt">-e</span> <span class="nv">TZ</span><span class="o">=</span>Europe/London <span class="se">\</span>
<span class="nt">--net</span><span class="o">=</span>container:vpn <span class="se">\</span>
<span class="nt">-v</span> /opt/appdata/jackett:/config <span class="se">\</span>
<span class="nt">-v</span> /opt/appdata/jackett/downloads:/downloads <span class="se">\</span>
linuxserver/jackett
</pre></td></tr></tbody></table></code></pre></div>
<p>Now I can access Jackett at <code>http://<host-ip>:9117</code> and all traffic will be sent
through the VPN container's network!</p>
How to replace a failed disk in a ZFS mirrorhttp://jordanelver.co.uk/blog/2018/11/26/how-to-replace-a-failed-disk-in-a-zfs-mirror/2018-11-26T00:00:00+00:002020-06-04T11:27:38+01:00Jordan Elver<p>I recently built a new file server for my media needs at home. Something I've
been thinking about doing for <em>literally</em> years. I chose to go with ZFS as the
storage technology after having used Linux software RAID for many years.
I went with a mirrored setup for a lot of the reasons <a href="http://www.openoid.net/zfs-you-should-use-mirror-vdevs-not-raidz/">outlined in this
article</a> - performance, simplicity, and in particular, easy recovery from
disk failures.</p>
<p>This is the setup I ended up with according to <code>zpool status</code>.</p>
<div class="highlight"><pre class="highlight plaintext"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
</pre></td><td class="rouge-code"><pre>$ zpool status
pool: storage
state: ONLINE
scan: none requested
config:
NAME STATE READ WRITE CKSUM
storage ONLINE 0 0 0
mirror-0 ONLINE 0 0 0
ata-WDC_WD80EFZX-68UW8N0_VJHDBDGX ONLINE 0 0 0
ata-WDC_WD80EFAX-68KNBN0_VAGASE7L ONLINE 0 0 0
mirror-1 ONLINE 0 0 0
ata-WDC_WD80EFZX-68UW8N0_VJHD6BAX ONLINE 0 0 0
ata-WDC_WD80EFAX-68KNBN0_VAGA5BPL ONLINE 0 0 0
mirror-2 ONLINE 0 0 0
ata-WDC_WD80EFZX-68UW8N0_VJHD982X ONLINE 0 0 0
ata-WDC_WD80EFAX-68KNBN0_VAG9X8YL ONLINE 0 0 0
errors: No known data errors
</pre></td></tr></tbody></table></code></pre></div>
<p>Well, no sooner had I completed the ZFS setup (a very straightforward process)
than one of my disks started reporting SMART errors. I don't think a disk that
is weeks old should do this, so I decided to start the RMA process.</p>
<p>And this is how I replaced the disk.</p>
<h2><a href="#replacing-the-disk" id="replacing-the-disk">Replacing the disk</a></h2>
<p>I started by physically removing the old disk, and replacing with a brand new
one. I originally setup my pool using the disk id from <code>/dev/disk/by-id/</code>, so
identifying the failed disk was very easy as the serial number is part of the
device name</p>
<p>Once I started back up, I ran <code>zpool status</code> and saw this output.</p>
<div class="highlight"><pre class="highlight plaintext"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
</pre></td><td class="rouge-code"><pre>$ zpools status
pool: storage
state: DEGRADED
status: One or more devices could not be used because the label is missing or
invalid. Sufficient replicas exist for the pool to continue
functioning in a degraded state.
action: Replace the device using 'zpool replace'.
see: http://zfsonlinux.org/msg/ZFS-8000-4J
scan: none requested
config:
NAME STATE READ WRITE CKSUM
storage DEGRADED 0 0 0
mirror-0 ONLINE 0 0 0
ata-WDC_WD80EFZX-68UW8N0_VJHDBDGX ONLINE 0 0 0
ata-WDC_WD80EFAX-68KNBN0_VAGASE7L ONLINE 0 0 0
mirror-1 ONLINE 0 0 0
ata-WDC_WD80EFZX-68UW8N0_VJHD6BAX ONLINE 0 0 0
ata-WDC_WD80EFAX-68KNBN0_VAGA5BPL ONLINE 0 0 0
mirror-2 DEGRADED 0 0 0
ata-WDC_WD80EFZX-68UW8N0_VJHD982X ONLINE 0 0 0
18311740819329882151 UNAVAIL 0 0 0 was /dev/disk/by-id/ata-WDC_WD80EFAX-68KNBN0_VAG9X8YL-part1
errors: No known data errors
</pre></td></tr></tbody></table></code></pre></div>
<p>ZFS noticed that it had a missing disk, and was now in a <code>DEGRADED</code> state, but
crucially, everything was still working and available.</p>
<p>The next step was to find out what the <em>new</em> device is called. I did this by
running <code>ls -1 /dev/disk/by-id/</code> and seeing which disk was new.</p>
<div class="highlight"><pre class="highlight plaintext"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
</pre></td><td class="rouge-code"><pre>$ ls -1 /dev/disk/by-id/ | grep ata
ata-WDC_WD80EFAX-68KNBN0_VAGA5BPL
ata-WDC_WD80EFAX-68KNBN0_VAGA5BPL-part1
ata-WDC_WD80EFAX-68KNBN0_VAGA5BPL-part9
ata-WDC_WD80EFAX-68KNBN0_VAGASE7L
ata-WDC_WD80EFAX-68KNBN0_VAGASE7L-part1
ata-WDC_WD80EFAX-68KNBN0_VAGASE7L-part9
ata-WDC_WD80EFAX-68LHPN0_7HJSWL7F
ata-WDC_WD80EFZX-68UW8N0_VJHD6BAX
ata-WDC_WD80EFZX-68UW8N0_VJHD6BAX-part1
ata-WDC_WD80EFZX-68UW8N0_VJHD6BAX-part9
ata-WDC_WD80EFZX-68UW8N0_VJHD982X
ata-WDC_WD80EFZX-68UW8N0_VJHD982X-part1
ata-WDC_WD80EFZX-68UW8N0_VJHD982X-part9
ata-WDC_WD80EFZX-68UW8N0_VJHDBDGX
ata-WDC_WD80EFZX-68UW8N0_VJHDBDGX-part1
ata-WDC_WD80EFZX-68UW8N0_VJHDBDGX-part9
</pre></td></tr></tbody></table></code></pre></div>
<p>The new disk is the one on line 8 - <code>ata-WDC_WD80EFAX-68LHPN0_7HJSWL7F</code>. It
stands out in this example as all the other disk serial numbers start with "V".</p>
<p>I now needed to tell ZFS to replace the missing disk with this one.</p>
<div class="highlight"><pre class="highlight plaintext"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>sudo zpool replace -f storage 18311740819329882151 /dev/disk/by-id/ata-WDC_WD80EFAX-68LHPN0_7HJSWL7F
</pre></td></tr></tbody></table></code></pre></div>
<p>ZFS automatically started the resilvering process (copying data to the new
disk). I wasn't sure how long that would take...</p>
<div class="highlight"><pre class="highlight plaintext"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">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
</pre></td><td class="rouge-code"><pre>$ zpool status
pool: storage
state: DEGRADED
status: One or more devices is currently being resilvered. The pool will
continue to function, possibly in a degraded state.
action: Wait for the resilver to complete.
scan: resilver in progress since Thu Nov 15 17:01:06 2018
7.97G scanned out of 7.51T at 233M/s, 9h22m to go
2.56G resilvered, 0.10% done
config:
NAME STATE READ WRITE CKSUM
storage DEGRADED 0 0 0
mirror-0 ONLINE 0 0 0
ata-WDC_WD80EFZX-68UW8N0_VJHDBDGX ONLINE 0 0 0
ata-WDC_WD80EFAX-68KNBN0_VAGASE7L ONLINE 0 0 0
mirror-1 ONLINE 0 0 0
ata-WDC_WD80EFZX-68UW8N0_VJHD6BAX ONLINE 0 0 0
ata-WDC_WD80EFAX-68KNBN0_VAGA5BPL ONLINE 0 0 0
mirror-2 DEGRADED 0 0 0
ata-WDC_WD80EFZX-68UW8N0_VJHD982X ONLINE 0 0 0
replacing-1 DEGRADED 0 0 0
18311740819329882151 UNAVAIL 0 0 0 was /dev/disk/by-id/ata-WDC_WD80EFAX-68KNBN0_VAG9X8YL-part1
ata-WDC_WD80EFAX-68LHPN0_7HJSWL7F ONLINE 0 0 0 (resilvering)
errors: No known data errors
</pre></td></tr></tbody></table></code></pre></div>
<p>The resilvering completed in 5 hours and 53 minutes. A figure I'm very satisfied
with. In this mirrored setup the data is at risk whilst resilvering completes,
so the quicker, the better.</p>
<div class="highlight"><pre class="highlight plaintext"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
</pre></td><td class="rouge-code"><pre>$ zpool status
pool: storage
state: ONLINE
scan: resilvered 2.50T in 5h53m with 0 errors on Thu Nov 15 22:54:41 2018
config:
NAME STATE READ WRITE CKSUM
storage ONLINE 0 0 0
mirror-0 ONLINE 0 0 0
ata-WDC_WD80EFZX-68UW8N0_VJHDBDGX ONLINE 0 0 0
ata-WDC_WD80EFAX-68KNBN0_VAGASE7L ONLINE 0 0 0
mirror-1 ONLINE 0 0 0
ata-WDC_WD80EFZX-68UW8N0_VJHD6BAX ONLINE 0 0 0
ata-WDC_WD80EFAX-68KNBN0_VAGA5BPL ONLINE 0 0 0
mirror-2 ONLINE 0 0 0
ata-WDC_WD80EFZX-68UW8N0_VJHD982X ONLINE 0 0 0
ata-WDC_WD80EFAX-68LHPN0_7HJSWL7F ONLINE 0 0 0
errors: No known data errors
</pre></td></tr></tbody></table></code></pre></div>
<p>ZFS is easy to setup and use for the most part. It <em>feels</em> solid. Stable.
If all disk replacements are this easy I will be very happy.</p>
A new way to deploy to Amazon S3http://jordanelver.co.uk/blog/2017/10/29/a-new-way-to-deploy-to-aws/2017-10-29T01:00:00+01:002020-06-04T11:27:38+01:00Jordan Elver<p>Pretty much since I began hosting this site on Amazon S3 I've been using
<a href="https://github.com/fredjean/middleman-s3_sync">middleman-s3_sync</a> to do the heavy lifting of syncing files to the bucket. Unfortunately, somewhere along the way it stopped picking up certain
files that should've been synced. I tried to be a good OSS citizen and find a fix, but
after delving into the Middleman innards, I couldn't find the problem.</p>
<h2><a href="#a-new-method" id="a-new-method">A new method</a></h2>
<p>A new method of deploying the site was needed and recently I moved to
using Amazon's own <a href="https://aws.amazon.com/cli/">aws cli</a> utility. After years of providing next to
nothing in regards to tooling, Amazon have released their own command line
utility for interacting with AWS. And the best part is that it seems very nice .</p>
<p>This is how I now deploy the site.</p>
<div class="highlight"><pre class="highlight shell"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
</pre></td><td class="rouge-code"><pre>aws s3 <span class="nb">sync </span>build s3://jordanelver.co.uk <span class="se">\</span>
<span class="nt">--profile</span> jordanelver.co.uk <span class="se">\</span>
<span class="nt">--delete</span> <span class="se">\</span>
<span class="nt">--acl</span> public-read <span class="se">\</span>
<span class="nt">--exclude</span> <span class="k">*</span>.DS_Store <span class="se">\</span>
</pre></td></tr></tbody></table></code></pre></div>
<p><code>build</code> is the local directory to be synced, and <code>s3://jordanelver.co.uk</code> is the remote
S3 bucket.</p>
<p><code>--delete</code> says to delete files at the destination that are not in the <code>build/</code>
directory.</p>
<p><code>--profile</code> controls which credentials and other settings to use when connecting
to S3. More on this below.</p>
<p><code>--acl public-read</code> makes the synced files publicly readable, <a href="http://docs.aws.amazon.com/AmazonS3/latest/dev/WebsiteAccessPermissionsReqd.html">as required by S3
static website hosting</a>.</p>
<p><code>--exclude</code> excludes those pesky <code>.DS_Store</code> files that OS X litters around the
filesystem.</p>
<h2><a href="#credentials" id="credentials">Credentials</a></h2>
<p>Credentials are stored in <code>~/.aws/credentials</code>. I have a section like this.</p>
<div class="highlight"><pre class="highlight shell"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre><span class="o">[</span>jordanelver.co.uk]
aws_access_key_id <span class="o">=</span> <ACCESSKEY>
aws_secret_access_key <span class="o">=</span> <SECRET>
</pre></td></tr></tbody></table></code></pre></div>
<p>You'll see that this matches the <code>--profile jordanelver.co.uk</code> line in the
<code>s3 sync</code> commandline.</p>
<p>That covers authentication, but you can also specify additional configuration
values in <code>~/.aws/config</code>. In this case, the S3 bucket region.</p>
<div class="highlight"><pre class="highlight shell"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre><span class="o">[</span>profile jordanelver.co.uk]
region <span class="o">=</span> eu-west-1
</pre></td></tr></tbody></table></code></pre></div>
<p>I used <code>aws configure --profile jordanelver.co.uk</code> to configure these values.</p>
<h2><a href="#one-last-thing" id="one-last-thing">One last thing</a></h2>
<p>One last thing that deserves to be mentioned is the <code>--dryrun</code> flag. It's very
handy for testing what's going to happen, before it happens. <a href="https://github.com/jordelver/jordanelver.co.uk/blob/master/bin/deploy">I ended up
wrapping my sync command in a Ruby script</a> to conditionally add <code>--dryrun</code> as
and when I need it.</p>
<p>Happy syncing!</p>
Split lines easily in Vimhttp://jordanelver.co.uk/blog/2017/09/25/split-lines-easily-in-vim/2017-09-25T01:00:00+01:002020-06-04T11:27:38+01:00Jordan Elver<p>Vim has a way of making me feel like a wizard one minute and a complete novice
the next. I constantly feel the need to revisit the basics, and this led me to
try and make <code>J</code> stick in my Vim vocabulary.</p>
<p><code>J</code> joins lines, I’ve been using it regularly for a while now; it’s finally
found its way into my muscle memory. It works like this.</p>
<script type="text/javascript"
src="https://asciinema.org/a/bozOxUMgIQV7TmK5OlbzUbazJ.js"
id="asciicast-bozOxUMgIQV7TmK5OlbzUbazJ" async></script>
<p>I recently discovered a complementary plugin called <a href="https://github.com/drzel/vim-split-line">vim-split-line</a>. It
makes splitting lines as easy as joining. It mirrors <code>J</code>, mapping by default to
<code>S</code>. See how it works below.</p>
<script type="text/javascript"
src="https://asciinema.org/a/i48SaXAXDGkCVM9l8Pte7wziQ.js"
id="asciicast-i48SaXAXDGkCVM9l8Pte7wziQ" async></script>
<p>There are, no doubt, countless ways to do this using Vim (<a href="https://github.com/drzel/vim-split-line#alternatives">in fact, the author
of the plugin points this out</a>), but I really like the simplicity of this
plugin. And it handles a couple of edgecases for you too such as auto indenting
the second line, and removing trailing whitespace.</p>
<p>Let’s hope this one sticks.</p>
Capturing output from Heroku with teehttp://jordanelver.co.uk/blog/2017/09/17/capturing-output-from-heroku-with-tee/2017-09-17T01:00:00+01:002020-06-04T11:27:38+01:00Jordan Elver<p>Have you ever been working with a console on Heroku and needed to capture the
output? I was recently tasked with generating a quick "report", and I decided
to spin up a <a href="http://guides.rubyonrails.org/command_line.html#rails-console">Rails console</a> on Heroku to connect to the database and munge
some data into the right format.</p>
<p>The problem is how to capture the output from Heroku. What are the options for
saving that on your computer? Well, you <em>can</em> write it to the filesystem, but
then how do you transfer the file? You could upload it to Amazon S3, or use
FTP, or something else, but it starts getting complicated fast...</p>
<p>Instead, we use <a href="https://en.wikipedia.org/wiki/Tee_(command)"><code>tee</code></a>.</p>
<p>Here we run a Rails console on Heroku but also redirect all output to a file
called <code>output.log</code>.</p>
<div class="highlight"><pre class="highlight plaintext"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>heroku run bundle exec rails console | tee output.log
</pre></td></tr></tbody></table></code></pre></div>
<p>We can work with the console as usual, but also capture the whole session in a
file for later inspection. We will get all of the input and output (commands we
typed, and their responses) so there is often some post processing to do. But
still, this is a quick and dirty way to save output from Heroku</p>
Renaming files like a prohttp://jordanelver.co.uk/blog/2017/01/31/renaming-files-like-a-pro/2017-01-31T00:00:00+00:002020-06-04T11:27:38+01:00Jordan Elver<p>Renaming files en masse was always something that I knew the commandline could
excel at but that I'd never managed to easily replicate daily. I'd always have
to look up the shell syntax for looping over a list of files, and the rules for
escaping paths, and so on and so forth. Whilst I'd like to improve my skills
with the shell, in the meantime I have discovered another option, <code>rename</code>.</p>
<p>Now, when I say <code>rename</code>, there is <a href="http://unix.stackexchange.com/questions/229230/whats-with-all-the-renames">some confusion</a> about various different
versions of this utility. I've been using the version included with Ubuntu
Trusty 14.04 and also <a href="http://plasmasturm.org/code/rename/">the version included in Homebrew</a> on macOS Sierra.
The examples below work with both of these versions.</p>
<p>A good understanding of regular expressions (the Perl variety) will allow you
to get the most out of <code>rename</code> but you can still use it in a basic fashion and
get great results.</p>
<h2><a href="#some-examples" id="some-examples">Some examples</a></h2>
<p>Take these files.</p>
<div class="highlight"><pre class="highlight plaintext"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre>01-run_the_jewels-run_the_jewels.flac
02-run_the_jewels-banana_clipper_(feat._big_boi).flac
03-run_the_jewels-36_inch_chain.flac
</pre></td></tr></tbody></table></code></pre></div>
<p>Let's make the filenames uppercase (because we are monsters).</p>
<div class="highlight"><pre class="highlight plaintext"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
</pre></td><td class="rouge-code"><pre>$ rename -n 'y/a-z/A-Z/ *.flac
01-run_the_jewels-run_the_jewels.flac renamed as 01-RUN_THE_JEWELS-RUN_THE_JEWELS.FLAC
02-run_the_jewels-banana_clipper_(feat._big_boi).flac renamed as 02-RUN_THE_JEWELS-BANANA_CLIPPER_(FEAT._BIG_BOI).FLAC
03-run_the_jewels-36_inch_chain.flac renamed as 03-RUN_THE_JEWELS-36_INCH_CHAIN.FLAC
</pre></td></tr></tbody></table></code></pre></div>
<p>You'll notice that <code>rename</code> prints out the renaming it <em>would</em> do. This is
because we've used the <code>-n</code> flag, which turns on dry runs allowing us to see
the changes that will be made before we commit to changing them. You'll find
<code>-n</code> invaluable when using <code>rename</code>, especially when new to it.</p>
<p>Let's change dashes and underscores to spaces instead.</p>
<div class="highlight"><pre class="highlight plaintext"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
</pre></td><td class="rouge-code"><pre>$ rename -n 's/-|_/ /g' *.flac
'01-run_the_jewels-run_the_jewels.flac' would be renamed to '01 run the jewels run the jewels.flac'
'02-run_the_jewels-banana_clipper_(feat._big_boi).flac' would be renamed to '02 run the jewels banana clipper (feat. big boi).flac'
'03-run_the_jewels-36_inch_chain.flac' would be renamed to '03 run the jewels 36 inch chain.flac'
</pre></td></tr></tbody></table></code></pre></div>
<p>Or remove <code>-run_the_jewels-</code> from the filename entirely.</p>
<div class="highlight"><pre class="highlight plaintext"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
</pre></td><td class="rouge-code"><pre>$ rename -n 's/-run_the_jewels-/ /g' *.flac
'01-run_the_jewels-run_the_jewels.flac' would be renamed to '01 run_the_jewels.flac'
'02-run_the_jewels-banana_clipper_(feat._big_boi).flac' would be renamed to '02 banana_clipper_(feat._big_boi).flac'
'03-run_the_jewels-36_inch_chain.flac' would be renamed to '03 36_inch_chain.flac'
</pre></td></tr></tbody></table></code></pre></div>
<p>Getting a bit more advanced, you can even use captures in your expression. Take
a directory of files with names like this.</p>
<div class="highlight"><pre class="highlight plaintext"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre>S03E01-oh-em-gee-i-love-telly.mkv
S03E02-thrilling-watch.mkv
S03E03-a-great-episode.mkv
</pre></td></tr></tbody></table></code></pre></div>
<p>We'll rename the filenames to look like <code>S??E??.mkv</code>.</p>
<div class="highlight"><pre class="highlight plaintext"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
</pre></td><td class="rouge-code"><pre>$ rename -n 's/.*(S\d{1,2}E\d{1,2}).*(.{3}$)/$1.$2/g' *.mkv
'S03E01-oh-em-gee-i-love-telly.mkv' would be renamed to 'S03E01.mkv'
'S03E02-thrilling-watch.mkv' would be renamed to 'S03E02.mkv'
'S03E03-a-great-episode.mkv' would be renamed to 'S03E03.mkv'
</pre></td></tr></tbody></table></code></pre></div>
<p>The regular expression matches the <code>S??E??</code> part of the filename as <code>$1</code>, and the extension <code>.mkv</code>
as <code>$2</code>, and uses them both to construct a new filename like <code>S03E01.mkv</code>.</p>
<p>That regular expression is quite a mouthfull, so I'd recommend using a tool
to help you construct your expressions. I personally use <a href="http://rubular.com/">Rubular</a> even
though it's Ruby-specific; it does a good job for me.</p>
<p>I hope you can get a sense of how powerful <code>rename</code> can be. Regular expressions
are the limit to how you can rename files.</p>
How I added heading anchors with Middlemanhttp://jordanelver.co.uk/blog/2016/03/19/how-i-added-heading-anchors-with-middleman/2016-03-19T00:00:00+00:002020-06-04T11:27:38+01:00Jordan Elver<p>I really like being able to link directly to certain parts of web page.
Unfortunately, many page authors fail to add the necessary anchors to make
this possible. Until recently, this included me!</p>
<p>Here is how I remedied the situation.</p>
<p>Outputting headings with id attributes <a href="https://forum.middlemanapp.com/t/using-a-custom-redcarpet-renderer/1004">looked like it should've already been
supported out of the box</a>. I could see a <code>toc_data</code> option, but for some
reason this didn't work for me, so I created a custom Markdown renderer
instead.</p>
<div class="highlight"><pre class="highlight ruby"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
</pre></td><td class="rouge-code"><pre><span class="nb">require</span> <span class="s2">"middleman-core/renderers/redcarpet"</span>
<span class="k">class</span> <span class="nc">CustomRenderer</span> <span class="o"><</span> <span class="no">Middleman</span><span class="o">::</span><span class="no">Renderers</span><span class="o">::</span><span class="no">MiddlemanRedcarpetHTML</span>
<span class="k">def</span> <span class="nf">header</span><span class="p">(</span><span class="n">text</span><span class="p">,</span> <span class="n">header_level</span><span class="p">)</span>
<span class="s2">"<h%s id=</span><span class="se">\"</span><span class="s2">%s</span><span class="se">\"</span><span class="s2">>%s</h%s>"</span> <span class="o">%</span> <span class="p">[</span><span class="n">header_level</span><span class="p">,</span> <span class="n">text</span><span class="p">.</span><span class="nf">parameterize</span><span class="p">,</span> <span class="n">text</span><span class="p">,</span> <span class="n">header_level</span><span class="p">]</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></td></tr></tbody></table></code></pre></div>
<p>I inherit from the default renderer so that I can replace the <code>header</code>
implementation to add the necessary <code>id</code> attributes.</p>
<p>I required it in my <code>config.rb</code> and then configured <a href="https://github.com/vmg/redcarpet">Redcarpet</a> to use the
new renderer.</p>
<div class="highlight"><pre class="highlight ruby"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
</pre></td><td class="rouge-code"><pre><span class="n">set</span> <span class="ss">:markdown</span><span class="p">,</span>
<span class="ss">fenced_code_blocks: </span><span class="kp">true</span><span class="p">,</span>
<span class="ss">smartypants: </span><span class="kp">true</span><span class="p">,</span>
<span class="ss">renderer: </span><span class="no">CustomRenderer</span>
</pre></td></tr></tbody></table></code></pre></div>
<p>There we have it. Heading tags now have ids, so we can link directly to them.</p>
Free SSL with Amazon Certificate Manager and Cloudfronthttp://jordanelver.co.uk/blog/2016/02/16/free-ssl-with-amazon-certificate-manager-and-cloudfront/2016-02-16T00:00:00+00:002020-06-04T11:27:38+01:00Jordan Elver<p>Following my <a href="/blog/2016/01/25/moving-to-amazon-s3/">recent move to Amazon S3</a> I've now gone one step further and
added <a href="https://aws.amazon.com/cloudfront/">Cloudfront</a> to the mix. Cloudfront is a <a href="https://en.wikipedia.org/wiki/Content_delivery_network">CDN</a>; it puts web pages
closer to the client so they're faster. Faster is better. However, my
motivation for adding Cloudfront was not speed, it was SSL.</p>
<p>I'd already been toying with the idea of adding SSL to this site with
<a href="https://www.cloudflare.com/">Cloudflare</a> when fellow <a href="http://www.meetup.com/bathruby/">Bath Ruby</a> attendee Paul Leader <a href="https://twitter.com/NoNeeeed/status/690544764706504705">told me
that Amazon were offering free SSL</a> through their <a href="https://aws.amazon.com/blogs/aws/new-aws-certificate-manager-deploy-ssltls-based-apps-on-aws/">recently announced
Amazon Certificate Manager</a> product. I decided to give it a try.</p>
<h2><a href="#why-ssl" id="why-ssl">Why SSL?</a></h2>
<p>Privacy and trust. Visitors to this site are getting the version of this page
that I publish. No one can interfere or tamper with it. No ads or malware can
be inserted. And why not? SSL is no longer expensive to implement. Gone are the
days of being extorted by companies selling SSL certificates. SSL is now freely
available from the likes of Cloudflare, Amazon and, perhaps more importantly,
<a href="https://letsencrypt.org/">Let's Encrypt</a>.</p>
<h2><a href="#amazon-certificate-manager" id="amazon-certificate-manager">Amazon Certificate Manager?</a></h2>
<p>As with all Amazon products it's often difficult to understand a) What it is;
and b) What it's for. Amazon Certificate Manager allows you to create SSL
certificates for free and easily use them with Amazon products. Currently,
<a href="https://aws.amazon.com/elasticloadbalancing/">ELB</a> and Cloudfront are supported. The best part is that they automatically
handle renewals for you! Once setup, there is no maintenance to do.</p>
<h2><a href="#setup" id="setup">Setup</a></h2><h3><a href="#distribution-setup" id="distribution-setup">Distribution setup</a></h3>
<p>Cloudfront has a concept called Distributions. A distribution is a set of files
that Amazon will distribute across the world to be served from it's <a href="https://aws.amazon.com/about-aws/global-infrastructure/">edge
locations</a> meaning the files will be geographically closer to the client
requesting them.</p>
<p>Setting up the distribution looks fairly complex but I went with the defaults
and it seems to work well. You can point a distribution straight to an S3
bucket but I found that not all of my pages were picked up by Cloudfront if I
did it that way. Index pages were missed for some reason. The fix was to
point the distribution at the Amazon S3 URL instead. The pages were then all
correctly picked up.</p>
<h3><a href="#dns-changes" id="dns-changes">DNS changes</a></h3>
<p>Like using S3 directly a very simple change is required. I already had an
<code>ALIAS</code> record to point from <code>jordanelver.co.uk</code> to
<code>jordanelver-co-uk.s3-website-eu-west-1.amazonaws.com</code>. I've now changed that
<code>ALIAS</code> to point to the Cloudfront address instead, which is
<code>d1jmxhequyb081.cloudfront.net</code>.</p>
<h3><a href="#invalidating-the-cache" id="invalidating-the-cache">Invalidating the cache</a></h3>
<p>Now that the site content is spread across Amazon's network and cached
appropriately, the cache will need to be invalidated when new files are
deployed to S3 or we'll be serving stale content. <a href="https://github.com/andrusha/middleman-cloudfront">There is a Middleman
extension to handle this</a> but at the moment I'm manually invalidating the
cache through the Amazon Cloudfront console. As I deploy infrequently it isn't
a problem for me to do it manually at the moment but I might end up using
that extension eventually.</p>
<h2><a href="#conclusion" id="conclusion">Conclusion</a></h2>
<p>I thought that the site was fairly speedy when served as a <a href="https://github.com/rack/rack">rack</a> app on
Heroku. The speed seemed to increase when I moved to S3 but now it's on
Cloudfront it <em>feels</em> noticably faster! Combine those speed increases with free
SSL and I'm very happy.</p>
Moving to Amazon S3http://jordanelver.co.uk/blog/2016/01/25/moving-to-amazon-s3/2016-01-25T00:00:00+00:002020-06-04T11:27:38+01:00Jordan Elver<p>A couple of years ago <a href="/blog/2014/02/17/how-i-deployed-middleman-to-heroku/">I wrote about how I deployed this blog to Heroku</a>.
Since then everything has changed; it is now served from <a href="https://aws.amazon.com/s3/">Amazon S3</a>.</p>
<h2><a href="#why-change" id="why-change">Why change?</a></h2>
<p>It was prompted by <a href="https://blog.heroku.com/archives/2015/6/15/dynos-pricing-ga">Heroku's price changes last year</a>. I think their new
pricing model is completely reasonable, and their "free" product still
generous, but Heroku <em>can</em> get expensive. This low traffic site will cost very
little to host with Amazon.</p>
<p>It seemed like a no-brainer to try it out, and this is how I did it.</p>
<h2><a href="#setting-up-static-website-hosting" id="setting-up-static-website-hosting">Setting up Static Website Hosting</a></h2>
<p>Amazon offer Static Website Hosting as a feature of S3. This serves a
bucket over HTTP at a URL that Amazon gives you. The first step was to create a
bucket to host the files. This is easily done through the <a href="https://aws.amazon.com/console/">Amazon S3
console</a>. I called mine jordanelver.co.uk, not because I'm self-obsessed,
but because that is the name of this website.</p>
<p>Configuring the bucket to serve the site was straight forward. Once I'd enabled
Static Website Hosting in the bucket properties, the site was available at
<code>jordanelver-co-uk.s3-website-eu-west-1.amazonaws.com</code>. Of course, I hadn't
transferred anything to Amazon at this point, and the permissions were not yet
set, so nothing but an error message was served at this point.</p>
<h2><a href="#permissions" id="permissions">Permissions</a></h2>
<p>Setting the correct permissions was by far the most confusing part of the whole
process. There are two parts to this 1) creating a user to interact with S3;
and 2) setting the correct bucket permissions.</p>
<p>I first setup an <a href="http://docs.aws.amazon.com/IAM/latest/UserGuide/introduction.html">IAM user</a> that had access to the correct bucket. This
allowed me to upload the files to Amazon. These credentials are used later as
part of the automated deployment.</p>
<p>The second part is allowing the files in S3 to be served over HTTP to the public.
By default files in S3 are not publically available, for obvious reasons. I
used the following policy to allow the files to be read. This is added through
the bucket properties.</p>
<div class="highlight"><pre class="highlight plaintext"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
</pre></td><td class="rouge-code"><pre>{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicReadGetObject",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::jordanelver.co.uk/*"
}
]
}
</pre></td></tr></tbody></table></code></pre></div><h2><a href="#deploying-with-middleman" id="deploying-with-middleman">Deploying with Middleman</a></h2>
<p>Moving away from Heroku meant losing the ability to deploy using <code>git push</code> so
I went looking for other methods. Luckily, <a href="https://directory.middlemanapp.com/#/extensions/deployment/">Middleman has many extensions
available</a> to help with automating deployment. I decided on
<a href="https://github.com/fredjean/middleman-s3_sync">Middleman::S3Sync</a> as it only transfers files that have changed, rather
than transferring everything each time, which seems like a good thing.</p>
<p>I added <code>gem 'middleman-s3_sync'</code> to my <code>Gemfile</code>, ran <code>bundle</code> and added the
following config to my <code>config.rb</code> file.</p>
<div class="highlight"><pre class="highlight ruby"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
</pre></td><td class="rouge-code"><pre><span class="n">activate</span> <span class="ss">:s3_sync</span> <span class="k">do</span> <span class="o">|</span><span class="n">s3_sync</span><span class="o">|</span>
<span class="n">s3_sync</span><span class="p">.</span><span class="nf">delete</span> <span class="o">=</span> <span class="kp">true</span>
<span class="n">s3_sync</span><span class="p">.</span><span class="nf">after_build</span> <span class="o">=</span> <span class="kp">false</span>
<span class="n">s3_sync</span><span class="p">.</span><span class="nf">prefer_gzip</span> <span class="o">=</span> <span class="kp">true</span>
<span class="n">s3_sync</span><span class="p">.</span><span class="nf">path_style</span> <span class="o">=</span> <span class="kp">true</span>
<span class="n">s3_sync</span><span class="p">.</span><span class="nf">reduced_redundancy_storage</span> <span class="o">=</span> <span class="kp">false</span>
<span class="n">s3_sync</span><span class="p">.</span><span class="nf">acl</span> <span class="o">=</span> <span class="s2">"public-read"</span>
<span class="n">s3_sync</span><span class="p">.</span><span class="nf">encryption</span> <span class="o">=</span> <span class="kp">false</span>
<span class="n">s3_sync</span><span class="p">.</span><span class="nf">version_bucket</span> <span class="o">=</span> <span class="kp">false</span>
<span class="n">s3_sync</span><span class="p">.</span><span class="nf">index_document</span> <span class="o">=</span> <span class="s2">"index.html"</span>
<span class="n">s3_sync</span><span class="p">.</span><span class="nf">error_document</span> <span class="o">=</span> <span class="s2">"404.html"</span>
<span class="k">end</span>
</pre></td></tr></tbody></table></code></pre></div>
<p>The sync process needs to know what the bucket name is, the Amazon region, and
the user credentials to upload the files. <a href="https://github.com/fredjean/middleman-s3_sync#setting-aws-credentials">There are several supported
methods</a> and I choose to use a <code>.s3_sync</code> file.</p>
<div class="highlight"><pre class="highlight yaml"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
</pre></td><td class="rouge-code"><pre><span class="nn">---</span>
<span class="na">bucket</span><span class="pi">:</span> <span class="s">jordanelver.co.uk</span>
<span class="na">region</span><span class="pi">:</span> <span class="s">eu-west-1</span>
<span class="na">aws_access_key_id</span><span class="pi">:</span> <span class="s"><ACCESS_KEY_ID></span>
<span class="na">aws_secret_access_key</span><span class="pi">:</span> <span class="s"><SECRET_ACCESS_KEY></span>
</pre></td></tr></tbody></table></code></pre></div>
<p>Deploying the site is now as simple as first building with <code>middleman build</code> and
then syncing with <code>middleman s3_sync</code>.</p>
<h2><a href="#dns-changes" id="dns-changes">DNS changes</a></h2>
<p>The final step is to hook up the domain name to the bucket. I use <a href="https://dnsimple.com/r/d7a9918c2a5dd7">DNSimple</a>
to host my DNS which makes the changes required simple. I setup an <code>ALIAS</code>
record to point from <code>jordanelver.co.uk</code> to
<code>jordanelver-co-uk.s3-website-eu-west-1.amazonaws.com</code> and the site is now
served at this domain name.</p>
<h2><a href="#conclusion" id="conclusion">Conclusion</a></h2>
<p>I'm very happy with this setup so far. Once you've set it up the first time, the
process can easily be replicated to add additional sites. I can see that I'll
continue to use this method of hosting for my "holding page" and static sites
needs.</p>
Working with Vim highlight groupshttp://jordanelver.co.uk/blog/2015/05/27/working-with-vim-colorschemes/2015-05-27T01:00:00+01:002020-06-04T11:27:38+01:00Jordan Elver<p>Highlight groups are used in Vim to control how the user interface will look.
For example, to change the colour of highlighted search items, you would add
this line to your <code>~/.vimrc</code> or colorscheme in <code>~/.vim/colors/</code>.</p>
<div class="highlight"><pre class="highlight plaintext"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>highlight Search ctermfg=0 ctermbg=226
</pre></td></tr></tbody></table></code></pre></div>
<p>This would make highlighted search items show up with a yellow background and
black text (when running in a terminal). This allows customisation of
everything you see on screen. However, given the breadth of customisation
possible it can be difficult to figure out which groups apply to the particular
item you want to change.</p>
<h2><a href="#showing-highlight-groups" id="showing-highlight-groups">Showing highlight groups</a></h2>
<p>This function will show what groups are being applied. Add to your <code>~/.vimrc</code>,
place your cursor over the item in question, and press <code><leader>sp</code> to output
the groups.</p>
<div class="highlight"><pre class="highlight viml"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
</pre></td><td class="rouge-code"><pre>nmap <span class="p"><</span>leader<span class="p">></span><span class="k">sp</span> <span class="p">:</span><span class="k">call</span> <span class="p"><</span>SID<span class="p">></span>SynStack<span class="p">()<</span>CR<span class="p">></span>
<span class="k">function</span><span class="p">!</span> <span class="p"><</span>SID<span class="p">></span>SynStack<span class="p">()</span>
<span class="k">if</span> <span class="p">!</span><span class="nb">exists</span><span class="p">(</span><span class="s2">"*synstack"</span><span class="p">)</span>
<span class="k">return</span>
<span class="k">endif</span>
echo <span class="nb">map</span><span class="p">(</span><span class="nb">synstack</span><span class="p">(</span><span class="nb">line</span><span class="p">(</span><span class="s1">'.'</span><span class="p">),</span> <span class="k">col</span><span class="p">(</span><span class="s1">'.'</span><span class="p">)),</span> <span class="s1">'synIDattr(v:val, "name")'</span><span class="p">)</span>
endfunc
</pre></td></tr></tbody></table></code></pre></div>
<p>For example, placing my cursor over a comment in a Ruby file gives this output.</p>
<div class="highlight"><pre class="highlight plaintext"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>['rubyMultilineComment', 'rubyComment']
</pre></td></tr></tbody></table></code></pre></div>
<p>You can now change how Ruby comments look using these groups.</p>
<p>This should greatly help when modifying colorschemes.</p>
<h2><a href="#outputting-all-highlight-groups" id="outputting-all-highlight-groups">Outputting all highlight groups</a></h2>
<p>It can also be very useful to see how all highlight groups look.</p>
<p>Whilst browsing the highlight help page recently (<code>:help highlight</code>) I found
that you can output all groups currently active using a script that comes with
Vim. Running <code>:so $VIMRUNTIME/syntax/hitest.vim</code> will show something similar to
below.</p>
<p><img src="/images/highlight-groups.png" /></p>
<p>There are many more lines in the full output, but it should be clear that this
is very useful when debugging colorschemes.</p>
<p>Vim colorschemes should now be easier to create and modify.</p>
Backups for the truly paranoidhttp://jordanelver.co.uk/blog/2015/04/29/backups-for-the-truly-paranoid/2015-04-29T01:00:00+01:002020-06-04T11:27:38+01:00Jordan Elver<p>I've been meaning to sort out a proper backup solution for a long, long time.</p>
<p>I've never lost data on my main machine, but I've been lucky. It does happen, I
recently had a file server with disks in <a href="http://en.wikipedia.org/wiki/Standard_RAID_levels#RAID_5">RAID5</a> fail. I'm most concerned
about hardware failure. I'm fairly conservative when it comes to deleting
files, so I don't tend to do it by accident. I'm sure it will happen one day,
but I think I'm much more likely to suffer data loss via catastrophic hardware
failure than by accidental deletion.</p>
<h2><a href="#strategy" id="strategy">Strategy</a></h2>
<p>My general strategy is to have several backups, in different locations, for
different purposes. To spread the data out. My current setup has a local clone,
a local incremental backup, and an offsite incremental backup.</p>
<h3><a href="#local-clone" id="local-clone">Local clone</a></h3>
<p>The idea of having a local clone is to get back up and running as soon as
possible in the event of hardware failure or loss. I can boot from the clone
and carry on working or use it to restore to a new computer.</p>
<p>I'm using <a href="http://www.shirt-pocket.com/SuperDuper/">SuperDuper!</a> to create a bootable clone to an external hard drive
every night. <a href="https://www.bombich.com/">Carbon Copy Cloner</a> is another popular alternative, but I've
found SuperDuper! meets my needs at the moment.</p>
<p>I used my clone to restore recently due to a non-catastrophic problem with a
new computer, so I'm fairly certain that it will work when it's needed. I will
boot from it periodically to test.</p>
<h3><a href="#local-incremental-backup" id="local-incremental-backup">Local incremental backup</a></h3>
<p>For a local incremental backup I'm using <a href="http://en.wikipedia.org/wiki/Time_Machine_(OS_X)">Time Machine</a> to an external hard
drive. This allows me to retrieve files from different time periods. Time
Machine backs up on an hourly basis.</p>
<p>It's free and stays out of the way most of the time.</p>
<h3><a href="#offsite-incremental-backup" id="offsite-incremental-backup">Offsite incremental backup</a></h3>
<p>For the offsite backup, I'm only uploading user data, not system files. This
is not a full system backup. I don't intend to ever use this backup except for
in truly disasterous sitations i.e. my computer and local backup disks are
either destroyed or lost.</p>
<p>I'm using <a href="https://www.haystacksoftware.com/arq/">Arq</a> to do the backing up. Arq has a good reputation and can
backup to <a href="https://www.google.co.uk/drive/">Google Drive</a>, <a href="https://www.dropbox.com">Dropbox</a>, <a href="http://aws.amazon.com/s3/">Amazon S3 or Glacier</a> and
<a href="https://onedrive.live.com">Microsoft OneDrive</a>. It can also backup to <a href="https://cloud.google.com/storage/docs/nearline-storage">Google Nearline</a> which is a
new competitor to Amazon's offerings. I decided to give Nearline a try.</p>
<p>The initial backup of approximately 200GB took around 5 days. Unfortunately, I
experienced some errors before it completed. It did complete in the end, and
seems fine, but any sort of error message does not inspire confidence.</p>
<p>This backup should only cost a few dollars per month. It does seem to be
backing up more data than I expected though, so I will need to monitor this to
stop it getting too large and increasing the cost.</p>
<p>I can see me trying out other services such as <a href="https://www.backblaze.com">Backblaze</a> or
<a href="http://www.code42.com/crashplan/">Crashplan</a> in the future.</p>
<h2><a href="#conclusion" id="conclusion">Conclusion</a></h2>
<p>This setup is still a work in progress. There are some parts that I'm happy
with and others which make me feel uneasy. I can see my offsite backup changing
in the future due to the flakiness I've experienced with Arq and Google.
However, I do feel that there is a good level of data redundancy and the
backups are spread out enough. I should now hopefully be able to sleep better
at night.</p>
How to compile the tmk_keyboard firmware for ErgoDox on Mac OS Xhttp://jordanelver.co.uk/blog/2015/03/30/how-to-compile-the-tmk-keyboard-firmware-for-ergodox-on-mac-os-x/2015-03-30T01:00:00+01:002020-06-04T11:27:38+01:00Jordan Elver<p>I received my <a href="http://ergodox.org/">ErgoDox keyboard</a> from <a href="https://www.massdrop.com/buy/ergodox?s=ergodox">Massdrop</a> a month or so ago. After
a false start because of some missing parts, and with the <a href="https://twitter.com/richbezz">help of a friend</a>
handy with a soldering iron, it is now up and running.</p>
<p>I've wanted to try the <a href="https://normanlayout.info/">Norman layout</a> for a while. This seemed like the
perfect opportunity. The ErgoDox is so different anyway, changing the layout
would should not pose too much of a problem? That's the theory. Unfortunately,
the <a href="https://github.com/benblazak/ergodox-firmware">"official" ErgoDox firmware</a> doesn't come with Norman. I didn't want to
start hacking at keyboard firmware before even getting it up and running, so I
decided to try <a href="https://github.com/tmk/tmk_keyboard">tmk_keyboard</a>.</p>
<p>This is how I got it working.</p>
<h2><a href="#install-dependencies" id="install-dependencies">Install dependencies</a></h2>
<p>We need to install <a href="http://www.nongnu.org/avr-libc/">AVR Libc</a>. This package contains C libraries for AVR
microcontrollers (used by the <a href="https://www.pjrc.com/teensy/">Teensy</a> microcontroller in the ErgoDox) and most
importantly for us, a version of GCC that will allow us to build the firmware.</p>
<blockquote>
<p>AVR Libc is a Free Software project whose goal is to provide a high quality C
library for use with GCC on Atmel AVR microcontrollers.</p>
</blockquote>
<p>You can install the dependencies using <a href="http://brew.sh/">Homebrew</a>.</p>
<div class="highlight"><pre class="highlight shell"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre><span class="nv">$ </span>brew tap larsimmisch/avr
<span class="nv">$ </span>brew <span class="nb">install </span>avr-libc
</pre></td></tr></tbody></table></code></pre></div><h2><a href="#build-the-firmware" id="build-the-firmware">Build the firmware</a></h2>
<p>There are many versions of the <code>tmk_keyboard</code> firmware available on GitHub. I'm
using <a href="https://github.com/tenderlove/tmk_keyboard">Tenderlove's fork</a>.</p>
<p>Get the source code.</p>
<div class="highlight"><pre class="highlight shell"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>git clone git@github.com:tenderlove/tmk_keyboard.git
</pre></td></tr></tbody></table></code></pre></div>
<p>Change to the ErgoDox keyboard firmware directory.</p>
<div class="highlight"><pre class="highlight shell"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre><span class="nb">cd </span>tmk_keyboard/keyboard/ergodox
</pre></td></tr></tbody></table></code></pre></div>
<p>Build the firmware.</p>
<div class="highlight"><pre class="highlight shell"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre>make clean
make <span class="nt">-f</span> Makefile
</pre></td></tr></tbody></table></code></pre></div>
<p>You should now have a file called <code>ergodox_pjrc.hex</code> in the directory. This is
what you need to load onto the keyboard.</p>
<h2><a href="#install-the-firmware" id="install-the-firmware">Install the firmware</a></h2>
<p>I used the Teensy Loader app. Follow the <a href="http://www.pjrc.com/teensy/loader_mac.html">instructions on their website</a>
choosing the <code>ergodox_pjrc.hex</code> file that we just built.</p>
<p>Hopefully, that should be it. Good luck fellow ErgoDox owners!</p>
Editing the command line in Vimhttp://jordanelver.co.uk/blog/2015/02/02/editing-the-commandline-in-vim/2015-02-02T00:00:00+00:002020-06-04T11:27:38+01:00Jordan Elver<p>Constructing long commands on the command line can quickly get out of control.
I use Vim keybindings in ZSH, which helps greatly. They allow you to navigate
the shell using familiar shortcuts from Vim. Wouldn't it be neat if you could
edit those commands directly in Vim though?</p>
<p>You can.</p>
<p><img src="/images/edit-in-vim.gif" /></p>
<p>Add this to your <code>~/.zshrc</code> file and reload your terminal.</p>
<div class="highlight"><pre class="highlight shell"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre>autoload edit-command-line
zle <span class="nt">-N</span> edit-command-line
bindkey <span class="nt">-M</span> vicmd v edit-command-line
</pre></td></tr></tbody></table></code></pre></div>
<p>Now when you hit <code>ESC</code> and then <code>v</code>, the current command line will open in Vim.
Saving and exiting (<code>:wq</code>) will return you to the command line with the changes
you made.</p>
Two handy CURL tips for working with RESTful APIshttp://jordanelver.co.uk/blog/2015/01/27/two-handy-curl-tips-for-working-with-restful-apis/2015-01-27T00:00:00+00:002020-06-04T11:27:38+01:00Jordan Elver<p>I was recently working on a REST API for an iPhone app. I used <a href="http://curl.haxx.se/">curl</a>
alongside automated tests to manually test as I built. curl is a great tool but
if you need to specify lots of arguments, as I did, it can soon become
unwieldy.</p>
<p>These two tips helped me out.</p>
<h2><a href="#1-read-arguments-from-a-file" id="1-read-arguments-from-a-file">1) Read arguments from a file</a></h2>
<p>I had many arguments to pass when testing the API including the <code>Accept</code> and
<code>Authorisation</code> headers. With those alone, the argument list is already pretty
big.</p>
<p>Luckily, the authors of curl have thought of this and have <a href="http://curl.haxx.se/docs/manpage.html#-K">built-in an option</a>
for them to be read from a file.</p>
<p>Create a file for your arguments and list them inside. Mine looked something
like this.</p>
<div class="highlight"><pre class="highlight plaintext"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre>-H "Accept:application/vnd.vendor-v1+json"
-H "Authorization:Basic cZHV0Z29vdVhBR1..."
</pre></td></tr></tbody></table></code></pre></div>
<p>Then tell curl where to find them.</p>
<div class="highlight"><pre class="highlight plaintext"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>curl <url> -K path/to/file
</pre></td></tr></tbody></table></code></pre></div><h2><a href="#2-reading-post-data-from-a-file" id="2-reading-post-data-from-a-file">2) Reading POST data from a file</a></h2>
<p>I needed to POST a lot of JSON data at various API endpoints. As with
arguments, you can also tell curl to <a href="http://curl.haxx.se/docs/manpage.html#-d">read the data from a file</a>. This is
especially useful as formatting JSON data on the command line is no fun!</p>
<p>To make curl read the data from a file, prefix the filename with an @ symbol.</p>
<div class="highlight"><pre class="highlight plaintext"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>curl <url> –data @filename
</pre></td></tr></tbody></table></code></pre></div>
<p>These tips should hopefully make using curl easier.</p>
Disable spell check on form fieldshttp://jordanelver.co.uk/blog/2014/07/13/disable-spell-check-on-form-fields/2014-07-13T01:00:00+01:002020-06-04T11:27:38+01:00Jordan Elver<p>TIL that you can turn off spell checking for text input form fields.</p>
<p>I know, this sounds like a terrible idea. Similar atrocities such as turning
off cut and paste have been committed by others and are very annoying indeed.
However, used carefully and in the right context, it's good to know that this
is possible.</p>
<p>My particular use case was for a form field designed to accept a Foursquare ID.
The value entered would only ever be a mixture of numbers and letters and would
<strong>always</strong> fail a spell check. The red dotted line signals to the user that
they have made a mistake, even if their input is valid.</p>
<p><img src="/images/spellcheck_before.png"></p>
<p>This is one such case where I think turning off spell checking is a good idea.</p>
<p>To do this, you simply add the <code>spellcheck</code> attribute to your input tag and set
it to <code>false</code>.</p>
<div class="highlight"><pre class="highlight html"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre><span class="nt"><input</span> <span class="na">name=</span><span class="s">"foursquare_id"</span> <span class="na">type=</span><span class="s">"text"</span> <span class="na">spellcheck=</span><span class="s">"false"</span><span class="nt">></span>
</pre></td></tr></tbody></table></code></pre></div>
<p><img src="/images/spellcheck_after.png"></p>
<p>And, there we go, no spell checking on the form field.</p>
Ruby method objectshttp://jordanelver.co.uk/blog/2014/06/25/ruby-method-objects/2014-06-25T01:00:00+01:002020-06-04T11:27:38+01:00Jordan Elver<p>If we want to capitalise all the values in an array, we can use <code>#map</code>.</p>
<div class="highlight"><pre class="highlight ruby"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre><span class="p">[</span><span class="s2">"apple"</span><span class="p">,</span> <span class="s2">"pear"</span><span class="p">,</span> <span class="s2">"banana"</span><span class="p">].</span><span class="nf">map</span> <span class="p">{</span> <span class="o">|</span><span class="n">fruit</span><span class="o">|</span> <span class="n">fruit</span><span class="p">.</span><span class="nf">upcase</span> <span class="p">}</span>
<span class="o">=></span> <span class="p">[</span><span class="s2">"APPLE"</span><span class="p">,</span> <span class="s2">"PEAR"</span><span class="p">,</span> <span class="s2">"BANANA"</span><span class="p">]</span>
</pre></td></tr></tbody></table></code></pre></div>
<p>Alternatively, a more terse version is available using the <code>Symbol#to_proc</code> trick.</p>
<div class="highlight"><pre class="highlight ruby"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre><span class="p">[</span><span class="s2">"apple"</span><span class="p">,</span> <span class="s2">"pear"</span><span class="p">,</span> <span class="s2">"banana"</span><span class="p">].</span><span class="nf">map</span><span class="p">(</span><span class="o">&</span><span class="ss">:upcase</span><span class="p">)</span>
<span class="o">=></span> <span class="p">[</span><span class="s2">"APPLE"</span><span class="p">,</span> <span class="s2">"PEAR"</span><span class="p">,</span> <span class="s2">"BANANA"</span><span class="p">]</span>
</pre></td></tr></tbody></table></code></pre></div>
<p>We can even achieve the same thing using an explicit <code>Proc</code> object.</p>
<div class="highlight"><pre class="highlight ruby"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre><span class="p">[</span><span class="s2">"apple"</span><span class="p">,</span> <span class="s2">"pear"</span><span class="p">,</span> <span class="s2">"banana"</span><span class="p">].</span><span class="nf">map</span><span class="p">(</span><span class="o">&</span><span class="no">Proc</span><span class="p">.</span><span class="nf">new</span> <span class="p">{</span> <span class="o">|</span><span class="n">fruit</span><span class="o">|</span> <span class="n">fruit</span><span class="p">.</span><span class="nf">upcase</span> <span class="p">})</span>
<span class="o">=></span> <span class="p">[</span><span class="s2">"APPLE"</span><span class="p">,</span> <span class="s2">"PEAR"</span><span class="p">,</span> <span class="s2">"BANANA"</span><span class="p">]</span>
</pre></td></tr></tbody></table></code></pre></div><h2><a href="#but-i-wanna-call-my-own-method" id="but-i-wanna-call-my-own-method">But, I wanna call my own method!</a></h2>
<p>But what if you want to call a method of your own creation?</p>
<p>In Ruby, methods are not objects. They are one of the few things in Ruby that
aren't. That's why we have the <code>Object#method</code> method.</p>
<p>We need to get an object instance which we can pass to <code>#map</code>.</p>
<div class="highlight"><pre class="highlight ruby"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
</pre></td><td class="rouge-code"><pre><span class="k">class</span> <span class="nc">Foo</span>
<span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">bar</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
<span class="n">value</span><span class="p">.</span><span class="nf">upcase</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="p">[</span><span class="s2">"apple"</span><span class="p">,</span> <span class="s2">"pear"</span><span class="p">,</span> <span class="s2">"banana"</span><span class="p">].</span><span class="nf">map</span><span class="p">(</span><span class="o">&</span><span class="no">Foo</span><span class="p">.</span><span class="nf">method</span><span class="p">(</span><span class="ss">:bar</span><span class="p">))</span>
<span class="o">=></span> <span class="p">[</span><span class="s2">"APPLE"</span><span class="p">,</span> <span class="s2">"PEAR"</span><span class="p">,</span> <span class="s2">"BANANA"</span><span class="p">]</span>
</pre></td></tr></tbody></table></code></pre></div>
<p>We first get an instance of the <code>.bar</code> method using <code>Foo.method</code> and then the
<code>Method</code> object is converted to a block using <code>&</code> and applied to every item in
the array.</p>
Sorting columns of text in Vim using sorthttp://jordanelver.co.uk/blog/2014/03/12/sorting-columnds-of-text-in-vim-using-sort/2014-03-12T00:00:00+00:002020-06-04T11:27:38+01:00Jordan Elver<p>I recently wanted to get some stats on some Mongo collections to see a) which
collection had the most documents; and b) which collections were the biggest by
data size.</p>
<p>I copy and pasted the stats from our hosted Mongo provider, MongoHQ, and then
sorted them in Vim. This is what the text looked like after pasting into Vim
(with the <code>:set paste</code> option set).</p>
<div class="highlight"><pre class="highlight plaintext"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
</pre></td><td class="rouge-code"><pre>affiliates 1038 680 KB
article_ratings 699 168 KB
authors 30 40 KB
fs.chunks 3401 633.89 MB
fs.files 1476 680 KB
nodes 1432 24.29 MB
nodes_search 91 2.8 MB
nodes_tags 272 40 KB
page_views 107769 16.37 MB
page_views_map 212 40 KB
recommendations 34305 45.1 MB
rewrite_rules 209 168 KB
sign_ups 10331 12.52 MB
sitemaps 1 14.84 MB
suppliers 13 8 KB
tariff_price_check_reports 34 540 KB
tariff_price_checks 1129 968 KB
tariffs 5 680 KB
users 17 64 KB
users_tags 2 8 KB
versions 18031 156.64 MB
</pre></td></tr></tbody></table></code></pre></div>
<p>First I sorted the text into "proper" columns using the <code>column</code> utility.</p>
<div class="highlight"><pre class="highlight plaintext"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>:%!column -t
</pre></td></tr></tbody></table></code></pre></div>
<p>Which resulted in nice, spaced out, columns.</p>
<div class="highlight"><pre class="highlight plaintext"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
</pre></td><td class="rouge-code"><pre>affiliates 1038 680 KB
article_ratings 699 168 KB
authors 30 40 KB
fs.chunks 3401 633.89 MB
fs.files 1476 680 KB
nodes 1432 24.29 MB
nodes_search 91 2.8 MB
nodes_tags 272 40 KB
page_views 107769 16.37 MB
page_views_map 212 40 KB
recommendations 34305 45.1 MB
rewrite_rules 209 168 KB
sign_ups 10331 12.52 MB
sitemaps 1 14.84 MB
suppliers 13 8 KB
tariff_price_check_reports 34 540 KB
tariff_price_checks 1129 968 KB
tariffs 5 680 KB
users 17 64 KB
users_tags 2 8 KB
versions 18031 156.64 MB
</pre></td></tr></tbody></table></code></pre></div>
<p>To sort the text by the total number of documents in the collection, I did this.</p>
<div class="highlight"><pre class="highlight plaintext"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>:%!sort -k2nr
</pre></td></tr></tbody></table></code></pre></div>
<p>This sorted by the second column (<code>-k2</code>), treats the text as a number (<code>n</code>) and
then sorts in reverse (<code>r</code>), which results in.</p>
<div class="highlight"><pre class="highlight plaintext"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
</pre></td><td class="rouge-code"><pre>page_views 107769 16.37 MB
recommendations 34305 45.1 MB
versions 18031 156.64 MB
sign_ups 10331 12.52 MB
fs.chunks 3401 633.89 MB
fs.files 1476 680 KB
nodes 1432 24.29 MB
tariff_price_checks 1129 968 KB
affiliates 1038 680 KB
article_ratings 699 168 KB
nodes_tags 272 40 KB
page_views_map 212 40 KB
rewrite_rules 209 168 KB
nodes_search 91 2.8 MB
tariff_price_check_reports 34 540 KB
authors 30 40 KB
users 17 64 KB
suppliers 13 8 KB
tariffs 5 680 KB
users_tags 2 8 KB
sitemaps 1 14.84 MB
</pre></td></tr></tbody></table></code></pre></div>
<p>Then, I sort by the the 4th column (<code>-k4</code>), followed by the 3rd column, but this
time we require a few more switches. We ignore leading blank spaces (<code>b</code>), and
this time we sort using a general numeric sort (<code>g</code>).</p>
<div class="highlight"><pre class="highlight plaintext"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>:%!sort -k4 -bk3g
</pre></td></tr></tbody></table></code></pre></div>
<p>And there we have it, sorted by file size.</p>
<div class="highlight"><pre class="highlight plaintext"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
</pre></td><td class="rouge-code"><pre>suppliers 13 8 KB
users_tags 2 8 KB
authors 30 40 KB
nodes_tags 272 40 KB
page_views_map 212 40 KB
users 17 64 KB
article_ratings 699 168 KB
rewrite_rules 209 168 KB
tariff_price_check_reports 34 540 KB
affiliates 1038 680 KB
fs.files 1476 680 KB
tariffs 5 680 KB
tariff_price_checks 1129 968 KB
nodes_search 91 2.8 MB
sign_ups 10331 12.52 MB
sitemaps 1 14.84 MB
page_views 107769 16.37 MB
nodes 1432 24.29 MB
recommendations 34305 45.1 MB
versions 18031 156.64 MB
fs.chunks 3401 633.89 MB
</pre></td></tr></tbody></table></code></pre></div>
<p>*nix is cool.</p>
How I deployed Middleman to Herokuhttp://jordanelver.co.uk/blog/2014/02/17/how-i-deployed-middleman-to-heroku/2014-02-17T00:00:00+00:002020-06-04T11:27:38+01:00Jordan Elver<p>I've recently redesigned this website and it is now built using <a href="http://www.middlemanapp.com">Middleman</a>.</p>
<blockquote>
<p>Middleman is a static site generator using all the shortcuts and tools in
modern web development</p>
</blockquote>
<p>This post is about how I deployed the site to <a href="http://www.heroku.com">Heroku</a>.</p>
<h2><a href="#building-the-site" id="building-the-site">Building the site</a></h2>
<p>Middleman is a <strong>static</strong> site generator, as such, we need to figure out how
to get the site built when we deploy. This saves having to build the site and
commit the result before deploying.</p>
<p>Heroku will automatically attempt to execute a rake task called <code>assets:precompile</code>.</p>
<p>This was originally for the benefit of Rails, but we can take advantage of this
now for our own needs.</p>
<p>I created a new <code>Rakefile</code> and added the following.</p>
<div class="highlight"><pre class="highlight ruby"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
</pre></td><td class="rouge-code"><pre><span class="n">namespace</span> <span class="ss">:assets</span> <span class="k">do</span>
<span class="n">task</span> <span class="ss">:precompile</span> <span class="k">do</span>
<span class="n">sh</span> <span class="s1">'middleman build'</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></td></tr></tbody></table></code></pre></div>
<p>The task simply shells out to call <code>middleman build</code> which builds the site
automatically when the site is pushed to Heroku. Middleman will output all
files to <code>./build</code>.</p>
<h2><a href="#serving-the-site" id="serving-the-site">Serving the site</a></h2>
<p>The process of serving a static Middleman site on Heroku is quite straight
forward once you understand the basics. The site will be running as a Rack
app, so we'll need a <code>config.ru</code> file. Here is what mine looks like.</p>
<div class="highlight"><pre class="highlight ruby"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
</pre></td><td class="rouge-code"><pre><span class="nb">require</span> <span class="s1">'rack'</span>
<span class="nb">require</span> <span class="s1">'rack/contrib/try_static'</span>
<span class="c1"># Serve files from the build directory</span>
<span class="n">use</span> <span class="no">Rack</span><span class="o">::</span><span class="no">TryStatic</span><span class="p">,</span>
<span class="ss">root: </span><span class="s1">'build'</span><span class="p">,</span>
<span class="ss">urls: </span><span class="sx">%w[/]</span><span class="p">,</span>
<span class="ss">try: </span><span class="p">[</span><span class="s1">'.html'</span><span class="p">,</span> <span class="s1">'index.html'</span><span class="p">,</span> <span class="s1">'/index.html'</span><span class="p">]</span>
<span class="n">run</span> <span class="nb">lambda</span><span class="p">{</span> <span class="o">|</span><span class="n">env</span><span class="o">|</span>
<span class="n">four_oh_four_page</span> <span class="o">=</span> <span class="no">File</span><span class="p">.</span><span class="nf">expand_path</span><span class="p">(</span><span class="s2">"../build/404/index.html"</span><span class="p">,</span> <span class="kp">__FILE__</span><span class="p">)</span>
<span class="p">[</span> <span class="mi">404</span><span class="p">,</span> <span class="p">{</span> <span class="s1">'Content-Type'</span> <span class="o">=></span> <span class="s1">'text/html'</span><span class="p">},</span> <span class="p">[</span> <span class="no">File</span><span class="p">.</span><span class="nf">read</span><span class="p">(</span><span class="n">four_oh_four_page</span><span class="p">)</span> <span class="p">]]</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div>
<p>The <code>Rack::TryStatic</code> section is how we serve up the static files that
Middleman builds when the site is pushed to Heroku. Middleman outputs all
files into <code>./build</code>.</p>
<p>If no page is served from the <code>Rack::Trystatic</code> app, the 404 page is served
using the next <code>run</code> section.</p>
<p><strong>UPDATE:</strong> Make sure to add <code>rack-contrib</code> to your <code>Gemfile</code> as pointed out by
<a href="https://twitter.com/fulljames/status/469128779777212417">@fulljames</a>.</p>
<p>I decided to use the <a href="http://puma.io">Puma</a> web server to do the <em>actual</em> web serving of the
files as I had never used it before and wanted to try it out. I added Puma to
my <code>Gemfile</code> and created this <code>Procfile</code>.</p>
<div class="highlight"><pre class="highlight ruby"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre><span class="ss">web: </span><span class="n">bundle</span> <span class="nb">exec</span> <span class="n">puma</span> <span class="o">-</span><span class="nb">p</span> <span class="vg">$PORT</span>
</pre></td></tr></tbody></table></code></pre></div>
<p>Puma is working great.</p>
<p>It's as simple as that. Once pushed to Heroku, everything just works.</p>
<h2><a href="#keeping-the-site-alive" id="keeping-the-site-alive">Keeping the site alive</a></h2>
<p>I'm using a single free <a href="https://devcenter.heroku.com/articles/dynos">Dyno</a> to serve the site and it's seriously fast. Granted,
this site is not receiving lots of traffic, but it's still very quick.</p>
<p>The only downside of a single Heroku Dyno is that it will idle after a period
of inactivity, which can happen often unless you get lots of regular traffic.</p>
<p>I order to keep the site alive, the sites needs to be requested periodically.</p>
<p>I'm using <a href="http://www.pingdom.com">Pingdom</a> for this.</p>
<h2><a href="#conclusion" id="conclusion">Conclusion</a></h2>
<p>Middleman is really easy to work with, especially for a Rails developer, and
Heroku serves the site very well. I would definitely recommend the combination
of Middleman and Heroku.</p>
The Middleman build environmenthttp://jordanelver.co.uk/blog/2014/02/10/the-middleman-build-environment/2014-02-10T00:00:00+00:002020-06-04T11:27:38+01:00Jordan Elver<p>Rails has long had the concept of environments built-in. That is, the ability
to set the environment to <strong>development</strong>, <strong>production</strong> or <strong>test</strong>, and only
run code when in one or more of those environments. For example, the
development environment has class caching turned off by default, so that code
is reloaded on every request, perfect whilst developing. In production, this is
turned on, for much improved performance.</p>
<p>Middleman has a similar idea, but the environments are <strong>build</strong> and
<strong>development</strong>.</p>
<p>I recently took advantage of this feature, specifically, the build environment.</p>
<p>The build environment is set when the site is being built using <code>middleman
build</code>. I used this to only include Google Analytics tracking code when the
site is built. This stops local web browsing from affecting my web statistics.</p>
<p>In my <code>layout.erb</code>, I've used the <code>build?</code> helper to conditionally include the
relevant JavaScript code.</p>
<div class="highlight"><pre class="highlight erb"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
</pre></td><td class="rouge-code"><pre><span class="cp"><%</span> <span class="k">if</span> <span class="n">build?</span> <span class="cp">%></span>
<span class="nt"><script </span><span class="na">type=</span><span class="s">"text/javascript"</span><span class="nt">></span>
<span class="kd">var</span> <span class="nx">_gaq</span> <span class="o">=</span> <span class="nx">_gaq</span> <span class="o">||</span> <span class="p">[];</span>
<span class="nx">_gaq</span><span class="p">.</span><span class="nx">push</span><span class="p">([</span><span class="dl">'</span><span class="s1">_setAccount</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">xxxxxxxxxx</span><span class="dl">'</span><span class="p">]);</span>
<span class="nx">_gaq</span><span class="p">.</span><span class="nx">push</span><span class="p">([</span><span class="dl">'</span><span class="s1">_trackPageview</span><span class="dl">'</span><span class="p">]);</span>
<span class="p">(</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">ga</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="dl">'</span><span class="s1">script</span><span class="dl">'</span><span class="p">);</span> <span class="nx">ga</span><span class="p">.</span><span class="nx">type</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">text/javascript</span><span class="dl">'</span><span class="p">;</span> <span class="nx">ga</span><span class="p">.</span><span class="k">async</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
<span class="nx">ga</span><span class="p">.</span><span class="nx">src</span> <span class="o">=</span> <span class="p">(</span><span class="dl">'</span><span class="s1">https:</span><span class="dl">'</span> <span class="o">==</span> <span class="nb">document</span><span class="p">.</span><span class="nx">location</span><span class="p">.</span><span class="nx">protocol</span> <span class="p">?</span> <span class="dl">'</span><span class="s1">https://ssl</span><span class="dl">'</span> <span class="p">:</span> <span class="dl">'</span><span class="s1">http://www</span><span class="dl">'</span><span class="p">)</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">.google-analytics.com/ga.js</span><span class="dl">'</span><span class="p">;</span>
<span class="kd">var</span> <span class="nx">s</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementsByTagName</span><span class="p">(</span><span class="dl">'</span><span class="s1">script</span><span class="dl">'</span><span class="p">)[</span><span class="mi">0</span><span class="p">];</span> <span class="nx">s</span><span class="p">.</span><span class="nx">parentNode</span><span class="p">.</span><span class="nx">insertBefore</span><span class="p">(</span><span class="nx">ga</span><span class="p">,</span> <span class="nx">s</span><span class="p">);</span>
<span class="p">})();</span>
<span class="nt"></script></span>
<span class="cp"><%</span> <span class="k">end</span> <span class="cp">%></span>
</pre></td></tr></tbody></table></code></pre></div>
<p>As you can see, this is very simple, but also very useful for customising
templates at build time.</p>
Troubleshooting Heroku deploys with empty Git commitshttp://jordanelver.co.uk/blog/2014/02/04/troubleshooting-heroku-deploys-with-empty-git-commits/2014-02-04T00:00:00+00:002020-06-04T11:27:38+01:00Jordan Elver<p>When troubleshooting Heroku deployments, you need to push to the Heroku remote
over and over until you've found the problem. It's necessary to add new Git
commits in order to force Heroku to re-compile your <a href="https://devcenter.heroku.com/articles/slug-compiler">application slug</a> or else
you will get the <code>Everything up-to-date</code> message back from Git.</p>
<p>Rather than making trivial changes all the time, you can add empty commits.</p>
<div class="highlight"><pre class="highlight plaintext"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>git commit --allow-empty -m "Force slug recompilation"
</pre></td></tr></tbody></table></code></pre></div>
<p>It still adds unnecessary commits to your repo, but it's cleaner and at least
you're not touching any code.</p>
<h2><a href="#update" id="update">Update</a></h2>
<p><a href="http://simonstarr.com">Simon Starr</a> has mentioned another method to achieve the same thing. Using
Git with the <code>--amend</code> flag.</p>
<div class="highlight"><pre class="highlight plaintext"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>git commit --amend
</pre></td></tr></tbody></table></code></pre></div>
<p>Usually, this is used to add missing files to your previous commit or change
the commit message, but it <em>also</em> removes the previous commit and creates a new
one, which means that Heroku will update and re-compile without a problem.</p>
<p>Be careful though, this is not a good method if you've already shared your
commits with someone else, but it's worth bearing in mind depending on the
situation as it saves you adding <em>any</em> new commits to your repo. Thanks, Simon.</p>
Find where your code broke with git bisecthttp://jordanelver.co.uk/blog/2014/01/31/find-where-your-code-broke-with-git-bisect/2014-01-31T00:00:00+00:002020-06-04T11:27:38+01:00Jordan Elver<p>Your code is broken. You know everything was working at a certain point in time
or that a particular commit was ok, but you're not sure which commit
<em>since</em> then has introduced the problem.</p>
<p>You need <code>git bisect</code>.</p>
<p>From the <a href="http://git-scm.com/docs/git-bisect">git documentation</a>.</p>
<blockquote>
<p>git-bisect - Find by binary search the change that introduced a bug</p>
</blockquote>
<p>Umm, right. Ok.</p>
<p>A <code>git bisect</code> starts by specifying a <strong>good</strong> and a <strong>bad</strong> revision. <code>git</code> will
then checkout each commit by splitting (or bisecting) the range of commits and
running the supplied command until it returns with an exit code of zero.</p>
<p>An exit code of zero means the command was successful.</p>
<p>The command can be anything that <strong>will</strong> return an exit code, which includes
most *nix binaries or scripts.</p>
<p>In this example, we're using <a href="http://cukes.info">Cucumber</a> as the command.</p>
<h2><a href="#a-working-example-using-cucumber" id="a-working-example-using-cucumber">A working example using Cucumber</a></h2><div class="highlight"><pre class="highlight plaintext"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
</pre></td><td class="rouge-code"><pre>git bisect start
git bisect good <commit-where-stuff-is-working>
git bisect bad <commit-where-stuff-is-not-working>
git bisect run bundle exec cucumber features/a-feature.feature
</pre></td></tr></tbody></table></code></pre></div>
<p>If the tests fail, Cucumber will return a non-zero exit code and <code>git</code> will
keep trying other commits until it finds one that is successful. We will then
know when the code was "good", and more importantly, the commit where it became
"bad".</p>
<p>When finished, reset your branch to the previous state before starting the <code>git
bisect</code>.</p>
<div class="highlight"><pre class="highlight plaintext"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>git bisect reset
</pre></td></tr></tbody></table></code></pre></div>
<p>As you'd expect with <code>git</code>, it <a href="http://git-scm.com/docs/git-bisect">can do <em>a lot</em> more than this</a>, but this is
the way in which I normally use it.</p>
It's the little thingshttp://jordanelver.co.uk/blog/2011/07/30/its-the-little-things/2011-07-30T01:00:00+01:002020-06-04T11:27:38+01:00Jordan Elver<p>I've been thinking about the <a href="http://littlebigdetails.com/">little things</a> that can be changed to make a
web page easier to use. Seemingly small changes can add up to have a big impact
on the way people will interact with the page.</p>
<p>On one particular site, I have a pretty standard widget used to select the
amount of products to show per page. I'm sure you've seen something similar to
that of below on many other websites.</p>
<p><img src="/images/dropdown.gif" alt="Example of a dropdown list used to select the amount of products to show on the page" /></p>
<p>The way this is currently implemented, it takes <strong>three</strong> clicks to complete
the intended operation. This could be reduced to <strong>two</strong> clicks by adding some
JavaScript to trigger the page change (and removing the button), but there is a
problem with this method: The options at <strong>not all visible</strong> at once. You have
to click to see what the options are.</p>
<h2><a href="#rethinking-the-implementation" id="rethinking-the-implementation">Rethinking the implementation</a></h2>
<p>I decided to change the way this was implemented, moving to a horizontal list of links.</p>
<p><img src="/images/list.gif" alt="Example of a list used to select the amount of products to show on the page" /></p>
<p>The advantages of this approach are:</p>
<ul>
<li>You can see all options at once. Please, <a href="http://www.amazon.co.uk/Dont-Make-Me-Think-Usability/dp/0321344758">don't make me think</a>.</li>
<li>It's only a <strong>single</strong> click to select an option.</li>
<li>It's better for accessibility because you don't have to fiddle with a
dropdown (and who wants to?).</li>
</ul>
<p>I think this is a fairly good example of how changing small things, perhaps
even those that seem inconsequential, can contribute to improving the overall
experience of the page, and delight the user.</p>
Rails Footnotes problemhttp://jordanelver.co.uk/blog/2011/07/24/rails-footnotes-problem/2011-07-24T01:00:00+01:002020-06-04T11:27:38+01:00Jordan Elver<p>I recently had a head-scratching time trying to figure out why code that
integrates with a 3rd party payment processor had suddenly stopped working. The
code in question simply returns a text only response to the 3rd party, who then
read, parse and act on it. Within my Rails controller:</p>
<div class="highlight"><pre class="highlight ruby"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre><span class="n">render</span> <span class="ss">:text</span> <span class="o">=></span> <span class="s2">"Status=OK,Detail=Some details here"</span>
</pre></td></tr></tbody></table></code></pre></div>
<p>I found the culprit by going through my git log to see what had changed
recently. Turns out it was the recent addition of Rails Footnotes causing the
problem. <a href="http://rubydoc.info/gems/rails-footnotes/3.7.4/frames">Rails Footnotes</a> is an awesome gem that adds various information
about your app to the bottom of every page including clickable links to
controllers, views and partials. Clicking these links opens the file in your
text editor of choice.</p>
<p>Rails Footnotes was appending its debugging HTML (as designed) to my text-only
response, breaking the payment processors parsing. After an <a href="http://railstips.org/blog/archives/2010/10/14/stop-googling/">inspection of the
source</a> I noticed that Footnotes are only output if the content type
includes "html", such as "text/html".</p>
<h2><a href="#the-fix" id="the-fix">The fix</a></h2>
<p>In my case the whole controller could (and probably should) be returned as
"text/plain". I hooked up a <code>before_filter</code> to add the <code>Content-Type</code> header to
the response and that fixed it.</p>
<div class="highlight"><pre class="highlight ruby"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
</pre></td><td class="rouge-code"><pre><span class="k">class</span> <span class="nc">NotificationController</span> <span class="o"><</span> <span class="no">ApplicationController</span>
<span class="c1"># Set the content type to text/plain so footnotes don't show</span>
<span class="n">before_filter</span> <span class="ss">:set_content_type</span>
<span class="k">def</span> <span class="nf">set_content_type</span>
<span class="n">headers</span><span class="p">[</span><span class="s1">'Content-Type'</span><span class="p">]</span> <span class="o">=</span> <span class="s1">'text/plain'</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></td></tr></tbody></table></code></pre></div>
<p>For versions of Rails Footnotes greater than 3.7.0 (which is Rails 3 only) you
can actually configure if Footnotes are added to the page using an initializer
(or similar). Unfortunately, I'm still using Rails 2 on this project so I
currently can't take advantage of that.</p>
<h2><a href="#in-conclusion" id="in-conclusion">In conclusion</a></h2>
<p><a href="http://rubydoc.info/gems/rails-footnotes/3.7.4/frames">Rails Footnotes</a> is a must-have, you really should check it out.</p>
Automatically attaching to a tmux session via SSHhttp://jordanelver.co.uk/blog/2010/11/27/automatically-attaching-to-a-tmux-session-via-ssh/2010-11-27T00:00:00+00:002020-06-04T11:27:38+01:00Jordan Elver<p>I've been using <a href="http://tmux.sourceforge.net/">tmux</a> as a
<a href="http://www.gnu.org/software/screen/">screen</a> replacement for a while now. I
find it easier to use and configure than screen. I tend to leave a tmux session
running on servers that I administer so that everything is just as it was when
I last connected. It's very handy.</p>
<p>To make this even more convenient, I wanted to be able to automatically attach
to a running tmux session when connecting to servers using
<a href="http://en.wikipedia.org/wiki/Secure_Shell">SSH</a>. The SSH client already comes
with the ability to run a command when connecting. It works like this:</p>
<div class="highlight"><pre class="highlight shell"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>ssh <<span class="nb">hostname</span><span class="o">></span> <<span class="nb">command</span><span class="o">></span>
</pre></td></tr></tbody></table></code></pre></div>
<p>Unfortunately, this didn't work when I tried attaching to a tmux session.</p>
<div class="highlight"><pre class="highlight shell"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre>ssh <<span class="nb">hostname</span><span class="o">></span> tmux a
not a terminal
</pre></td></tr></tbody></table></code></pre></div>
<p>After a bit of Googling, it turns out that you need to supply the <code>-t</code> option
to the <code>ssh</code> command. The ssh man page describes the option as such:</p>
<div class="highlight"><pre class="highlight shell"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
</pre></td><td class="rouge-code"><pre><span class="nt">-t</span> Force pseudo-tty allocation. This can be used to execute
arbitrary screen-based programs on a remote machine,
which can be very useful, e.g., when implementing menu
services.
</pre></td></tr></tbody></table></code></pre></div>
<p>If we do that, we're in business:</p>
<div class="highlight"><pre class="highlight shell"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>ssh <<span class="nb">hostname</span><span class="o">></span> <span class="nt">-t</span> tmux a
</pre></td></tr></tbody></table></code></pre></div>
<p>To make the command even shorter, I've been adding bash aliases to my
<code>~/.bash_profile</code> for each server I connect to, like so:</p>
<div class="highlight"><pre class="highlight shell"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre><span class="nb">alias </span><span class="nv">servername</span><span class="o">=</span><span class="s2">"ssh servername -t tmux a"</span>
</pre></td></tr></tbody></table></code></pre></div>
<p>Now, I can just type <code>servername</code> and get a SSH connection to servername with
tmux automatically attached. Issuing a <code><ctrl> + b + d</code> will detach the tmux
session and disconnect the SSH connection.</p>
<p>Command line magic!</p>
How to upgrade Phusion Passenger for nginxhttp://jordanelver.co.uk/blog/2009/11/27/how-to-upgrade-phusion-passenger-for-nginx/2009-11-27T00:00:00+00:002020-06-04T11:27:38+01:00Jordan Elver<p>I keep forgetting the procedure to upgrade Passenger and nginx, so thought I'd
note them down here in case anyone finds it useful. These instructions assume
that:</p>
<ul>
<li>You already have <a href="http://www.modrails.com/">Phusion Passenger</a> installed and
configured for <a href="http://nginx.net/">nginx</a></li>
<li>You're using <a href="http://www.rubyenterpriseedition.com/">Ruby Enterprise Edition</a></li>
</ul>
<h2><a href="#upgrade" id="upgrade">Upgrade</a></h2>
<p>Download the latest nginx source and uncompress to a temporary directory.</p>
<div class="highlight"><pre class="highlight shell"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre><span class="nb">cd</span> ~/sources
wget http://sysoev.ru/nginx/nginx-0.7.64.tar.gz
<span class="nb">tar </span>xzvf nginx-0.7.64.tar.gz
</pre></td></tr></tbody></table></code></pre></div>
<p>Install the latest Passenger gem</p>
<div class="highlight"><pre class="highlight shell"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre><span class="nb">sudo</span> /opt/ruby-enterprise-1.8.6-20090520/bin/gem <span class="nb">install </span>passenger
</pre></td></tr></tbody></table></code></pre></div>
<p>Run the Passenger installer</p>
<div class="highlight"><pre class="highlight shell"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre><span class="nb">sudo</span> /opt/ruby-enterprise-1.8.6-20090520/bin/passenger-install-nginx-module
</pre></td></tr></tbody></table></code></pre></div>
<p>Follow the onscreen instructions, choose option 2 to use your own sources.
Specify the location of the nginx sources when prompted.</p>
<p>If you want to use any other optional nginx modules, enable them when prompted.
I want to use the SSL and Status modules, so I enable them like this:</p>
<div class="highlight"><pre class="highlight shell"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre><span class="nt">--with-http_ssl_module</span> <span class="nt">--with-http_stub_status_module</span>
</pre></td></tr></tbody></table></code></pre></div>
<p>The full configure line will look something like this:</p>
<div class="highlight"><pre class="highlight shell"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre>./configure <span class="nt">--prefix</span><span class="o">=</span><span class="s1">'/opt/nginx'</span> <span class="nt">--with-pcre</span><span class="o">=</span><span class="s1">'/tmp/pcre-7.8'</span>
<span class="nt">--add-module</span><span class="o">=</span><span class="s1">'/opt/ruby-enterprise-1.8.6-20090520/lib/ruby/gems/1.8/gems/passenger-2.2.5/ext/nginx'</span>
<span class="nt">--with-http_ssl_module</span> <span class="nt">--with-http_stub_status_module</span>
</pre></td></tr></tbody></table></code></pre></div>
<p>After the installer has finished, the new version will be installed in
<code>/opt/nginx</code> and the old version will be renamed to <code>nginx.old</code>.</p>
<h2><a href="#configure" id="configure">Configure</a></h2>
<p>Update the <code>nginx.conf</code> with the correct paths, you should only need to change
the path to the <code>passenger_root</code> directive.</p>
<div class="highlight"><pre class="highlight shell"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
</pre></td><td class="rouge-code"><pre>http <span class="o">{</span>
...
passenger_root /opt/ruby-enterprise-1.8.6-20090520/lib/ruby/gems/1.8/gems/passenger-2.2.5<span class="p">;</span>
...
<span class="o">}</span>
</pre></td></tr></tbody></table></code></pre></div><h2><a href="#restart" id="restart">Restart</a></h2>
<p>I like to do a config file syntax check before I restart nginx, just in case
of typos and the like.</p>
<div class="highlight"><pre class="highlight shell"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre><span class="nb">sudo</span> /opt/nginx/sbin/nginx <span class="nt">-t</span> <span class="nt">-c</span> /opt/nginx/conf/nginx.conf
</pre></td></tr></tbody></table></code></pre></div>
<p>If the check is successful, restart nginx.</p>
<div class="highlight"><pre class="highlight shell"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre><span class="nb">sudo</span> /etc/init.d/nginx restart
</pre></td></tr></tbody></table></code></pre></div>
<p>You should now be running the latest version of Passenger and nginx.</p>
jQuery Event Delegationhttp://jordanelver.co.uk/blog/2009/06/03/jquery-event-delegation/2009-06-03T01:00:00+01:002020-06-04T11:27:38+01:00Jordan Elver<p>Recently, I've been working on adding some extra features to a shopping cart
page using <a href="http://jquery.com/">jQuery</a>. The idea is that you select your
country from a dropdown and an AJAX(Asynchronous JavaScript and XML) request is
performed, which updates a list of radio buttons on the page. If you select one
of these radio buttons, it triggers another AJAX(Asynchronous JavaScript and
XML) request, which updates the postage prices for the given items in the
basket.</p>
<p>Each of the radio buttons has an event like this bound to it.</p>
<div class="highlight"><pre class="highlight javascript"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre><span class="nx">$</span><span class="p">(</span><span class="dl">'</span><span class="s1">#options input</span><span class="dl">'</span><span class="p">).</span><span class="nx">click</span><span class="p">(</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
<span class="c1">// lookup postage prices via ajax</span>
<span class="p">});</span>
</pre></td></tr></tbody></table></code></pre></div>
<p>This triggers the AJAX(Asynchronous JavaScript and XML) request which looks up
the postage prices from the database. It works fine with the default set of
radio buttons that are present on the page when it loads. The problem comes
when new radio buttons are added to the page via AJAX(Asynchronous JavaScript
and XML). When a new radio button is added to the list, it doesn't
automatically have a click event (like the one above) bound to it like the
existing items do. The events are not automatically carried across to the new
item you've added.</p>
<p>We can get around this problem by re-binding the events each time a new radio
button is added to the page. But this can be messy and there is a much cleaner
way of solving the problem.</p>
<h2><a href="#event-delegation-to-the-rescue" id="event-delegation-to-the-rescue">Event delegation to the rescue</a></h2>
<p>This is where event delegation comes in. Event delegation takes advantage of
the fact that browsers "bubble" events up the DOM(Document Object Model) when
they are triggered on a page. For example, given the HTML(HyperText Markup
Language) below, when clicking the radio button the browser will generate a
click event which will "bubble" upwards so that first the <code><input></code> element
receives the event, followed by the <code><p></code>, then the <code><div></code>, and so on.</p>
<div class="highlight"><pre class="highlight html"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
</pre></td><td class="rouge-code"><pre><span class="nt"><div</span> <span class="na">id=</span><span class="s">"options"</span><span class="nt">></span>
<span class="nt"><p></span>
<span class="nt"><input</span> <span class="na">type=</span><span class="s">"radio"</span> <span class="na">value=</span><span class="s">"1"</span> <span class="na">id=</span><span class="s">"postage_method_1"</span> <span class="nt">/></span>
<span class="nt"></p></span>
<span class="nt"><p></span>
<span class="nt"><input</span> <span class="na">type=</span><span class="s">"radio"</span> <span class="na">value=</span><span class="s">"2"</span> <span class="na">id=</span><span class="s">"postage_method_2"</span> <span class="nt">/></span>
<span class="nt"></p></span>
<span class="nt"></div></span>
</pre></td></tr></tbody></table></code></pre></div>
<p>We can take advantage of this "event bubbling". Instead of binding the event to
each individual <code>input</code>, we'll bind it to a parent element instead and let the
click events from the inputs bubble up to the parent element. In this case, the
div called options.</p>
<div class="highlight"><pre class="highlight javascript"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
</pre></td><td class="rouge-code"><pre><span class="nx">$</span><span class="p">(</span><span class="dl">'</span><span class="s1">#options</span><span class="dl">'</span><span class="p">).</span><span class="nx">click</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">event</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">$</span><span class="p">(</span><span class="nx">event</span><span class="p">.</span><span class="nx">target</span><span class="p">).</span><span class="nx">is</span><span class="p">(</span><span class="dl">'</span><span class="s1">input</span><span class="dl">'</span><span class="p">)){</span>
<span class="c1">// do ajax request</span>
<span class="p">});</span>
<span class="p">});</span>
</pre></td></tr></tbody></table></code></pre></div>
<p>Now, each time we add an additional input to the page, it will automatically
respond to the same event as the rest of the inputs.</p>
<p>It's worth noting that this event will get triggered whenever any element under
the <code>#options</code> div is clicked, so inside the event we check that the target of
the event was an <code>input</code> element. This will restrict the code inside the event
so that it only executes when it's an <code>input</code> that's been clicked.</p>
<h2><a href="#conclusion" id="conclusion">Conclusion</a></h2>
<p>Event delegation greatly improves the readability of the code. It sounds scary,
but really isn't. It also has the added benefit of speeding up the processing
of the page because you're not spending time looping through many DOM elements
(which takes time) in order to add your event handlers.</p>
<p>Check out the references below for a much better explanation of how event
delegation and binding of event handlers work.</p>
<h2><a href="#references" id="references">References</a></h2>
<ul>
<li><a href="http://icant.co.uk/sandbox/eventdelegation/">Event Delegation versus Event Handling</a></li>
<li><a href="http://lab.distilldesign.com/event-delegation/">Event Delegation with jQuery</a></li>
<li><a href="http://www.danwebb.net/2008/2/8/event-delegation-made-easy-in-jquery">Event Delegation Made Easy</a></li>
<li>Learning jQuery - Working with Events, <a href="http://www.learningjquery.com/2008/03/working-with-events-part-1">part 1</a>, and <a href="http://www.learningjquery.com/2008/05/working-with-events-part-2">part 2</a></li>
</ul>
htop - a prettier tophttp://jordanelver.co.uk/blog/2008/12/31/htop-a-prettier-top/2008-12-31T00:00:00+00:002020-06-04T11:27:38+01:00Jordan Elver<p>You use <a href="http://en.wikipedia.org/wiki/Top_%28Unix%29">top</a> right? No? Well,
that's probably because top is not the friendliest program in the world. Yes,
it gets the job done, but it could be prettier.</p>
<p>As I was using top more and more for monitoring my VPS, I looked for something
a bit nicer.</p>
<p>This is where <a href="http://htop.sourceforge.net/">htop</a> comes in. It's top, but with
colours, progress bars, and a process tree view. Much better. Try it, you
might like it.</p>
<p><img src="/images/htop.jpg" /></p>
Changing Recent Commits with Githttp://jordanelver.co.uk/blog/2008/12/16/changing-recent-commits-with-git/2008-12-16T00:00:00+00:002020-06-04T11:27:38+01:00Jordan Elver<p>Have you ever committed to your repo and realised you've done something a bit
silly? I just did that. I moved to a new machine and forgot to setup my
<code>.gitconfig</code> with the correct username and email, so when I committed, the
commit had the wrong user against it. Not a big problem, but it just looks a
bit...well, untidy.</p>
<p>Fear not. Git is all-powerful and allows you to change your mistake even
though you've already committed it. Thanks to Tekkub on the
<a href="http://groups.google.com/group/github">GitHub Google Group</a> for
the nudge in the right direction.</p>
<h2><a href="#how-does-i-dos-it" id="how-does-i-dos-it">How does I dos it?</a></h2>
<p>Like this.</p>
<div class="highlight"><pre class="highlight shell"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>git reset <span class="nt">--soft</span> HEAD^
</pre></td></tr></tbody></table></code></pre></div>
<p>This will reset your working copy to the state before you committed and remove
the commit so you can make changes to your working copy (if needed) and
re-commit.</p>
<p>Re-commit the changes.</p>
<div class="highlight"><pre class="highlight shell"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>git commit <span class="nt">-m</span> <span class="s2">"Changed something"</span>
</pre></td></tr></tbody></table></code></pre></div>
<p>If you've already pushed to "GitHub":http://www.github.com or another external
repo, you can re-push, but you'll need to add <code>--force</code> because we have removed
a commit locally and Git doesn't like that.</p>
<div class="highlight"><pre class="highlight shell"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>git push origin <span class="nt">--force</span>
</pre></td></tr></tbody></table></code></pre></div>
<p>I'm not sure whether this would be a good idea if someone else has cloned your
repo, but I'm ok because I am the only person who pushes to this one.</p>
<p>Git is great, but powerful, so be careful. I'm sure it would be easy to
completely trash your repo with the wrong series of commands.</p>
Google Analytics Ecommerce Trackinghttp://jordanelver.co.uk/blog/2008/08/07/google-analytics-ecommerce-tracking/2008-08-07T01:00:00+01:002020-06-04T11:27:38+01:00Jordan Elver<p>So you've installed <a href="http://www.google.com/analytics">Google Analytics</a>, the
superb free web analytics software and it's telling you all sorts of things
about your site, most of which you don't even understand. You're learning what
a <a href="http://en.wikipedia.org/wiki/Bounce_Rate">Bounce Rate</a> is and realising how
powerful this thing really is.</p>
<p>Well, it gets even better. I've recently discovered its' Ecommerce tracking
feature. It allows you to capture Ecommerce related statistics alongside your
normal web related statistics. Once you have it correctly setup for your site
you can see how much money you've taken in sales, how many transactions have
taken place, how many products have been purchased, and your Ecommerce
conversion rate. Very nice.</p>
<h2><a href="#make-it-so" id="make-it-so">Make it so</a></h2>
<p>In order to activate Ecommerce tracking, you need to firstly turn on the
feature within Google Analytics, and secondly add some extra JavaScript code to
your receipt or thanks page.</p>
<p>To turn on the feature, go to the Profile Settings for your site, click Edit
(top right) and under E-Commerce Website, select "Yes, an E-Commerce Site".</p>
<p>Here's the extra code you need to add to the receipt page straight from
<a href="http://www.google.com/support/googleanalytics/bin/answer.py?hl=en&answer=55528">Google's help page</a></p>
<div class="highlight"><pre class="highlight html"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">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
29
30
31
</pre></td><td class="rouge-code"><pre><span class="nt"><script </span><span class="na">type=</span><span class="s">"text/javascript"</span><span class="nt">></span>
<span class="kd">var</span> <span class="nx">gaJsHost</span> <span class="o">=</span> <span class="p">((</span><span class="dl">"</span><span class="s2">https:</span><span class="dl">"</span> <span class="o">==</span> <span class="nb">document</span><span class="p">.</span><span class="nx">location</span><span class="p">.</span><span class="nx">protocol</span><span class="p">)</span> <span class="p">?</span> <span class="dl">"</span><span class="s2">https://ssl.</span><span class="dl">"</span> <span class="p">:</span> <span class="dl">"</span><span class="s2">http://www.</span><span class="dl">"</span><span class="p">);</span>
<span class="nb">document</span><span class="p">.</span><span class="nx">write</span><span class="p">(</span><span class="nx">unescape</span><span class="p">(</span><span class="dl">"</span><span class="s2">%3Cscript src='</span><span class="dl">"</span> <span class="o">+</span> <span class="nx">gaJsHost</span> <span class="o">+</span> <span class="dl">"</span><span class="s2">google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E</span><span class="dl">"</span><span class="p">));</span>
<span class="nt"></script></span>
<span class="nt"><script </span><span class="na">type=</span><span class="s">"text/javascript"</span><span class="nt">></span>
<span class="kd">var</span> <span class="nx">pageTracker</span> <span class="o">=</span> <span class="nx">_gat</span><span class="p">.</span><span class="nx">_getTracker</span><span class="p">(</span><span class="dl">"</span><span class="s2">UA-XXXXX-1</span><span class="dl">"</span><span class="p">);</span>
<span class="nx">pageTracker</span><span class="p">.</span><span class="nx">_trackPageview</span><span class="p">();</span>
<span class="nx">pageTracker</span><span class="p">.</span><span class="nx">_addTrans</span><span class="p">(</span>
<span class="dl">"</span><span class="s2">1234</span><span class="dl">"</span><span class="p">,</span> <span class="c1">// Order ID</span>
<span class="dl">"</span><span class="s2">Mountain View</span><span class="dl">"</span><span class="p">,</span> <span class="c1">// Affiliation</span>
<span class="dl">"</span><span class="s2">11.99</span><span class="dl">"</span><span class="p">,</span> <span class="c1">// Total</span>
<span class="dl">"</span><span class="s2">1.29</span><span class="dl">"</span><span class="p">,</span> <span class="c1">// Tax</span>
<span class="dl">"</span><span class="s2">5</span><span class="dl">"</span><span class="p">,</span> <span class="c1">// Shipping</span>
<span class="dl">"</span><span class="s2">San Jose</span><span class="dl">"</span><span class="p">,</span> <span class="c1">// City</span>
<span class="dl">"</span><span class="s2">California</span><span class="dl">"</span><span class="p">,</span> <span class="c1">// State</span>
<span class="dl">"</span><span class="s2">USA</span><span class="dl">"</span> <span class="c1">// Country</span>
<span class="p">);</span>
<span class="nx">pageTracker</span><span class="p">.</span><span class="nx">_addItem</span><span class="p">(</span>
<span class="dl">"</span><span class="s2">1234</span><span class="dl">"</span><span class="p">,</span> <span class="c1">// Order ID</span>
<span class="dl">"</span><span class="s2">DD44</span><span class="dl">"</span><span class="p">,</span> <span class="c1">// SKU</span>
<span class="dl">"</span><span class="s2">T-Shirt</span><span class="dl">"</span><span class="p">,</span> <span class="c1">// Product Name</span>
<span class="dl">"</span><span class="s2">Green Medium</span><span class="dl">"</span><span class="p">,</span> <span class="c1">// Category</span>
<span class="dl">"</span><span class="s2">11.99</span><span class="dl">"</span><span class="p">,</span> <span class="c1">// Price</span>
<span class="dl">"</span><span class="s2">1</span><span class="dl">"</span> <span class="c1">// Quantity</span>
<span class="p">);</span>
<span class="nx">pageTracker</span><span class="p">.</span><span class="nx">_trackTrans</span><span class="p">();</span>
<span class="nt"></script></span>
</pre></td></tr></tbody></table></code></pre></div>
<p>If you're already using Analytics, the first few lines will be familier to you.
They're just the standard page tracking calls. The bits we're interested in are
the <code>pageTacker._addTrans()</code>, <code>pageTracker._addItem()</code> and
<code>pageTracker._trackTrans()</code> methods. You'll need to fill in the relevant
details, and more importantly repeat the <code>_addItem</code> call for each item within
the order. The method for doing this will obviously differ depending on your
server side choices, but for Rails I've got something similar to this.</p>
<div class="highlight"><pre class="highlight erb"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
</pre></td><td class="rouge-code"><pre><span class="cp"><%</span> <span class="vi">@order</span><span class="p">.</span><span class="nf">items</span><span class="p">.</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">item</span><span class="o">|</span> <span class="cp">%></span>
pageTracker._addItem(
<span class="cp"><%=</span> <span class="vi">@order</span><span class="p">.</span><span class="nf">order_id</span> <span class="cp">%></span>, // Order ID
<span class="cp"><%=</span> <span class="vi">@order</span><span class="p">.</span><span class="nf">sku</span> <span class="cp">%></span>, // SKU
<span class="cp"><%=</span> <span class="n">item</span><span class="p">.</span><span class="nf">product</span><span class="p">.</span><span class="nf">name</span> <span class="cp">%></span>, // Product Name
<span class="cp"><%=</span> <span class="n">item</span><span class="p">.</span><span class="nf">product</span><span class="p">.</span><span class="nf">category</span> <span class="cp">%></span>, // Category
<span class="cp"><%=</span> <span class="n">item</span><span class="p">.</span><span class="nf">unit_price</span> <span class="cp">%></span>, // Price
<span class="cp"><%=</span> <span class="n">item</span><span class="p">.</span><span class="nf">quantity</span> <span class="cp">%></span> // Quantity
);
<span class="cp"><%</span> <span class="k">end</span> <span class="cp">%></span>
</pre></td></tr></tbody></table></code></pre></div>
<p>You should now start to get E-Commerce stats showing up under the Ecommerce
section of Google Analytics.</p>
<h2><a href="#references" id="references">References</a></h2>
<ul>
<li><a href="http://www.google.com/support/googleanalytics/bin/answer.py?hl=en&answer=55528">Google Analytics Help</a></li>
<li><a href="http://www.epikone.com/blog/2008/07/02/google-analytics-e-commerce-tracking-pt-4-tacking-lead-gen-forms/">Analytics Talk</a></li>
</ul>
Git Patching Flexibilityhttp://jordanelver.co.uk/blog/2008/08/04/git-patching-flexibility/2008-08-04T01:00:00+01:002020-06-04T11:27:38+01:00Jordan Elver<p>I don't know about you, but when I'm coding I'll often need to make a change
that's unrelated to the current feature or bug I am working on. I'll notice a
spelling mistake, a typo, or some other small code change. You don't want to
commit the unrelated change as part of your overall commit because, well, it's
unrelated. It should be its own commit with its own commit message. This will
make it much easier to read the commit log and cuts down on confusion. People
won't have to ask "Why did they change that? It has nothing to do with the
change"</p>
<p>When I used Subversion, you had to remove the unrelated change, commit, then
add it back in and commit again. Maybe there was a nicer way, but I didn't
know it. Git is much nicer.</p>
<p>When you stage a file ready for committing, Git allows you to only stage a
certain part or parts of the file, which it calls hunks.</p>
<p>So, run:</p>
<div class="highlight"><pre class="highlight shell"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>git add <span class="nt">--patch</span> <filename>
</pre></td></tr></tbody></table></code></pre></div>
<p>Git will show you a diff of the first difference that it wants to stage and
will ask if you'd like to stage the current hunk, like this:</p>
<div class="highlight"><pre class="highlight shell"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>Stage this hunk <span class="o">[</span>y/n/a/d/s/?]?
</pre></td></tr></tbody></table></code></pre></div>
<p>The options in this example are:</p>
<div class="highlight"><pre class="highlight shell"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
</pre></td><td class="rouge-code"><pre>y - stage this hunk
n - <span class="k">do </span>not stage this hunk
a - stage this and all the remaining hunks <span class="k">in </span>the file
d - <span class="k">do </span>not stage this hunk nor any of the remaining hunks <span class="k">in </span>the file
s - <span class="nb">split </span>the current hunk into smaller hunks
</pre></td></tr></tbody></table></code></pre></div>
<p>Choose what you'd like to do with the current hunk. Git will continue to go
through the rest of the hunks in the file, asking you what to do with each one.</p>
<p>When you've decided which hunks should be staged, commit as normal and only
those changes will form part of the commit. You can then add the rest of
your changes and commit again.</p>
<p>It's the flexibility that's great. It works with you, not against you.</p>
<p>For a much more in depth look at the capabilities of <code>git add --patch</code>, see
the following article.</p>
<ul>
<li><a href="http://tomayko.com/writings/the-thing-about-git%20by
"Ryan%20Tomayko":http://tomayko.com/">The Thing About Git</a></li>
</ul>
How to generate / request an SSL certificatehttp://jordanelver.co.uk/blog/2008/07/24/how-to-generate-request-an-ssl-certificate/2008-07-24T01:00:00+01:002020-06-04T11:27:38+01:00Jordan Elver<p>Generating an SSL certificate can be confusing if you've never done it
before. Actually, it's confusing if you have done it before. Hopefully this
should remind me how to do it in the future!</p>
<p><strong>PLEASE NOTE:</strong> I am no expert on SSL, but this does the job for me.</p>
<p>I recently had to do this after the <a href="http://www.ubuntu.com/usn/usn-612-1">Debian security
vulnerability</a> affected one of my SSL
certificates.</p>
<p>I currently get my SSL cerficiates through <a href="http://www.namecheap.com">NameCheap</a>
for $10. They are re-sellers of RapidSSL and GeoTrust certificates. Mine is a
RapidSSL.</p>
<h2><a href="#generate-a-private-key-and-certificate-signing-request" id="generate-a-private-key-and-certificate-signing-request">Generate a private key and Certificate Signing Request</a></h2>
<p>We need to generate an OpenSSL keypair and a <a href="http://en.wikipedia.org/wiki/Certificate_signing_request">Certificate Signing Request
(CSR)</a>.</p>
<p>The keypair consists of two cryptographic keys. A public and private. The
public key is included with the CSR along with other applicant information such
as name, company, etc. The private key is used to sign the CSR request.</p>
<p>A CSR is what you send to your chosen Certificate Authority (CA) to request
that they supply you with an SSL certificate. It includes your identifying
information and the public key for your server/site.</p>
<p>Generate it like this.</p>
<div class="highlight"><pre class="highlight shell"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>openssl req <span class="nt">-new</span> <span class="nt">-newkey</span> rsa:1024 <span class="nt">-nodes</span> <span class="nt">-keyout</span> example.key <span class="nt">-out</span> example.csr
</pre></td></tr></tbody></table></code></pre></div>
<p>You'll be prompted to enter information such as Common Name, Organisation,
Country etc.</p>
<p>It should be fairly straight forward, but your CA will let you know if you've
done it wrong, I'm sure.</p>
<p>This will create <code>example.key</code> (the private key) and <code>example.csr</code> (the CSR).</p>
<h2><a href="#requesting-your-certificate" id="requesting-your-certificate">Requesting your certificate</a></h2>
<p>This part should be easy. Normally your CA will have a form on their website
which allows you to paste in your CSR. This obviously varies from company to
company. Once you have given them your CSR, they will first ask you for money,
and then generate your CRT file. Yay!</p>
<p>I'm only scratching the surface of SSL here. OpenSSL has a massive amount of
options. There is much to learn.</p>
<h2><a href="#references" id="references">References</a></h2>
<ul>
<li><a href="https://help.ubuntu.com/community/OpenSSL">More information on OpenSSL commands</a></li>
</ul>
Rails Deployment with Git, Vlad and SSH Agent Forwardinghttp://jordanelver.co.uk/blog/2008/07/10/rails-deployment-with-git-vlad-and-ssh-agent-forwarding/2008-07-10T01:00:00+01:002020-06-04T11:27:38+01:00Jordan Elver<p>I'm switching my code repositories over to Git from Subversion. For the most
part it's going well. I'm having some issues with line endings, but that's for
another post. I'm still getting to grips with day to day usage, but can already
see that it's going to be a great improvement on Subversion.</p>
<p>This is how I've setup Vlad, Git, and SSH to work together.</p>
<h2><a href="#vlad-configuration" id="vlad-configuration">Vlad Configuration</a></h2>
<p>I use <a href="http://rubyhitsquad.com/Vlad_the_Deployer.html">Vlad</a> to deploy my
projects to the staging/live server, so I needed to re-configure Vlad to
support Git. This was very simple to do. In my <code>Rakefile</code>, I included the Git
class instead of the Subversion class. This removes the Subversion specific
commands and includes the Git specific commands instead.</p>
<div class="highlight"><pre class="highlight diff"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre><span class="gd">-require 'vlad/subversion'
</span><span class="gi">+require 'vlad/git'
</span></pre></td></tr></tbody></table></code></pre></div>
<p>I also changed the repository address in my <code>config/deploy.rb</code> file.</p>
<div class="highlight"><pre class="highlight diff"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre><span class="gd">-set :repository, 'https://sub.version.server/repo'
</span><span class="gi">+set :repository, 'git@github.com:username/repo.git'
</span></pre></td></tr></tbody></table></code></pre></div>
<p>These are the only changes I needed to make.</p>
<h2><a href="#ssh-and-git-setup" id="ssh-and-git-setup">SSH and Git setup</a></h2>
<p>Git uses SSH keys to control access to repositories. As I'm using GitHub as my
deployment Git repository, I needed to identify myself with them. To do this, I
supplied them with my SSH public key. <a href="http://github.com/guides/providing-your-ssh-key#linux">GitHub have good instructions on how to
do this</a>.</p>
<p>As the code is cloned from GitHub directly to the deployment server (not my
machine), my private SSH key would also need to be installed on the deployment
server.</p>
<p>Vlad deployment now works! This will checkout the latest HEAD and deploy it to
the server. <a href="http://scie.nti.st/2007/9/25/vlad-the-deployer-and-git">You can set the specific revision that you
want</a> in your
<code>config/deploy.rb</code> as well if required. I'm not doing that at the moment.</p>
<h2><a href="#so-what-39-s-the-problem" id="so-what-39-s-the-problem">So, what's the problem?</a></h2>
<p>The problem is that you need to install your private SSH key on the deployment
server. I didn't want to do this in case the server was ever compromised.
If it was compromised, the attacker could also theoretically get into
other systems using that key if it didn't have a passphrase. This would be bad.
My key does have a passphrase, but why install it when you don't really need to?</p>
<h2><a href="#making-it-more-secure" id="making-it-more-secure">Making it more secure</a></h2>
<p>Fortunately, there's a rather nifty solution to this problem — SSH agent
forwarding. In order to do so, you must first be using SSH Agent. SSH Agent
allows you to authenticate yourself once per session, so you don't have to
enter your password every time you connect to the server. If you're already
using SSH keys and have SSH Agent running on your system (which is the default
in Ubuntu), you just need to do:</p>
<div class="highlight"><pre class="highlight shell"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>ssh-add ~/.ssh/<keyname>
</pre></td></tr></tbody></table></code></pre></div>
<p>You'll be prompted for the passphrase to the key. Once you enter it, you should
be able to connect to any servers using that key without entering it again.
This is very handy anyway regardless of whether you're going to be using
forwarding or not.</p>
<p>See <a href="http://en.wikipedia.org/wiki/Ssh-agent#Setting_Up_Ssh_Agent">this wikipedia
page</a> for more
information.</p>
<h2><a href="#how-to-setup-the-forwarding" id="how-to-setup-the-forwarding">How to setup the forwarding</a></h2>
<p>This is where the forwarding comes in. SSH can forward requests for
authentication back to the original SSH Agent process running on your machine.
So, you can connect to your deployment server and then connect to another
server without re-entering your passphrase or having to install any keys on the
deployment machine itself. Much coolness. This negates the need to install any
other keys on the deployment server itself.</p>
<p>There is a very well explained article called <a href="http://www.unixwiz.net/techtips/ssh-agent-forwarding.html">An Illustrated Guide to SSH
Agent Forwarding</a>
over at <a href="http://www.unixwiz.net">Unixwiz.net</a> that goes into a lot of detail
about how this all works. It's well worth a read.</p>
<p>This is how to set it up.</p>
<p>Edit your <code>~/.ssh/config</code> file and add something like this:</p>
<div class="highlight"><pre class="highlight shell"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
</pre></td><td class="rouge-code"><pre>Host <name>
HostName <ip or host>
User <username>
IdentityFile ~/.ssh/<filename>
ForwardAgent <span class="nb">yes</span>
</pre></td></tr></tbody></table></code></pre></div>
<p>This is all you need. If you've got SSH Agent running, you're sorted.</p>
<p>Incidentally, you can add these sections for as many hosts as required. It saves you
having to type out lots of command line switches all the time. Just leave out the
<code>ForwardAgent yes</code> line.</p>
<h2><a href="#references" id="references">References</a></h2>
<ul>
<li><a href="http://github.com/guides/providing-your-ssh-key#linux">GitHub - Providing your SSH key</a></li>
<li><a href="http://scie.nti.st/2007/9/25/vlad-the-deployer-and-git">Vlad the Deployer and Git</a></li>
<li><a href="http://en.wikipedia.org/wiki/Ssh-agent#Setting_Up_Ssh_Agent">SSH Agent on Wikipedia</a></li>
<li><a href="http://www.unixwiz.net/techtips/ssh-agent-forwarding.html">An Illustrated Guide to SSH Agent Forwarding</a></li>
<li><a href="http://dysinger.net/2008/04/30/deploying-with-capistrano-git-and-ssh-agent/">Deploying with Capistrano, Git and SSH-Agent</a></li>
</ul>
Rake dependencieshttp://jordanelver.co.uk/blog/2008/06/30/rake-dependencies/2008-06-30T01:00:00+01:002020-06-04T11:27:38+01:00Jordan Elver<p>Rake tasks are great for all sorts of small tasks. They give you a bit of
structure to what would normally be a shell or ruby script. However, the thing
I like about rake tasks are dependencies.</p>
<h2><a href="#a-simple-example" id="a-simple-example">A simple example</a></h2>
<p>You see, you can make one task rely on another. Take this.</p>
<div class="highlight"><pre class="highlight ruby"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
</pre></td><td class="rouge-code"><pre><span class="n">desc</span> <span class="s2">"Make coffee"</span>
<span class="n">task</span> <span class="ss">:coffee</span> <span class="o">=></span> <span class="ss">:get_mug</span> <span class="k">do</span>
<span class="nb">puts</span> <span class="s2">"Making a coffee"</span>
<span class="k">end</span>
<span class="n">task</span> <span class="ss">:get_mug</span> <span class="k">do</span>
<span class="nb">puts</span> <span class="s2">"Getting a mug"</span>
<span class="k">end</span>
<span class="n">rake</span> <span class="n">get_mug</span>
<span class="o">></span> <span class="no">Getting</span> <span class="n">a</span> <span class="n">mug</span>
<span class="n">rake</span> <span class="n">coffee</span>
<span class="o">></span> <span class="no">Getting</span> <span class="n">a</span> <span class="n">mug</span>
<span class="o">></span> <span class="no">Making</span> <span class="n">a</span> <span class="n">coffee</span>
</pre></td></tr></tbody></table></code></pre></div>
<p>Easy to understand isn't it? <code>coffee</code> depends on <code>get_mug</code>, so <code>get_mug</code> will be
called before the main <code>coffee</code> task every time <code>rake coffee</code> is run.</p>
<h2><a href="#a-better-example" id="a-better-example">A better example</a></h2>
<p>You can also redefine task dependencies without altering the original task.
For example, on this blog I have a task which creates index pages for the
category pages. I want this to run everytime the <code>build</code> task is run. I could
modify the original <code>build</code> task, but a much nicer and cleaner way is to
redefine the task and add the dependant task like so.</p>
<div class="highlight"><pre class="highlight ruby"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre><span class="n">task</span> <span class="ss">:build</span> <span class="o">=></span> <span class="s1">'blog:categories:create_indexes'</span>
</pre></td></tr></tbody></table></code></pre></div>
<p>This will keep any existing dependencies that the build task originally had and
also add my new one. Ain't it pretty?</p>
<h2><a href="#references" id="references">References</a></h2>
<p>Thanks to the guys at <a href="http://www.railsenvy.com">Rails Envy</a> for their tutorial</p>
<ul>
<li><a href="http://www.railsenvy.com/2007/6/11/ruby-on-rails-rake-tutorial">Ruby on Rails Rake Tutorial [aka. How rake turned me into an
alcoholic]</a></li>
</ul>
Git bash completion = Yay!http://jordanelver.co.uk/blog/2008/06/18/git-completion-yay/2008-06-18T01:00:00+01:002020-06-04T11:27:38+01:00Jordan Elver<p>If you've heard of, or are already using bash completion, you'll know how great
it is. Here's how to use it with <a
href="http://en.wikipedia.org/wiki/Git_(software)">Git</a>.</p>
<h2><a href="#how-to-get-the-sweetness" id="how-to-get-the-sweetness">How to get the sweetness?</a></h2>
<p>I'm using Ubuntu (Hardy), but setup instructions should be similar for others.
For Ubuntu, uncomment this section in <code>/etc/bash.bashrc</code> and you've enabled bash
completion. You'll need to open a new terminal for it to take affect.</p>
<div class="highlight"><pre class="highlight shell"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre><span class="k">if</span> <span class="o">[</span> <span class="nt">-f</span> /etc/bash_completion <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
<span class="nb">.</span> /etc/bash_completion
<span class="k">fi</span>
</pre></td></tr></tbody></table></code></pre></div><h2><a href="#how-to-enable-git-completion" id="how-to-enable-git-completion">How to enable Git completion</a></h2>
<p>You'll need the bash completion file for Git. This file details how the Git
commands will be completed. The file comes as part of the Git distribution.</p>
<ul>
<li>Download the lastest git release from
<a href="http://www.kernel.org/pub/software/scm/git/">http://www.kernel.org/pub/software/scm/git/</a></li>
<li>Find the <code>git-completion.bash</code> file in the <code>contrib/</code> directory and copy to
the <code>/etc/bash_completion.d/</code> directory</li>
<li>Start a new login shell (logout/login or start a new Terminal tab)</li>
</ul>
<h2><a href="#try-it-out" id="try-it-out">Try it out</a></h2>
<p>Now, typing <code>git <tab><tab></code> should list all of the various git commands (of
which there are many). However, the really cool part is that it will also list
your branches for you. Typing <code>git checkout <tab><tab></code> within your Git repo
will list all of the branches you have.</p>
<div class="highlight"><pre class="highlight shell"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre>jord@jordan /home/jord/webby<span class="o">(</span>master<span class="o">)</span> <span class="nv">$ </span>git checkout <tab><tab>
HEAD master
</pre></td></tr></tbody></table></code></pre></div>
<p>RSI, be gone!</p>
<h2><a href="#thanks" id="thanks">Thanks</a></h2>
<ul>
<li><a href="http://blog.bitfluent.com/post/27983389/git-utilities-you-cant-live-without">Bitfluent</a></li>
</ul>
w00t! First post evar!http://jordanelver.co.uk/blog/2008/06/17/hello-and-welcome/2008-06-17T01:00:00+01:002020-06-04T11:27:38+01:00Jordan Elver<p>Hello. The proper site is now live. It's been way too long coming, but now it's
here. I built it using <a href="http://webby.rubyforge.net/">Webby</a>. Hopefully there'll
be some posts explaining how that was done soon.</p>
<p>P.S. I know some people won't like the colours, but I do.</p>