<?xml version="1.0" encoding="utf-8" standalone="no"?><feed xmlns="http://www.w3.org/2005/Atom"><title>CHRISBERKHOUT.COM</title><link href="https://chrisberkhout.com/" rel="alternate"/><link href="https://chrisberkhout.com/blog/feed.xml" rel="self"/><id>https://chrisberkhout.com/</id><updated>2017-06-15T00:00:00+00:00</updated><xhtml:meta content="noindex" name="robots" xmlns:xhtml="http://www.w3.org/1999/xhtml"/><entry><title>Printing plain text</title><link href="https://chrisberkhout.com/blog/printing-plain-text/" rel="alternate"/><published>2017-06-15T00:00:00+00:00</published><updated>2017-06-15T00:00:00+00:00</updated><author><name>Chris Berkhout</name></author><id>tag:chrisberkhout.com,2017-06-15:/blog/printing-plain-text/</id><summary type="html">&lt;p&gt;Plain text formats are great for working with on screens. They&amp;#8217;re easy to
generate and manipulate, and often presented with basic styling to highlight
their&amp;nbsp;structure.&lt;/p&gt;
&lt;p&gt;For example, the output of the &lt;code&gt;tree&lt;/code&gt; command shows a directory tree, with
&lt;a href="https://en.wikipedia.org/wiki/Box-drawing_character"&gt;box drawing characters&lt;/a&gt;
indicating the hierarchy, directory names in bold …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Plain text formats are great for working with on screens. They&amp;#8217;re easy to
generate and manipulate, and often presented with basic styling to highlight
their&amp;nbsp;structure.&lt;/p&gt;
&lt;p&gt;For example, the output of the &lt;code&gt;tree&lt;/code&gt; command shows a directory tree, with
&lt;a href="https://en.wikipedia.org/wiki/Box-drawing_character"&gt;box drawing characters&lt;/a&gt;
indicating the hierarchy, directory names in bold, and different colors for
different file&amp;nbsp;types.&lt;/p&gt;
&lt;p&gt;Generating a printable equivalent of such things can be tricky.
Let&amp;#8217;s take a look at the&amp;nbsp;options.&lt;/p&gt;
&lt;h2&gt;Problematic&amp;nbsp;options&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Text editors&lt;/strong&gt; will sometimes let you print, but generally without much
  control over how the output is&amp;nbsp;styled.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Screenshots&lt;/strong&gt; let you capture what exactly what&amp;#8217;s on screen, however, as
  relatively low-resolution raster data the text won&amp;#8217;t be very sharp, and will
  be a real problem if you want to adjust the size. The on-screen style may not
  be suited to to a medium that isn&amp;#8217;t&amp;nbsp;backlit.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.latex-project.org/"&gt;LaTeX&lt;/a&gt;&lt;/strong&gt; involves many concepts beyond
  plain text and basic styling. Use it for native print or multi-format
  documents, not for a paper equivalent of the plain text screen&amp;nbsp;experience.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="http://pandoc.org"&gt;Pandoc&lt;/a&gt;&lt;/strong&gt; converts between many markup formats, but is
  focussed on structured document formats rather than free-form text. Document
  semantics determine default styling but anything more needs to be applied&amp;nbsp;separately.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="http://dag.wiee.rs/home-made/unoconv/"&gt;unoconv&lt;/a&gt;&lt;/strong&gt; converts between office
  document formats, including from plain text to &lt;span class="caps"&gt;ODF&lt;/span&gt; or &lt;span class="caps"&gt;PDF&lt;/span&gt;. You can specify a
  template document, but you&amp;#8217;d need to open LibreOffice and apply manual styles
  to highlight particular parts of your&amp;nbsp;content.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.cups.org/doc/man-cupsfilter.html"&gt;cupsfilter&lt;/a&gt;&lt;/strong&gt; converts plain
  text to a &lt;span class="caps"&gt;PDF&lt;/span&gt;, but offers no styling&amp;nbsp;options.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.gnu.org/software/a2ps/"&gt;a2ps&lt;/a&gt;&lt;/strong&gt; will convert plain text to
  PostScript, but it&amp;#8217;s styling options are only for very basic syntax
  highlighting of code, and it doesn&amp;#8217;t support&amp;nbsp;Unicode.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://linux.die.net/man/1/enscript"&gt;Enscript&lt;/a&gt;&lt;/strong&gt; can convert plain text to
  PostScript. It has a lot of options, can do automatic syntax highlighting of
  many programming languages, and lets you add your own basic styling using
  special escape sequences. Unfortunately it doesn&amp;#8217;t support&amp;nbsp;Unicode.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="http://paps.sourceforge.net/"&gt;paps version 0.6.8&lt;/a&gt;&lt;/strong&gt; is widely packaged
  and often recommended as a &lt;span class="caps"&gt;UTF&lt;/span&gt;-8 compatible alternative to Enscript and a2ps.
  It allows basic styling via command-line options and
  &lt;a href="https://developer.gnome.org/pango/stable/PangoMarkupFormat.html"&gt;Pango markup&lt;/a&gt;.
  There are a couple of issues with version 0.6.8. It&amp;#8217;s support of Pango markup
  is limited: bold works but colors don&amp;#8217;t. Also, rather than putting actual
  text into the output document, it renders text using its own mechanism, which
  results in &lt;a href="https://stackoverflow.com/questions/26066535/ps2pdf-creates-a-very-big-pdf-file-from-paps-created-ps-file/26067865#26067865"&gt;large file sizes&lt;/a&gt;
  and unselectable&amp;nbsp;text.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;A lightweight&amp;nbsp;solution&lt;/h2&gt;
&lt;p&gt;Fortunately, in 2015 the paps author released a new version on Github:
&lt;strong&gt;&lt;a href="https://github.com/dov/paps"&gt;paps 0.7.0&lt;/a&gt;&lt;/strong&gt;. It now uses Cairo for its
rendering and generates compact text-based files. It offers good basic styling
options via a more complete implementation of Pango&amp;nbsp;markup.&lt;/p&gt;
&lt;p&gt;If you&amp;#8217;re generating your own data, it&amp;#8217;s easy to apply basic styling with Pango&amp;nbsp;markup.&lt;/p&gt;
&lt;p&gt;If you want to print some data that&amp;#8217;s already already styled for the terminal,
you can use &lt;strong&gt;&lt;a href="http://www.andre-simon.de/doku/ansifilter/en/ansifilter.php"&gt;Ansifilter&lt;/a&gt;&lt;/strong&gt;
to convert terminal control codes to Pango markup (or one of several other
markup formats). You can remap the 16-color palette to your own set of &lt;span class="caps"&gt;RGB&lt;/span&gt;&amp;nbsp;values.&lt;/p&gt;
&lt;p&gt;This command converts the output of &lt;code&gt;tree&lt;/code&gt; to a printable &lt;span class="caps"&gt;PDF&lt;/span&gt; with styling&amp;nbsp;intact:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;tree -C &lt;span class="p"&gt;|&lt;/span&gt; ansifilter --pango &lt;span class="p"&gt;|&lt;/span&gt; paps --markup --format&lt;span class="o"&gt;=&lt;/span&gt;pdf &amp;gt; tree.pdf
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;For printing programming languages, there are
&lt;a href="https://unix.stackexchange.com/questions/267361/syntax-highlighting-in-the-terminal"&gt;several ways&lt;/a&gt;
to apply syntax highlighting with terminal control codes, which you can then
feed into Ansifilter. I like the &lt;code&gt;pygmentize&lt;/code&gt; command from the
&lt;strong&gt;&lt;a href="http://pygments.org/"&gt;Pygments&lt;/a&gt;&lt;/strong&gt;&amp;nbsp;project.&lt;/p&gt;
&lt;h2&gt;Heavyweights: &lt;span class="caps"&gt;HTML&lt;/span&gt; and &lt;span class="caps"&gt;CSS&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;To go beyond basic styling, you need to commit to a heavier-weight styling
language, but you don&amp;#8217;t have to write raw PostScript. You can use &lt;span class="caps"&gt;HTML&lt;/span&gt; and
&lt;span class="caps"&gt;CSS&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;First, generate some &lt;span class="caps"&gt;HTML&lt;/span&gt; yourself, or take &lt;span class="caps"&gt;HTML&lt;/span&gt; output from another tool. If
your data can represented in a plain-text markup language like
&lt;a href="https://en.wikipedia.org/wiki/Markdown"&gt;Markdown&lt;/a&gt;,
&lt;a href="http://docutils.sourceforge.net/rst.html"&gt;reStructuredText&lt;/a&gt; or
&lt;a href="http://www.methods.co.nz/asciidoc/"&gt;AsciiDoc&lt;/a&gt;, a tool like Pandoc can convert
it to &lt;span class="caps"&gt;HTML&lt;/span&gt;, to which you can apply your own&amp;nbsp;stylesheet.&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;CSS&lt;/span&gt; gives you a lot of control over presentation, including some features made
specifically &lt;a href="https://www.smashingmagazine.com/2015/01/designing-for-print-with-css/"&gt;for print design&lt;/a&gt;,
although you will need to check which features are implemented by your&amp;nbsp;renderer.&lt;/p&gt;
&lt;p&gt;You can manually print from a web browser, but there are also several tools for
converting an &lt;span class="caps"&gt;HTML&lt;/span&gt;/&lt;span class="caps"&gt;CSS&lt;/span&gt; document to a &lt;span class="caps"&gt;PDF&lt;/span&gt; non-interactively.
&lt;strong&gt;&lt;a href="http://weasyprint.org/"&gt;WeasyPrint&lt;/a&gt;&lt;/strong&gt; focusses on &lt;span class="caps"&gt;CSS&lt;/span&gt; layout for paged media.
&lt;a href="http://phantomjs.org/api/webpage/method/render.html"&gt;PhantomJS&lt;/a&gt; and
&lt;a href="https://github.com/wkhtmltopdf/wkhtmltopdf"&gt;wkhtmltopdf&lt;/a&gt; can both produce
PDFs using versions of the WebKit rendering&amp;nbsp;engine.&lt;/p&gt;</content><category term="misc"/></entry><entry><title>A simple webhook with Netcat and systemd</title><link href="https://chrisberkhout.com/blog/simple-webhook/" rel="alternate"/><published>2017-05-29T00:00:00+00:00</published><updated>2017-05-29T00:00:00+00:00</updated><author><name>Chris Berkhout</name></author><id>tag:chrisberkhout.com,2017-05-29:/blog/simple-webhook/</id><summary type="html">&lt;style&gt;
  .screenshot {
    float: left;
    margin-left: 36px;
    margin-bottom: 36px;
  }
&lt;/style&gt;

&lt;p&gt;A webhook is a little program that listens for web requests and triggers a
given action when it receives one. It lets you use simple web protocols
and tools to trigger an action on a certain system, from&amp;nbsp;anywhere.&lt;/p&gt;
&lt;p&gt;Unix-like systems make it …&lt;/p&gt;</summary><content type="html">&lt;style&gt;
  .screenshot {
    float: left;
    margin-left: 36px;
    margin-bottom: 36px;
  }
&lt;/style&gt;

&lt;p&gt;A webhook is a little program that listens for web requests and triggers a
given action when it receives one. It lets you use simple web protocols
and tools to trigger an action on a certain system, from&amp;nbsp;anywhere.&lt;/p&gt;
&lt;p&gt;Unix-like systems make it easy to string together commands to perform just
about any action you&amp;#8217;d like. Even a webhook itself can be implemented with a
one-line shell script and some system&amp;nbsp;config.&lt;/p&gt;
&lt;p&gt;In the following example I&amp;#8217;ll show the webhook that I run on my
&lt;a href="https://en.wikipedia.org/wiki/Linux"&gt;&lt;span class="caps"&gt;GNU&lt;/span&gt;/Linux&lt;/a&gt; home server so I can
restart &lt;a href="https://kodi.tv/"&gt;Kodi&lt;/a&gt; (the free and open-source media center
software) from a button on my&amp;nbsp;phone.&lt;/p&gt;
&lt;h2&gt;Handling a&amp;nbsp;request&lt;/h2&gt;
&lt;p&gt;With &lt;a href="http://netcat.sourceforge.net/"&gt;&lt;span class="caps"&gt;GNU&lt;/span&gt; Netcat&lt;/a&gt;&lt;sup id="fnref:Netcat"&gt;&lt;a class="footnote-ref" href="#fn:Netcat"&gt;1&lt;/a&gt;&lt;/sup&gt; we can write a shell
command to wait for a single web request, answer it, and restart&amp;nbsp;Kodi:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-e&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;HTTP/1.1 204 No Content\r\nConnection: close\r\n\r&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;nc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-l&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-p&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;7777&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-c&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="nt"&gt;sudo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;systemctl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;restart&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;kodi&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;nc&lt;/code&gt; command here waits until it receives a connection on port 7777, on any
interface. Once a connection is established, it reads the data we piped into
its standard input and sends it over the network to the client. When it&amp;#8217;s
reached the end of the input data, it closes (&lt;code&gt;-c&lt;/code&gt;) the connection and&amp;nbsp;exits.&lt;/p&gt;
&lt;p&gt;The data we send here is just enough to gracefully&lt;sup id="fnref:broken pipes"&gt;&lt;a class="footnote-ref" href="#fn:broken pipes"&gt;2&lt;/a&gt;&lt;/sup&gt; close a
&lt;span class="caps"&gt;HTTP&lt;/span&gt;&amp;nbsp;connection:&lt;/p&gt;
&lt;pre&gt;
HTTP/1.1 204 No Content
Connection: close

&lt;/pre&gt;

&lt;h2&gt;Handling successive&amp;nbsp;requests&lt;/h2&gt;
&lt;p&gt;Many &lt;span class="caps"&gt;GNU&lt;/span&gt;/Linux distributions use
&lt;a href="https://www.freedesktop.org/wiki/Software/systemd/"&gt;systemd&lt;/a&gt; to manage
start-up and system&amp;nbsp;services.&lt;/p&gt;
&lt;p&gt;Here&amp;#8217;s a systemd service definition that will run our shell command, and
restart it after it finishes handling each request. I put this in
&lt;code&gt;/usr/lib/systemd/system/kodi-restart.service&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;[Unit]&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;Kodi Restart webhook&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="na"&gt;After&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;network.target&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="k"&gt;[Service]&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="na"&gt;User&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;http&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;simple&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="na"&gt;ExecStart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/bin/bash -xc &amp;#39;echo -e &amp;quot;HTTP/1.1 204 No Content\\r\\nConnection: close\\r\\n\\r&amp;quot; | nc -l 0.0.0.0 -p 7777 -c; sudo systemctl restart kodi&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="na"&gt;Restart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;always&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="na"&gt;StartLimitInterval&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;1min&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="na"&gt;StartLimitBurst&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;60&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="k"&gt;[Install]&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="na"&gt;WantedBy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;multi-user.target&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Note that the newline escaping (&lt;code&gt;\\r\\n&lt;/code&gt;) now has double slashes: one for Bash,
one for&amp;nbsp;systemd.&lt;/p&gt;
&lt;p&gt;The command is run as the &lt;code&gt;http&lt;/code&gt; user (more on this later). The &lt;code&gt;Type=simple&lt;/code&gt;
says that we&amp;#8217;re just executing a normal command, not one that launces a
separate background processes. We want to &lt;code&gt;Restart=always&lt;/code&gt;, whenever the
command exits. The &lt;code&gt;StartLimit&lt;/code&gt; options say that the command can be restarted
up to 60 times per minute before the service is marked as&amp;nbsp;failed.&lt;/p&gt;
&lt;p&gt;With the service definition in place, we can start the&amp;nbsp;service:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ sudo systemctl start kodi-restart
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And check that it&amp;#8217;s running&amp;nbsp;correctly:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;systemctl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;kodi&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;restart&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="err"&gt;●&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;kodi&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;restart&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Kodi&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Restart&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;webhook&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;Loaded&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;loaded&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;usr&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;systemd&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;system&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;kodi&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;restart&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;disabled&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;vendor&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;preset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;disabled&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;Active&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;active&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;running&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;since&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Tue&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2017&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;05&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;38&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;09&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CEST&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ago&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Main&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;24341&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bash&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;CGroup&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;system&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;slice&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;kodi&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;restart&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="err"&gt;├─&lt;/span&gt;&lt;span class="mi"&gt;24341&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;bin&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;bash&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;xc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;HTTP/1.1 204 No Content&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="s2"&gt;Connection: close&lt;/span&gt;&lt;span class="se"&gt;\r\n\r&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;7777&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sudo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;systemctl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;restart&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;kodi&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="err"&gt;└─&lt;/span&gt;&lt;span class="mi"&gt;24343&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;7777&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="n"&gt;May&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;38&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;09&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;hostname&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;systemd&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Started&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Kodi&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Restart&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;webhook&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="n"&gt;May&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;38&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;09&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;hostname&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bash&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;24341&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;HTTP/1.1 204 No Content&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="s1"&gt;Connection: close&lt;/span&gt;&lt;span class="se"&gt;\r\n\r&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="n"&gt;May&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;38&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;09&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;hostname&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bash&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;24341&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;7777&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;To have it automatically start after rebooting the system, we can enable&amp;nbsp;it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ sudo systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; kodi-restart
Created symlink /etc/systemd/system/multi-user.target.wants/kodi-restart.service → /usr/lib/systemd/system/kodi-restart.service.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Granting&amp;nbsp;permissions&lt;/h2&gt;
&lt;p&gt;Our new service runs as the &lt;code&gt;http&lt;/code&gt; user, but that user doesn&amp;#8217;t yet have
permission to run the restart&amp;nbsp;command.&lt;/p&gt;
&lt;p&gt;We can grant the appropriate&lt;sup id="fnref:security"&gt;&lt;a class="footnote-ref" href="#fn:security"&gt;3&lt;/a&gt;&lt;/sup&gt; permissions with the following
&lt;a href="https://www.sudo.ws/man/sudoers.man.html"&gt;sudoers&lt;/a&gt; configuration, in a new
file named &lt;code&gt;/etc/sudoers.d/http-kodi-restart&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;http ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart kodi
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Remote&amp;nbsp;control&lt;/h2&gt;
&lt;p&gt;This webhook can be triggered from any web browser, but a helper app makes for
a nicer remote control&amp;nbsp;experience.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/Waboodoo/HTTP-Shortcuts"&gt;&lt;span class="caps"&gt;HTTP&lt;/span&gt; Shortcuts&lt;/a&gt; is a simple,
open-source (&lt;a href="https://en.wikipedia.org/wiki/MIT_License"&gt;&lt;span class="caps"&gt;MIT&lt;/span&gt;&lt;/a&gt;) Android app that
lets you create shortcuts for web requests, and place buttons on your home
screen to trigger&amp;nbsp;them.&lt;/p&gt;
&lt;p&gt;Here&amp;#8217;s the one I set up for restarting&amp;nbsp;Kodi:&lt;/p&gt;
&lt;p&gt;&lt;img alt="screenshot" class="screenshot s1" src="https://chrisberkhout.com/blog/simple-webhook/http-shortcuts.png"&gt;
&lt;img alt="screenshot" class="screenshot s2" src="https://chrisberkhout.com/blog/simple-webhook/home-screen.jpg"&gt;&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;A one-line shell script wrapped in a systemd service definition is a good way
to expose webhooks for triggering a few simple actions within a trusted&amp;nbsp;network.&lt;/p&gt;
&lt;p&gt;More demanding scenarios may call for validation or processing of request data,
authentication and authorization, handling of concurrent requests, or &lt;span class="caps"&gt;TLS&lt;/span&gt;
encryption. In those cases you&amp;#8217;ll want a more sophisticated&amp;nbsp;tool.&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://github.com/adnanh/webhook"&gt;webhook&lt;/a&gt; project is an open-source
(&lt;a href="https://en.wikipedia.org/wiki/MIT_License"&gt;&lt;span class="caps"&gt;MIT&lt;/span&gt;&lt;/a&gt;) tool, written in Go, that
might be a good&amp;nbsp;fit.&lt;/p&gt;
&lt;div class="footnote"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:Netcat"&gt;
&lt;p&gt;There are quite a few &lt;a href="https://en.wikipedia.org/wiki/Netcat"&gt;Netcat&lt;/a&gt;
implementations.
The &lt;a href="http://nc110.sourceforge.net/"&gt;original implementation&lt;/a&gt; was rewritten
to add IPv6 support for &lt;a href="http://man.openbsd.org/nc"&gt;BSDs&lt;/a&gt;.
&lt;a href="http://netcat.sourceforge.net/"&gt;&lt;span class="caps"&gt;GNU&lt;/span&gt; Netcat&lt;/a&gt; is a full rewrite.
Ncat is an implementation included in the &lt;a href="https://nmap.org/"&gt;Nmap suite&lt;/a&gt;.
Each one implements similar functionality, but has its own set of options.
Here I&amp;#8217;m using &lt;span class="caps"&gt;GNU&lt;/span&gt; Netcat.&amp;#160;&lt;a class="footnote-backref" href="#fnref:Netcat" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:broken pipes"&gt;
&lt;p&gt;This response, when delivered completely, is enough to gracefully respond to
the &lt;span class="caps"&gt;HTTP&lt;/span&gt; request. However, sometimes the client will receive an incomplete&amp;nbsp;response:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Kodi Restart failed: sendto failed: &lt;span class="caps"&gt;EPIPE&lt;/span&gt; (Broken&amp;nbsp;pipe)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The problem seems to be a Netcat issue that others have also
&lt;a href="http://ask.metafilter.com/133905/Why-did-tar-and-nc-not-play-nice#1913922"&gt;observed&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;in the past I&amp;#8217;ve been burnt by race conditions where the connection is
closed before the last few bytes to arrive on stdin have actually been
transmitted over&amp;nbsp;it&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In the &lt;span class="caps"&gt;GNU&lt;/span&gt; Netcat source code, &lt;a href="https://sourceforge.net/p/netcat/code/HEAD/tree/trunk/src/netcore.c#l907"&gt;the connection is
closed&lt;/a&gt;
by a &lt;code&gt;shutdown()&lt;/code&gt;, followed immedately by a &lt;code&gt;close()&lt;/code&gt;. Some people
&lt;a href="https://stackoverflow.com/questions/4160347/close-vs-shutdown-socket/23483487#23483487"&gt;recommend&lt;/a&gt;
waiting for a read of size zero between the &lt;code&gt;shutdown()&lt;/code&gt; and &lt;code&gt;close()&lt;/code&gt;
calls, so perhaps that would resolve the&amp;nbsp;issue.&lt;/p&gt;
&lt;p&gt;Another way to have the connection closed cleanly would be to leave off the
&lt;code&gt;-c&lt;/code&gt; option, so that Netcat keeps the connection open until it is closed by
the client, which it will only do after it has read the full &lt;span class="caps"&gt;HTTP&lt;/span&gt; 204
response.&amp;#160;&lt;a class="footnote-backref" href="#fnref:broken pipes" title="Jump back to footnote 2 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:security"&gt;
&lt;p&gt;With this setup, anyone who can make a request to the right port can
restart Kodi up to 60 times per minute. I&amp;#8217;m happy to expose this
functionality within my home network, but in other environments additional
security measures would be appropriate.&amp;#160;&lt;a class="footnote-backref" href="#fnref:security" title="Jump back to footnote 3 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content><category term="misc"/></entry><entry><title>Maths learning</title><link href="https://chrisberkhout.com/blog/maths-learning/" rel="alternate"/><published>2017-01-20T00:00:00+00:00</published><updated>2017-01-20T00:00:00+00:00</updated><author><name>Chris Berkhout</name></author><id>tag:chrisberkhout.com,2017-01-20:/blog/maths-learning/</id><summary type="html">&lt;style&gt;
  .cover-list-wrapper ul {
    list-style: none;
  }
  .inline-cover img {
    display: inline;
    margin: 0px;
  }
  .inline-cover {
    min-width: 125px;
    float: left;
    margin-right: 30px;
    margin-bottom: 20px;
    margin-left: -35px;
  }
  .inline-cover:hover {
    background-color: #fff;
  }
  h2, li {
    clear: both;
  }
&lt;/style&gt;

&lt;p&gt;The following is a list of resources for reviewing maths, starting from the beginning and covering undergraduate-level calculus, linear algebra, probability …&lt;/p&gt;</summary><content type="html">&lt;style&gt;
  .cover-list-wrapper ul {
    list-style: none;
  }
  .inline-cover img {
    display: inline;
    margin: 0px;
  }
  .inline-cover {
    min-width: 125px;
    float: left;
    margin-right: 30px;
    margin-bottom: 20px;
    margin-left: -35px;
  }
  .inline-cover:hover {
    background-color: #fff;
  }
  h2, li {
    clear: both;
  }
&lt;/style&gt;

&lt;p&gt;The following is a list of resources for reviewing maths, starting from the beginning and covering undergraduate-level calculus, linear algebra, probability and&amp;nbsp;statistics.&lt;/p&gt;
&lt;h2&gt;Other lists and&amp;nbsp;advice&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="http://www.stumblingrobot.com/best-math-books/"&gt;Best Math Books – A Comprehensive Reading List (StumblingRobot.com)&lt;/a&gt;&lt;br&gt;
    This guide&amp;#8217;s recommendations are consistent with the advice I found elsewhere. It describes and compares several of the best books on each topic. It&amp;#8217;s aimed at people interested in pure maths, but the basics section seems very relevant to my interests, and it intentionally chooses challenging&amp;nbsp;texts.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://www.ocf.berkeley.edu/~abhishek/chicmath.htm"&gt;Chicago undergraduate mathematics bibliography&lt;/a&gt;&lt;br&gt;
    This well-respected list is the one I saw most commonly referenced. Its comments are briefer, but come from several contributors and cover many topics and&amp;nbsp;books.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="http://danluu.com/programming-books/#math"&gt;Programming books you might want to consider reading (Dan Luu)&lt;/a&gt;&lt;br&gt;
    This list of programming books includes a math section. Dan Luu also has an interesting post asking &lt;a href="http://danluu.com/math-bias/"&gt;How much math do programmers&amp;nbsp;need?&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://www.quora.com/What-are-some-good-online-courses-and-books-available-for-learning-probability-and-statistics"&gt;What are some good books available for learning probability and statistics? (Quora)&lt;/a&gt;&lt;br&gt;
    Several good&amp;nbsp;answers.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://www.stat.berkeley.edu/mediawiki/index.php/Recommended_Books"&gt;Recommended books (&lt;span class="caps"&gt;UC&lt;/span&gt; Berkeley Statistics Graduate Student Association)&lt;/a&gt;&lt;br&gt;
    A list of books in many categories, without much&amp;nbsp;commentary.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;School&amp;nbsp;maths&lt;/h2&gt;
&lt;div class="cover-list-wrapper"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a class="inline-cover" href="https://www.khanacademy.org/mission/math"&gt;&lt;img alt="Khan Academy" src="https://chrisberkhout.com/blog/maths-learning/khan-academy.png"&gt;&lt;/a&gt;
      &lt;a href="https://www.khanacademy.org/mission/math"&gt;The World of Math (Khan Academy)&lt;/a&gt;&lt;br&gt;
      Khan Academy covers all of school maths and a little more. It starts from counting and goes up to calculus, differential equations and linear algebra, with short video lectures and comprehensive online exercises. The explanations are very clear, but the format makes it slower than other resources for refreshing prior&amp;nbsp;knowledge.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a class="inline-cover" href="https://www.amazon.com/Basic-Mathematics-Serge-Lang/dp/0387967877"&gt;&lt;img alt="Basic Mathematics (Lang)" src="https://chrisberkhout.com/blog/maths-learning/basic-mathematics-lang.jpg"&gt;&lt;/a&gt;
      &lt;a href="https://www.amazon.com/Basic-Mathematics-Serge-Lang/dp/0387967877"&gt;Basic Mathematics (Lang)&lt;/a&gt;&lt;br&gt;
      This book is aimed at high school students or beginning university students and includes everything needed to prepare a student for going on to calculus, linear algebra and other topics. It makes a point of including some vector geometry earlier than is commonly done and &amp;#8220;deals with mathematics on both a manipulative (or computational) level and the theoretical&amp;nbsp;level.&amp;#8221;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a class="inline-cover" href="https://www.amazon.com/Engineering-Mathematics-K-Stroud/dp/1137031204/"&gt;&lt;img alt="Engineering Mathematics (Stroud &amp;amp; Booth)" src="https://chrisberkhout.com/blog/maths-learning/engineering-mathematics-stroud.jpg"&gt;&lt;/a&gt;
      &lt;a href="https://www.amazon.com/Engineering-Mathematics-K-Stroud/dp/1137031204/"&gt;Engineering Mathematics (Stroud &lt;span class="amp"&gt;&amp;amp;&lt;/span&gt; Booth)&lt;/a&gt;&lt;br&gt;
      This was highly recommended by a colleague, for building practical maths skills from the beginning. It stops at Fourier transforms (covered in &lt;a href="https://www.amazon.com/d/Books/Advanced-Engineering-Mathematics-K-Stroud/0230275486"&gt;Advanced Engineering Mathematics&lt;/a&gt;) and focuses on practice rather than theory, lacking formal definitions. It is designed for self study, with checklists and exercises&amp;nbsp;included.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;h2&gt;Mathematical thinking and proof&amp;nbsp;writing&lt;/h2&gt;
&lt;div class="cover-list-wrapper"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a class="inline-cover" href="https://www.amazon.com/dp/0199661316/"&gt;&lt;img alt="How to Study as a Mathematics Major (Alcock)" src="https://chrisberkhout.com/blog/maths-learning/how-to-study-as-a-mathmatics-major-alcock.jpg"&gt;&lt;/a&gt;
      &lt;a href="https://www.amazon.com/dp/0199661316/"&gt;How to Study as a Mathematics Major (Alcock)&lt;/a&gt;&lt;br&gt;
      A useful and easy to read explanation of what university-level mathematics is about and how to approach studying it. It goes a deeper than Devlin in the discussion, and refers users to other resources (including Velleman) for practice of the proof-writing techniques it introduces. The general study advice won&amp;#8217;t be of much interest unless you&amp;#8217;re really fresh out of&amp;nbsp;high-school.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a class="inline-cover" href="https://www.amazon.com/How-Prove-Structured-Approach-2nd/dp/0521675995"&gt;&lt;img alt="How to Prove It: A Structured Approach (Velleman)" src="https://chrisberkhout.com/blog/maths-learning/how-to-prove-it-velleman.jpg"&gt;&lt;/a&gt;
      &lt;a href="https://www.amazon.com/How-Prove-Structured-Approach-2nd/dp/0521675995"&gt;How to Prove It: A Structured Approach (Velleman)&lt;/a&gt;&lt;br&gt;
      The most widely recommended book for learning how to work with&amp;nbsp;proofs.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a class="inline-cover" href="https://www.amazon.com/Introduction-Mathematical-Thinking-Keith-Devlin/dp/0615653634"&gt;&lt;img alt="Introduction to Mathematical Thinking (Devlin)" src="https://chrisberkhout.com/blog/maths-learning/introduction-to-mathematical-thinking-devlin.jpg"&gt;&lt;/a&gt;
      &lt;a href="https://www.amazon.com/Introduction-Mathematical-Thinking-Keith-Devlin/dp/0615653634"&gt;Introduction to Mathematical Thinking (Devlin)&lt;/a&gt;&lt;br&gt;
      This book starts with an interesting discussion of how modern mathematics differs from its past, and from what you might have learned in school. The second half introduces proofs and assumes you&amp;#8217;re following along with all the exercises. My preference is the previous two books, but this is a good alternative for both. This material is also covered by the author in a &lt;a href="https://www.coursera.org/learn/mathematical-thinking"&gt;Coursera course&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;h2&gt;Calculus&lt;/h2&gt;
&lt;p&gt;I found a particularly helpful &lt;a href="http://math.stackexchange.com/questions/69848/preparing-for-spivak/71146#71146"&gt;Mathematics Stack Exchange answer&lt;/a&gt; comparing Apostol and &lt;a href="https://www.amazon.com/Calculus-4th-Michael-Spivak/dp/0914098918"&gt;Spivak&lt;/a&gt;. Apostol covers integration before differentiation, introduces linear algebra before multi-variable calculus, and has been particularly &lt;a href="http://www.stumblingrobot.com/best-math-books/best-calculus-book-best-linear-algebra-book/"&gt;recommended&lt;/a&gt; for older students coming back to mathematics and as the best calculus book for building mathematical&amp;nbsp;maturity.&lt;/p&gt;
&lt;div class="cover-list-wrapper"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a class="inline-cover" href="http://eu.wiley.com/WileyCDA/WileyTitle/productCd-EHEP001951.html"&gt;&lt;img alt="Calculus (Apostol) Vol. 1: One-Variable Calculus, with an Introduction to Linear Algebra" src="https://chrisberkhout.com/blog/maths-learning/calculus-apostol-1.jpg"&gt;&lt;/a&gt;
      &lt;a href="https://www.amazon.com/gp/product/0471000051/"&gt;Calculus (Apostol) Vol. 1&lt;/a&gt;&lt;br&gt;
      One-Variable Calculus, with an Introduction to Linear&amp;nbsp;Algebra.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a class="inline-cover" href="http://eu.wiley.com/WileyCDA/WileyTitle/productCd-0471000078.html"&gt;&lt;img alt="Calculus (Apostol) Vol. 2: Multi-Variable Calculus and Linear Algebra with Applications to Differential Equations and Probability" src="https://chrisberkhout.com/blog/maths-learning/calculus-apostol-2.jpg"&gt;&lt;/a&gt;
      &lt;a href="https://www.amazon.com/Calculus-Vol-Multi-Variable-Applications-Differential/dp/0471000078/"&gt;Calculus (Apostol) Vol. 2&lt;/a&gt;&lt;br&gt;
      Multi-Variable Calculus and Linear Algebra with Applications to Differential Equations and&amp;nbsp;Probability.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;h2&gt;Linear&amp;nbsp;algebra&lt;/h2&gt;
&lt;div class="cover-list-wrapper"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a class="inline-cover" href="https://www.amazon.com/dp/0135367972/"&gt;&lt;img alt="Linear Algebra (Hoffman &amp;amp; Kunze)" src="https://chrisberkhout.com/blog/maths-learning/linear-algebra-hoffman.jpg"&gt;&lt;/a&gt;
      &lt;a href="https://www.amazon.com/dp/0135367972/"&gt;Linear Algebra (Hoffman &lt;span class="amp"&gt;&amp;amp;&lt;/span&gt; Kunze)&lt;/a&gt;&lt;br&gt;
      Apparently covers a lot, and has very good reviews on Amazon and&amp;nbsp;elsewhere.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a class="inline-cover" href="https://www.amazon.com/Linear-Algebra-Right-Undergraduate-Mathematics/dp/3319110799/"&gt;&lt;img alt="Linear Algebra Done Right (Axler)" src="https://chrisberkhout.com/blog/maths-learning/linear-algebra-done-right-axler.jpg"&gt;&lt;/a&gt;
      &lt;a href="https://www.amazon.com/Linear-Algebra-Right-Undergraduate-Mathematics/dp/3319110799/"&gt;Linear Algebra Done Right (Axler)&lt;/a&gt;&lt;br&gt;
      A popular alternative, also recommended by a&amp;nbsp;colleague.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;h2&gt;Probability&lt;/h2&gt;
&lt;p&gt;These two (and Wasserman, below) were recommended in &lt;a href="https://www.cs.ubc.ca/~murphyk/MLbook/"&gt;Machine Learning: a Probabilistic Perspective&lt;/a&gt;:&lt;/p&gt;
&lt;div class="cover-list-wrapper"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a class="inline-cover" href="https://www.amazon.com/Probability-Theory-Science-T-Jaynes/dp/0521592712"&gt;&lt;img alt="Probability Theory: The Logic of Science (Jaynes)" src="https://chrisberkhout.com/blog/maths-learning/probability-theory-jaynes.jpg"&gt;&lt;/a&gt;
      &lt;a href="https://www.amazon.com/Probability-Theory-Science-T-Jaynes/dp/0521592712"&gt;Probability Theory: The Logic of Science&amp;nbsp;(Jaynes)&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a class="inline-cover" href="https://www.amazon.com/Introduction-Probability-2nd-Dimitri-Bertsekas/dp/188652923X"&gt;&lt;img alt="Introduction to Probability (Bertsekas &amp;amp; Tsitsiklis)" src="https://chrisberkhout.com/blog/maths-learning/introduction-to-probability-bertsekas.jpg"&gt;&lt;/a&gt;
      &lt;a href="https://www.amazon.com/Introduction-Probability-2nd-Dimitri-Bertsekas/dp/188652923X"&gt;Introduction to Probability (Bertsekas &lt;span class="amp"&gt;&amp;amp;&lt;/span&gt;&amp;nbsp;Tsitsiklis)&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;h2&gt;Statistics&lt;/h2&gt;
&lt;div class="cover-list-wrapper"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p class="clear"&gt;&lt;a class="inline-cover" href="https://www.amazon.com/Statistics-4th-David-Freedman/dp/0393929728/"&gt;&lt;img alt="Statistics (Freedman et al.)" src="https://chrisberkhout.com/blog/maths-learning/statistics-freedman.jpg"&gt;&lt;/a&gt;
      &lt;a href="https://www.amazon.com/Statistics-4th-David-Freedman/dp/0393929728/"&gt;Statistics (Freedman et al.)&lt;/a&gt;&lt;br&gt;
      Highly recommended in reviews and by a colleague. Focuses on fundamental ideas and their application rather than equations or&amp;nbsp;proofs.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a class="inline-cover" href="https://www.khanacademy.org/mission/math"&gt;&lt;img alt="All of Statistics: A Concice Course in Statistical Inference (Wasserman)!" src="https://chrisberkhout.com/blog/maths-learning/all-of-statistics-wasserman.jpg"&gt;&lt;/a&gt;
      &lt;a href="http://www.springer.com/gb/book/9780387402727"&gt;All of Statistics: A Concice Course in Statistical Inference (Wasserman)&lt;/a&gt;&lt;br&gt;
      From the publisher: &amp;#8220;This book covers a much wider range of topics than a typical introductory text on mathematical statistics. It includes modern topics like nonparametric curve estimation, bootstrapping and classification, topics that are usually relegated to follow-up courses. The reader is assumed to know calculus and a little linear algebra. No previous knowledge of probability and statistics is required. The text can be used at the advanced undergraduate and graduate&amp;nbsp;level.&amp;#8221;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;p&gt;The course &lt;a href="https://www.coursera.org/learn/basic-statistics"&gt;Basic Statistics (Coursera)&lt;/a&gt; is nice and clear, but aimed at humanities&amp;nbsp;students.&lt;/p&gt;
&lt;h2&gt;Surveys&lt;/h2&gt;
&lt;p&gt;After the topics above, it might be nice to take a broader look at mathmatics. These two books are often recommended for that&amp;nbsp;purpose.&lt;/p&gt;
&lt;div class="cover-list-wrapper"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a class="inline-cover" href="https://www.amazon.com/dp/0486409163/"&gt;&lt;img alt="Mathematics: Its Content, Methods and Meaning (Aleksandrov et al.)" src="https://chrisberkhout.com/blog/maths-learning/mathematics-its-content-methods-and-meaning-alexandrov.jpg"&gt;&lt;/a&gt;
    &lt;a href="https://www.amazon.com/dp/0486409163/"&gt;Mathematics: Its Content, Methods and Meaning (Aleksandrov et al.)&lt;/a&gt;&lt;br&gt;
    I found this survey by Soviet mathematicians via a highly upvoted Mathematics Stack Exchange &lt;a href="http://math.stackexchange.com/a/69062"&gt;answer&lt;/a&gt;, and noticed it recommended in several other&amp;nbsp;places.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a class="inline-cover" href="https://www.amazon.com/dp/0195105192/"&gt;&lt;img alt="What is Mathematics? An Elementry Approach to Ideas and Methods (Courant)" src="https://chrisberkhout.com/blog/maths-learning/what-is-mathematics-courant.jpg"&gt;&lt;/a&gt;
    &lt;a href="https://www.amazon.com/dp/0195105192/"&gt;What is Mathematics? An Elementry Approach to Ideas and Methods (Courant)&lt;/a&gt;&lt;br&gt;
    Einstein called it &amp;#8220;a lucid representation of the fundamental concepts and methods of the whole field of&amp;nbsp;mathematics.&amp;#8221;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;h2&gt;Other&amp;nbsp;resources&lt;/h2&gt;
&lt;p&gt;There are a lot of online courses on these topics. Here is a list of many of&amp;nbsp;them:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.analyticsvidhya.com/blog/2017/01/19-mooc-mathematics-statistics-datascience-machine-learning/"&gt;19 MOOCs on Mathematics &lt;span class="amp"&gt;&amp;amp;&lt;/span&gt; Statistics for Data Science &lt;span class="amp"&gt;&amp;amp;&lt;/span&gt; Machine&amp;nbsp;Learning&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I was quite inspired by reading about &lt;a href="https://www.scotthyoung.com/blog/myprojects/mit-challenge-2/"&gt;Scott Young&amp;#8217;s &lt;span class="caps"&gt;MIT&lt;/span&gt; challenge&lt;/a&gt;, in which he went through 4 years worth of &lt;span class="caps"&gt;MIT&lt;/span&gt; computer science materials in 1 year. &lt;span class="caps"&gt;MIT&lt;/span&gt; has a lot of course materials freely available online, especially in computer science and mathematics. Sometimes it&amp;#8217;s video lectures, other times it&amp;#8217;s written notes, of varying detail. It&amp;#8217;s a great&amp;nbsp;resource.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://ocw.mit.edu/courses/mathematics/"&gt;&lt;span class="caps"&gt;MIT&lt;/span&gt; OpenCourseWare mathematics&amp;nbsp;courses&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://math.mit.edu/academics/undergrad/major/index.php"&gt;&lt;span class="caps"&gt;MIT&lt;/span&gt; Math Major course&amp;nbsp;structure&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><category term="misc"/></entry><entry><title>Log tailing tips</title><link href="https://chrisberkhout.com/blog/log-tailing-tips/" rel="alternate"/><published>2016-12-14T00:00:00+00:00</published><updated>2016-12-14T00:00:00+00:00</updated><author><name>Chris Berkhout</name></author><id>tag:chrisberkhout.com,2016-12-14:/blog/log-tailing-tips/</id><summary type="html">&lt;p&gt;The following examples assume you can tail live logs as&amp;nbsp;follows:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;tail -f service.log
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That command will show lines being added to the file &lt;code&gt;service.log&lt;/code&gt;. Replace
it with whatever command will stream logs in your&amp;nbsp;environment.&lt;/p&gt;
&lt;p&gt;Although many unix tools (&lt;code&gt;head&lt;/code&gt;, &lt;code&gt;wc&lt;/code&gt;, &lt;code&gt;sort&lt;/code&gt;, &lt;code&gt;xargs&lt;/code&gt;, etc.) are available
with …&lt;/p&gt;</summary><content type="html">&lt;p&gt;The following examples assume you can tail live logs as&amp;nbsp;follows:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;tail -f service.log
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That command will show lines being added to the file &lt;code&gt;service.log&lt;/code&gt;. Replace
it with whatever command will stream logs in your&amp;nbsp;environment.&lt;/p&gt;
&lt;p&gt;Although many unix tools (&lt;code&gt;head&lt;/code&gt;, &lt;code&gt;wc&lt;/code&gt;, &lt;code&gt;sort&lt;/code&gt;, &lt;code&gt;xargs&lt;/code&gt;, etc.) are available
with the same name and similar functionality on both &lt;span class="caps"&gt;BSD&lt;/span&gt;-based platforms (such
as Mac &lt;span class="caps"&gt;OS&lt;/span&gt; X) and &lt;span class="caps"&gt;GNU&lt;/span&gt;-based platforms (such as Linux), they often have small
differences. All of the examples here assume &lt;span class="caps"&gt;GNU&lt;/span&gt; tools. If you are not sure
which version of a tool you have on your system, or what options are available
for it, check its &lt;code&gt;man&lt;/code&gt; page.&lt;/p&gt;
&lt;h3&gt;Show only the first 5k new error&amp;nbsp;lines&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;tail -f service.log | grep ERROR | head -n5000
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Lines not including &amp;#8216;&lt;span class="caps"&gt;SUCCESS&lt;/span&gt;&amp;#8217; or &amp;#8216;200 &lt;span class="caps"&gt;OK&lt;/span&gt;&amp;#8217; or&amp;nbsp;backtraces&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;tail -f service.log | grep -v SUCCESS | grep -v &amp;#39;200 OK&amp;#39; | grep -Pv &amp;#39;\tat &amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Grep&amp;#8217;s &lt;code&gt;-v&lt;/code&gt; will print lines that don&amp;#8217;t match the pattern. &lt;code&gt;-P&lt;/code&gt; is a Perl-style
regular expression (used here to indicate a tab&amp;nbsp;character).&lt;/p&gt;
&lt;h3&gt;Show the 3 lines before and 10 lines after each&amp;nbsp;exception&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;tail -f service.log | grep -B3 -A10 Exception
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Grep&amp;#8217;s &lt;code&gt;-B&lt;/code&gt; says how many lines before the matching line to print, &lt;code&gt;-A&lt;/code&gt; is for
lines after. Blocks of matching input will be separated by lines with &lt;code&gt;--&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Watch the rate of a certain&amp;nbsp;exception&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;tail -f service.log | grep IndividualRequestTimeoutException | \
  pv -lra &amp;gt; /dev/null
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;pv&lt;/code&gt; is &lt;a href="http://www.ivarch.com/programs/pv.shtml"&gt;Pipe Viewer&lt;/a&gt;. It is commonly
used to show a progress bar or counter when writing or transferring data using
pipes in the shell. In this case we specify &lt;code&gt;-l&lt;/code&gt; (line mode - counting newline
characters instead of bytes), &lt;code&gt;-r&lt;/code&gt; (display the current rate of data transfer)
and &lt;code&gt;-a&lt;/code&gt; (display the average rate of data transferred so far). &lt;code&gt;pv&lt;/code&gt; echos its
&lt;code&gt;STDIN&lt;/code&gt; to &lt;code&gt;STDOUT&lt;/code&gt; (often to another program), but in this case we just want
to see the rates and not keep the data, so we redirect the &lt;code&gt;STDOUT&lt;/code&gt; to
&lt;code&gt;/dev/null&lt;/code&gt; to discard&amp;nbsp;it.&lt;/p&gt;
&lt;h3&gt;Show 10k log lines and save them in a&amp;nbsp;file&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;tail -f service.log | head -n10000 | tee captured.log
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Tee_(command)"&gt;&lt;code&gt;tee&lt;/code&gt;&lt;/a&gt; lets you write input into
a file for later use, but also to see the data as it passes through. In this
case the file will be overwritten if it already&amp;nbsp;exists.&lt;/p&gt;
&lt;h3&gt;Show 10k log lines and append them to a&amp;nbsp;file&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;tail -f service.log | head -n10000 | tee -a captured.log
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You can append rather than overwrite by adding &lt;code&gt;-a&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;List instances in captured&amp;nbsp;logs&lt;/h3&gt;
&lt;p&gt;Assuming each log line starts with an instance &lt;span class="caps"&gt;ID&lt;/span&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;cat captured.log | cut -f1 | uniq
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;cat&lt;/code&gt; simply prints the contents of a file. &lt;code&gt;cut&lt;/code&gt; lets you pick out a certain
field from each line of input data. By default, fields are delimited by tabs,
and we use that to select the instance &lt;span class="caps"&gt;ID&lt;/span&gt; (the first field) from each line. You
can specify a different field delimiter with &lt;code&gt;-d&lt;/code&gt;. For example, a space: &lt;code&gt;-d'
'&lt;/code&gt;. &lt;code&gt;uniq&lt;/code&gt; prints only unique lines of input (removing repeat&amp;nbsp;lines).&lt;/p&gt;
&lt;h3&gt;Count instances in captured&amp;nbsp;logs&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;cat captured.log | cut -f1 | uniq | wc -l
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;wc&lt;/code&gt; is the word counter. &lt;code&gt;-l&lt;/code&gt; tells it to just count&amp;nbsp;lines.&lt;/p&gt;
&lt;h3&gt;Group captured logs by instance, in one&amp;nbsp;file&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;cat captured.log | sort -sk 1,1 | tee grouped.log
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;sort&lt;/code&gt; will sort lines. &lt;code&gt;-k&lt;/code&gt; tells it to sort based on certain (whitespace
separated) fields. In this case we want to sort using field 1 to 1 (so, only 1
- the instance &lt;span class="caps"&gt;ID&lt;/span&gt;) (note that if the ending field is unspecified it defaults to
the end of the line). Usually when two lines have equal sort fields, the whole
lines will also be compared to determine the final order, but &lt;code&gt;-s&lt;/code&gt; specifies
&amp;#8216;stable sort&amp;#8217;, which preserves the incoming order for lines with equal sort&amp;nbsp;keys.&lt;/p&gt;
&lt;h3&gt;Group captured logs by instance, in separate&amp;nbsp;files&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;cat captured.log | cut -f1 | uniq | \
  xargs -n1 -I{} bash -c &amp;#39;grep ^{} captured.log &amp;gt; instance-{}.log&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;xargs&lt;/code&gt; transforms lines in its &lt;code&gt;STDIN&lt;/code&gt; to arguments to another command. By
default, multiple input lines will be converted to multiple arguments to a
single command. In this case we specify &lt;code&gt;-n1&lt;/code&gt; to use just one input line at a
time. &lt;code&gt;-I{}&lt;/code&gt; says that in the command, &lt;code&gt;{}&lt;/code&gt; should be replaced with the input
string. We use &lt;code&gt;grep ^{} captured.log &amp;gt; instance-{}.log&lt;/code&gt; to find the lines in
&lt;code&gt;captured.log&lt;/code&gt; that begin with the given instance &lt;span class="caps"&gt;ID&lt;/span&gt; and write them to a file
with the instance &lt;span class="caps"&gt;ID&lt;/span&gt; in the name. Since this this command involves a shell
redirect (&lt;code&gt;&amp;gt;&lt;/code&gt;), it needs to be run in a shell rather than as a direct command,
so we wrap it in &lt;code&gt;bash -c '...'&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Count log lines in each instance file, sort&amp;nbsp;ascending&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;wc -l instance-* | LC_ALL=C sort
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;In most locales, by default, sort will consider whitespace insignificant. This
means that a &lt;code&gt;2&lt;/code&gt; will be ordered after &lt;code&gt;123&lt;/code&gt;, because the first significant
character in the former (&lt;code&gt;2&lt;/code&gt;) comes after the first significant character in
the latter (&lt;code&gt;1&lt;/code&gt;). We can specify the &lt;code&gt;C&lt;/code&gt; locale in an environment variable to
force sort to consider whitespace significant and sort &lt;code&gt;2&lt;/code&gt; before &lt;code&gt;123&lt;/code&gt;. Note
that here we are asking &lt;code&gt;wc&lt;/code&gt; to count multiple files rather than the &lt;code&gt;STDIN&lt;/code&gt;
data (as we did&amp;nbsp;above).&lt;/p&gt;</content><category term="misc"/></entry><entry><title>Log visualization with gnuplot</title><link href="https://chrisberkhout.com/blog/log-visualization-with-gnuplot/" rel="alternate"/><published>2016-07-04T00:00:00+00:00</published><updated>2016-07-04T00:00:00+00:00</updated><author><name>Chris Berkhout</name></author><id>tag:chrisberkhout.com,2016-07-04:/blog/log-visualization-with-gnuplot/</id><summary type="html">&lt;style&gt;
  .plot { max-width: 680px; }
  table code { white-space: nowrap; }
&lt;/style&gt;

&lt;p&gt;Often when investigating an issue there is information in logs that isn&amp;#8217;t
captured well by &lt;a href="https://prometheus.io/"&gt;Prometheus&lt;/a&gt; metrics. This includes
qualitative information such as stack traces, but the frequency of certain
messages may also be of interest and that is what this we …&lt;/p&gt;</summary><content type="html">&lt;style&gt;
  .plot { max-width: 680px; }
  table code { white-space: nowrap; }
&lt;/style&gt;

&lt;p&gt;Often when investigating an issue there is information in logs that isn&amp;#8217;t
captured well by &lt;a href="https://prometheus.io/"&gt;Prometheus&lt;/a&gt; metrics. This includes
qualitative information such as stack traces, but the frequency of certain
messages may also be of interest and that is what this we will focus on&amp;nbsp;here.&lt;/p&gt;
&lt;h2&gt;Selecting interesting log&amp;nbsp;messages&lt;/h2&gt;
&lt;p&gt;A recent issue showed memcached exceptions in the Prometheus metric
&lt;code&gt;serviceapi_memcached_exceptions_total&lt;/code&gt; (including the exceptions
&lt;code&gt;IndividualRequestTimeoutException&lt;/code&gt; and &lt;code&gt;KetamaFailureAccrualFactory$$anon$1&lt;/code&gt;),
but the logs also included messages such&amp;nbsp;as:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="mf"&gt;2016&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;04&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;13&lt;/span&gt;&lt;span class="n"&gt;_18&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;09&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;17.87161&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;18&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;09&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;17.871&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;[&lt;/span&gt;&lt;span class="n"&gt;finagle&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;netty3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;9&lt;/span&gt;&lt;span class="err"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ERROR&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SERVICE_API_THRIFT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;traceId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="n"&gt;ac36e44bf4c0a96&lt;/span&gt;&lt;span class="mf"&gt;.2&lt;/span&gt;&lt;span class="n"&gt;ac36e44bf4c0a96&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="n"&gt;ac36e44bf4c0a96&lt;/span&gt;&lt;span class="err"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="err"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;updateByActor&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;FAILURE&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="mf"&gt;2016&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;04&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;13&lt;/span&gt;&lt;span class="n"&gt;_18&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;09&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;17.87162&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lang&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Failed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;adding&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memcached&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;Target&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;218468078&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="mf"&gt;2016&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;04&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;13&lt;/span&gt;&lt;span class="n"&gt;_18&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;09&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;17.87162&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RecordRepository&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com$example$service$record$RecordRepository$&lt;/span&gt;&lt;span class="err"&gt;$$&lt;/span&gt;&lt;span class="n"&gt;anonfun$&lt;/span&gt;&lt;span class="mf"&gt;15&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RecordRepository&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scala&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;102&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="mf"&gt;2016&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;04&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;13&lt;/span&gt;&lt;span class="n"&gt;_18&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;09&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;17.87163&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RecordRepository$lambda$&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;addToRemoteCache$&lt;/span&gt;&lt;span class="mf"&gt;1.&lt;/span&gt;&lt;span class="n"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RecordRepository&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scala&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="mf"&gt;2016&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;04&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;13&lt;/span&gt;&lt;span class="n"&gt;_18&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;09&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;17.87164&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RecordRepository$lambda$&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;addToRemoteCache$&lt;/span&gt;&lt;span class="mf"&gt;1.&lt;/span&gt;&lt;span class="n"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RecordRepository&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scala&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;which could more easily be tied to a specific code&amp;nbsp;path.&lt;/p&gt;
&lt;p&gt;I could see these messages appearing throughout the incident, but I wanted to
get a sense of the frequency and at what stages of the incident they&amp;nbsp;appeared.&lt;/p&gt;
&lt;p&gt;After retrieving some logs from the relevant instance, I identified a few
interesting subsets of log&amp;nbsp;messages:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;grep &amp;#39;MEMCACHED set failed with .*IndividualRequestTimeoutException&amp;#39;
grep &amp;#39;WARN.*Endpoint is marked dead by failureAccrual&amp;#39;
grep &amp;#39;Failed adding record to memcached&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Extracting times &lt;span class="amp"&gt;&amp;amp;&lt;/span&gt; generating counts per&amp;nbsp;second&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;strptime&lt;/code&gt; utility from &lt;a href="http://www.fresse.org/dateutils/"&gt;dateutils&lt;/a&gt; can
parse and convert timestamps. By default it discards non-matching portions, so
piping in log lines beginning with timestamps of the&amp;nbsp;form:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="mf"&gt;2016&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;04&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;13&lt;/span&gt;&lt;span class="n"&gt;_18&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;09&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;17...&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;and specifying the &lt;a href="https://github.com/hroptatyr/dateutils/blob/master/info/format.texi"&gt;input and output
formats&lt;/a&gt;
to be &lt;code&gt;%F_%T&lt;/code&gt; and &lt;code&gt;%s%n&lt;/code&gt; generated output lines with a Unix timestamp only, in
seconds since the&amp;nbsp;epoch.&lt;/p&gt;
&lt;p&gt;The next step in processing was to count how many times each second-resolution
timestamp occurred, using &lt;code&gt;uniq&lt;/code&gt;&lt;span class="quo"&gt;&amp;#8216;&lt;/span&gt;s &lt;code&gt;-c&lt;/code&gt; (count)&amp;nbsp;option.&lt;/p&gt;
&lt;p&gt;The full data preparation process&amp;nbsp;was:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;cat logs.txt | grep &amp;#39;ERROR&amp;#39; | strptime -i &amp;#39;%F_%T&amp;#39; -f &amp;#39;%s%n&amp;#39; | uniq -c &amp;gt; error.dat
cat logs.txt | grep &amp;#39;MEMCACHED set failed with .*IndividualRequestTimeoutException&amp;#39; | strptime -i &amp;#39;%F_%T&amp;#39; -f &amp;#39;%s%n&amp;#39; | uniq -c &amp;gt; memcached-set-failed.dat
cat logs.txt | grep &amp;#39;WARN.*Endpoint is marked dead by failureAccrual&amp;#39; | strptime -i &amp;#39;%F_%T&amp;#39; -f &amp;#39;%s%n&amp;#39; | uniq -c &amp;gt; dead-by-failureaccrual.dat
cat logs.txt | grep &amp;#39;Failed adding record to memcached&amp;#39; | strptime -i &amp;#39;%F_%T&amp;#39; -f &amp;#39;%s%n&amp;#39; | uniq -c &amp;gt; fail-adding.dat
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Each of the four generated files was in this format (count,&amp;nbsp;timestamp):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="mf"&gt;685&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1460570400&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="mf"&gt;483&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1460570401&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="mf"&gt;953&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1460570402&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="mf"&gt;587&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1460570403&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="mf"&gt;694&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1460570404&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="mf"&gt;...&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Plotting with&amp;nbsp;gnuplot&lt;/h2&gt;
&lt;p&gt;&lt;a href="http://www.gnuplot.info/"&gt;gnuplot&lt;/a&gt; is an open source plotting tool, first
released 1986. It is a command line program that is useful both for interactive
data analysis and scripted presentation of final graphics in many different
output&amp;nbsp;formats.&lt;/p&gt;
&lt;p&gt;I like to build up a gnuplot script by running a text editor in one window and
sending commands from there into an interactive gnuplot terminal in another
window. If your editor isn&amp;#8217;t set up for that (e.g. with
&lt;a href="https://github.com/jgdavey/tslime.vim"&gt;tslime.vim&lt;/a&gt;), you can always use cut
and paste or tell gnuplot to load your script from a file
(&lt;code&gt;load 'yourfile.gnuplot'&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;I began with these gnuplot&amp;nbsp;commands:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;set xdata time
set timefmt &amp;quot;%s&amp;quot;
set format x &amp;quot;%H:%M&amp;quot;
set yrange [0:*]
plot &amp;#39;error.dat&amp;#39; using 2:1 title &amp;#39;error&amp;#39; with impulses lw 0.1, \
     &amp;#39;memcached-set-failed.dat&amp;#39; using 2:1 title &amp;#39;memcached-set-failed&amp;#39; with impulses lw 1, \
     &amp;#39;dead-by-failureaccrual.dat&amp;#39; using 2:1 title &amp;#39;dead-by-failureaccrual&amp;#39; with impulses lw 1, \
     &amp;#39;fail-adding.dat&amp;#39; using 2:1 title &amp;#39;fail-adding&amp;#39; with impulses lw 2
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Which generated this&amp;nbsp;plot:&lt;/p&gt;
&lt;p&gt;&lt;img alt="plot1" class="plot" src="https://chrisberkhout.com/blog/log-visualization-with-gnuplot/plot1.png"&gt;&lt;/p&gt;
&lt;p&gt;Next I restricted the range of the y-axis and refreshed the&amp;nbsp;plot:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;set yrange [0:500]
refresh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;img alt="plot2" class="plot" src="https://chrisberkhout.com/blog/log-visualization-with-gnuplot/plot2.png"&gt;&lt;/p&gt;
&lt;p&gt;And&amp;nbsp;again:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;set yrange [0:100]
refresh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;img alt="plot3" class="plot" src="https://chrisberkhout.com/blog/log-visualization-with-gnuplot/plot3.png"&gt;&lt;/p&gt;
&lt;p&gt;Here it is clear that &lt;code&gt;IndividualRequestTimeoutExceptions&lt;/code&gt; were only present
during an initial 30s period, after which Finagle&amp;#8217;s &lt;code&gt;Endpoint is marked dead by
failureAccrual&lt;/code&gt; warnings began, with a much greater frequency than our own
&lt;code&gt;Failed adding record to memcached&lt;/code&gt; messages. This indicated that the
&lt;code&gt;finagle-memcached&lt;/code&gt; client&amp;#8217;s internal retries were responsible for a large
proportion of the exceptions, and the finally failed incoming requests were
actually much fewer. These error remained stable from 18:10 to 18:20 (despite
the &lt;code&gt;serviceapi_finagle_memcached_client_dead_nodes&lt;/code&gt; metric in Prometheus
showing recovery of all dead nodes by&amp;nbsp;18:10).&lt;/p&gt;
&lt;p&gt;In this chart, the x-axis range is set automatically by the range of the input
data, and gnuplot&amp;#8217;s defaults are used for tick spacing, color selection and
legend formatting, although it is possible to control all this and more if&amp;nbsp;required.&lt;/p&gt;
&lt;p&gt;Let&amp;#8217;s take a closer look at the plot&amp;nbsp;command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;plot &amp;#39;error.dat&amp;#39; using 2:1 title &amp;#39;error&amp;#39; with impulses lw 0.1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;part&lt;/th&gt;
&lt;th&gt;meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;plot&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The gnuplot command&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;'error.dat'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Our data file, with space separated values (by default)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;using 2:1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Use columns 2 (time) and 1 (count) for x and y values.&lt;br/&gt;Column numbers always start from 1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;title 'error'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The title for this series, to be displayed in the legend&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;with impulses&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Use the &lt;code&gt;impulses&lt;/code&gt; plotting style for this data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;lw 0.1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;lw&lt;/code&gt; is the abbreviated form of &lt;code&gt;linewidth&lt;/code&gt;.&lt;br/&gt;Many gnuplot options have abbreviations&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;When multiple data sets are to be plotted, they should be separated by commas.
Note that the different series of data are drawn in the order they are
specified in the plot command, so it is important to find an order that does
not obscure significant features of the data. I also chose different line
widths in order to improve the visibility of certain&amp;nbsp;features.&lt;/p&gt;
&lt;h2&gt;Plotting style&amp;nbsp;choice&lt;/h2&gt;
&lt;p&gt;In this example we used the &lt;code&gt;impulses&lt;/code&gt; plotting style, which draws a line from
&lt;code&gt;y=0&lt;/code&gt; up to the value of each data point. This is very simple and quite
readable, and it&amp;#8217;s what I recommend for plotting frequencies from log&amp;nbsp;data.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;lines&lt;/code&gt; style would plot data from the same files, have the benefit of not
drawing over the top of smaller values, and perhaps even look a little nicer,
but it is complicated by interpolation. If there is no data point for a given x
coordinate, it will be interpolated from the values of the points to either
side of it, which means that times for which there was no count observed will
not display as zero. Additional pre-processing could be done to insert zero
values for any missing x coordinate in the data range, but for ad hoc analysis
my preference is to just use &lt;code&gt;impulses&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Another alternative would be to draw a histogram using the &lt;code&gt;boxes&lt;/code&gt; plotting
style, but this requires &lt;a href="http://stackoverflow.com/a/22721769"&gt;additional logic for assigning data points to
different buckets&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Lower resolution&amp;nbsp;counts&lt;/h2&gt;
&lt;p&gt;In this example we generated counts per second for the different log messages.
To have wider counts, for example, per minute, we can use dateutil&amp;#8217;s &lt;code&gt;dateround&lt;/code&gt;
instead of &lt;code&gt;strptime&lt;/code&gt; and round the timestamps as we parse&amp;nbsp;them:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;cat logs.txt | grep ERROR | dateround -i &amp;#39;%F_%T&amp;#39; -f &amp;#39;%s%n&amp;#39; /15s | uniq -c &amp;gt; errors-per-quarter-minute.dat
cat logs.txt | grep ERROR | dateround -i &amp;#39;%F_%T&amp;#39; -f &amp;#39;%s%n&amp;#39; /1m | uniq -c &amp;gt; errors-per-minute.dat
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Further&amp;nbsp;reading&lt;/h2&gt;
&lt;p&gt;&lt;a href="http://www.gnuplot.info/"&gt;Gnuplot&lt;/a&gt; has excellent reference
&lt;a href="http://www.gnuplot.info/documentation.html"&gt;documentation&lt;/a&gt;, and it&amp;#8217;s easy to
find blog posts describing many different tasks, but if you would like a more
complete guide in tutorial form, I highly recommend
&lt;a href="https://www.manning.com/books/gnuplot-in-action-second-edition"&gt;Gnuplot in Action: Understanding data with graphs&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.manning.com/books/gnuplot-in-action-second-edition"&gt;&lt;img alt="Book cover: Gnuplot in Action" src="https://chrisberkhout.com/blog/log-visualization-with-gnuplot/janert_cover_2e.jpg"&gt;&lt;/a&gt;&lt;/p&gt;</content><category term="misc"/></entry><entry><title>Dealing with localStorage errors</title><link href="https://chrisberkhout.com/blog/localstorage-errors/" rel="alternate"/><published>2013-08-30T00:00:00+00:00</published><updated>2013-08-30T00:00:00+00:00</updated><author><name>Chris Berkhout</name></author><id>tag:chrisberkhout.com,2013-08-30:/blog/localstorage-errors/</id><summary type="html">&lt;p&gt;Web storage is pretty straightforward, but several otherwise helpful tutorials I went through brushed over error&amp;nbsp;handling:&lt;/p&gt;
&lt;h6&gt;&lt;a href="http://diveintohtml5.info/storage.html"&gt;Dive Into &lt;span class="caps"&gt;HTML5&lt;/span&gt;: Local&amp;nbsp;Storage&lt;/a&gt;&lt;/h6&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;span class="dquo"&gt;&amp;#8220;&lt;/span&gt;QUOTA_EXCEEDED_ERR&amp;#8221; is the exception that will get thrown if you exceed your storage quota of 5&amp;nbsp;megabytes.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h6&gt;&lt;a href="http://paperkilledrock.com/2010/05/html5-localstorage-part-one/"&gt;PaperKilledRock.com: &lt;span class="caps"&gt;HTML5&lt;/span&gt;&amp;nbsp;localStorage&lt;/a&gt;&lt;/h6&gt;
&lt;blockquote&gt;
&lt;p&gt;Due to browser specific quotas you would …&lt;/p&gt;&lt;/blockquote&gt;</summary><content type="html">&lt;p&gt;Web storage is pretty straightforward, but several otherwise helpful tutorials I went through brushed over error&amp;nbsp;handling:&lt;/p&gt;
&lt;h6&gt;&lt;a href="http://diveintohtml5.info/storage.html"&gt;Dive Into &lt;span class="caps"&gt;HTML5&lt;/span&gt;: Local&amp;nbsp;Storage&lt;/a&gt;&lt;/h6&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;span class="dquo"&gt;&amp;#8220;&lt;/span&gt;QUOTA_EXCEEDED_ERR&amp;#8221; is the exception that will get thrown if you exceed your storage quota of 5&amp;nbsp;megabytes.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h6&gt;&lt;a href="http://paperkilledrock.com/2010/05/html5-localstorage-part-one/"&gt;PaperKilledRock.com: &lt;span class="caps"&gt;HTML5&lt;/span&gt;&amp;nbsp;localStorage&lt;/a&gt;&lt;/h6&gt;
&lt;blockquote&gt;
&lt;p&gt;Due to browser specific quotas you would want to check for exceptions so you would change line 1 above to the&amp;nbsp;following.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nv"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;{&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;localStorage&lt;/span&gt;.&lt;span class="nv"&gt;setItem&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Hello World!&amp;quot;&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;&lt;span class="c1"&gt;; //saves to the database, &amp;quot;key&amp;quot;, &amp;quot;value&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
}&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;catch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;e&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;{&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;e&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;QUOTA_EXCEEDED_ERR&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;{&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;alert&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Quota exceeded!&amp;#39;&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;&lt;span class="c1"&gt;; //data wasn&amp;#39;t successfully saved due to quota exceed so throw an error&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;}&lt;span class="w"&gt;&lt;/span&gt;
}&lt;span class="w"&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/blockquote&gt;
&lt;p&gt;Is &lt;code&gt;QUOTA_EXCEEDED_ERR&lt;/code&gt; some kind of global&amp;nbsp;constant?&lt;/p&gt;
&lt;p&gt;Chrome Developer tools says: &lt;code&gt;ReferenceError: QUOTA_EXCEEDED_ERR is not defined&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.google.com/search?q=QUOTA_EXCEEDED_ERR"&gt;Google&lt;/a&gt; suggests it might actually be &lt;code&gt;DOMException.QUOTA_EXCEEDED_ERR&lt;/code&gt;, and Chrome agrees - &lt;code&gt;DOMException.QUOTA_EXCEEDED_ERR&lt;/code&gt; is &lt;code&gt;22&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Is there a certain attribute of the exception that we should be matching to error code &lt;code&gt;22&lt;/code&gt;?&lt;/p&gt;
&lt;p&gt;Let&amp;#8217;s take a look. To generate the &lt;code&gt;QUOTA_EXCEEDED_ERR&lt;/code&gt; exception we&amp;#8217;ll attempt to store a &lt;span class="caps"&gt;12MB&lt;/span&gt; string, which should exceed the quota. Open your developer tools console and&amp;nbsp;enter:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;exception&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;repeat&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb nb-Type"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;too_big&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;repeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;each&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;JS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;character&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="n"&gt;localStorage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;clear&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="n"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;localStorage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;test&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;too_big&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;catch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;exception&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Then inspect &lt;code&gt;exception&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Trying that in a few browsers, we&amp;nbsp;observe:&lt;/p&gt;
&lt;h4&gt;Chrome&lt;/h4&gt;
&lt;p&gt;&lt;img alt="Exception in Chrome" class="outlined" src="https://chrisberkhout.com/blog/localstorage-errors/exception-chrome.png"&gt;&lt;/p&gt;
&lt;h4&gt;Firefox&lt;/h4&gt;
&lt;p&gt;&lt;img alt="Exception in Firefox" class="outlined" src="https://chrisberkhout.com/blog/localstorage-errors/exception-firefox.png"&gt;&lt;/p&gt;
&lt;h4&gt;Safari&lt;/h4&gt;
&lt;p&gt;&lt;img alt="Exception in Safari" class="outlined" src="https://chrisberkhout.com/blog/localstorage-errors/exception-safari.png"&gt;&lt;/p&gt;
&lt;p&gt;There is no single attribute that we can depend on across browsers to detect that the error is in fact a &lt;code&gt;QUOTA_EXCEEDED_ERR&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We could check &lt;code&gt;exception instanceof DOMException&lt;/code&gt;, but to test such code we would need to be able to produce a mock &lt;code&gt;DOMException&lt;/code&gt; instance. A SitePoint &lt;a href="http://reference.sitepoint.com/javascript/DOMException"&gt;reference&lt;/a&gt;&amp;nbsp;notes:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In practise, what we find is that most browsers implement &lt;span class="caps"&gt;DOM&lt;/span&gt; exceptions as part of their native&amp;nbsp;mechanism&amp;#8230;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It&amp;#8217;s not possible to &lt;a href="http://stackoverflow.com/questions/5136727/manually-artificially-throwing-a-domexception-with-javascript"&gt;produce them cleanly&lt;/a&gt; from normal&amp;nbsp;JavaScript.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Although &lt;code&gt;localStorage&lt;/code&gt; quota errors are known by names such as &lt;code&gt;QuotaExceededError&lt;/code&gt; and &lt;code&gt;QUOTA_EXCEEDED_ERR&lt;/code&gt;, the only way to write testable, non-browser specific code is to accept any exception thrown during a storage attempt as a quota error. To differentiate between quota and support/security errors, be sure to &lt;a href="http://diveintohtml5.info/detect.html#storage"&gt;detect the availability&lt;/a&gt; of &lt;code&gt;localStorage&lt;/code&gt; separately.&lt;/p&gt;</content><category term="misc"/></entry><entry><title>Mapping suburbs with jVectorMap</title><link href="https://chrisberkhout.com/blog/mapping-suburbs-at-rea/" rel="alternate"/><published>2012-10-16T00:00:00+00:00</published><updated>2012-10-16T00:00:00+00:00</updated><author><name>Chris Berkhout</name></author><id>tag:chrisberkhout.com,2012-10-16:/blog/mapping-suburbs-at-rea/</id><summary type="html">&lt;p&gt;At &lt;a href="http://realestate.com.au"&gt;&lt;span class="caps"&gt;REA&lt;/span&gt;&lt;/a&gt; we have a lot of data about the Australian property market. On our last hack day we explored new ways to put it to use. I prepared some &lt;a href="https://github.com/chrisberkhout/jvectormap_data_au"&gt;map data and examples&lt;/a&gt; with &lt;a href="http://jvectormap.com/"&gt;jVectorMap&lt;/a&gt; to help visualise suburb&amp;nbsp;data.&lt;/p&gt;
&lt;p&gt;jVectorMap comes with border data for the countries of …&lt;/p&gt;</summary><content type="html">&lt;p&gt;At &lt;a href="http://realestate.com.au"&gt;&lt;span class="caps"&gt;REA&lt;/span&gt;&lt;/a&gt; we have a lot of data about the Australian property market. On our last hack day we explored new ways to put it to use. I prepared some &lt;a href="https://github.com/chrisberkhout/jvectormap_data_au"&gt;map data and examples&lt;/a&gt; with &lt;a href="http://jvectormap.com/"&gt;jVectorMap&lt;/a&gt; to help visualise suburb&amp;nbsp;data.&lt;/p&gt;
&lt;p&gt;jVectorMap comes with border data for the countries of the world and the states of the &lt;span class="caps"&gt;USA&lt;/span&gt; and several other countries. We needed to map suburbs in Sydney and Melbourne, so I took some data from the Australian Bureau of Statistics, selected a relevant subset using &lt;a href="http://www.qgis.org/"&gt;&lt;span class="caps"&gt;QGIS&lt;/span&gt;&lt;/a&gt;, and ran it through jVectorMap&amp;#8217;s conversion script to produce files in its &lt;span class="caps"&gt;JSON&lt;/span&gt; format. I ended up making a few changes to the conversion script to get the results I wanted. The process is explained in more detail &lt;a href="https://github.com/chrisberkhout/jvectormap_data_au"&gt;on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It is good for heat maps, but jVectorMap also lets you add interactivity by providing hooks to respond to hover, enter, exit and click&amp;nbsp;events.&lt;/p&gt;
&lt;p&gt;It can zoom in and out, but since it works with a single set of vector data, it may not be the best choice for applications where your zoom range is big enough to call for data at several levels of detail. For example, data that&amp;#8217;s detailed enough to show individual Australian suburbs looks great at the city level, but doesn&amp;#8217;t render so well at the national&amp;nbsp;level.&lt;/p&gt;
&lt;p&gt;Several teams put it to use on hack day, with great&amp;nbsp;results.&lt;/p&gt;
&lt;p&gt;&lt;img alt="A colleague demonstrating his team's hack day project" src="https://chrisberkhout.com/blog/mapping-suburbs-at-rea/tracer.jpg"&gt;&lt;/p&gt;
&lt;div class="caption"&gt;A colleague demonstrating his team&amp;#8217;s hack day project&lt;/div&gt;</content><category term="misc"/></entry><entry><title>Clean documentation</title><link href="https://chrisberkhout.com/blog/clean-docs/" rel="alternate"/><published>2012-08-29T00:00:00+00:00</published><updated>2012-08-29T00:00:00+00:00</updated><author><name>Chris Berkhout</name></author><id>tag:chrisberkhout.com,2012-08-29:/blog/clean-docs/</id><summary type="html">&lt;p class="preamble"&gt;I spoke about this at &lt;a href="http://2012.eurucamp.org/"&gt;Eurucamp 2012&lt;/a&gt; in&amp;nbsp;Berlin.&lt;/p&gt;
&lt;p&gt;My goal here is not to give you a step by step guide to writing a readme. Instead, I want give you a few ideas to think about the next time you consider writing documentation or actually end up doing&amp;nbsp;it …&lt;/p&gt;</summary><content type="html">&lt;p class="preamble"&gt;I spoke about this at &lt;a href="http://2012.eurucamp.org/"&gt;Eurucamp 2012&lt;/a&gt; in&amp;nbsp;Berlin.&lt;/p&gt;
&lt;p&gt;My goal here is not to give you a step by step guide to writing a readme. Instead, I want give you a few ideas to think about the next time you consider writing documentation or actually end up doing&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;&lt;img alt="presentation slide" class="outlined" src="https://chrisberkhout.com/blog/clean-docs/001.png"&gt;&lt;/p&gt;
&lt;p&gt;What do I mean when I say Clean Docs? There&amp;#8217;s an explanation of clean code in &lt;a href="http://en.wikipedia.org/wiki/Robert_Cecil_Martin"&gt;Uncle Bob&amp;#8217;s&lt;/a&gt; &lt;a href="http://www.goodreads.com/book/show/3735293-clean-code"&gt;Clean Code&lt;/a&gt; book, but even if you haven&amp;#8217;t read that, I&amp;#8217;m sure you can tell the difference between code that&amp;#8217;s clean and code that&amp;#8217;s dirty. We have rules in our heads about that and alarm bells go off when we start writing code that&amp;#8217;s dirty and going to cost us later on. I want to encourage you to develop that same kind of awareness and discipline for when you&amp;#8217;re writing&amp;nbsp;documentation.&lt;/p&gt;
&lt;p&gt;&lt;img alt="presentation slide" src="https://chrisberkhout.com/blog/clean-docs/002.jpg"&gt;&lt;/p&gt;
&lt;p&gt;One of the &lt;a href="http://agilemanifesto.org/"&gt;Agile principles&lt;/a&gt; states: &amp;#8216;We have come to value working software over comprehensive documentation&amp;#8217;. When you read that it&amp;#8217;s easy to get a feeling of relief and think &amp;#8216;okay, great! I don&amp;#8217;t have to worry about documentation. I&amp;#8217;m just going to make software that works.&amp;#8217; But of course that&amp;#8217;s not quite what it&amp;#8217;s saying. It&amp;#8217;s actually saying that comprehensive documentation has value, but that working software has more. I agree. However, I believe this is referring largly to project documentation - things like requirements documents and design plans - and that&amp;#8217;s not what I&amp;#8217;m talking about here. Also, working software &lt;em&gt;is&lt;/em&gt; good but I suggest we aim even higher and create usable software. Documentation can help you achieve&amp;nbsp;that.&lt;/p&gt;
&lt;p&gt;&lt;img alt="presentation slide" class="outlined" src="https://chrisberkhout.com/blog/clean-docs/003.png"&gt;&lt;/p&gt;
&lt;p&gt;There are many kinds of documentation. One way to break them down is by proximity to code. We can start with the actual code itself, which can serve as a form of documentation if it is clear enough, and move through technical documentation, project websites and general documentation, all the way to published books. What I have to say about Clean Docs applies to the forms of documentation that are closer to the code. The readme is right in the middle of that range but the rest is relevan as&amp;nbsp;well.&lt;/p&gt;
&lt;p&gt;&lt;img alt="presentation slide" class="outlined" src="https://chrisberkhout.com/blog/clean-docs/004.png"&gt;&lt;/p&gt;
&lt;p&gt;It has been said that &amp;#8216;good code is its own best documentation&amp;#8217;. That&amp;#8217;s very true in a sense, but unfortunately it&amp;#8217;s become the biggest excuse for developers to completely ignore documentation. Without digging too deep - just by rereading that statement - we can see that code being its own best documentation is dependent on the code being good. Beyond that immediate observation I would add that the relevance of code as documentation depends on what the user is trying to get out of it. Even the best code will have a hard time explaining the purpose and scope of your project in the first few seconds that a new user looks at it, but that&amp;#8217;s easily acheivable in a couple of sentences of&amp;nbsp;English.&lt;/p&gt;
&lt;p&gt;&lt;img alt="presentation slide" class="outlined" src="https://chrisberkhout.com/blog/clean-docs/005.png"&gt;&lt;/p&gt;
&lt;p&gt;The users (including the maintainers) of your documentation will vary in their proximity to the code and their proximity to the present time. You should consider users across this whole plane when writing documentation. Writing more will help users unfamiliar with the code today, but require more work in maintenance later. Writing less will save on writing time, but may make it harder to maintain a clear scope, attract new users, and remember what the project is all about two years from now. Think about who your documentation is serving, and who is it costing, and&amp;nbsp;when.&lt;/p&gt;
&lt;p&gt;&lt;img alt="presentation slide" src="https://chrisberkhout.com/blog/clean-docs/006.png"&gt;&lt;/p&gt;
&lt;p&gt;Without too much rewording, the principles of clean code listed above also all apply to&amp;nbsp;documentation.&lt;/p&gt;
&lt;p&gt;&lt;img alt="presentation slide" src="https://chrisberkhout.com/blog/clean-docs/007.png"&gt;&lt;/p&gt;
&lt;p&gt;Take particular notice of the last point. A lot of people facing the prospect of writing documentation fear that it&amp;#8217;s going to take a long time and it&amp;#8217;s going to be difficult to maintain. The more you write, the more you really have to fear. So, minimise&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;&lt;img alt="presentation slide" class="outlined" src="https://chrisberkhout.com/blog/clean-docs/008.png"&gt;&lt;/p&gt;
&lt;p&gt;You can minimise what you write by referencing things rather than reproducing them. For example, if your &lt;span class="caps"&gt;API&lt;/span&gt; documentation contains an explanation of some concept that readers of your readme will need in order to understand discussion there, just refer them to it, rather than reproducing the explanation in a second&amp;nbsp;location.&lt;/p&gt;
&lt;p&gt;&lt;img alt="presentation slide" class="outlined" src="https://chrisberkhout.com/blog/clean-docs/009.png"&gt;&lt;/p&gt;
&lt;p&gt;What is brittle documentation? It&amp;#8217;s the same a brittle code, it&amp;#8217;s something that&amp;#8217;s going to break as soon as you touch&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;For example, I had some code in which we had an &lt;span class="caps"&gt;API&lt;/span&gt; key. Let&amp;#8217;s put aside the fact that it probably would have been better as configuration than as code. The immediate problem I had was that there had been confusion about whether this was a company key or a team key. I clarified that for myself and then I wanted to make it clear to future readers of the&amp;nbsp;code.&lt;/p&gt;
&lt;p&gt;I thought that I could use a more explicit variable name or alternatively I could put in a short comment that would document what the key was, but I realised that both of these solutions would be quite brittle. Someone would one day come along and change the &lt;span class="caps"&gt;API&lt;/span&gt; key. They probably wouldn&amp;#8217;t update the comment and they almost certainly wouldn&amp;#8217;t update a variable&amp;nbsp;name.&lt;/p&gt;
&lt;p&gt;&lt;img alt="presentation slide" class="outlined" src="https://chrisberkhout.com/blog/clean-docs/010.png"&gt;&lt;/p&gt;
&lt;p&gt;A better solution was to come up with a form of documentation that would automatically expire if careless changes were made. In the last line above I&amp;#8217;m saying that they key is &lt;code&gt;cde0fc8a5c2b1ecc&lt;/code&gt;, and the key starting &lt;code&gt;cde0&lt;/code&gt; is the company key. That explains the situation to readers today and if the key is carelessly changed at some point, future readers will not be misled by the&amp;nbsp;comment.&lt;/p&gt;
&lt;p&gt;That code comment example is a small one, but the same principle can be used throughout your documentation. Try to write things that will tolerant of careless changes; that will make sense for as long as possible and that will be obviously irrelevant if things change in the&amp;nbsp;future.&lt;/p&gt;
&lt;p&gt;&lt;img alt="presentation slide" class="outlined" src="https://chrisberkhout.com/blog/clean-docs/011.png"&gt;&lt;/p&gt;
&lt;p&gt;The first way to test your documentation is to test it against your code. Most of the time that just means cutting and pasting your code examples into &lt;span class="caps"&gt;IRB&lt;/span&gt; or another &lt;span class="caps"&gt;REPL&lt;/span&gt; and checking that you&amp;#8217;ve got your syntax correct and that the examples run against the current version of your software. Sometimes it will be appropriate to automate that. If you&amp;#8217;re writing a book, for example, you might write a script to pull lines of code out of executable files and insert them into your readable documentation. But manual checks are good enough most of the&amp;nbsp;time.&lt;/p&gt;
&lt;p&gt;You can also test against readers. A teammate will be able to read your writing and give you feedback, but the best person to check with is someone who doesn&amp;#8217;t already know the information you&amp;#8217;re trying to convey. Take a fresh person and see if they can understand you project by reading the&amp;nbsp;documentation.&lt;/p&gt;
&lt;p&gt;&lt;img alt="presentation slide" class="outlined" src="https://chrisberkhout.com/blog/clean-docs/012.png"&gt;&lt;/p&gt;
&lt;p&gt;When we think about good documentation several examples come to mind. We&amp;#8217;re generally pretty lucky in the Ruby world. The &lt;a href="http://www.sinatrarb.com/intro"&gt;Sinatra readme&lt;/a&gt; is a fine example. Several people have told me they really love it and I know why. It&amp;#8217;s well structured, it&amp;#8217;s easy to read, it&amp;#8217;s got appropriate code examples, you can easily search within the page, it links to external resources&amp;#8230; it&amp;#8217;s great! A lot of people use Sinatra, so it&amp;#8217;s obvious that all of the effort that has been put into building up its comprehensive documentation is&amp;nbsp;worthwhile.&lt;/p&gt;
&lt;p&gt;But what about those projects that don&amp;#8217;t seem worth documentation? Maybe it&amp;#8217;s something that you hacked up on a weekend. You&amp;#8217;re the only person who will ever use it and only a handful of other people might ever look at it. Even those projects deserve a&amp;nbsp;readme.&lt;/p&gt;
&lt;p&gt;&lt;img alt="presentation slide" class="outlined" src="https://chrisberkhout.com/blog/clean-docs/013.png"&gt;&lt;/p&gt;
&lt;p&gt;There are some obvious things to put in: what it is, who you are and how to get started. To those I would add that you should be linking to whatever &lt;em&gt;other&lt;/em&gt; things there are. If your project is at the point where there are additional resources that are relevant then link to them. In the example above I&amp;#8217;ve linked to a blog post that explains what that demo app&amp;nbsp;is.&lt;/p&gt;
&lt;p&gt;Also consider adding a status section. That can be just one word, or up to a few sentences. It might just be something like &amp;#8216;broken&amp;#8217; or &amp;#8216;unmaintained&amp;#8217; or &amp;#8216;in development&amp;#8217; but it could be a bit longer and say &amp;#8216;this is a spike I did to learn X. It is retained only for reference.&amp;#8217; Having a status section helps new people looking at the project, but it also offers the benefit of taking unpolished code that&amp;#8217;s not fit for public consumption and making it respectable, with just a few words. Also, it will make it easier for you to come back and pick up the project again if you decide to do something with it in the&amp;nbsp;future.&lt;/p&gt;
&lt;p&gt;&lt;img alt="presentation slide" class="outlined" src="https://chrisberkhout.com/blog/clean-docs/014.png"&gt;&lt;/p&gt;
&lt;p&gt;The idea of readme driven development is that you write your readme before your code. That gives you an early chance to think about your software from the user&amp;#8217;s perspective, and potentially, to discuss it. Of course, you continue to develop your readme and your other documentation as the implementation code&amp;nbsp;grows.&lt;/p&gt;
&lt;p&gt;Whether you write your readme before or after your code, you should definitely take the opportunity that documentation production and maintenance offers to re-examine your project; to refresh your view of how things fit together and what might come&amp;nbsp;next.&lt;/p&gt;
&lt;p&gt;&lt;img alt="presentation slide" class="outlined" src="https://chrisberkhout.com/blog/clean-docs/015.png"&gt;&lt;/p&gt;
&lt;p&gt;You documentation should have a single starting point. Usually it will be a readme, but sometimes it will something else, like a project website. Users should be able to start there and find everything that&amp;#8217;s relevant to the project. Don&amp;#8217;t make Google you starting point, because although it makes it easy to find individual resources, it&amp;#8217;s not good at communicating how they fit together and what the status is, which is exactly what your documentation should be&amp;nbsp;doing.&lt;/p&gt;
&lt;p&gt;&lt;img alt="presentation slide" class="outlined" src="https://chrisberkhout.com/blog/clean-docs/016.png"&gt;&lt;/p&gt;
&lt;p&gt;In summary: consider the needs of various readers and writers, both now and in the future. Have a single starting point for your documentation, link to everything from there, maintain that cohesive structure and use its maintenance as a chance to keep a fresh understanding of how things fit together. Cover the basics, usually in a readme and always as briefly as possible, and keep it clean, like you would your&amp;nbsp;code.&lt;/p&gt;</content><category term="misc"/></entry><entry><title>Isolated subclass specs in Ruby</title><link href="https://chrisberkhout.com/blog/isolated-subclass-specs-1/" rel="alternate"/><published>2012-07-01T00:00:00+00:00</published><updated>2012-07-01T00:00:00+00:00</updated><author><name>Chris Berkhout</name></author><id>tag:chrisberkhout.com,2012-07-01:/blog/isolated-subclass-specs-1/</id><summary type="html">&lt;p&gt;Unit tests should isolate their subject, but tests of subclass functionality usually test the class as a whole. One reason for this is that in Ruby there is no way to dynamically change the superclass, but more importantly, it&amp;#8217;s because you don&amp;#8217;t want to modify the thing you …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Unit tests should isolate their subject, but tests of subclass functionality usually test the class as a whole. One reason for this is that in Ruby there is no way to dynamically change the superclass, but more importantly, it&amp;#8217;s because you don&amp;#8217;t want to modify the thing you&amp;#8217;re testing. Sometimes however, isolating a subclass from its parent makes&amp;nbsp;sense.&lt;/p&gt;
&lt;p&gt;Schemabot, our tool for applying database changes in a replication friendly way, is based on the &lt;a href="http://sequel.rubyforge.org/"&gt;Sequel&lt;/a&gt; gem and its migration &lt;span class="caps"&gt;DSL&lt;/span&gt;. We added a preview function by subclassing the MySQL adapter and having it ignore any &lt;span class="caps"&gt;SQL&lt;/span&gt; that would change the&amp;nbsp;database.&lt;/p&gt;
&lt;p&gt;A simplified version of our implementation (allowing just &lt;code&gt;SELECT&lt;/code&gt; and &lt;code&gt;DESCRIBE&lt;/code&gt; queries) looks like&amp;nbsp;this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="n"&gt;Sequel&lt;/span&gt;
  &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="n"&gt;MySQLPreview&lt;/span&gt;
    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="n"&gt;Database&lt;/span&gt; &amp;lt; &lt;span class="n"&gt;Sequel::MySQL::Database&lt;/span&gt;

      &lt;span class="n"&gt;def&lt;/span&gt; &lt;span class="n"&gt;execute&lt;/span&gt;(&lt;span class="n"&gt;sql&lt;/span&gt;, &lt;span class="n"&gt;opts&lt;/span&gt;={})
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;sql&lt;/span&gt;.&lt;span class="nb"&gt;match&lt;/span&gt;(&lt;span class="sr"&gt;/^SELECT/&lt;/span&gt;) || &lt;span class="n"&gt;sql&lt;/span&gt;.&lt;span class="nb"&gt;match&lt;/span&gt;(&lt;span class="sr"&gt;/^DESCRIBE/&lt;/span&gt;)
          &lt;span class="n"&gt;puts&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;RUNNING: #{sql}&amp;quot;&lt;/span&gt;
          &lt;span class="n"&gt;super&lt;/span&gt;(&lt;span class="n"&gt;sql&lt;/span&gt;, &lt;span class="n"&gt;opts&lt;/span&gt;)
        &lt;span class="k"&gt;else&lt;/span&gt;
          &lt;span class="n"&gt;puts&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;PREVIEW: #{sql}&amp;quot;&lt;/span&gt;
        &lt;span class="nb"&gt;end&lt;/span&gt;
      &lt;span class="nb"&gt;end&lt;/span&gt;

    &lt;span class="nb"&gt;end&lt;/span&gt;
  &lt;span class="nb"&gt;end&lt;/span&gt;
&lt;span class="nb"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Satisfied with the assumption that all queries go through &lt;code&gt;#execute&lt;/code&gt; and the fact that Sequel itself is already well tested, we wanted to add a set of specs that would simply validate that our &lt;code&gt;#execute&lt;/code&gt; method passed queries on to the superclass or ignored them as&amp;nbsp;appropriate.&lt;/p&gt;
&lt;h2&gt;Set an expectation on #execute or on&amp;nbsp;#super?&lt;/h2&gt;
&lt;p&gt;Our &lt;code&gt;#execute&lt;/code&gt; calls the &lt;code&gt;#execute&lt;/code&gt; in the superclass, and that action is what we want to set an expection on. RSpec lets us set message expectations on a class (&lt;code&gt;ClassName.should_receive&lt;/code&gt;), on an instance (&lt;code&gt;ClassName.new.should_receive&lt;/code&gt;), or all instances (&lt;code&gt;ClassName.any_instance.should_receive&lt;/code&gt;), but the &lt;code&gt;#execute&lt;/code&gt; we care about is an instance method on the superclass. It&amp;#8217;s not a class method, and it&amp;#8217;s not a method on any regular&amp;nbsp;instance.&lt;/p&gt;
&lt;p&gt;Instead of checking calls to the superclass&amp;#8217;s &lt;code&gt;#execute&lt;/code&gt;, it would be nice to set expectation on the call to &lt;code&gt;super&lt;/code&gt;. Although Ruby language features are often implemented as methods (&lt;code&gt;require&lt;/code&gt;, &lt;code&gt;method_missing&lt;/code&gt;, etc.) &lt;code&gt;super&lt;/code&gt; is actually a keyword. It is possible to create a method named &lt;code&gt;super&lt;/code&gt;, but mocking such a method would be no help, as it would remain completely separate from the &lt;code&gt;super&lt;/code&gt; keyword, and require a different, more explicit syntax to&amp;nbsp;invoke.&lt;/p&gt;
&lt;p&gt;RSpec&amp;#8217;s David Chelimsky has &lt;a href="http://rubyforge.org/pipermail/rspec-users/2009-May/014656.html"&gt;commented&lt;/a&gt; on this&amp;nbsp;issue:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Since we don&amp;#8217;t get into the class hierarchy in rspec&amp;#8217;s mock/stub framework, there is no facility for managing&amp;nbsp;this.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So, it&amp;#8217;s up to us to intercept the call if we want to verify that the subclass is behaving&amp;nbsp;correctly.&lt;/p&gt;
&lt;h2&gt;Sneak a module into the inheritance&amp;nbsp;chain&lt;/h2&gt;
&lt;p&gt;Somehow we need to get some in between the subclass and its superclass. Including a module in a class will not modify its superclass (which can&amp;#8217;t be changed), but the module will be inserted into the class&amp;#8217;s list of ancestors, and be able to intercept &lt;code&gt;super&lt;/code&gt; before the actual superclass is&amp;nbsp;reached.&lt;/p&gt;
&lt;p&gt;To demonstrate this, let&amp;#8217;s start with a simple parent class and subclass, both implementing &lt;code&gt;#execute&lt;/code&gt; methods:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="n"&gt;ParentClass&lt;/span&gt;
  &lt;span class="n"&gt;def&lt;/span&gt; &lt;span class="n"&gt;execute&lt;/span&gt;
    &lt;span class="n"&gt;puts&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;ParentClass#execute&amp;quot;&lt;/span&gt;
  &lt;span class="nb"&gt;end&lt;/span&gt;
&lt;span class="nb"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="n"&gt;SubClass&lt;/span&gt; &amp;lt; &lt;span class="n"&gt;ParentClass&lt;/span&gt;
  &lt;span class="n"&gt;def&lt;/span&gt; &lt;span class="n"&gt;execute&lt;/span&gt;
    &lt;span class="n"&gt;puts&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;SubClass#execute&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;super&lt;/span&gt;
  &lt;span class="nb"&gt;end&lt;/span&gt;
&lt;span class="nb"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The output of &lt;code&gt;SubClass.new.execute&lt;/code&gt; will&amp;nbsp;be:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;SubClass#execute
ParentClass#execute
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Including a module changes the ancestors&amp;nbsp;list:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="n"&gt;SneakyModule&lt;/span&gt;
  &lt;span class="n"&gt;def&lt;/span&gt; &lt;span class="n"&gt;execute&lt;/span&gt;
    &lt;span class="n"&gt;puts&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;SneakyModule#execute&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;super&lt;/span&gt;
  &lt;span class="nb"&gt;end&lt;/span&gt;
&lt;span class="nb"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;SubClass&lt;/span&gt;.&lt;span class="n"&gt;ancestors&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; [SubClass, ParentClass, Object, Kernel, BasicObject]&lt;/span&gt;
&lt;span class="n"&gt;SubClass&lt;/span&gt;.&lt;span class="n"&gt;instance_eval&lt;/span&gt; { &lt;span class="n"&gt;include&lt;/span&gt; &lt;span class="n"&gt;SneakyModule&lt;/span&gt; }
&lt;span class="n"&gt;SubClass&lt;/span&gt;.&lt;span class="n"&gt;ancestors&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; [SubClass, SneakyModule, ParentClass, Object, Kernel, BasicObject]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And &lt;code&gt;SubClass.new.execute&lt;/code&gt; shows the sequence of execution to&amp;nbsp;be:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;SubClass#execute
SneakyModule#execute
ParentClass#execute
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This technique allows us to programatically verify the behaviour of the subclass&amp;#8217;s &lt;code&gt;#execute&lt;/code&gt; method by having the sneaky module in the middle record and report on method calls it intercepts. Here&amp;#8217;s an example&amp;nbsp;implementation:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="n"&gt;FakeMySQLAdapter&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &amp;lt;&amp;lt; &lt;span class="nb"&gt;self&lt;/span&gt;
    &lt;span class="n"&gt;attr_accessor&lt;/span&gt; :&lt;span class="n"&gt;last_execute&lt;/span&gt;
  &lt;span class="nb"&gt;end&lt;/span&gt;
  &lt;span class="n"&gt;def&lt;/span&gt; &lt;span class="n"&gt;execute&lt;/span&gt;(*&lt;span class="nb"&gt;args&lt;/span&gt;)
    &lt;span class="n"&gt;FakeMySQLAdapter&lt;/span&gt;.&lt;span class="n"&gt;last_execute&lt;/span&gt; = &lt;span class="nb"&gt;args&lt;/span&gt;
  &lt;span class="nb"&gt;end&lt;/span&gt;
&lt;span class="nb"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;With this done, we can write an example to verify that &lt;code&gt;super&lt;/code&gt; is being invoked&amp;nbsp;appropriately:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nv"&gt;before&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;:&lt;span class="nv"&gt;all&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;{&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;subject&lt;/span&gt;.&lt;span class="nv"&gt;class&lt;/span&gt;.&lt;span class="nv"&gt;instance_eval&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;{&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;FakeMySQLAdapter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;}&lt;span class="w"&gt; &lt;/span&gt;}&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="nv"&gt;before&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;:&lt;span class="nv"&gt;each&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;{&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;FakeMySQLAdapter&lt;/span&gt;.&lt;span class="nv"&gt;last_execute&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;}&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="nv"&gt;it&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;should pass on SELECT statements&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;subject&lt;/span&gt;.&lt;span class="nv"&gt;execute&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;SELECT * FROM foo;&amp;quot;&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;FakeMySQLAdapter&lt;/span&gt;.&lt;span class="nv"&gt;last_execute&lt;/span&gt;.&lt;span class="nv"&gt;should&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;[&lt;span class="s2"&gt;&amp;quot;SELECT * FROM foo;&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;{}]&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You can see a full implemenation of this &lt;a href="https://github.com/chrisberkhout/isolated_subclass_spec/blob/master/spec/module_spec.rb"&gt;on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This is a solution. It exercises the logic of our subclass&amp;#8217;s &lt;code&gt;#execute&lt;/code&gt; and verifies its query handling. It&amp;#8217;s not a great solution. One problem is that this code modifies the subclass being tested (by including the module), and another is that the subclass stays modified. It&amp;#8217;s not possible to uninclude the module. If we had other tests that relied on the subclass, running the subclass&amp;#8217;s specs first might break&amp;nbsp;others.&lt;/p&gt;
&lt;p&gt;There is a cleaner way, as we&amp;#8217;ll see&amp;nbsp;next.&lt;/p&gt;
&lt;h2&gt;Extract a module with the method&amp;nbsp;override&lt;/h2&gt;
&lt;p&gt;Rather than overriding &lt;code&gt;#execute&lt;/code&gt; in the subclass directly, we can override it by including a module that implements &lt;code&gt;#execute&lt;/code&gt;. This structure allows us to include the logic of interest in a dummy class to test it in&amp;nbsp;isolation.&lt;/p&gt;
&lt;p&gt;First, we refactor our preview adapter to the&amp;nbsp;following:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="n"&gt;Sequel&lt;/span&gt;
  &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="n"&gt;PreviewMySQL&lt;/span&gt;

    &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="n"&gt;MethodOverrides&lt;/span&gt;
      &lt;span class="n"&gt;def&lt;/span&gt; &lt;span class="n"&gt;execute&lt;/span&gt;(&lt;span class="n"&gt;sql&lt;/span&gt;, &lt;span class="n"&gt;opts&lt;/span&gt;={})
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;sql&lt;/span&gt;.&lt;span class="nb"&gt;match&lt;/span&gt;(&lt;span class="sr"&gt;/^SELECT/&lt;/span&gt;) || &lt;span class="n"&gt;sql&lt;/span&gt;.&lt;span class="nb"&gt;match&lt;/span&gt;(&lt;span class="sr"&gt;/^DESCRIBE/&lt;/span&gt;)
          &lt;span class="n"&gt;puts&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;RUNNING: #{sql}&amp;quot;&lt;/span&gt;
          &lt;span class="n"&gt;super&lt;/span&gt;(&lt;span class="n"&gt;sql&lt;/span&gt;, &lt;span class="n"&gt;opts&lt;/span&gt;)
        &lt;span class="k"&gt;else&lt;/span&gt;
          &lt;span class="n"&gt;puts&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;PREVIEW: #{sql}&amp;quot;&lt;/span&gt;
        &lt;span class="nb"&gt;end&lt;/span&gt;
      &lt;span class="nb"&gt;end&lt;/span&gt;
    &lt;span class="nb"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="n"&gt;Database&lt;/span&gt; &amp;lt; &lt;span class="n"&gt;Sequel::MySQL::Database&lt;/span&gt;
      &lt;span class="n"&gt;include&lt;/span&gt; &lt;span class="n"&gt;MethodOverrides&lt;/span&gt;
    &lt;span class="nb"&gt;end&lt;/span&gt;

  &lt;span class="nb"&gt;end&lt;/span&gt;
&lt;span class="nb"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Before we test the module, let&amp;#8217;s write an assertion that &lt;code&gt;Database&lt;/code&gt; is actually using the&amp;nbsp;module:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nv"&gt;describe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Sequel&lt;/span&gt;::&lt;span class="nv"&gt;PreviewMySQL&lt;/span&gt;::&lt;span class="nv"&gt;Database&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;it&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;should include the override methods&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;subject&lt;/span&gt;.&lt;span class="nv"&gt;class&lt;/span&gt;.&lt;span class="nv"&gt;ancestors&lt;/span&gt;.&lt;span class="nv"&gt;should&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;include&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;Sequel&lt;/span&gt;::&lt;span class="nv"&gt;PreviewMySQL&lt;/span&gt;::&lt;span class="nv"&gt;MethodOverrides&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;After that, we can test the module. We start by creating a dummy superclass for the module to be included in. We&amp;#8217;ll use &lt;code&gt;method_missing&lt;/code&gt; to record all method calls passed up to the superclass by its&amp;nbsp;subclasses.&lt;/p&gt;
&lt;p&gt;By using &lt;code&gt;Class.new&lt;/code&gt; to create anonymous subclasses that include the module being tested, we can ensure that each example in our spec runs with a fresh copy of the&amp;nbsp;subject.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;describe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;Sequel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="nl"&gt;PreviewMySQL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="n"&gt;MethodOverrides&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;do&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;FakeSuperClass&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;attr_reader&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="n"&gt;last_super_call&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;method_missing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;@last_super_call&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;method, args&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;do&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;Class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FakeSuperClass&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;instance_eval&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;do&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;Sequel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="nl"&gt;PreviewMySQL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="n"&gt;MethodOverrides&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This solution is easier to read than the one in the previous post, and it avoids the problems of modifying the subject. Although we are now testing a module rather than the actual subclass we&amp;#8217;ll be using it in, our assertion that the subclass should include the module of override methods completes the loop and will automatically warn us if we make changes to our hookup&amp;nbsp;code.&lt;/p&gt;
&lt;p&gt;With this in place, our individual examples are clean and simple, as&amp;nbsp;follows:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nv"&gt;it&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;should allow SELECTs&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;sql&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;SELECT * FROM sometable&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;subject&lt;/span&gt;.&lt;span class="nv"&gt;execute&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;sql&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;subject&lt;/span&gt;.&lt;span class="nv"&gt;last_super_call&lt;/span&gt;.&lt;span class="nv"&gt;should&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;[:&lt;span class="nv"&gt;execute&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;[&lt;span class="nv"&gt;sql&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;{}]]&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You can see a full implemenation of this &lt;a href="https://github.com/chrisberkhout/isolated_subclass_spec/blob/master/spec/iso_module_spec.rb"&gt;on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This is a clean solution, and one that I&amp;#8217;d have been satisfied with if I&amp;#8217;d come up with it first. However, the extra time on this got me thinking&amp;#8230; it really shouldn&amp;#8217;t be that hard to stub out irrelevant and troublemaking parts of the full&amp;nbsp;class.&lt;/p&gt;
&lt;h2&gt;Test the full class and stub problematic&amp;nbsp;parts&lt;/h2&gt;
&lt;p&gt;Testing the full class really shouldn&amp;#8217;t be hard and RSpec should point out any code that gets in the way. I gave it another&amp;nbsp;go.&lt;/p&gt;
&lt;p&gt;First, I reverted to the original implementation of the&amp;nbsp;subclass:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="n"&gt;Sequel&lt;/span&gt;
  &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="n"&gt;MySQLPreview&lt;/span&gt;
    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="n"&gt;Database&lt;/span&gt; &amp;lt; &lt;span class="n"&gt;Sequel::MySQL::Database&lt;/span&gt;

      &lt;span class="n"&gt;def&lt;/span&gt; &lt;span class="n"&gt;execute&lt;/span&gt;(&lt;span class="n"&gt;sql&lt;/span&gt;, &lt;span class="n"&gt;opts&lt;/span&gt;={})
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;sql&lt;/span&gt;.&lt;span class="nb"&gt;match&lt;/span&gt;(&lt;span class="sr"&gt;/^SELECT/&lt;/span&gt;) || &lt;span class="n"&gt;sql&lt;/span&gt;.&lt;span class="nb"&gt;match&lt;/span&gt;(&lt;span class="sr"&gt;/^DESCRIBE/&lt;/span&gt;)
          &lt;span class="n"&gt;puts&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;RUNNING: #{sql}&amp;quot;&lt;/span&gt;
          &lt;span class="n"&gt;super&lt;/span&gt;(&lt;span class="n"&gt;sql&lt;/span&gt;, &lt;span class="n"&gt;opts&lt;/span&gt;)
        &lt;span class="k"&gt;else&lt;/span&gt;
          &lt;span class="n"&gt;puts&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;PREVIEW: #{sql}&amp;quot;&lt;/span&gt;
        &lt;span class="nb"&gt;end&lt;/span&gt;
      &lt;span class="nb"&gt;end&lt;/span&gt;

    &lt;span class="nb"&gt;end&lt;/span&gt;
  &lt;span class="nb"&gt;end&lt;/span&gt;
&lt;span class="nb"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And started with a simple&amp;nbsp;spec:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nv"&gt;describe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Sequel&lt;/span&gt;::&lt;span class="nv"&gt;MySQLPreview&lt;/span&gt;::&lt;span class="nv"&gt;Database&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;it&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;should allow SELECTs&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;sql&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;SELECT * FROM sometable&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;subject&lt;/span&gt;.&lt;span class="nv"&gt;should_receive&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;:&lt;span class="nv"&gt;puts&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;.&lt;span class="nv"&gt;with&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="o"&gt;/^&lt;/span&gt;&lt;span class="nv"&gt;RUNNING&lt;/span&gt;:&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;subject&lt;/span&gt;.&lt;span class="nv"&gt;execute&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;sql&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The assertion here is on &lt;code&gt;#puts&lt;/code&gt; rather than the superclass&amp;#8217;s &lt;code&gt;#execute&lt;/code&gt; method, but we&amp;#8217;ll add more comprehensive assertions&amp;nbsp;later.&lt;/p&gt;
&lt;p&gt;That example raised an error with a rather brief&amp;nbsp;backtrace:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Sequel&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;MySQLPreview&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="kd"&gt;Data&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;should&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;allow&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SELECTs&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;Failure&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;Sequel&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="kd"&gt;Data&lt;/span&gt;&lt;span class="n"&gt;baseConnectionError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="n"&gt;Mysql&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Can&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;connect&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;MySQL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;through&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;tmp&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;mysql&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="n"&gt;ernal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;prelude&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;10&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="n"&gt;synchronize&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;whole_spec&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;11&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;whole_spec&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;30&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;levels&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;to&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;RSpec tries to show you just the parts of the full backtrace that are relevant to the code you&amp;#8217;re testing, but you can tell it to be more verbose by running it with the &lt;code&gt;--backtrace&lt;/code&gt; option. That got me&amp;nbsp;this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Sequel&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;MySQLPreview&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="kd"&gt;Data&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;should&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;allow&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SELECTs&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;Failure&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;Sequel&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="kd"&gt;Data&lt;/span&gt;&lt;span class="n"&gt;baseConnectionError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="n"&gt;Mysql&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Can&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;connect&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;MySQL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;through&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;tmp&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;mysql&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;chrisberkhout&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rvm&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gems&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;ruby&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.9.2&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;p290&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;patched&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gems&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;sequel&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;3.35.0&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;sequel&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;adapters&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;mysql&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;110&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="n"&gt;real_connect&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;chrisberkhout&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rvm&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gems&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;ruby&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.9.2&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;p290&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;patched&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gems&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;sequel&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;3.35.0&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;sequel&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;adapters&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;mysql&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;110&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="n"&gt;connect&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;chrisberkhout&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rvm&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gems&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;ruby&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.9.2&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;p290&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;patched&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gems&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;sequel&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;3.35.0&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;sequel&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="kd"&gt;data&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;misc&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;48&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;initialize&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;chrisberkhout&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rvm&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gems&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;ruby&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.9.2&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;p290&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;patched&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gems&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;sequel&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;3.35.0&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;sequel&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;connection_pool&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;100&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;chrisberkhout&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rvm&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gems&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;ruby&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.9.2&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;p290&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;patched&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gems&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;sequel&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;3.35.0&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;sequel&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;connection_pool&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;100&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="n"&gt;make_new&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;chrisberkhout&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rvm&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gems&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;ruby&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.9.2&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;p290&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;patched&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gems&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;sequel&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;3.35.0&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;sequel&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;connection_pool&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;threaded&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;144&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="n"&gt;make_new&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;chrisberkhout&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rvm&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gems&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;ruby&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.9.2&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;p290&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;patched&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gems&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;sequel&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;3.35.0&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;sequel&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;connection_pool&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;threaded&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;130&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="n"&gt;available&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;chrisberkhout&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rvm&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gems&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;ruby&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.9.2&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;p290&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;patched&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gems&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;sequel&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;3.35.0&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;sequel&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;connection_pool&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;threaded&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;120&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;acquire&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;chrisberkhout&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rvm&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gems&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;ruby&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.9.2&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;p290&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;patched&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gems&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;sequel&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;3.35.0&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;sequel&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;connection_pool&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;threaded&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;162&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sync&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="n"&gt;ernal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;prelude&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;10&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="n"&gt;synchronize&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;chrisberkhout&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rvm&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gems&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;ruby&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.9.2&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;p290&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;patched&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gems&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;sequel&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;3.35.0&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;sequel&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;connection_pool&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;threaded&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;162&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="n"&gt;sync&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;chrisberkhout&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rvm&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gems&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;ruby&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.9.2&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;p290&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;patched&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gems&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;sequel&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;3.35.0&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;sequel&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;connection_pool&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;threaded&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;119&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="n"&gt;acquire&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;chrisberkhout&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rvm&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gems&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;ruby&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.9.2&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;p290&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;patched&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gems&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;sequel&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;3.35.0&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;sequel&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;connection_pool&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;threaded&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;91&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="n"&gt;hold&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;chrisberkhout&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rvm&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gems&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;ruby&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.9.2&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;p290&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;patched&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gems&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;sequel&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;3.35.0&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;sequel&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="kd"&gt;data&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;connecting&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;229&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="n"&gt;synchronize&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;chrisberkhout&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rvm&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gems&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;ruby&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.9.2&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;p290&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;patched&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gems&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;sequel&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;3.35.0&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;sequel&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;adapters&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;mysql_prepared_statements&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;23&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;whole_spec&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;11&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;whole_spec&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;30&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;levels&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;to&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;chrisberkhout&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rvm&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gems&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;ruby&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.9.2&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;p290&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;patched&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gems&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;rspec&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2.10.1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;rspec&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;87&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="n"&gt;instance_eval&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;chrisberkhout&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rvm&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gems&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;ruby&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.9.2&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;p290&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;patched&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gems&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;rspec&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2.10.1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;rspec&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;87&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;run&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;chrisberkhout&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rvm&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gems&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;ruby&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.9.2&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;p290&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;patched&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gems&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;rspec&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2.10.1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;rspec&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;195&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="n"&gt;with_around_each_hooks&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;chrisberkhout&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rvm&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gems&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;ruby&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.9.2&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;p290&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;patched&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gems&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;rspec&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2.10.1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;rspec&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;84&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="kr"&gt;run&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;chrisberkhout&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rvm&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gems&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;ruby&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.9.2&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;p290&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;patched&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gems&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;rspec&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2.10.1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;rspec&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;example_group&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;353&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;run&lt;/span&gt;&lt;span class="n"&gt;_examples&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;chrisberkhout&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rvm&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gems&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;ruby&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.9.2&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;p290&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;patched&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gems&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;rspec&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2.10.1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;rspec&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;example_group&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;349&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;chrisberkhout&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rvm&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gems&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;ruby&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.9.2&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;p290&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;patched&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gems&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;rspec&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2.10.1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;rspec&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;example_group&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;349&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="kr"&gt;run&lt;/span&gt;&lt;span class="n"&gt;_examples&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;chrisberkhout&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rvm&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gems&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;ruby&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.9.2&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;p290&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;patched&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gems&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;rspec&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2.10.1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;rspec&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;example_group&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;335&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="kr"&gt;run&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;chrisberkhout&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rvm&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gems&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;ruby&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.9.2&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;p290&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;patched&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gems&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;rspec&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2.10.1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;rspec&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;command_line&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;28&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;levels&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;run&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;chrisberkhout&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rvm&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gems&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;ruby&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.9.2&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;p290&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;patched&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gems&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;rspec&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2.10.1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;rspec&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;command_line&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;28&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;chrisberkhout&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rvm&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gems&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;ruby&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.9.2&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;p290&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;patched&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gems&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;rspec&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2.10.1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;rspec&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;command_line&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;28&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;run&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;chrisberkhout&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rvm&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gems&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;ruby&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.9.2&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;p290&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;patched&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gems&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;rspec&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2.10.1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;rspec&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;reporter&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;34&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="n"&gt;report&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;chrisberkhout&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rvm&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gems&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;ruby&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.9.2&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;p290&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;patched&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gems&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;rspec&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2.10.1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;rspec&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;command_line&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;25&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="kr"&gt;run&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;chrisberkhout&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rvm&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gems&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;ruby&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.9.2&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;p290&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;patched&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gems&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;rspec&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2.10.1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;rspec&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="kr"&gt;run&lt;/span&gt;&lt;span class="n"&gt;ner&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;69&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="kr"&gt;run&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;chrisberkhout&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rvm&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gems&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;ruby&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.9.2&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;p290&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;patched&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gems&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;rspec&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2.10.1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;rspec&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="kr"&gt;run&lt;/span&gt;&lt;span class="n"&gt;ner&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;10&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;autorun&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I could see that a &lt;code&gt;#synchronize&lt;/code&gt; method was responsible for acquiring a connection to the database. This was the method I needed to stub to to isolate my test from an actual database&amp;nbsp;instance.&lt;/p&gt;
&lt;p&gt;Following through the execution path showed that the superclass&amp;#8217;s &lt;code&gt;#execute&lt;/code&gt; would eventually call &lt;code&gt;#_execute&lt;/code&gt; to finish its work. Setting an expectation on this method would allow me to test whether or not the &lt;span class="caps"&gt;SQL&lt;/span&gt; query was in fact being passed up to the superclass for&amp;nbsp;execution.&lt;/p&gt;
&lt;p&gt;Incorporating these two points brought me to the final form of my&amp;nbsp;specs:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nv"&gt;describe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Sequel&lt;/span&gt;::&lt;span class="nv"&gt;MySQLPreview&lt;/span&gt;::&lt;span class="nv"&gt;Database&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;let&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;:&lt;span class="nv"&gt;connection&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;{&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;stub&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;connection&amp;quot;&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;}&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;before&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;{&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;subject&lt;/span&gt;.&lt;span class="nv"&gt;stub&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;:&lt;span class="nv"&gt;synchronize&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;.&lt;span class="nv"&gt;and_yield&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;connection&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;}&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;it&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;should allow SELECTs&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;sql&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;SELECT * FROM sometable&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;subject&lt;/span&gt;.&lt;span class="nv"&gt;should_receive&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;:&lt;span class="nv"&gt;puts&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;.&lt;span class="nv"&gt;with&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="o"&gt;/^&lt;/span&gt;&lt;span class="nv"&gt;RUNNING&lt;/span&gt;:&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;subject&lt;/span&gt;.&lt;span class="nv"&gt;should_receive&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;:&lt;span class="nv"&gt;_execute&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;.&lt;span class="nv"&gt;with&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;connection&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;sql&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;anything&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;subject&lt;/span&gt;.&lt;span class="nv"&gt;execute&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;sql&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The two assertions here cover both the direct actions of the subclass, as well as its correct interaction with the&amp;nbsp;superclass.&lt;/p&gt;
&lt;p&gt;So, it turns out that (with &lt;code&gt;--backtrace&lt;/code&gt;) this was quite straightforward. You can see the full code &lt;a href="https://github.com/chrisberkhout/isolated_subclass_spec/blob/master/spec/whole_spec.rb"&gt;on&amp;nbsp;GitHub&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;In&amp;nbsp;conclusion&lt;/h2&gt;
&lt;p&gt;If I&amp;#8217;d gotten this full class spec working first, I probably would have stopped there. However, even though this tests the class as a whole - the normal way - it is not necessarily the best&amp;nbsp;approach.&lt;/p&gt;
&lt;p&gt;The downside here is that it requires stubbing the class under test (even though it&amp;#8217;s a part of the class we&amp;#8217;re not specifically interested in testing). The other thing is that it couples itself more tightly to the internal implementation of the superclass. It requires the &lt;code&gt;#execute&lt;/code&gt;, &lt;code&gt;#synchronize&lt;/code&gt; and &lt;code&gt;#_execute&lt;/code&gt; methods to behave in certain ways. It also places on the reader the burden of understanding the significance of the &lt;code&gt;#_execute&lt;/code&gt; expectation.&lt;/p&gt;
&lt;p&gt;The assumptions made by the isolated module spec are simpler: that all &lt;span class="caps"&gt;SQL&lt;/span&gt; goes via the &lt;code&gt;#execute&lt;/code&gt; method and that it can be stopped there without any problems. It&amp;#8217;s a smaller, cleaner set of assumptions, and there&amp;#8217;s no stubbing of the subject. I&amp;#8217;d say that extracting and testing a module in isolation is the best approach&amp;nbsp;here.&lt;/p&gt;</content><category term="misc"/></entry><entry><title>Local variables - the new global variables?</title><link href="https://chrisberkhout.com/blog/local-variables/" rel="alternate"/><published>2012-05-31T00:00:00+00:00</published><updated>2012-05-31T00:00:00+00:00</updated><author><name>Chris Berkhout</name></author><id>tag:chrisberkhout.com,2012-05-31:/blog/local-variables/</id><summary type="html">&lt;p class="preamble"&gt;The topic of tonight&amp;#8217;s &lt;a href="http://ruby.org.au/meetups/mel.html"&gt;Melbourne Ruby&lt;/a&gt; meetup was &amp;#8220;things I wish I&amp;#8217;d known earlier&amp;#8221;.&lt;br&gt;
This was my&amp;nbsp;contribution.&lt;/p&gt;
&lt;p&gt;&lt;img alt="presentation slide" src="https://chrisberkhout.com/blog/local-variables/001.png"&gt;&lt;/p&gt;
&lt;p&gt;Last year I read the &lt;a href="http://www.informit.com/store/product.aspx?isbn=0321603508"&gt;Refactoring book&lt;/a&gt; and it gave me some surprising new insights into local&amp;nbsp;variables.&lt;/p&gt;
&lt;p&gt;&lt;img alt="presentation slide" src="https://chrisberkhout.com/blog/local-variables/002.png"&gt;&lt;/p&gt;
&lt;p&gt;If you look through the contents of the book you&amp;#8217;ll quickly …&lt;/p&gt;</summary><content type="html">&lt;p class="preamble"&gt;The topic of tonight&amp;#8217;s &lt;a href="http://ruby.org.au/meetups/mel.html"&gt;Melbourne Ruby&lt;/a&gt; meetup was &amp;#8220;things I wish I&amp;#8217;d known earlier&amp;#8221;.&lt;br&gt;
This was my&amp;nbsp;contribution.&lt;/p&gt;
&lt;p&gt;&lt;img alt="presentation slide" src="https://chrisberkhout.com/blog/local-variables/001.png"&gt;&lt;/p&gt;
&lt;p&gt;Last year I read the &lt;a href="http://www.informit.com/store/product.aspx?isbn=0321603508"&gt;Refactoring book&lt;/a&gt; and it gave me some surprising new insights into local&amp;nbsp;variables.&lt;/p&gt;
&lt;p&gt;&lt;img alt="presentation slide" src="https://chrisberkhout.com/blog/local-variables/002.png"&gt;&lt;/p&gt;
&lt;p&gt;If you look through the contents of the book you&amp;#8217;ll quickly find the refactoring &amp;#8220;Introduce explaining&amp;nbsp;variable&amp;#8221;.&lt;/p&gt;
&lt;p&gt;Imagine you have a method that does some processing that is not particularly obvious. Here&amp;#8217;s an example that selects a certain number of evenly spaced elements from an&amp;nbsp;array:&lt;/p&gt;
&lt;p&gt;&lt;img alt="presentation slide" src="https://chrisberkhout.com/blog/local-variables/003.png"&gt;&lt;/p&gt;
&lt;p&gt;You can make it more readable by assigning an intermediate value to a well-named local variable, like&amp;nbsp;so:&lt;/p&gt;
&lt;p&gt;&lt;img alt="presentation slide" src="https://chrisberkhout.com/blog/local-variables/004.png"&gt;&lt;/p&gt;
&lt;p&gt;There are times when that will be a great help, and you should definitely perform such&amp;nbsp;refactorings.&lt;/p&gt;
&lt;p&gt;Tonight, however, I want to focus on the more general case, which is that local variables&amp;nbsp;stink.&lt;/p&gt;
&lt;p&gt;&lt;img alt="presentation slide" src="https://chrisberkhout.com/blog/local-variables/005.png"&gt;&lt;/p&gt;
&lt;p&gt;Local variables are bad because they are often introduced as a premature&amp;nbsp;optimisation.&lt;/p&gt;
&lt;p&gt;Take this &lt;code&gt;Library&lt;/code&gt; class for example, which can prepare a report on overdue books for the library detective. It uses the total number of overdue books twice, but it manages to only calculate it once, by storing the value in a local variable. The same is done with the total count of all&amp;nbsp;books.&lt;/p&gt;
&lt;p&gt;&lt;img alt="presentation slide" src="https://chrisberkhout.com/blog/local-variables/006.png"&gt;&lt;/p&gt;
&lt;p&gt;These are premature optimisations that don&amp;#8217;t aid readability or have any perceptible influence on performance, yet they do add additional lines of&amp;nbsp;code.&lt;/p&gt;
&lt;p&gt;They should be removed by repeating the calculation call wherever its result is&amp;nbsp;required.&lt;/p&gt;
&lt;p&gt;&lt;img alt="presentation slide" src="https://chrisberkhout.com/blog/local-variables/007.png"&gt;&lt;/p&gt;
&lt;p&gt;The example of counting elements in an array is perhaps trivial. The mistake is easier to make with other kinds of calculations. Data on disk or across a network may require further consideration, but when it is in memory you should assume that the results of sorting, arithmetic, string manipulation and most other processing can be instantly recalculated as&amp;nbsp;required.&lt;/p&gt;
&lt;p&gt;Our &lt;code&gt;#detective_report&lt;/code&gt; method still uses the explaining variable &lt;code&gt;percent_overdue&lt;/code&gt;. The problem with this is that the logic that went into calculating that value is not ready for reuse. When we add a method to tell us whether late fees need to be reviewed (which they obviously should be if more than 50% of our books are overdue), we end up with&amp;nbsp;duplication.&lt;/p&gt;
&lt;p&gt;&lt;img alt="presentation slide" src="https://chrisberkhout.com/blog/local-variables/008.png"&gt;&lt;/p&gt;
&lt;p&gt;If we replace that explaining local variable with a method, we significantly improve readability as well as reducing&amp;nbsp;duplication.&lt;/p&gt;
&lt;p&gt;&lt;img alt="presentation slide" src="https://chrisberkhout.com/blog/local-variables/009.png"&gt;&lt;/p&gt;
&lt;p&gt;Local variables are bad because they hide &lt;a href="http://devblog.avdi.org/2011/07/05/demeter-its-not-just-a-good-idea-its-the-law/"&gt;Law of Demeter&lt;/a&gt; violations and other&amp;nbsp;complexity.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;#can_borrow?&lt;/code&gt; method says a borrower can borrow a book if they don&amp;#8217;t already have a book out and their membership record shows no fees are&amp;nbsp;due.&lt;/p&gt;
&lt;p&gt;It&amp;#8217;s a fairly short and clear method. We just make a couple of simple method calls, and we don&amp;#8217;t have the long method chains that Law of Demeter violations are often recognised&amp;nbsp;by.&lt;/p&gt;
&lt;p&gt;&lt;img alt="presentation slide" src="https://chrisberkhout.com/blog/local-variables/010.png"&gt;&lt;/p&gt;
&lt;p&gt;However, if we remove our local variables, the equivalent code reveals itself as ridiculously inappropriate. It is reaching deep into the record object - coupling itself to fee structures and introducing duplication of fee handling&amp;nbsp;logic.&lt;/p&gt;
&lt;p&gt;ActiveSupport&amp;#8217;s &lt;code&gt;#try&lt;/code&gt; method would shorten this, but it&amp;#8217;s still a clear candidate for&amp;nbsp;refactoring.&lt;/p&gt;
&lt;p&gt;We can fully resolve these issues by introducing a new method, &lt;code&gt;good?&lt;/code&gt;, which shows whether a borrower&amp;#8217;s record finds them in good&amp;nbsp;standing.&lt;/p&gt;
&lt;p&gt;&lt;img alt="presentation slide" src="https://chrisberkhout.com/blog/local-variables/011.png"&gt;&lt;/p&gt;
&lt;p&gt;Local variables are bad because they go together with big&amp;nbsp;methods.&lt;/p&gt;
&lt;p&gt;&lt;img alt="presentation slide" src="https://chrisberkhout.com/blog/local-variables/012.png"&gt;&lt;/p&gt;
&lt;p&gt;Big methods usually do several things, and local methods help them store the intermediate values that appear in their multi-part&amp;nbsp;processing.&lt;/p&gt;
&lt;p&gt;As well as being involved in the work of methods that are already big, local variables make methods even bigger because they usually add at least one line of code each as they come into&amp;nbsp;existence.&lt;/p&gt;
&lt;p&gt;So, are they the new global&amp;nbsp;variables?&lt;/p&gt;
&lt;p&gt;&lt;img alt="presentation slide" src="https://chrisberkhout.com/blog/local-variables/013.png"&gt;&lt;/p&gt;
&lt;p&gt;Yes!&lt;/p&gt;
&lt;p&gt;Local variables are the new global variables because as you write better code, you will use them less, and because when they appear in existing code, it is usually a sign that you should&amp;nbsp;refactor.&lt;/p&gt;
&lt;p&gt;&lt;img alt="presentation slide" src="https://chrisberkhout.com/blog/local-variables/014.png"&gt;&lt;/p&gt;
&lt;p&gt;The next time you discover or create a local variable, consider extracting a method instead. You&amp;#8217;ll find yourself with cleaner code and a clearer mind in no time&amp;nbsp;flat.&lt;/p&gt;</content><category term="misc"/></entry><entry><title>Find in project - TextMate vs. Vim</title><link href="https://chrisberkhout.com/blog/find-in-project-textmate-vs-vim/" rel="alternate"/><published>2012-03-31T00:00:00+00:00</published><updated>2012-03-31T00:00:00+00:00</updated><author><name>Chris Berkhout</name></author><id>tag:chrisberkhout.com,2012-03-31:/blog/find-in-project-textmate-vs-vim/</id><summary type="html">&lt;p class="preamble"&gt;I spoke on this topic at the March meetup of the &lt;a href="http://ruby.org.au/meetups/mel.html"&gt;Melbourne Ruby&lt;/a&gt;&amp;nbsp;group.&lt;/p&gt;
&lt;p&gt;TextMate&amp;#8217;s &amp;#8216;Find in Project&amp;#8217; is an extremely useful feature. It presents search hits in a single window and lets you inspect and replace selected occurrences. It understands plain strings and regular expressions. It makes code …&lt;/p&gt;</summary><content type="html">&lt;p class="preamble"&gt;I spoke on this topic at the March meetup of the &lt;a href="http://ruby.org.au/meetups/mel.html"&gt;Melbourne Ruby&lt;/a&gt;&amp;nbsp;group.&lt;/p&gt;
&lt;p&gt;TextMate&amp;#8217;s &amp;#8216;Find in Project&amp;#8217; is an extremely useful feature. It presents search hits in a single window and lets you inspect and replace selected occurrences. It understands plain strings and regular expressions. It makes code exploration, renaming and other refactoring a breeze. Responsiveness is fine in small to medium-sized projects that exclude vendor code and log files, and &lt;a href="http://blog.macromates.com/2011/textmate-2-0-alpha/"&gt;TextMate 2&lt;/a&gt; enhancements address the search performance issues seen in larger projects. There&amp;#8217;s not much more to say about TextMate; it just&amp;nbsp;works.&lt;/p&gt;
&lt;p&gt;But how do you do it in Vim? When I switched over I was surprised to find no easy answer. Several Vim users told me they don&amp;#8217;t need that feature very often and just do it manually. But renaming is an important part of refactoring. It should be quick and easy. So I kept looking, and eventually, solutions began to&amp;nbsp;emerge.&lt;/p&gt;
&lt;p&gt;In this post I&amp;#8217;ll be using &lt;code&gt;ack&lt;/code&gt; (see &lt;a href="http://betterthangrep.com"&gt;betterthangrep.com&lt;/a&gt;) as a replacement for &lt;code&gt;grep&lt;/code&gt; and &lt;code&gt;find&lt;/code&gt;. Most of what I&amp;#8217;ll say remains relevant regardless of which you choose. If you go with &lt;code&gt;ack&lt;/code&gt;, do consider &lt;a href="https://github.com/chrisberkhout/dot-other/blob/master/.ackrc"&gt;custom configuration like mine&lt;/a&gt;, which includes text files and excludes log files by&amp;nbsp;default.&lt;/p&gt;
&lt;h2&gt;Go to&amp;nbsp;file&lt;/h2&gt;
&lt;p&gt;The first tip I got was that with external search results read into a buffer, files can be jumped into one by one to make changes across a&amp;nbsp;project.&lt;/p&gt;
&lt;p&gt;Say you want to replace &amp;#8216;persisted&amp;#8217; with &amp;#8216;recorded&amp;#8217; in the code of &lt;a href="https://github.com/karmi/tire"&gt;tire&lt;/a&gt; (an ElasticSearch client). You can start by running this Vim&amp;nbsp;command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;:new | r ! ack persisted
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Where &lt;code&gt;:new&lt;/code&gt; creates a new buffer, &lt;code&gt;|&lt;/code&gt; runs an additional Vim command, &lt;code&gt;r&lt;/code&gt; reads into the buffer, &lt;code&gt;!&lt;/code&gt; executes a shell command and &lt;code&gt;ack persisted&lt;/code&gt; actually performs the search. The buffer of ack results looks like&amp;nbsp;this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;tire&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;model&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;persistence&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nd"&gt;66&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nt"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;persisted&lt;/span&gt;&lt;span class="o"&gt;?;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="nt"&gt;id&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="nt"&gt;end&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="nt"&gt;test&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;integration&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;active_record_searchable_test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nd"&gt;56&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;assert&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="nt"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;first&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;persisted&lt;/span&gt;&lt;span class="o"&gt;?,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Record should be persisted&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="nt"&gt;test&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;models&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;active_model_article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nd"&gt;28&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;persisted&lt;/span&gt;&lt;span class="o"&gt;?;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;true&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;end&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="nt"&gt;test&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;models&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;active_model_article_with_callbacks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nd"&gt;30&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;persisted&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="nt"&gt;test&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;models&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;supermodel_article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nd"&gt;16&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;alias&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nd"&gt;persisted&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nd"&gt;exists&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="nt"&gt;test&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;unit&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;model_persistence_test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nd"&gt;262&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;assert&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;persisted&lt;/span&gt;&lt;span class="o"&gt;?,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;#{article.inspect} should be `persisted?`&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;With your cursor over a filename, in normal mode, you can press &lt;code&gt;gf&lt;/code&gt; to open the file (then &lt;code&gt;:b#&lt;/code&gt; to return to the previous buffer). Even better than that, &lt;code&gt;gF&lt;/code&gt; will open the relevant file and jump right to the specified line&amp;nbsp;number.&lt;/p&gt;
&lt;p&gt;Perform your first substitution with &lt;code&gt;:s/persisted/replaced/g&lt;/code&gt;, then repeat it later with &lt;code&gt;:s&lt;/code&gt;. Don&amp;#8217;t forget to save the modified buffers as you go with &lt;code&gt;:w&lt;/code&gt; (write) or all together at the end with &lt;code&gt;:wa&lt;/code&gt; (write&amp;nbsp;all).&lt;/p&gt;
&lt;h2&gt;The ack.vim&amp;nbsp;plugin&lt;/h2&gt;
&lt;p&gt;Pulling in the output of a shell command is a great general-purpose trick, but when it comes to ack you can save yourself some typing by installing the &lt;a href="https://github.com/mileszs/ack.vim"&gt;ack.vim&lt;/a&gt;&amp;nbsp;plugin.&lt;/p&gt;
&lt;p&gt;This gives you the &lt;code&gt;:Ack&lt;/code&gt; command, which opens ack search results in the the quickfix window. Our search command&amp;nbsp;becomes:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;:Ack persisted
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;From the quickfix window, ack.vim lets you open a match with &lt;code&gt;o&lt;/code&gt; or enter, or preview a match (keeping the cursor in the quickfix window) with &lt;code&gt;go&lt;/code&gt;. Pressing &lt;code&gt;v&lt;/code&gt; will open the match in a vertical&amp;nbsp;split.&lt;/p&gt;
&lt;p&gt;The quickfix window is not specific to ack.vim. It&amp;#8217;s a general purpose feature of Vim itself. For more information check out &lt;code&gt;:help quickfix&lt;/code&gt;. You&amp;#8217;ll find that you can (re)open the quickfix window with &lt;code&gt;:copen&lt;/code&gt;, but beware that some of the ack.vim shortcuts won&amp;#8217;t work&amp;nbsp;anymore.&lt;/p&gt;
&lt;h2&gt;The args&amp;nbsp;list&lt;/h2&gt;
&lt;p&gt;Derek Wyatt&amp;#8217;s excellent &lt;a href="http://derekwyatt.org/vim/tutorials/"&gt;Vim tutorial screencasts&lt;/a&gt; do cover &lt;a href="http://derekwyatt.org/vim/tutorials/intermediate/#manyfilemacro"&gt;editing many files&lt;/a&gt;. His example demonstrated loading relevant files into Vim&amp;#8217;s args (arguments) list from the shell then recording a macro to process the first file, save it and move on (&lt;code&gt;:n&lt;/code&gt; for next, &lt;code&gt;:p&lt;/code&gt; for previous). The recorded macro was then repeated to cycle through each file in the list of filename arguments, making the required changes along the&amp;nbsp;way.&lt;/p&gt;
&lt;p&gt;This is clearly a very promising technique. Unfortunately, loading the args list by invoking Vim on the command line isn&amp;#8217;t always ideal - particularly if you&amp;#8217;re not running Vim in client/server mode. You want to be able to set up the args list for a batch edit without leaving&amp;nbsp;Vim.&lt;/p&gt;
&lt;p&gt;And it turns out that you can! Run &lt;code&gt;:args&lt;/code&gt; (or &lt;code&gt;:ar&lt;/code&gt;) to see the current args list. Set the args list by giving filenames to the same&amp;nbsp;command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;:args fileone filetwo filethree
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Even better (but less obvious from &lt;code&gt;:help args&lt;/code&gt;) is that you can load the args list using wildcards or the output of a shell&amp;nbsp;command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;:args **/*.rb
:args `ack -l persisted`
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Check out &lt;code&gt;:help argadd&lt;/code&gt; and &lt;code&gt;:help argdelete&lt;/code&gt; as&amp;nbsp;well.&lt;/p&gt;
&lt;h2&gt;Processing all args with&amp;nbsp;:argdo&lt;/h2&gt;
&lt;p&gt;Not only can you cycle through the args list one by one, you can also run a Vim command on all those files in one go. I discovered the &lt;code&gt;:argdo&lt;/code&gt; command &lt;a href="http://webcache.googleusercontent.com/search?q=cache:mc3mpBGwOPAJ:jameso.be/2011/01/16/vim-search-and-replace.html"&gt;via James O&amp;#8217;Beirne&lt;/a&gt;. You can read more about it in &lt;code&gt;:help argdo&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;With &lt;code&gt;argdo&lt;/code&gt;, our seach and replace in project (confirming each substitution after examining it in context) becomes a two step process. First, load the relevant files into the args list. Then, in all those files (&lt;code&gt;:argdo&lt;/code&gt;), on every line of each file (&lt;code&gt;%&lt;/code&gt;), substitute &amp;#8216;persisted&amp;#8217; with &amp;#8216;recorded&amp;#8217; (&lt;code&gt;s/persisted/recorded/&lt;/code&gt;), everywhere in each line (&lt;code&gt;g&lt;/code&gt;), asking for confirmation each time (&lt;code&gt;c&lt;/code&gt;), and save each buffer if changes were made (&lt;code&gt;update&lt;/code&gt;):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;:args `ack -l persisted`
:argdo %s/persisted/recorded/gc | update
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Processing the quickfix&amp;nbsp;list&lt;/h2&gt;
&lt;p&gt;Vim provides ways to iterate over args (&lt;code&gt;:argdo&lt;/code&gt;), buffers (&lt;code&gt;:bufdo&lt;/code&gt;), windows (&lt;code&gt;:windo&lt;/code&gt;) and tabs (&lt;code&gt;:tabdo&lt;/code&gt;), but surprisingly it has no equivalent for processing the contents of the quickfix list. Fortunately, this is easy to&amp;nbsp;fix.&lt;/p&gt;
&lt;p&gt;You can install a solution in the form of the &lt;a href="https://github.com/mdp/QFDo"&gt;QFDo plugin&lt;/a&gt;, or head over to Stack Overflow to read the code from &lt;a href="http://stackoverflow.com/questions/4792561/how-to-do-search-replace-with-ack-in-vim/4793316#4793316"&gt;its original source&lt;/a&gt;. With QFDo in place, our search and replace can be performed as&amp;nbsp;follows:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;:Ack persisted
:QFDo %s/persisted/recorded/gc | update
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Another Stack Overflow contributor has offered an alternative approach: &lt;a href="http://stackoverflow.com/questions/5686206/search-replace-using-quickfix-list-in-vim/5686810#5686810"&gt;Qargs&lt;/a&gt;, which will load the quickfix list into the args list, allowing you to proceed with &lt;code&gt;:argdo&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;:Ack persisted
:Qargs | argdo %s/persisted/recorded/gc | update
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I&amp;#8217;m not yet sure which I prefer, but I&amp;#8217;m leaning towards Qargs with &lt;code&gt;:cclose&lt;/code&gt; added, to close the quickfix window as it is loaded into args (returning the cursor to the main window, ready for the next&amp;nbsp;command).&lt;/p&gt;
&lt;h2&gt;Closer to TextMate with&amp;nbsp;greplace.vim&lt;/h2&gt;
&lt;p&gt;Those who want something closer to the experience of TextMate&amp;#8217;s &amp;#8216;Find in Project&amp;#8217; might like to try the &lt;a href="http://www.vim.org/scripts/script.php?script_id=1813"&gt;greplace.vim&lt;/a&gt;&amp;nbsp;plugin.&lt;/p&gt;
&lt;p&gt;With greplace.vim you can search across your project with &lt;code&gt;:Gsearch&lt;/code&gt;, edit hits together, in the new buffer it creates, and then write those changes back into their respective files with &lt;code&gt;:Greplace&lt;/code&gt;. It&amp;#8217;s also possible to restrict your search to files in the args list (&lt;code&gt;:Gargsearch&lt;/code&gt;), and to load hits into the editing buffer from the quickfix list (&lt;code&gt;:Gqfopen&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;The greplace.vim plugin&amp;#8217;s interpretation of filename masks (which &lt;code&gt;:Gsearch&lt;/code&gt; requires) remains something of a mystery to me, but if you find you really want to see search hits in one place when editing them, rather than jumping from file to file to confirm changes, greplace.vim is a strong candidate for integration into your Vim search and replace&amp;nbsp;workflow.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;TextMate makes find in project easy. It is something you&amp;#8217;ll miss for a while if you&amp;#8217;re making the switch to Vim. But Vim does have equivalent functionality, and with a bit of practice and streamlining it&amp;#8217;s probably more effective&amp;nbsp;overall.&lt;/p&gt;
&lt;p&gt;Vim matches TextMate&amp;#8217;s power, and raises it. For example, TextMate can do find and replace of strings or regular expressions across selected files, but in Vim it is trivial to perform batch processing of selected files with a complex macro (e.g. &lt;code&gt;:argdo normal @a&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;You can use your shell to select files to pass to TextMate, but from Vim you can drive powerful two-way interaction with the shell from your editor, combining and exploiting &lt;span class="caps"&gt;UNIX&lt;/span&gt;&amp;#8217;s many tools to a greater degree, without needing to write extensions for each use&amp;nbsp;case.&lt;/p&gt;</content><category term="misc"/></entry><entry><title>Processing PDFs for Kindle readability</title><link href="https://chrisberkhout.com/blog/processing-pdfs-for-kindle-readability/" rel="alternate"/><published>2011-12-31T00:00:00+00:00</published><updated>2011-12-31T00:00:00+00:00</updated><author><name>Chris Berkhout</name></author><id>tag:chrisberkhout.com,2011-12-31:/blog/processing-pdfs-for-kindle-readability/</id><summary type="html">&lt;p&gt;I find it a pleasure to read appropriately formatted eBooks on my new Kindle
Touch. However, PDFs are a fixed layout format and the Kindle presents them one
full page at a time. Zooming and panning are possible, but not practical for
normal&amp;nbsp;reading.&lt;/p&gt;
&lt;p&gt;A typically formatted &lt;span class="caps"&gt;PDF&lt;/span&gt; will be …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I find it a pleasure to read appropriately formatted eBooks on my new Kindle
Touch. However, PDFs are a fixed layout format and the Kindle presents them one
full page at a time. Zooming and panning are possible, but not practical for
normal&amp;nbsp;reading.&lt;/p&gt;
&lt;p&gt;A typically formatted &lt;span class="caps"&gt;PDF&lt;/span&gt; will be presented as the following illegible&amp;nbsp;mess:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Unprocessed PDF on a Kindle Touch" src="https://chrisberkhout.com/blog/processing-pdfs-for-kindle-readability/kindle-pdf-portrait-small.jpg"&gt;&lt;/p&gt;
&lt;p&gt;I&amp;#8217;ll show you how to quickly and easily split and rotate &lt;span class="caps"&gt;PDF&lt;/span&gt; pages for results
like&amp;nbsp;this:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Processed PDF on a Kindle Touch" src="https://chrisberkhout.com/blog/processing-pdfs-for-kindle-readability/kindle-pdf-landscape-small.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Mileage will vary with the source document dimensions, but our example of
&lt;span class="caps"&gt;US&lt;/span&gt;-letter sized pages with a small, serif font enclosed in modest margins does
go from illegible to relatively comfortable reading. Many documents will yield
greater&amp;nbsp;improvements.&lt;/p&gt;
&lt;h2&gt;Divide and crop with&amp;nbsp;Briss&lt;/h2&gt;
&lt;p&gt;&lt;a href="http://briss.sourceforge.net/"&gt;Briss&lt;/a&gt; (the BRIght Snippet Sire) is free, open
source software for quickly turning each page of a &lt;span class="caps"&gt;PDF&lt;/span&gt; into several pages
defined by rectangles you draw. It runs on Mac, Linux and Windows.
&lt;a href="http://sourceforge.net/projects/briss/files/"&gt;Download it&lt;/a&gt;&amp;nbsp;now.&lt;/p&gt;
&lt;p&gt;If you&amp;#8217;re not on Windows, you&amp;#8217;ll want to ignore the &lt;code&gt;briss.exe&lt;/code&gt; file and run
the file named &lt;code&gt;briss-0.0.13.jar&lt;/code&gt;. Note that Mac &lt;span class="caps"&gt;OS&lt;/span&gt; X, since Lion, no longer
pre-installs the Java Runtime Environment (&lt;span class="caps"&gt;JRE&lt;/span&gt;), which Briss requires. So, if
you don&amp;#8217;t already have it, you might need to
&lt;a href="http://kb2.adobe.com/cps/909/cpsid_90908.html"&gt;install the &lt;span class="caps"&gt;JRE&lt;/span&gt;&lt;/a&gt;&amp;nbsp;first.&lt;/p&gt;
&lt;p&gt;Fire up Briss and load your &lt;span class="caps"&gt;PDF&lt;/span&gt;. Briss will group pages of equivalent geometry
(e.g. left pages and right pages) and overlay all of each group&amp;#8217;s content on a
single&amp;nbsp;page.&lt;/p&gt;
&lt;p&gt;You&amp;#8217;re then free to modify the size of the initial cop box and add additional
boxes by clicking and dragging on the page. I suggest splitting your pages in
half horizontally and removing as much border space as possible. I&amp;#8217;d usually
remove page numbers as well. The Kindle shows its own page numbers (although
doubled, assuming you&amp;#8217;ve split your pages in half) and PDFs with embedded
bookmarks will remain navigable via the Kindle&amp;#8217;s &amp;#8216;Table of Contents&amp;#8217; menu. It&amp;#8217;s
also a good idea to overlap the two areas a bit, so that any line cut off by
the new page border will be repeated in full on the following&amp;nbsp;page.&lt;/p&gt;
&lt;p&gt;The cropping areas defining your new sub-pages should look something like&amp;nbsp;this:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Page crop areas" src="https://chrisberkhout.com/blog/processing-pdfs-for-kindle-readability/briss-crop-areas.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Select &lt;code&gt;Action &amp;gt; Crop PDF&lt;/code&gt; from the menu and save your new &lt;span class="caps"&gt;PDF&lt;/span&gt;. Open it up in a
&lt;span class="caps"&gt;PDF&lt;/span&gt; reader and you&amp;#8217;ll see that you now have twice as many pages, each with half
the&amp;nbsp;content.&lt;/p&gt;
&lt;h2&gt;Rotate&lt;/h2&gt;
&lt;p&gt;The Kindle Touch lacks a page rotation function, but even if you have a Kindle
that allows it, you&amp;#8217;re probably better off doing your page rotation in&amp;nbsp;advance.&lt;/p&gt;
&lt;p&gt;Rotate your pages 90° counterclockwise. This ensures that the button or screen
area you press to move to the next page will be at the bottom of your rotated,
landscape-oriented&amp;nbsp;pages.&lt;/p&gt;
&lt;p&gt;On Mac you can rotate pages using the default &lt;span class="caps"&gt;PDF&lt;/span&gt; reader, &lt;code&gt;Preview.app&lt;/code&gt;, by
turning on the &lt;code&gt;Thumbnail&lt;/code&gt; view, selecting all the pages, and choosing &lt;code&gt;Rotate
Left&lt;/code&gt; from the &lt;code&gt;Tools&lt;/code&gt; menu. Many other programs will do this too. If you get
stuck finding one for your platform, try
&lt;a href="http://www.pdflabs.com/tools/pdftk-the-pdf-toolkit/"&gt;Pdftk - the &lt;span class="caps"&gt;PDF&lt;/span&gt; toolkit&lt;/a&gt;.
It is a free command line tool that runs on Windows, Linux, Mac &lt;span class="caps"&gt;OS&lt;/span&gt; X, FreeBSD
and Solaris.  Once installed, you&amp;#8217;d run &lt;code&gt;pdftk unrotated.pdf cat 1-endW output
unrotated.pdf&lt;/code&gt; to rotate all the pages of your document 90°&amp;nbsp;counterclockwise.&lt;/p&gt;
&lt;p&gt;You&amp;#8217;re all done! Send the &lt;span class="caps"&gt;PDF&lt;/span&gt; to your Kindle using your Send-to-Kindle email
address or a &lt;span class="caps"&gt;USB&lt;/span&gt; cable and enjoy your book without a magnifying&amp;nbsp;glass.&lt;/p&gt;</content><category term="misc"/></entry><entry><title>Internet access from China</title><link href="https://chrisberkhout.com/blog/internet-access-from-china/" rel="alternate"/><published>2011-11-09T00:00:00+00:00</published><updated>2011-11-09T00:00:00+00:00</updated><author><name>Chris Berkhout</name></author><id>tag:chrisberkhout.com,2011-11-09:/blog/internet-access-from-china/</id><summary type="html">&lt;p&gt;Parts of the Internet are blocked in China. How much impact does that have on everyday Internet&amp;nbsp;life?&lt;/p&gt;
&lt;p&gt;I usually access the Internet via a &lt;span class="caps"&gt;VPN&lt;/span&gt; (an encrypted tunnel) that passes my traffic straight out to the rest of the world, bypassing the blocks. I decided to look back over …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Parts of the Internet are blocked in China. How much impact does that have on everyday Internet&amp;nbsp;life?&lt;/p&gt;
&lt;p&gt;I usually access the Internet via a &lt;span class="caps"&gt;VPN&lt;/span&gt; (an encrypted tunnel) that passes my traffic straight out to the rest of the world, bypassing the blocks. I decided to look back over a month of my Internet history without a &lt;span class="caps"&gt;VPN&lt;/span&gt; and see what was&amp;nbsp;missing.&lt;/p&gt;
&lt;p&gt;In the course of the month I used around 100 unique blocked sites. That&amp;#8217;s around 3 new blocked sites per day, but do bear in mind that there are a number of blocked sites that I use many times per&amp;nbsp;month.&lt;/p&gt;
&lt;h2&gt;How are sites&amp;nbsp;blocked?&lt;/h2&gt;
&lt;p&gt;The technical details of the Great Firewall&amp;#8217;s various blocking methods are beyond the scope of this article. However, I would like to mention that the various techniques employed lead to a range of experiences for users who attempt to access blocked&amp;nbsp;sites.&lt;/p&gt;
&lt;p&gt;The most extreme case is a site that doesn&amp;#8217;t load at all. Either the host name can&amp;#8217;t be resolved to an address or the connection is reset before anything is loaded. A step down from there are the sites that take a long time before failing to fully load, or that have certain elements that don&amp;#8217;t load. Flickr suffers from the latter. It does mostly work, but is regularly missing photos. Then there are sites that do load entirely, but so slowly that they are unusable. The Basecamp project management application falls into this category. Finally, there are some sites that fully function (and fail) intermittently. Google&amp;#8217;s Gmail will often work for several days at a time, then fail completely for the next couple of&amp;nbsp;days.&lt;/p&gt;
&lt;p&gt;Public holidays and other days of national significance tend to see stronger blocking. Perhaps this is intended to discourage political activity during the holidays, or to test more restrictive systems at times of reduced commercial impact, or a combination of those, or something else&amp;nbsp;entirely.&lt;/p&gt;
&lt;h2&gt;What was&amp;nbsp;blocked?&lt;/h2&gt;
&lt;p&gt;To demonstrate the incompatibility of my web browsing habits with the Great Firewall, I&amp;#8217;ve broken down a selection of blocked sites I visited over the month into representative categories. They&amp;nbsp;are:&lt;/p&gt;
&lt;h4&gt;Google and&amp;nbsp;Twitter&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.google.com/"&gt;Google Search &lt;span class="caps"&gt;USA&lt;/span&gt;&lt;/a&gt; (Hong Kong usually&amp;nbsp;works)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mail.google.com/mail/"&gt;Gmail&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://groups.google.com/"&gt;Google Groups&lt;/a&gt; (discussion platform for many public software&amp;nbsp;projects)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.google.com/"&gt;Google Docs&lt;/a&gt; (only encrypted connections are&amp;nbsp;blocked)&lt;/li&gt;
&lt;li&gt;&lt;a href="http://twitter.com/"&gt;Twitter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://twitpic.com/"&gt;Twitpic&lt;/a&gt; (for sharing media on&amp;nbsp;Twitter)&lt;/li&gt;
&lt;li&gt;&lt;a href="http://yfrog.com/"&gt;yfrog&lt;/a&gt; (more Twitter media&amp;nbsp;sharing)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Things starting with&amp;nbsp;&amp;#8216;face&amp;#8217;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://facebook.com/"&gt;Facebook&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://facedetection.jaysalvat.com/"&gt;Face detection jQuery plugin&lt;/a&gt; (free code for finding faces in&amp;nbsp;photos)&lt;/li&gt;
&lt;li&gt;&lt;a href="http://facelette.com/"&gt;Facelette&lt;/a&gt; (a decommissioned experimental app related to Apple&amp;nbsp;FaceTime)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Books and&amp;nbsp;films&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://goodreads.com/"&gt;Goodreads&lt;/a&gt; (a place to discover and discuss&amp;nbsp;books)&lt;/li&gt;
&lt;li&gt;&lt;a href="http://imdb.com/"&gt;The Internet Movie&amp;nbsp;Database&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Video&amp;nbsp;sharing&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.youtube.com/"&gt;YouTube&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.vimeo.com/"&gt;Vimeo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.ustream.tv/"&gt;Ustream&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Major blogging platforms and the many blogs they&amp;nbsp;host&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.wordpress.com"&gt;WordPress.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.blogger.com"&gt;Blogger and&amp;nbsp;BlogSpot&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.tumblr.com"&gt;Tumblr&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.posterous.com/"&gt;Posterous&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Other technology&amp;nbsp;blogs&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://techcrunch.com/"&gt;TechCrunch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://freelancing-gods.com/"&gt;Freelancing&amp;nbsp;Gods&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://rhnh.net/"&gt;Robot Has No&amp;nbsp;Heart&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://alexmaccaw.co.uk/"&gt;Alex MacCaw&amp;#8217;s&amp;nbsp;blog&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Tools and resources for software&amp;nbsp;development&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://travis-ci.org/"&gt;Travis &lt;span class="caps"&gt;CI&lt;/span&gt;&lt;/a&gt; integration testing&amp;nbsp;platform&lt;/li&gt;
&lt;li&gt;&lt;a href="http://instacss.com/"&gt;Instant &lt;span class="caps"&gt;CSS&lt;/span&gt; Documentation&amp;nbsp;Search&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://fabricationgem.org/"&gt;Fabrication object&amp;nbsp;generator&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://emscripten.org/"&gt;Emscripten: An &lt;span class="caps"&gt;LLVM&lt;/span&gt;-to-JavaScript&amp;nbsp;compiler&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://dotfiles.org/"&gt;dotfiles.org&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://coderetreat.com/"&gt;Coderetreats w/ Corey&amp;nbsp;Haines&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://codeacademy.org/"&gt;Code&amp;nbsp;Academy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://areweplayingyet.org/"&gt;AreWePlayingYet?&lt;/a&gt; (browser compatibility&amp;nbsp;test)&lt;/li&gt;
&lt;li&gt;&lt;a href="http://bostonrb.org/"&gt;Boston Ruby&amp;nbsp;Group&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Software&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.dropbox.com/"&gt;Dropbox&lt;/a&gt; file backup and synchronization (an encrypted connection&amp;nbsp;works)&lt;/li&gt;
&lt;li&gt;&lt;a href="http://linuxmint.com/"&gt;Linux Mint&lt;/a&gt; (a distribution of the Linux operating&amp;nbsp;system)&lt;/li&gt;
&lt;li&gt;&lt;a href="http://basecamphq.com/"&gt;Basecamp&lt;/a&gt; online project management&amp;nbsp;tool&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.suavetech.com/"&gt;Suavetech&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.yazsoft.com/"&gt;Yazsoft&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Miscellaneous&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.openstreetmap.org/"&gt;OpenStreetMap&lt;/a&gt; (like Wikipedia but for&amp;nbsp;maps)&lt;/li&gt;
&lt;li&gt;&lt;a href="http://wikimapia.org/"&gt;Wikimapia&lt;/a&gt; (open metadata on copyrighted maps and satellite&amp;nbsp;images)&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.dw-world.de/"&gt;Deutsche Welle&lt;/a&gt; world&amp;nbsp;news&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.pragprog.com/"&gt;The Pragmatic Bookshelf&lt;/a&gt; (publisher of programming&amp;nbsp;books)&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.khanacademy.org/"&gt;Khan Academy&lt;/a&gt; (an incredible online learning&amp;nbsp;resource)&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.flickr.com/"&gt;Flickr&lt;/a&gt; (photo&amp;nbsp;sharing)&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.trns.fr/"&gt;Trnsfr&lt;/a&gt; (for sending web pages to your&amp;nbsp;phone)&lt;/li&gt;
&lt;li&gt;&lt;a href="http://strongvpn.com/"&gt;StrongVPN&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As you can see, it goes well beyond Google, Twitter and Facebook. In particular, the major blogging platforms make up a large chunk of the English-language Internet. The blocking covers the platform sites themselves, but also the many millions of individual blogs hosted by these&amp;nbsp;services.&lt;/p&gt;
&lt;h2&gt;Was this&amp;nbsp;indended?&lt;/h2&gt;
&lt;p&gt;One can imagine political, economic or cultural motivations for much of China&amp;#8217;s Internet filtering, but the examples from my Internet usage suggest there is also a lot of unintentional&amp;nbsp;over-blocking.&lt;/p&gt;</content><category term="misc"/></entry><entry><title>ElasticSearch as a primary data store</title><link href="https://chrisberkhout.com/blog/elasticsearch-as-a-primary-data-store/" rel="alternate"/><published>2011-10-18T00:00:00+00:00</published><updated>2011-10-18T00:00:00+00:00</updated><author><name>Chris Berkhout</name></author><id>tag:chrisberkhout.com,2011-10-18:/blog/elasticsearch-as-a-primary-data-store/</id><summary type="html">&lt;p class="preamble"&gt;Note: This post explored the idea that for some applications, it may be possible to model data more naturally and avoid some overheads by using ElasticSearch without a relational database backing it. I warned that this may not be possible or advisable for a range of reasons, including functional requirements …&lt;/p&gt;</summary><content type="html">&lt;p class="preamble"&gt;Note: This post explored the idea that for some applications, it may be possible to model data more naturally and avoid some overheads by using ElasticSearch without a relational database backing it. I warned that this may not be possible or advisable for a range of reasons, including functional requirements, performance characteristics and operational&amp;nbsp;concerns.&lt;/p&gt;
&lt;p&gt;These are interesting times to be working with data; particularly because of the new possibilities that NoSQL (non-relational) databases offer for data modelling and manipulation. In addition to the new document databases, key-value stores and graph databases, there is an emerging option that has been hiding in plain sight: using your search engine as the&amp;nbsp;database.&lt;/p&gt;
&lt;p&gt;A conventional application begins with data stored in a relational database. If search and aggregation needs exceed what it can provide, an additional system is added: the search engine. The former remains the authoritative data store and the latter is a tacked-on hack that stays downstream but earns its keep by finding results&amp;nbsp;quickly.&lt;/p&gt;
&lt;p&gt;As ElasticSearch has developed to address the complications of previous search engines and adapt to modern NoSQL and hosting environments, it has come to resemble a data persistence platform in its own right. It can store and retrieve arbitrary &lt;span class="caps"&gt;JSON&lt;/span&gt; data, so it is useful for richly structured data as well as free-form text. It can be updated in near real-time, so there need not be any appreciable delay between the creation of new data and its availability for&amp;nbsp;retrieval.&lt;/p&gt;
&lt;p&gt;Extra tools incur extra overheads. If ElasticSearch already has all your data in a form that is persistent, updatable, and accessible by &lt;span class="caps"&gt;ID&lt;/span&gt; lookup or search queries, why go to the effort of replicating a subset of that functionality in a separate &amp;#8220;primary&amp;#8221; data&amp;nbsp;store?&lt;/p&gt;
&lt;p&gt;Some applications - such as those involving full scans that process whole records and produce large results - are not suited to the retrieval and aggregation options provided by search engines. Others require sophisticated transaction support during writes. Many more are perfectly suited, but there are still a couple of reasons for you to tread&amp;nbsp;carefully.&lt;/p&gt;
&lt;p&gt;ElasticSearch provides redundancy that can protect against hardware failure, and recent versions appear free of data corruption issues, but durability and facilities for taking recoverable backups have not yet had as much attention as in other data stores. From an application programming point of view, you should also keep in mind that object-document mappers (ODMs) targeting ElasticSearch are not yet as mature and full-featured as those for document stores such as&amp;nbsp;MongoDB.&lt;/p&gt;
&lt;p&gt;Despite these considerations, ElasticSearch is certinaly ready for experimental use as a data store, and depending on the requirements of your application, it may even be the only one you&amp;nbsp;need.&lt;/p&gt;
&lt;h2&gt;A demonstration in&amp;nbsp;Rails&lt;/h2&gt;
&lt;p&gt;To show you how simple it is, I&amp;#8217;ve whipped up a basic note taking application that uses ElasticSearch as its primary and sole data store. Much of the credit for this should go to &lt;a href="http://twitter.com/karmiq"&gt;Karel Minařík&lt;/a&gt;, whose ElasticSearch &lt;span class="caps"&gt;API&lt;/span&gt;, &lt;a href="https://github.com/karmi/tire/"&gt;Tire&lt;/a&gt;, made it a cinch. You can find the demo application &lt;a href="https://github.com/chrisberkhout/es_demo"&gt;on GitHub&lt;/a&gt; and run it locally by following the instructions in the &lt;span class="caps"&gt;README&lt;/span&gt;&amp;nbsp;file.&lt;/p&gt;
&lt;p&gt;Read on here for tips on building your&amp;nbsp;own.&lt;/p&gt;
&lt;h3&gt;Disabling&amp;nbsp;ActiveRecord&lt;/h3&gt;
&lt;p&gt;After creating a fresh Rails application, the first step is to disable ActiveRecord. In &lt;code&gt;config/application.rb&lt;/code&gt; find the line with &lt;code&gt;require 'rails/all'&lt;/code&gt; and replace it&amp;nbsp;with:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nv"&gt;require&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;rails&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
[&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;#&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;active_record&amp;#39;&lt;/span&gt;,&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;action_controller&amp;#39;&lt;/span&gt;,&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;action_mailer&amp;#39;&lt;/span&gt;,&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;active_resource&amp;#39;&lt;/span&gt;,&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;rails/test_unit&amp;#39;&lt;/span&gt;,&lt;span class="w"&gt;&lt;/span&gt;
].&lt;span class="nv"&gt;each&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nv"&gt;framework&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;begin&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;require&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;#{framework}/railtie&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;rescue&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;LoadError&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Then you can delete &lt;code&gt;config/database.yml&lt;/code&gt;, remove &lt;code&gt;gem 'sqlite3'&lt;/code&gt; from your Gemfile and rerun &lt;code&gt;bundle install&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;ElasticSearch-backed models and near real-time&amp;nbsp;updates&lt;/h3&gt;
&lt;p&gt;With the database stuff out of the way you are free to create models that persist to ElasticSearch. Check out the &lt;a href="https://github.com/karmi/tire/#readme"&gt;Tire &lt;span class="caps"&gt;README&lt;/span&gt;&lt;/a&gt;, paying particular attention to the last part, which discusses its persistence features. You&amp;#8217;ll be up and running in no&amp;nbsp;time.&lt;/p&gt;
&lt;p&gt;There is one small trick you should know. After adding a record to an &lt;span class="caps"&gt;ES&lt;/span&gt;-backed model, you may find that you need to reload your index page before the new record appears. This is because ElasticSearch performs updates in &amp;#8220;near&amp;nbsp;real-time&amp;#8221;.&lt;/p&gt;
&lt;p&gt;Near real-time means that it may take up to one second before the creation or update of records is reflected in search results. This is different from GETing a record based on its &lt;span class="caps"&gt;ID&lt;/span&gt;, which in recent version of &lt;span class="caps"&gt;ES&lt;/span&gt; will work immediately. The one second period can be configured to be shorter, but to ensure that a recently indexed record is available you should make an explicit refresh&amp;nbsp;call.&lt;/p&gt;
&lt;p&gt;For any application that is not write-heavy this should pose no performance problems and it is easily implemented. In my &lt;code&gt;Note&lt;/code&gt; model I do it in these 3&amp;nbsp;lines:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;refresh = lambda { Yire::Index.new(ES_INDEX_NAME).refresh }
after_save &amp;amp;refresh
after_destroy &amp;amp;refresh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Do you need your&amp;nbsp;database?&lt;/h2&gt;
&lt;p&gt;You might like to read through the &lt;a href="http://elasticsearch-users.115913.n3.nabble.com/template/NamlServlet.jtp?macro=search_page&amp;amp;node=115913&amp;amp;query=primary+database"&gt;related discussions&lt;/a&gt; on the &lt;a href="http://groups.google.com/a/elasticsearch.com/"&gt;mailing list&lt;/a&gt;. I hope you&amp;#8217;ll give this question some thought, and have fun with ElasticSearch either&amp;nbsp;way!&lt;/p&gt;</content><category term="misc"/></entry><entry><title>Blogging with Serve</title><link href="https://chrisberkhout.com/blog/blogging-with-serve/" rel="alternate"/><published>2011-10-05T00:00:00+00:00</published><updated>2011-10-05T00:00:00+00:00</updated><author><name>Chris Berkhout</name></author><id>tag:chrisberkhout.com,2011-10-05:/blog/blogging-with-serve/</id><summary type="html">&lt;p&gt;This blog was built by generating a static site with &lt;a href="http://get-serve.com/"&gt;Serve&lt;/a&gt;. It was deployed with &lt;a href="http://en.wikipedia.org/wiki/Rsync"&gt;rsync&lt;/a&gt; to an &lt;a href="http://en.wikipedia.org/wiki/Ubuntu_(operating_system)"&gt;Ubuntu&lt;/a&gt; &lt;span class="caps"&gt;VPS&lt;/span&gt; at &lt;a href="http://linode.com"&gt;Linode&lt;/a&gt;, itself built using my &lt;a href="http://babushka.me"&gt;Babushka&lt;/a&gt; &lt;a href="http://github.com/chrisberkhout/babushka-deps"&gt;deps&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Selecting a static site&amp;nbsp;generator&lt;/h2&gt;
&lt;p&gt;I decided to use a static site generator because I wanted something Ruby-based, very simple, and easy …&lt;/p&gt;</summary><content type="html">&lt;p&gt;This blog was built by generating a static site with &lt;a href="http://get-serve.com/"&gt;Serve&lt;/a&gt;. It was deployed with &lt;a href="http://en.wikipedia.org/wiki/Rsync"&gt;rsync&lt;/a&gt; to an &lt;a href="http://en.wikipedia.org/wiki/Ubuntu_(operating_system)"&gt;Ubuntu&lt;/a&gt; &lt;span class="caps"&gt;VPS&lt;/span&gt; at &lt;a href="http://linode.com"&gt;Linode&lt;/a&gt;, itself built using my &lt;a href="http://babushka.me"&gt;Babushka&lt;/a&gt; &lt;a href="http://github.com/chrisberkhout/babushka-deps"&gt;deps&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Selecting a static site&amp;nbsp;generator&lt;/h2&gt;
&lt;p&gt;I decided to use a static site generator because I wanted something Ruby-based, very simple, and easy to extend. I considered several before settling on&amp;nbsp;Serve.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="http://jekyllrb.com/"&gt;Jekyll&lt;/a&gt;&lt;/strong&gt; is the static site generator that powers GitHub Pages. It uses &lt;a href="http://liquidmarkup.org/"&gt;Liquid&lt;/a&gt; templates, which do the job of &lt;span class="caps"&gt;ERB&lt;/span&gt; but can be safely rendered without trusting their author. This is perfect - and necessary - for GitHub Pages, but as I have full control of the rendering and hosting environments, it didn&amp;#8217;t seem like the right fit. In hindsight, lighter templates are a good thing, and I wouldn&amp;#8217;t let Liquid put me off using Jekyll for trusted sites in the future. I&amp;#8217;ve also heard good things about &lt;a href="http://octopress.org/"&gt;Octopress&lt;/a&gt;, which builds on Jekyll to get you started blogging&amp;nbsp;quicker.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="http://nanoc.stoneship.org/"&gt;Nanoc&lt;/a&gt;&lt;/strong&gt; looked perfect. It has clean and extensive documentation. It uses a Rules file to specify mappings from content files, via filters and layouts, to final output. It has blogging helpers baked in. Things were going okay, but after a while the Rules file got frustrating. I felt like I was having to jump through too many hoops just to get back to simple static&amp;nbsp;pages.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="http://get-serve.com/"&gt;Serve&lt;/a&gt;&lt;/strong&gt; Serve has a nice introductory screencast and straightforward documentation. It works like a slimmed down version of Rails views. You don&amp;#8217;t have routes, controllers or models, just templating languages, layouts and helpers. You can run it as a rack app, or export your site to static files as I&amp;nbsp;do.&lt;/p&gt;
&lt;h2&gt;Blogging with&amp;nbsp;Serve&lt;/h2&gt;
&lt;p&gt;Serve wasn&amp;#8217;t built with blogging in mind, so I needed to handle that myself. My requirements were modest: I wanted to have a table of contents page and an Atom feed generated automatically, and to write new posts in single files without repeating myself&amp;nbsp;anywhere.&lt;/p&gt;
&lt;h3&gt;Embedding metadata in &lt;span class="caps"&gt;ERB&lt;/span&gt;&amp;nbsp;templates&lt;/h3&gt;
&lt;p&gt;The first challenge was gathering post metadata. Nanoc and Jekyll let you include &lt;span class="caps"&gt;YAML&lt;/span&gt; at the top of your templates. Serve doesn&amp;#8217;t, but I found a solution using just &lt;span class="caps"&gt;ERB&lt;/span&gt; and&amp;nbsp;helpers.&lt;/p&gt;
&lt;p&gt;I have each post append a hash of metdata to the end of an array in an instance variable, which is created if it doesn&amp;#8217;t already exist. It also includes the template file name and the captured post &lt;span class="caps"&gt;HTML&lt;/span&gt; in the hash. I wrapped this process up in a helper, which also prepares data from the last post for presentation in the blog post layout, using &lt;code&gt;#content_for&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;An individual post template look like&amp;nbsp;this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt;
  &lt;span class="n"&gt;capture_post_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;__FILE__&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ss"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;An example post&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;published_on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;1970-01-01&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;updated_on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;1970-01-02&amp;quot;&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;A blog post!&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;

&lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The helper&amp;nbsp;is:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;capture_post_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;@post_data_raw&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;@post_data_raw&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;file&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nl"&gt;containing_dir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;-2&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nl"&gt;updated_on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nl"&gt;topless_html&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;capture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;@post_data_raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;last&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;each&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;content_for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;last_post_#{k}&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Rendering all the post templates, in any order, will build up the instance variable of all the metadata, which is used by other helpers to create the table of contents and generate the Atom feed. Another helper, &lt;code&gt;#dont_capture_post_data&lt;/code&gt;, does the same without leaving the metadata hash in the array, allowing me to easily toggle posts in and out of publication in the index and&amp;nbsp;feed.&lt;/p&gt;
&lt;h3&gt;Multi-level&amp;nbsp;layouts&lt;/h3&gt;
&lt;p&gt;Serve lets you wrap your templates in layouts, written in files named &lt;code&gt;_layout.html.erb&lt;/code&gt;. It will use the one in the current directory, or the first it finds as it ascends the directory tree. This makes it easy to have branches of your site use the same layout, and to let certain lower directories override a more general layout from higher up. You can also render partial templates to factor out common&amp;nbsp;fragments.&lt;/p&gt;
&lt;p&gt;Duplication emerged in two places in my templates. The first was in my individual blog post directories, each of which had a &lt;code&gt;_layout.html.erb&lt;/code&gt; file pointing to the blog post layout (overriding the blog index layout from the parent directory). I resolved this by putting logic into the blog root&amp;#8217;s layout file, to decide whether to render the post layout, the blog index layout or, for the feed file also in that directory, no&amp;nbsp;layout.&lt;/p&gt;
&lt;p&gt;The next issue was that I wanted the content of blog posts to be rendered by a blog post layout, but to have it share a wrapper with the blog index page, and to have those two use the same top level wrapper as the site&amp;#8217;s other pages. It would be nice to let every template or layout specify what it should be wrapped in. This didn&amp;#8217;t work out of the box, but I was able to cook up something similar using the &lt;code&gt;#capture&lt;/code&gt; helper.&lt;/p&gt;
&lt;p&gt;My mid-level layouts embed themselves in higher level ones by capturing their own content and explicitly rendering the desired outer&amp;nbsp;layout:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="n"&gt;inner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;capture&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;Mid-level markup&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="n"&gt;content_for&lt;/span&gt; &lt;span class="ss"&gt;:inner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;inner&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;/layouts/application&amp;quot;&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The outer layout, of course, runs &lt;code&gt;yield :inner&lt;/code&gt; to wrap itself around content captured at lower&amp;nbsp;levels.&lt;/p&gt;
&lt;h3&gt;On&amp;nbsp;reflection&lt;/h3&gt;
&lt;p&gt;Serve is really just about templates and view helpers and I&amp;#8217;ve shoehorned in more structured blog content and metadata, and methods to manipulate them. These things would be more at home in a model class (database backed or otherwise). It works well enough for now, and it is still quite simple and very &lt;span class="caps"&gt;DRY&lt;/span&gt;, but there are surely better ways to do&amp;nbsp;this.&lt;/p&gt;</content><category term="misc"/></entry></feed>