<rss xmlns:dc="http://purl.org/dc/elements/1.1/" version="2.0"><channel><title>Hacker News Personal Blogs 2013 | Top 100 Blogs</title><link>https://hn-blogs.kronis.dev/feed-top100.xml</link><description>A collection of blog posts from users of Hacker News, based on RSS feeds.</description><language>en-US</language><lastBuildDate>Wed, 07 Oct 2026 04:03:27 GMT</lastBuildDate><generator>rfeed v1.1.1</generator><docs>https://github.com/svpino/rfeed/blob/master/README.md</docs><item><title>2013 Year in Review</title><link>https://benovermyer.com/blog/2013/12/2013-year-in-review/</link><description>&lt;p&gt;A great deal happened in 2013 for me and my family. Sadly, though, I missed the mark on all six of my goals for the year. Despite that, I think 2013 can be counted a success in general.&lt;/p&gt;
&lt;h1 id="my-goals-for-2013"&gt;My Goals for 2013&lt;/h1&gt;
&lt;p&gt;My first goal for 2013 was to release a computer game. For a variety of reasons, this never came close to completion. I overestimated my free time and, to be honest, my motivation. I ended up spending far more time developing tabletop games. I’ll get into that in a second. My second goal was to join a local developer group. Technically, I succeeded at this, since I started going to MNPHP meetups for awhile. However, because I didn’t sustain my attendance, I’m counting this one failed. There is promise here, though. My third goal was ambitious - take a vacation to Hawaii. Saving up the money to do so, especially amidst changing jobs, proved too difficult for me. My fourth goal, lead a yoga class, failed for more than just inability to do so. Most yoga schools these days seem to focus solely on the physical side of yoga, with no thought for the spiritual side. I had a moment of clarity when I realized that I couldn’t get to the level of proficiency in just a year to be qualified to teach both the physical and spiritual sides. I may revisit this goal some year in the future, but not in the near tense. My fifth goal was well on its way to success when I sabotaged it by re-incorporating Dicejockey into my main site. Though I wanted to reach 1,000 visitors per month for at least one month in 2013, my desire to pull in everything to one destination representing me was greater than my desire to continue a blog dedicated solely to MMORPGs and tabletop RPGs. My sixth and final goal for 2013, to collaborate on a project with my sister Tasha, went fairly well for the first half of the year. Unfortunately, I think I was too ambitious in the project we chose to work on, because my contribution would have been a large number of artworks that my skill level and free time did not allow for. Still, this goal was inspiring, and I’ll do something along these lines in 2014.&lt;/p&gt;
&lt;h1 id="major-happenings-of-2013"&gt;Major Happenings of 2013&lt;/h1&gt;
&lt;p&gt;I changed jobs twice this past year. The first time, I left Group 3 Marketing because I felt I was stagnating as a developer. I joined a startup called Sportsdigita and worked heavily on Symfony2 projects. While I certainly grew as a developer at Sportsdigita, the culture was damaging everything else about my life. In August, then, I left that company to work for the Minneapolis Star Tribune, the largest newspaper in Minnesota (and, possibly, the Midwest). The Star Tribune has proved to be a wonderful balance between interesting projects and good culture, and it doesn’t hurt that it pays well, too. My contributions to Silver Gryphon Games grew this year. My efforts on the web side increased, and my interest in this side of the business is definitely heating up. I released the first web tool for Ingenium, and it’s proved very useful - internally, anyway. I did more writing this year in previous years, releasing no less than three adventures for Ingenium. Two of those I did all the writing for, and the third I contributed a third of the final word count to. This also marked the first year where I managed a convention on my own, using the newly-minted “Booth 2.” All in all, 2013 was a successful year for me. I got back into tabletop gaming in a big way this year. I started what has become my longest-running D&amp;amp;D campaign ever. I started playing Warhammer 40k and Warmachine on a weekly basis with some good friends. I did a lot of miniature painting, and produced some works that I’m actually proud of, which hasn’t happened before. I also began writing the rules for my own war game, and that could bode well for Silver Gryphon Games, based on early playtest feedback. I met several new friends and strengthened relationships with others. I’ve been more social-minded this year than the past couple years. I’ve definitely gone to more conventions this year than in past years, also.&lt;/p&gt;
&lt;h1 id="closing-thoughts"&gt;Closing Thoughts&lt;/h1&gt;
&lt;p&gt;Many positive things happened this year, but I’m disappointed that I wasn’t able to follow through with a single one of my goals. I grew as a developer, a gamer, and a person in general in 2013. Overall, despite the lack of goal completion, I think 2013 was a win for me. Here’s to an even better 2014!&lt;/p&gt;</description><author>Ben Overmyer's Site</author><pubDate>Sun, 29 Dec 2013 02:00:00 GMT</pubDate><guid isPermaLink="true">https://benovermyer.com/blog/2013/12/2013-year-in-review/</guid></item><item><title>Using Beyond Compare 4 Mac Beta with Git</title><link>https://donatstudios.com/Beyond-Compare-Mac-Beta-with-Git-in-OSX</link><description>&lt;p&gt;A little over a year ago I wrote a post about using &lt;a href="https://donatstudios.com/Beyond-Compare-with-Git-in-OSX"&gt;Beyond Compare on Mac&lt;/a&gt; via Wine.  A native version is now in beta and open to everyone! If you haven't tried Beyond Compare, I suggest you do. If you have you already know how awesome it is.&lt;/p&gt;
&lt;p&gt;I've been testing the Alpha for many months now and thought I'd throw together some instructions for getting it going with git.&lt;/p&gt;
&lt;p&gt;You can download the beta here: &lt;a href="https://www.scootersoftware.com/beta.php?zz=beta4_dl"&gt;http://www.scootersoftware.com/beta.php&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;After you have the App installed, the next step is to install the command line tools. All other steps require this.&lt;/p&gt;
&lt;p&gt;From the App, go to the &amp;quot;Beyond Compare&amp;quot; drop down menu and choose &amp;quot;Install Command Line Tools...&amp;quot;. It will then prompt you for your password. After this, you &lt;code&gt;bcomp file1.txt file2.txt&lt;/code&gt; &lt;/p&gt;
&lt;h2&gt;Using with Git CLI&lt;/h2&gt;
&lt;p&gt;The next step is to set it up as a git diff tool and merge tool. Git already has native support for it built in thanks to the Linux client having existed for years now.&lt;/p&gt;
&lt;p&gt;This can be achieved simply by running the following commands.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-console"&gt;$ git config --global diff.tool bc3
$ git config --global difftool.prompt false
$ git config --global merge.tool bc3&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This simple setup will leave &lt;code&gt;git diff&lt;/code&gt; and &lt;code&gt;git merge&lt;/code&gt; functioning as usual, as well as allowing you to use Beyond Compare for the task by doing &lt;code&gt;git difftool&lt;/code&gt; and &lt;code&gt;git mergetool&lt;/code&gt;. &lt;/p&gt;
&lt;p&gt;I &lt;strong&gt;highly&lt;/strong&gt; recommend giving Beyond Compare a go as a &lt;strong&gt;merge tool&lt;/strong&gt; next time you need to do a merge as it makes it wonderfully simple (when you understand whats going on with the 4 sections anyway).&lt;/p&gt;
&lt;h2&gt;Using with Tower 1.x&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Update:&lt;/strong&gt; Tower 2.0.6+ include built in support for Beyond Compare which the following instructions will interfere with.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Quit Tower&lt;/li&gt;
&lt;li&gt;Navigate to &lt;code&gt;~/Library/Application\ Support/Tower&lt;/code&gt; &lt;/li&gt;
&lt;li&gt;Create a &lt;code&gt;CompareScripts&lt;/code&gt; folder if one does not already exist.&lt;/li&gt;
&lt;li&gt;Download &lt;a href="https://jdon.at/3tWV"&gt;bcomp.sh&lt;/a&gt; and place it in the new &lt;code&gt;CompareScripts&lt;/code&gt; folder.&lt;/li&gt;
&lt;li&gt;Make it executable via &lt;code&gt;chmod +x bcomp.sh&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Download &lt;a href="https://jdon.at/J4cR"&gt;CompareTools.plist&lt;/a&gt; and place it in the &lt;code&gt;~/Library/Application\ Support/Tower&lt;/code&gt; directory. Rename / move any old version that might exist.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Using with SourceTree&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Open SourceTree&lt;/li&gt;
&lt;li&gt;Open &amp;quot;Preferences&amp;quot;  from the SourceTree menu&lt;/li&gt;
&lt;li&gt;Along the top bar, choose the &amp;quot;Diff&amp;quot; tab.&lt;/li&gt;
&lt;li&gt;Under External Diff / Merge
&lt;ol&gt;
&lt;li&gt;for Visual Diff Tool choose &lt;code&gt;Other&lt;/code&gt; then in the Diff Command enter &lt;code&gt;/usr/local/bin/bcomp&lt;/code&gt; and for Arguments enter &lt;code&gt;$LOCAL $REMOTE&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;For Merge Tool choose &lt;code&gt;Other&lt;/code&gt; and in Merge Command enter &lt;code&gt;/usr/local/bin/bcomp&lt;/code&gt; and for Arguments enter &lt;code&gt;$LOCAL $REMOTE $BASE $MERGED&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;&lt;/li&gt;
&lt;/ol&gt;</description><author>Donat Studios</author><pubDate>Fri, 27 Dec 2013 01:29:40 GMT</pubDate><guid isPermaLink="true">https://donatstudios.com/Beyond-Compare-Mac-Beta-with-Git-in-OSX</guid></item><item><title>On GitHub</title><link>https://captnemo.in/blog/2013/12/25/on-github/</link><description>&lt;p&gt;I am an Internet addict. And the website I’m most addicted to is called GitHub. GitHub is a social code hosting website that is totally awesome. Not your general run-of-the-mill awesome, but rather ass-kicking best-thing-in-the-world awesome. If GitHub was a ninja, it would be Po, defeating the evil clutches of SourceForge.&lt;/p&gt;

&lt;p&gt;When Linux was released a couple of decades ago, it was hosted on a university FTP server. Today, the days of FTP are way past, and unless your code is hosted on a code-sharing website like GitHub, it is as good as dead. The last decade has seen an explosive grown in software and the open source movement. Things like GitHub, Linux, Android, Facebook have been made possible due to the combine efforts of millions across the globe following the Unix philosophy of doing one thing well.&lt;/p&gt;

&lt;p&gt;Ahh, I digress. The point I’m trying to make is that it is very hard to explain to a layman how important GitHub  has been to the software community. It has been used as a collaboration platform for writers, law makers, governments, programmers, and even musicians. People have used its issue tracking feature to even plan weddings. And above all, people adore GitHub. It is one of the few startups that have been accorded God status in the community.&lt;/p&gt;

&lt;p&gt;The question here is what makes GitHub tick? As the largest hosting site for code, it obviously makes a huge impact just by virtue of being there. But its tide of features and innovative progression had made it a darling of all. For instance, their 1 click to fork feature has allowed people to contribute to any project so much easier.&lt;/p&gt;

&lt;p&gt;GitHub has an amazing User Experience, making sure it works  perfectly on all devices large or small. Their amazing support makes sure all of its users are happy as possible. Their regular meetups makes sure that GitHub is invested in the software community themselves. And their GitHub Store lets people invest back in GitHub by romoting them via Tees, stickers and  even laptop sleeves.&lt;/p&gt;

&lt;p&gt;My personal experience with GitHub has been overwhelmingly positive. People have stood beside GitHub even as they faced major issues, and for me that is indicative of a trust in GitHub that no money can buy. The GitHub API (which I’ve used more than once) lets developers create their own apps on top of GitHub which others can use to create even more awesome things.&lt;/p&gt;

&lt;p&gt;For me GitHub is more than just a coding website. It is a testament to creativity and the Hacker Way, reminding me every day that anything is possible.&lt;/p&gt;</description><author>Nemo's Home</author><pubDate>Wed, 25 Dec 2013 02:00:00 GMT</pubDate><guid isPermaLink="true">https://captnemo.in/blog/2013/12/25/on-github/</guid></item><item><title>Testing a NAS hard drive over FTP</title><link>https://anisse.astier.eu/testing-a-nas-hard-drive-over-ftp.html</link><description>&lt;p&gt;So I have this NAS made by my ISP, that does a lot of things; but recently, I
started having issues with its behavior. Recorded TV shows had lag/jitter while replaying, and the
same happened with other types of videos I put on it. I narrowed it down to …&lt;/p&gt;</description><author>Linux Engineer's random thoughts</author><pubDate>Fri, 20 Dec 2013 01:00:00 GMT</pubDate><guid isPermaLink="true">https://anisse.astier.eu/testing-a-nas-hard-drive-over-ftp.html</guid></item><item><title>Digging in with Foreign Tables</title><link>/2013/12/19/Digging-in-with-Foreign-Tables/</link><description>&lt;p&gt;I wrote a couple months back about exploring FDWs. Its become quite clear to me, despite still having ample room for improvement, they&amp;rsquo;re not getting enough attention. Foreign data wrappers are perhaps better thought of as a foreign table, or even better yet as a view into some remote data source. They don&amp;rsquo;t take care of auto-updating or syncing data, thats all up to you, but it gives you a straight forward mapping to work with remote data easier.&lt;/p&gt;
&lt;p&gt;The first step in working with FDWs is getting them setup. I wrote an earlier post on how to do this manually. And if you&amp;rsquo;re on Heroku theres an even easier solution if you want to setup a mapping entirely from one DB to another. The &lt;a href=""&gt;pg-extras CLI plugin&lt;/a&gt; has a command &lt;code&gt;fdwsql&lt;/code&gt; which will generate the SQL to map all the tables for you. To run it simply specify the prefix app and database:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;heroku pg:fdwsql yourprefix APP::DATABASE_URL
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will generate a lot of SQL. From here you&amp;rsquo;ll want to connect to the database where you want those foreign tables to be visible. Then run all the SQL. This will create all the foreign tables, this will mostly look just like another view or table to you in &lt;code&gt;\d&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="tips-on-working-with-them"&gt;
&lt;div&gt;
Tips on working with them
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;For the most part you can work with your foreign tables just like any other view or table. You can insert into them, read from them, join against them. Though currently foreign tables have some performance limitations, such as when joining it may return a lot more data than you expect then join. To make your performance a bit more ideal you can follow a few basic principles.&lt;/p&gt;
&lt;p&gt;Lets look at some example tables to highlight this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt; \d
users
todos
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this case users is local and the todos are a foreign table. Looking at each of the schemas we have something like you might expect:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt; \d users
Table &amp;quot;public.users&amp;quot;
Column | Type | Modifiers
-------------+-----------------------------+-----------
id | integer | not null
email | text |
created_at | timestamp without time zone |
Indexes:
&amp;quot;users_pkey&amp;quot; PRIMARY KEY, btree (id)
&amp;quot;users_created&amp;quot; btree (created_at)
&amp;gt; \d todo
Foreign Table &amp;quot;public.todo&amp;quot;
Column | Type | Modifiers
-------------+-----------------------------+-----------
id | integer | not null
user_id | integer |
desc | text |
created_at | timestamp without time zone |
status | boolean |
Indexes:
&amp;quot;users_pkey&amp;quot; PRIMARY KEY, btree (id)
&amp;quot;todo_created&amp;quot; btree (created_at)
&lt;/code&gt;&lt;/pre&gt;</description><author>CRAIG KERSTIENS</author><pubDate>Thu, 19 Dec 2013 22:55:56 GMT</pubDate><guid isPermaLink="true">/2013/12/19/Digging-in-with-Foreign-Tables/</guid></item><item><title>A Practical Exercise in Web Scraping</title><link>https://www.petekeen.net/a-practical-exercise-in-web-scraping/</link><description>&lt;p&gt;Yesterday a friend of mine linked me to a fictional web serial that he was reading and enjoying, but could be enjoying more if it was available as a Kindle book. The author, as of yet, hasn't made one available and has asked that fan-made versions not be linked publicly.
That said, it's a very long story and would be much easier to read using a dedicated reading app, so I built my own Kindle version to enjoy. This post is the story of how I built it.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h3&gt;Step 1: Source Analysis&lt;/h3&gt;
&lt;p&gt;The first step of any kind of web scraping is to understand your target. Here's what the first blog post looks like (with different content):&lt;/p&gt;
&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"entry-title"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;The Whale&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"entry-content"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Next Chapter"&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"http://example.com/the/next/chapter"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      Next Chapter
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;"And what tune is it ye pull to, men?"&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;"A dead whale or a stove boat!"&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    More and more strangely and fiercely glad and approving, grew the
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;     countenance of the old man at every shout; while the mariners
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;     began to gaze curiously at each other, as if marvelling how it
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;     was that they themselves became so excited at such seemingly
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;     purposeless questions.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    But, they were all eagerness again, as Ahab, now half-revolving in
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    his pivot-hole, with one hand reaching high up a shroud, and
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    tightly,   almost convulsively grasping it, addressed them
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    thus:&lt;span class="ni"&gt;&amp;amp;mdash;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Next Chapter"&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"http://example.com/the/next/chapter"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      Next Chapter
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;After browsing around I found a table of contents, but since all of the posts were linked together with &amp;quot;Next Chapter&amp;quot; pointers it seemed easier to just walk those. The other interesting thing here is that there's a comment section that I didn't really care about.&lt;/p&gt;
&lt;h3&gt;Step 2: Choose Your Tools&lt;/h3&gt;
&lt;p&gt;The next stage of web scraping is to choose the appropriate tools. I started with just &lt;code&gt;curl&lt;/code&gt; and probably could have gotten pretty far I knew the DOM futzing I wanted to do would require something more powerful later on. At the moment Ruby is where I turn to for most things, so naturally I picked &lt;a href="http://nokogiri.org/"&gt;Nokogiri&lt;/a&gt;. The first example on the Nokogiri docs page is actually a web scraping example, and that's basically what I cribbed from. Here's the initial version of the scraping function:&lt;/p&gt;
&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;scrape_page&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;doc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Nokogiri&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HTML&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encoding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'utf-8'&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'div.entry-content'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'h1.entry-title'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;next_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'a[title="Next Chapter"]'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;next_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'href'&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;remove&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="ss"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="ss"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="ss"&gt;next_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;next_url&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Ruby has a built-in capability for opening URLs as readable files with the &lt;a href="http://ruby-doc.org/stdlib-2.0.0/libdoc/open-uri/rdoc/OpenURI.html"&gt;&lt;code&gt;open-uri&lt;/code&gt;&lt;/a&gt; standard library module. Because of various problems with Nokogiri's unicode handling I learned about in previous web scraping experiences, the best thing to do is to pass a string to Nokogiri instead of passing it the actual IO handle. Setting the encoding explicitly is also a best practice.&lt;/p&gt;
&lt;p&gt;Then it's a simple matter of using Nokogiri's &lt;code&gt;css&lt;/code&gt; selector method to pick out the nodes we're interested in and return them to the caller. The idea is that, since each page is linked to it's successor we can just follow the links.&lt;/p&gt;
&lt;h3&gt;Step 3: The Inevitable Bugfix Iteration&lt;/h3&gt;
&lt;p&gt;Of course it's never that easy. Turns out these links are generated by hand, and across hundreds of blog posts of course there will be some inconsistencies. At some point the author stopped using the title attribute. Instead of using the super clever CSS selector &lt;code&gt;a[title=&amp;quot;Next Chapter&amp;quot;]&lt;/code&gt; I had to switch to grabbing all of the anchor tags and selecting based on the text:&lt;/p&gt;
&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"Next Chapter"&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;next_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'href'&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;remove&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This works great, except that in a few cases there's some whitespace in the text of the anchor node, so I had to switch to a regex:&lt;/p&gt;
&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=~&lt;/span&gt; &lt;span class="sr"&gt;/\s*Next Chapter\s*/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;next_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'href'&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;remove&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Another sticking point was that sometimes (but not always) the author used non-ASCII in their URLs. The trick for dealing with possibly-escaped URLs is to check to see if decoding does anything. If it does, it's already escaped and shouldn't be messed with:&lt;/p&gt;
&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;escape_if_needed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="no"&gt;URI&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;unescape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;URI&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;escape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;url&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Step 4: Repeat As Necessary&lt;/h3&gt;
&lt;p&gt;Now that we can reliably scrape one URL, it's time to actually follow the links:&lt;/p&gt;
&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="ss"&gt;:scrape&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;next_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'http://example.com/the/first/chapter/'&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;sh&lt;/span&gt; &lt;span class="s2"&gt;"mkdir -p output"&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;next_url&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;next_url&lt;/span&gt; &lt;span class="o"&gt;=~&lt;/span&gt; &lt;span class="sr"&gt;/example.com/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="no"&gt;STDERR&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;puts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;next_url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;scrape_page&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;next_url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;next_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:next_url&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:title&lt;/span&gt;&lt;span class="o"&gt;].&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"output/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="nb"&gt;sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'%04d'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.html"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"w+"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:title&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:content&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="nb"&gt;sleep&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is pretty simple. Set some initial state, make a directory to put the scraped pages, then follow each link in turn and write out the interesting content to sequential files. Note that file names are all four digit numbers so that the sequence is preserved even with lexicographical sorting.&lt;/p&gt;
&lt;h3&gt;Step 5: Actually Build The Book&lt;/h3&gt;
&lt;p&gt;At first I wanted to use &lt;a href="http://www.docverter.com"&gt;Docverter&lt;/a&gt;, my project that mashes up &lt;a href="http://johnmacfarlane.net/pandoc/"&gt;pandoc&lt;/a&gt; and &lt;a href="http://calibre-ebook.com/"&gt;calibre&lt;/a&gt; for building rich documents (including ebooks) out of plain text files. I tried the demo installation first, but that runs on Heroku and repeatedly ran out of memory so I tried a local installation. That timed out (did I mention that this web serial is also very long?) so instead I just ran &lt;code&gt;pandoc&lt;/code&gt; and &lt;code&gt;ebook-convert&lt;/code&gt; directly:&lt;/p&gt;
&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="ss"&gt;:build&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"input.html"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"w+"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="no"&gt;Dir&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'output/*.html'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sort&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="no"&gt;STDERR&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Running conversion..."&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;sh&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"pandoc --standalone --output=output.epub --from=html --to=epub --epub-metadata=metadata.xml --epub-stylesheet=epub_stylesheet.css input.html"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;sh&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"ebook-convert output.epub output.mobi"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Pandoc can take multiple input files but it was easier to manage one input file on the command line. The stylesheet and metadata xml files are lifted directly from the &lt;a href="https://github.com/peterkeen/mmp-builder"&gt;&lt;code&gt;mmp-builder&lt;/code&gt;&lt;/a&gt; project that I use to build &lt;a href="https://www.petekeen.net/mastering-modern-payments"&gt;Mastering Modern Payments&lt;/a&gt;, with appropriate authorship information changes.&lt;/p&gt;
&lt;h3&gt;In Conclusion, Please Don't Violate Copyright&lt;/h3&gt;
&lt;p&gt;Making your own ebooks is not hard with the tools that are out there. It's really just a matter of gluing them together with an appropriate amount of duct tape and bailing twine.&lt;/p&gt;
&lt;p&gt;That said, distributing content that isn't yours without permission directly affects authors and platform shifting like this is sort of a gray area. The author of this web serial seems to be fine with fan-made ebooks editions as long as they don't get distributed, so that's why I anonymized this post.&lt;/p&gt;</description><author>Pete Keen</author><pubDate>Sun, 15 Dec 2013 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.petekeen.net/a-practical-exercise-in-web-scraping/</guid></item><item><title>Quora answer about writing a search engine</title><link>https://boyter.org/2013/12/quora-answer-writing-search-engine/</link><description>&lt;p&gt;The following I posted on Quora in response to the question &lt;a href="http://www.quora.com/Search-Engines/I-am-planning-to-make-a-small-scale-search-engine-on-my-local-system-but-I-dont-know-from-where-to-start"&gt;&amp;ldquo;I am planning to make a small scale search engine on my local system, but I don&amp;rsquo;t know from where to start?&amp;rdquo;&lt;/a&gt;. It&amp;rsquo;s a reasonable answer so like my &lt;a href="http://www.boyter.org/2009/01/athlon-64-pc-512-ram-hold-home-server/"&gt;Mahalo&lt;/a&gt; one I thought I would make a copy for myself.&lt;/p&gt;
&lt;p&gt;I agree with Wolf Garbe and that you are better off in your case starting with existing technologies, have a look at &lt;!-- raw HTML omitted --&gt;&lt;a href="http://yacy.net/"&gt;http://yacy.net/&lt;/a&gt;&lt;!-- raw HTML omitted --&gt; and SphinxSearch as well. However if you are doing this to learn and not just deliver a product I can provide a few links for you.&lt;/p&gt;</description><author>Ben E. C. Boyter</author><pubDate>Fri, 06 Dec 2013 03:31:30 GMT</pubDate><guid isPermaLink="true">https://boyter.org/2013/12/quora-answer-writing-search-engine/</guid></item><item><title>Simple Git-backed Microsites</title><link>https://www.petekeen.net/simple-git-backed-microsites/</link><description>&lt;p&gt;A few days ago I built a new tool I'm calling &lt;a href="https://github.com/peterkeen/sites"&gt;Sites&lt;/a&gt;. It builds on top of git-backed wikis powered by GitHub's &lt;a href="https://github.com/gollum/gollum"&gt;Gollum&lt;/a&gt; system and lets me build and deploy microsites in the amount of time it takes me to create a CNAME.&lt;/p&gt;
&lt;p&gt;Something that I've wanted for a very long time is a way to stand up new websites with little more than a CNAME and a few clicks. I've gone through a few rounds of trying to make that happen but nothing ever stuck. Furthest progressed was a Rails app exclusively hosting &lt;a href="https://github.com/comfy/comfortable-mexican-sofa"&gt;Comfortable Mexican Sofa&lt;/a&gt;, a simple CMS engine. I never ended up putting any sites on it, though.&lt;/p&gt;
&lt;p&gt;GitHub's Pages are of course one of the best answers, but I'm sticking to my self-hosting, built-at-home guns.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h3&gt;A Short Code Tour&lt;/h3&gt;
&lt;p&gt;The code is split up into four distinct parts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://github.com/peterkeen/sites/blob/master/viewer.rb"&gt;viewer&lt;/a&gt; is a &lt;a href="http://www.sinatrarb.com/"&gt;Sinatra&lt;/a&gt; app that presents wiki content as web pages. It also can serve static assets right from the wiki repo and caches everything in an in-memory LRU cache. If you have a file &lt;code&gt;layout.erb&lt;/code&gt; it will wrap the pages in that layout, otherwise it'll pass the content straight to the browser.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://github.com/peterkeen/sites/blob/master/manager.rb"&gt;manager&lt;/a&gt; is another Sinatra app that allows me to create new sites on the fly. Because a site is just a Gollum wiki and a Gollum wiki is just a git repo, it just has to create a git repo at the right place and do a redirect. It bakes in HTTP Basic Auth so other people can't create sites all willy nilly.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;some small &lt;a href="https://github.com/peterkeen/sites/blob/master/gollum_ext.rb"&gt;extensions to Gollum&lt;/a&gt; add basic auth and override the method Gollum uses to find the correct wiki repo. By default Gollum wants to be told exactly where the repo is in a class-level Sinatra setting, but that doesn't work when things are dynamic.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;a &lt;a href="https://github.com/peterkeen/sites/blob/master/middleware.rb"&gt;Rack middleware&lt;/a&gt; ties it all together. The middleware has three jobs. If the incoming hostname maps to a CNAME that one of the sites has declared in a special wiki page named &lt;code&gt;cnames&lt;/code&gt;, pass the request to the viewer app. Otherwise, either pass the request to Gollum for existing sites or to the manager to create a new site. The hard work of building the CNAME to site mapping is cached for a short period of time to minimize disk hits.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The neatest part about this setup is that, since sites are just git repos, I can clone the repo to my laptop and work with it in Emacs instead of directly editing in Gollum if I don't want to. This also lets me easily add asset files and layouts.&lt;/p&gt;
&lt;h3&gt;Demo Time&lt;/h3&gt;
&lt;p&gt;Here's what the manager app presents when you just go to &lt;code&gt;sites.bugsplat.info&lt;/code&gt;:&lt;/p&gt;
&lt;img src="https://d2s7foagexgnc2.cloudfront.net/files/9eb9e104328da51fff72/manager.png" /&gt;
&lt;p&gt;I'm the only one that's ever going to be looking at this, so it doesn't really need to be anything fancy.&lt;/p&gt;
&lt;p&gt;If you click on one of those links, you'll get the familiar Gollum interface:&lt;/p&gt;
&lt;img src="https://d2s7foagexgnc2.cloudfront.net/files/210052500230dac13a38/gollum.png" /&gt;
&lt;p&gt;To create a new site, just append its name to &lt;code&gt;sites.bugsplat.info&lt;/code&gt;:&lt;/p&gt;
&lt;img src="https://d2s7foagexgnc2.cloudfront.net/files/03f53229df6da8920ab3/create.png" /&gt;
&lt;p&gt;Notice the new little button. Clicking that creates the repo and sends you back to Gollum to populate the home page:&lt;/p&gt;
&lt;img src="https://d2s7foagexgnc2.cloudfront.net/files/7cba47bcfdc430c509f4/editor.png" /&gt;
&lt;p&gt;If you want to see how the demo site is put together, it's running &lt;a href="http://demosite.petekeen.net"&gt;here&lt;/a&gt; and the source is on GitHub &lt;a href="https://github.com/peterkeen/demosite"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Installation&lt;/h3&gt;
&lt;p&gt;I built Sites to fit my infrastructure which is a delightful bastardization of &lt;a href="http://12factor.net/"&gt;12 Factor&lt;/a&gt; so I haven't tried installing it elsewhere. I know it won't run properly on Heroku because it needs to be able to put the git repos in a persistent place, but it might work well as a Docker image. If you get it running somewhere please let me know and I'll link it here.&lt;/p&gt;</description><author>Pete Keen</author><pubDate>Thu, 05 Dec 2013 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.petekeen.net/simple-git-backed-microsites/</guid></item><item><title>Go Binary Sizes Are Growing out of Control</title><link>https://donatstudios.com/Golang-Binary-Sizes</link><description>&lt;p&gt;&lt;strong&gt;Update 2019-07-17:&lt;/strong&gt; I have released a follow up post &lt;a href="https://donatstudios.com/Golang-Binary-Sizes-Part-2"&gt;Go Binary Sizes Are Relatively Stable&lt;/a&gt; which speaks on what's happened in the time since this was published.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; This is not intended as a &amp;quot;Go Sucks&amp;quot; post. I &lt;strong&gt;&lt;em&gt;love&lt;/em&gt;&lt;/strong&gt; Go. I &lt;strong&gt;am not&lt;/strong&gt; saying the developers are &lt;em&gt;lazy&lt;/em&gt; or &lt;em&gt;dumb&lt;/em&gt; or any of the things Reddit has implied. I am not implying I could build a better compiler. Rob Pike and the Go team are geniuses whom I look up to. The tone of this was intended to be a light &amp;quot;man I wish binaries were smaller&amp;quot; but that is not how Reddit seemed to take it.  Also, the title is &lt;a href="https://en.wikipedia.org/wiki/Hyperbole"&gt;hyperbole&lt;/a&gt; and not meant to literally mean no one has control over it.&lt;/p&gt;
&lt;p&gt;I started toying with Go about two years ago before the 1.0 release. I love the language, it meets me in a comfortable place between C and a scripting languages like PHP. I find it fun to write and an easy way to bust out performant code quickly. The binaries for a simple &lt;code&gt;Hello, 世界&lt;/code&gt; were large  though, around 55KB.  I figured this was something the Go team would work out later; it is a lot of space just to send a string to standard out.&lt;/p&gt;
&lt;p&gt;With the release of Go 1.0 though, I noticed the binary size had grown slightly to 242KB. Discouraging but livable. I was quite hopeful with the 1.1 release, but it had actually nearly doubled in size coming out to a whopping 405KB. Then this morning I compiled it again in the newly release 1.2 and to my horror the ever simple program came out to 557KB.&lt;/p&gt;
&lt;p&gt;What on earth is going on in that binary? I am no expert on compilers but I would imagine the vast majority of what is in there is completely unnecessary and could be optimized out.  Changing the script to &amp;quot;Hello, World&amp;quot; rather than Go's flaunted unicode version has zero effect on binary size, as one would expect but I was hopeful.&lt;/p&gt;
&lt;p&gt;For reference the complete source of my &lt;code&gt;Hello, 世界&lt;/code&gt; follows. Using &lt;code&gt;fmt&lt;/code&gt; comes out even larger.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;package main

func main() {
    println("Hello, 世界")
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here is a collection of binaries compiled in different versions of Go.  Notice how imgavg grows from 1.9MB to 3.7MB. My applications are all small tools.  I can only imagine the effect this has an already large application.&lt;/p&gt;
&lt;img alt="Golang binary sizes grow with each release" src="https://donatstudios.com/assets/48/golangBinarySizes.png" style="display: block; margin: 0 auto; border: 1px solid #aaa;" /&gt;
&lt;p&gt;I really hope something is done about this. I would love to see tiny (&amp;lt;32KB) binaries from Go but right now that doesn't seem to be a priority for them. The fact that &lt;code&gt;Hello, 世界&lt;/code&gt; fills half a floppy means the language is not be useful for lighter or embedded environments.&lt;/p&gt;
&lt;p&gt;As noted in the header there is an addendum to this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://donatstudios.com/Golang-Binary-Sizes-Part-2"&gt;Go Binary Sizes Are Relatively Stable&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description><author>Donat Studios</author><pubDate>Tue, 03 Dec 2013 01:25:43 GMT</pubDate><guid isPermaLink="true">https://donatstudios.com/Golang-Binary-Sizes</guid></item><item><title>Simulating a Market in Ruby</title><link>https://www.petekeen.net/simulating-a-market-in-ruby/</link><description>&lt;p&gt;Trading markets of all kinds are in the news pretty much continuously. The flavor of the week is of course the Bitcoin markets but equity and bond markets are always in the background. Just today there is an article on Hacker News about &lt;a href="http://edmarkovich.blogspot.com/2013/12/why-i-dont-trade-stocks-and-probably.html"&gt;why you shouldn't invest in the stock market&lt;/a&gt;. I've participated in markets in one way or another for about a decade now but I haven't really understood how they work at a base level. Yesterday I built a &lt;a href="https://github.com/peterkeen/trading"&gt;tiny market simulator&lt;/a&gt; to fix that.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;Basic Concepts&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;tl;dr&lt;/strong&gt;: a market for a commodity is two sorted lists, one of prices someone is willing to pay and another of prices is willing to accept.&lt;/p&gt;
&lt;p&gt;A market exists to enable people to trade something, whether that be shares of stock or pork futures contracts or cryptocurrency tokens. In modern markets the fundamental core is called the &lt;strong&gt;order book&lt;/strong&gt;. This is an open listing of offers to buy and sell a given commodity at some price. For example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Pete has 10 shares of TSLA and is willing to sell them at $10 per share&lt;/li&gt;
&lt;li&gt;Andrew would like to buy 10 shares of TSLA and is willing to pay $9.99 per share&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The order book looks like this:&lt;/p&gt;
&lt;table class="table table-condensed table-striped table-bordered"&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Buy&lt;/th&gt;
      &lt;th&gt;Sell&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;10 @ $9.99&lt;/td&gt;
      &lt;td&gt;10 @ $10.00&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Emily can see the order book because it's public and open. She also has 10 shares of TSLA and decides to match Andrew's bid. She puts in a sell order at $9.99. Now the order book looks like this:&lt;/p&gt;
&lt;table class="table table-condensed table-striped table-bordered"&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Buy&lt;/th&gt;
      &lt;th&gt;Sell&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;10 @ $9.99&lt;/td&gt;
      &lt;td&gt;10 @ $9.99&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&amp;nbsp;&lt;/td&gt;
      &lt;td&gt;10 @ $10.00&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Hold on for a second. Why did Emily's offer bump Pete's down in the list? Each column of the order book is sorted by &lt;em&gt;price&lt;/em&gt; and then by &lt;em&gt;time&lt;/em&gt;. Buy orders are sorted highest price first, sell orders are sorted lowest first.&lt;/p&gt;
&lt;p&gt;Trades only happen at the top of the order book. Because there's a match at the top of the order book the market executes the trade. Emily and Andrew exchange $9.99 for 10 shares of TSLA and Pete is left waiting for another suitor to come along and match his price.&lt;/p&gt;
&lt;p&gt;This is how the notional price of an equity or contract is determined by the market. Each time a trade happens that price gets broadcast to the world as &lt;em&gt;the&lt;/em&gt; price.&lt;/p&gt;
&lt;p&gt;These orders are called &lt;em&gt;limit orders&lt;/em&gt; because they say &amp;quot;buy TSLA for $9.99 but &lt;em&gt;no more&lt;/em&gt;&amp;quot; or &amp;quot;sell TSLA for $10 but &lt;em&gt;no less&lt;/em&gt;.&amp;quot; In our example, if Fred didn't look at the book and decides to put in a buy for $10.05, he'll get his 10 shares at $10.00 from Pete.&lt;/p&gt;
&lt;p&gt;Limit orders are the fundamental building block of a market. There are other order types but they're almost always built using one or more limit orders. One notable exception is a &lt;em&gt;market&lt;/em&gt; order which orders a specific quantity at whatever the current market price is. (thanks for the corrections, &lt;a href="https://news.ycombinator.com/item?id=6834599"&gt;minimax&lt;/a&gt;!)&lt;/p&gt;
&lt;h2&gt;Building a Simulation&lt;/h2&gt;
&lt;p&gt;Almost all of the above I learned by reading articles about basic trades and actually building a simulation. I decided to practice &lt;a href="http://tom.preston-werner.com/2010/08/23/readme-driven-development.html"&gt;readme driven development&lt;/a&gt; and test driven development for this project, mainly because RDD helps me organize my thoughts and TDD helps me keep the programming going in the right direction. My first pass at the simulation was... well I guess you could say terrible. I completely misunderstood how the order book worked so I built this thing where &lt;em&gt;any&lt;/em&gt; price match would execute a trade, not just at the top of the book. You can see that in &lt;a href="https://github.com/peterkeen/trading/tree/f713308de2965df20a335e192dbf2c15648fe301"&gt;the first working commit of my simulator&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;After thinking about the problem really hard and reading about how order books are actually supposed to work I did some research and came across the &lt;a href="https://github.com/kanwei/algorithms"&gt;algorithms&lt;/a&gt; gem. Among other awesome things, this gem includes several implementations of a data structure named the &lt;a href="http://en.wikipedia.org/wiki/Red%E2%80%93black_tree"&gt;Red Black Tree&lt;/a&gt;. This structure keeps it's keys sorted during insert and removal, which is perfect for the order book. Each order book consists of a pair of these tree maps, one for buy and one for sell. The keys are the actual &lt;a href="https://github.com/peterkeen/trading/blob/master/lib/trading/order.rb"&gt;Order object&lt;/a&gt; and the value is just &lt;code&gt;true&lt;/code&gt;, since we only really care about the keys.&lt;/p&gt;
&lt;p&gt;The core of the simulation is submitting orders and checking to see if there's a match. Submitting is fairly trivial:&lt;/p&gt;
&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;submit_order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;order_type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="ss"&gt;:buy&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;buy_map&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;sell_map&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Because the book is kept sorted, determining a match is also relatively straightforward. Here's the code in the book:&lt;/p&gt;
&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;match?&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;buy_map&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;sell_map&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;sell_map&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;min_key&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;match?&lt;/span&gt; &lt;span class="n"&gt;buy_map&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max_key&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We just have get the top of each column in the order book and compare them. The tree map takes care that we can efficiently get both the min and max key.&lt;/p&gt;
&lt;p&gt;Here is the implementation of &lt;code&gt;Order#match?&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;match?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;price_match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;order_type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="ss"&gt;:buy&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;order_type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="ss"&gt;:sell&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;commodity&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;commodity&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;quantity&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;quantity&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;price_match&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We first determine how to compare the price and then do some sanity checking on commodity and quantity. This simulator is limited to trading orders of exactly the same quantity but real markets can fulfill orders in more complicated ways.&lt;/p&gt;
&lt;h2&gt;Learning&lt;/h2&gt;
&lt;p&gt;So what did I learn yesterday afternoon? A few things. First, readme driven development and test driven development go hand in hand when building a project like this. Writing (and rewriting) the readme helped to clarify what I actually wanted to build, and writing tests both before and after building the implementation helped immensely with keeping the goal clear and my implementation correct.&lt;/p&gt;
&lt;p&gt;Second, I gained a &lt;em&gt;much&lt;/em&gt; better understanding of how markets work on a basic level. Actually getting in and building something seems to cement the ideas a whole lot better than just reading about them.&lt;/p&gt;</description><author>Pete Keen</author><pubDate>Mon, 02 Dec 2013 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.petekeen.net/simulating-a-market-in-ruby/</guid></item><item><title>Making HackerTray</title><link>https://captnemo.in/blog/2013/11/28/making-hackertray/</link><description>&lt;p&gt;A few days back, I found the excellent &lt;a href="http://hackerbarapp.com/"&gt;HackerBarApp&lt;/a&gt; via Hacker News. Hacker News,
for those of you who don’t know, is  tech news website run by &lt;a href="http://ycombinator.com"&gt;YCombinator&lt;/a&gt;.
Hacker Bar was the simplest way of accessing HN stories that I’d ever seen. Unfortunately,
it was only for Mac (made using &lt;a href="http://www.rubymotion.com/"&gt;rubymotion&lt;/a&gt;) and even though the source was available,
it was of no use to me as a Linux User.&lt;/p&gt;

&lt;p&gt;I decided to make a clone of Hacker Bar that would work on Linux. My first choice of the
stack was &lt;a href="https://github.com/rogerwang/node-webkit"&gt;node-webkit&lt;/a&gt;, an application framework that allows you to build cross-platform
applications using HTML, CSS, JS, and modules from the node.js ecosystem. After reading a lot
about node-webkit, I figured out that building this application in node-webkit (as it stands)
would not be possible. Or rather, it would not work under Ubuntu and its derivatives because
of lacking appindicator support in &lt;a href="https://github.com/rogerwang/node-webkit/issues/1087"&gt;node-webkit&lt;/a&gt;. More details &lt;a href="https://groups.google.com/d/topic/node-webkit/FeX7YYwK8jI/discussion"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The next obvious language and stack of choice was Python + Gtk. I’d already played a little bit
with Gtk and Python some time back, so I knew the basics. But I’d never build a real application
with PyGtk, just toys and small scripts. I found a &lt;a href="http://www.eurion.net/python-snippets/snippet/Create%20an%20Application%20Indicator.html"&gt;basic skeleton app&lt;/a&gt;
that was written for AppIndicator and modified it somewhat to form the base of HackerTray.&lt;/p&gt;

&lt;p&gt;The next challenge I faced was keeping the check boxes always checked despite of any number
of clicks after the first. That is, we don’t want any menu item to be “un-checked” at
any moment. A basic idea is to do this (partial code):&lt;/p&gt;

&lt;figure class="highlight"&gt;&lt;pre&gt;&lt;code class="language-python"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;widget&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
	&lt;span class="nf"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;widget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_active&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
		&lt;span class="n"&gt;widget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_active&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
	&lt;span class="n"&gt;webbrowser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;widget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;addItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
	&lt;span class="c1"&gt;#create a new CheckMenuItem (i)
&lt;/span&gt;	&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;activate&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;However, this does not work as expected, because the &lt;code class="language-plaintext highlighter-rouge"&gt;widget.set_active()&lt;/code&gt; call also results
in the &lt;code class="language-plaintext highlighter-rouge"&gt;activate&lt;/code&gt; event being fired, which ultimately calls &lt;code class="language-plaintext highlighter-rouge"&gt;open&lt;/code&gt;. This means on a click to
an unchecked menuItem, the open function is called twice. This results in the browser opening
the link twice.&lt;/p&gt;

&lt;p&gt;As a workaround, I disabled the event handler in case it is a checked menuItem:&lt;/p&gt;

&lt;figure class="highlight"&gt;&lt;pre&gt;&lt;code class="language-python"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;widget&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
	&lt;span class="nf"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;widget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_active&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
		&lt;span class="n"&gt;wiget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;disconnect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;widget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;signal_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
		&lt;span class="n"&gt;widget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_active&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
		&lt;span class="n"&gt;widget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;signal_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;widget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;activate&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
	&lt;span class="n"&gt;webbrowser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;widget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;addItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
	&lt;span class="c1"&gt;#create a new CheckMenuItem (i)
&lt;/span&gt;	&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;activate&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
	&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;signal_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;activate&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The next thing I worked on was a persistent memory for the app. In a nutshell, I needed to
make sure that the tick on an item remained there, even if the app was restarted. This meant
writing a list of all the “viewed” items into a file. After looking at &lt;a href="http://docs.python.org/lib/module-shelve.html"&gt;shelve&lt;/a&gt; for a
bit, I just &lt;a href="https://github.com/captn3m0/hackertray/commit/167397e51f665847400617935653027ebba0b396"&gt;rolled my own implementation&lt;/a&gt;
, based on storing the data into &lt;code class="language-plaintext highlighter-rouge"&gt;~/.hackertray.json&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;After that I worked on packaging the app into a python package, so that it could be easily installed.
The &lt;a href="https://python-packaging.readthedocs.io/en/latest/"&gt;python packaging tutorial&lt;/a&gt; was an easy to use guide
that let me &lt;a href="https://github.com/captn3m0/hackertray/commit/6ca735ea089d9189f152612ed016d31d72f9c36b"&gt;create the package&lt;/a&gt;
easily and push it to the &lt;a href="https://pypi.python.org/pypi/hackertray/"&gt;Python Package Index&lt;/a&gt;. A few issues in the package
were found, and were fixed quickly thanks to &lt;a href="https://github.com/captn3m0/hackertray/pull/7"&gt;the pull request&lt;/a&gt; by &lt;a href="https://github.com/brunal"&gt;@brunal&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;After improving the README a bit, I posted about it on Hacker News, where it failed to get any traction. I re-tried with a link
to the HackerTray website, and that fell flat as well. It was on the next day, when I &lt;a href="https://news.ycombinator.com/item?id=6819042"&gt;posted it to HN&lt;/a&gt;
for the third time, that it took off. After 50 or so upvotes, I found that my instance refused to run because it had
hit the API Rate Limit on the excellent &lt;a href="https://node-hnapi.herokuapp.com/"&gt;node-hnapi&lt;/a&gt;. I quickly &lt;a href="https://github.com/captn3m0/hackertray/commit/8f0b08137b6c4b05ebe63a33029a36c068cfbc05"&gt;pushed a fix&lt;/a&gt;
that used a list of servers to hit as fallback in case it crossed the Rate Limits.&lt;/p&gt;

&lt;p&gt;After a lot of feedback from HN, I started work on a &lt;code class="language-plaintext highlighter-rouge"&gt;node-webkit&lt;/code&gt; based clone of hackertray for Windows. I should be able to release
it in a few more days, if nothing else crops up. Keep watching this space for info. If you have any queries, just &lt;a href="https://github.com/captn3m0/hackertray/issues/new"&gt;file an issue on GitHub&lt;/a&gt;
or &lt;a href="mailto:capt.n3m0@gmail.com"&gt;contact me&lt;/a&gt;.&lt;/p&gt;</description><author>Nemo's Home</author><pubDate>Thu, 28 Nov 2013 02:00:00 GMT</pubDate><guid isPermaLink="true">https://captnemo.in/blog/2013/11/28/making-hackertray/</guid></item><item><title>University Presentation</title><link>https://tomforb.es/blog/university-presentation/</link><description>So I did a presentation on Information Security at University today. I think it went rather well, however I couldn’t show a couple of the demonstrations due to some SkyDrive files only being available online. That sucked because those were my best demonstrations, but overall I was happy. A few peopl...</description><author>Tom Forbes</author><pubDate>Wed, 27 Nov 2013 20:19:28 GMT</pubDate><guid isPermaLink="true">https://tomforb.es/blog/university-presentation/</guid></item><item><title>PostgreSQL - It's kind've a nifty database</title><link>https://www.brightball.com/articles/postgresql-its-kindve-a-nifty-database</link><description>This is a presentation I recently gave to provide an overview of PostgreSQL and some of it's excellent features, including full-text search, multiple built in datatypes, data compression and extensions.  Also, Morgan Freeman is narrating. You're welcome.</description><author>Brightball Articles</author><pubDate>Wed, 27 Nov 2013 11:42:00 GMT</pubDate><guid isPermaLink="true">https://www.brightball.com/articles/postgresql-its-kindve-a-nifty-database</guid></item><item><title>My Dimension in Rift</title><link>https://benovermyer.com/blog/2013/11/my-dimension-in-rift/</link><description>&lt;p&gt;I started playing Rift again, after trying out Everquest 2 and Guild Wars 2 also. Until Everquest Next Landmark comes out, Rift may be my fantasy MMO of choice. We’ll see.&lt;/p&gt;</description><author>Ben Overmyer's Site</author><pubDate>Wed, 27 Nov 2013 02:00:00 GMT</pubDate><guid isPermaLink="true">https://benovermyer.com/blog/2013/11/my-dimension-in-rift/</guid></item><item><title>Hack things, acquire clothing.</title><link>https://tomforb.es/blog/hack-things-acquire-clothing/</link><description>I discovered an XSS flaw in a website a month ago and reported it to the owners. As a thank you they sent me a hat, a rather large american sized t-shirt and a pair of “DeFeet” socks (guaranteed to stay cooler and drier than any other brand). I didn’t expect them to ship something like that overseas...</description><author>Tom Forbes</author><pubDate>Fri, 22 Nov 2013 18:59:42 GMT</pubDate><guid isPermaLink="true">https://tomforb.es/blog/hack-things-acquire-clothing/</guid></item><item><title>Books I read in 2013</title><link>https://nindalf.com/posts/books-in-2013/</link><description>A list of books read in 2013, including fantasy, historical fiction, and science fiction.</description><author>Krishna's blog</author><pubDate>Tue, 19 Nov 2013 02:00:00 GMT</pubDate><guid isPermaLink="true">https://nindalf.com/posts/books-in-2013/</guid></item><item><title>The best Postgres feature you're not using – CTEs aka WITH clauses</title><link>/2013/11/18/best-postgres-feature-youre-not-using/</link><description>&lt;p&gt;SQL by default isn&amp;rsquo;t typically friendly to dive into, and especially so if you&amp;rsquo;re reading someone else&amp;rsquo;s already created queries. For some reason most people throw out principles we follow in other languages &lt;a href="http://www.craigkerstiens.com/2013/07/29/documenting-your-postgres-database/"&gt;such as commenting&lt;/a&gt; and composability just for SQL. I was recently reminded of a key feature in Postgres that most don&amp;rsquo;t use by &lt;a href="http://www.twitter.com/timonk"&gt;@timonk&lt;/a&gt; highlighting it in his AWS Re:Invent Redshift talk. The simple feature actually makes SQL both readable and composable, and even for my own queries capable of coming back to them months later and understanding them, where previously they would not be.&lt;/p&gt;
&lt;p&gt;The feature itself is known as CTEs or common table expressions, you may also here it referred to as &lt;code&gt;WITH&lt;/code&gt; clauses. The general idea is that it allows you to create something somewhat equivilant to a view that only exists during that transaction. You can create multiple of these which then allow for clear building blocks and make it simple to follow what you&amp;rsquo;re doing.&lt;/p&gt;
&lt;p&gt;Lets take a look at a nice simple one:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;WITH users_tasks AS (
SELECT
users.email,
array_agg(tasks.name) as task_list,
projects.title
FROM
users,
tasks,
project
WHERE
users.id = tasks.user_id
projects.title = tasks.project_id
GROUP BY
users.email,
projects.title
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Using this I could now just append some basic other query on to the end that references this CTE &lt;code&gt;users_tasks&lt;/code&gt;. Something akin to:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT *
FROM users_tasks;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But where it becomes more interesting is chaining these together. So while I have all tasks assigned to each user here, perhaps I want to then find which users are responsible for more than 50% of the tasks on a given project, thus being the bottleneck. To oversimplify this we could do it a couple of ways, total up the tasks for each project, and then total up the tasks for each user per project:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;total_tasks_per_project AS (
SELECT
project_id,
count(*) as task_count
FROM tasks
GROUP BY project_id
),
tasks_per_project_per_user AS (
SELECT
user_id,
project_id,
count(*) as task_count
FROM tasks
GROUP BY user_id, project_id
),
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we would want to combine and find the users that are now over that 50%:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;overloaded_users AS (
SELECT tasks_per_project_per_user.user_id,
FROM tasks_per_project_per_user,
total_tasks_per_project
WHERE tasks_per_project_per_user.task_count &amp;gt; (total_tasks_per_project / 2)
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now as a final goal I&amp;rsquo;d want to get a comma separated list of tasks of the overloaded users. So we&amp;rsquo;re simply giong to join against that &lt;code&gt;overloaded_users&lt;/code&gt; and our initial list of &lt;code&gt;users_tasks&lt;/code&gt;. Putting it all together it looks somewhat long, but becomes much more readable. And as a bonus I layered in some comments.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;--- Created by Craig Kerstiens 11/18/2013
--- Query highlights users that have over 50% of tasks on a given project
--- Gives comma separated list of their tasks and the project
--- Initial query to grab project title and tasks per user
WITH users_tasks AS (
SELECT
users.id as user_id,
users.email,
array_agg(tasks.name) as task_list,
projects.title
FROM
users,
tasks,
project
WHERE
users.id = tasks.user_id
projects.title = tasks.project_id
GROUP BY
users.email,
projects.title
),
--- Calculates the total tasks per each project
total_tasks_per_project AS (
SELECT
project_id,
count(*) as task_count
FROM tasks
GROUP BY project_id
),
--- Calculates the projects per each user
tasks_per_project_per_user AS (
SELECT
user_id,
project_id,
count(*) as task_count
FROM tasks
GROUP BY user_id, project_id
),
--- Gets user ids that have over 50% of tasks assigned
overloaded_users AS (
SELECT tasks_per_project_per_user.user_id,
FROM tasks_per_project_per_user,
total_tasks_per_project
WHERE tasks_per_project_per_user.task_count &amp;gt; (total_tasks_per_project / 2)
)
SELECT
email,
task_list,
title
FROM
users_tasks,
overloaded_users
WHERE
users_tasks.user_id = overloaded_users.user_id
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;CTEs won&amp;rsquo;t always be quite as performant as optimizing your SQL to be as concise as possible. In most cases I have seen performance differences smaller than a 2X difference, this tradeoff for readability is a nobrainer as far as I&amp;rsquo;m concerned. And with time the Postgres optimizer should continue to get better about such performance.&lt;/p&gt;
&lt;p&gt;As for the verbosity, yes I could have done this query in probably 10-15 lines of very concise SQL. Yet, most may not be able to understand it quickly if at all. Readability is huge when it comes to SQL to ensure its doing the right thing. SQL will almost always tell you an answer, it just may not be to the question you think you&amp;rsquo;re asking. Ensuring your queries can be reasoned about is critical to ensuring accuracy and CTEs are one great way of accomplishing that.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;If you&amp;rsquo;re looking for a deeper resource on Postgres I recommend the book &lt;a href="https://theartofpostgresql.com/?affiliate=cek"&gt;The Art of PostgreSQL&lt;/a&gt;. It is by a personal friend that has aimed to create the definitive guide to Postgres, from a developer perspective. If you use code CRAIG15 you&amp;rsquo;ll receive 15% off as well.&lt;/em&gt;&lt;/p&gt;</description><author>CRAIG KERSTIENS</author><pubDate>Mon, 18 Nov 2013 22:55:56 GMT</pubDate><guid isPermaLink="true">/2013/11/18/best-postgres-feature-youre-not-using/</guid></item><item><title>Little Data: How do we query personal data?</title><link>https://www.petekeen.net/little-data/</link><description>&lt;p&gt;My wife and I recently moved from Portland, OR to Ann Arbor, MI. Among the cacophony of change that is involved with a move like that, we of course changed to the local utility company. Browsing around in their billing application one day I came across a page that showed a daily graph of our energy usage, supposedly valid through yesterday for both gas and electric. And it has a button that spits out a CSV file of the date, which means if I actually wanted to I could build my own tool to analyze our usage.&lt;/p&gt;
&lt;!--more--&gt;
&lt;img alt="Energy Usage Graphs" class="thumbnail" src="https://d2s7foagexgnc2.cloudfront.net/files/ca6e7d1b07db4cdc94a7/dte_usage.png" /&gt;
&lt;p&gt;This got me thinking about all of the little databases out there that have data about me that I might actually consider useful. Useful, in this case, means that by looking some combination of the data I could draw some useful conclusions and take some sort of action. Here's a list of the ones that immediately came to mind:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Energy usage&lt;/li&gt;
&lt;li&gt;&lt;a href="http://loseit.com/"&gt;LoseIt&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.withings.com"&gt;Withings&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.runkeeper.com"&gt;RunKeeper&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Bank records&lt;/li&gt;
&lt;li&gt;Email&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Some of the things in that list have easily accessed APIs, like RunKeeper and Withings. Some of them you can export the data manually to CSVs. And then some of them really &lt;em&gt;should&lt;/em&gt; have an API but don't, like LoseIt. And then what if we could add &lt;em&gt;more&lt;/em&gt; data? Things like how often we run the clothes dryer, how often the furnace or AC kicks in and for how long, whether we're in the house or not, etc.&lt;/p&gt;
&lt;p&gt;None of this is Big Data. This is all Little Data. It's reminiscent of the &lt;a href="http://quantifiedself.com/"&gt;Quantified Self&lt;/a&gt; movement, but it's more about combining existing data sets into a unified whole than synthesizing new data.&lt;/p&gt;
&lt;p&gt;I've gone so far as to build isolated silos for some of these data sets. For example, I track my finances using &lt;a href="http://www.ledger-cli.org"&gt;ledger&lt;/a&gt; and a &lt;a href="/tag/Ledger"&gt;variety of home-grown tools&lt;/a&gt;. My email gets backed up hourly and that's provided some useful data occasionally. Even so, all of it is still in individual silos. Ideally I would be able to dump all of this into some kind of personal &amp;quot;data soup&amp;quot; that I could query with a unified interface and build analytics tools on top of.&lt;/p&gt;
&lt;p&gt;At this point I have a bunch of questions and no answers. What shape does the data soup take? How does data get in, how would it get queried? What questions could I realistically answer? I'm going to keep thinking about this and hopefully you will too.&lt;/p&gt;</description><author>Pete Keen</author><pubDate>Tue, 12 Nov 2013 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.petekeen.net/little-data/</guid></item><item><title>Restricting Thrift clients to specific IP addresses with Twisted</title><link>https://tomforb.es/blog/restricting-thrift-clients-to-specific-ip-addresses-with-twisted/</link><description>Apache Thrift is pretty awesome - you can build Twisted bindings for your Thrift interface file that work fantastically. There is one thing that took me a while to figure out: I want to restrict clients connecting to the service to a specific set of IP addresses stored in a database. There were thre...</description><author>Tom Forbes</author><pubDate>Mon, 11 Nov 2013 19:17:11 GMT</pubDate><guid isPermaLink="true">https://tomforb.es/blog/restricting-thrift-clients-to-specific-ip-addresses-with-twisted/</guid></item><item><title>Load a Github Gist with Composer</title><link>https://donatstudios.com/Load-Gist-With-Composer</link><description>&lt;p&gt;Composer is amazing for pulling in packages, but what if you find a Gist that isn't Composer aware? Fear not, as Composer has the magical ability to pull in repositories not explicitly set up in Packagist by defining them in the &amp;quot;repositories&amp;quot; section. &lt;/p&gt;
&lt;p&gt;But what if you wanted to load a single class from a git gist? &lt;/p&gt;
&lt;p&gt;You can add the following &lt;code&gt;repositories&lt;/code&gt; section to your &lt;code&gt;composer.json&lt;/code&gt; file, adjusting &lt;code&gt;name&lt;/code&gt; and &lt;code&gt;url&lt;/code&gt; as necessary. The &lt;code&gt;name&lt;/code&gt; should be a &lt;code&gt;vendor/package&lt;/code&gt; (all lower) style name you make up for it, and &lt;code&gt;url&lt;/code&gt; will be from the &lt;em&gt;Clone this gist&lt;/em&gt; box on GitHub. &lt;/p&gt;
&lt;pre&gt;&lt;code&gt;"repositories": [
    {
        "type":"package",
        "package": {
            "name": "turin86/wssoapclient",
            "version": "master",
            "source": {
                "url": "https://gist.github.com/5569152.git",
                "type": "git",
                "reference":"master"
            },
            "autoload": {
                "classmap": ["."]
            }
        }
    }
]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then in your require section, add your selected &lt;code&gt;name&lt;/code&gt; as follows.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;"require": {
    "turin86/wssoapclient": "dev-master"
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the name of the class doesn't match the filename in a friendly autoload-able manner, you'll have to replace &lt;code&gt;"classmap": ["."]&lt;/code&gt; with &lt;code&gt;"files": ["ClassToAutoload.php"]&lt;/code&gt; replacing &lt;code&gt;ClassToAutoload.php&lt;/code&gt; with the filename. More info on autoloading can be found &lt;a href="https://getcomposer.org/doc/04-schema.md#autoload"&gt;here&lt;/a&gt;. Note that if you are experimenting with autoload settings you may need to remove your vendor directory and &lt;code&gt;composer install&lt;/code&gt;, as updates to the autoload were not reflected for me after a &lt;code&gt;composer update&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;After this, just run &lt;code&gt;composer update&lt;/code&gt; and voilà, you are loading a gist with Composer.&lt;/p&gt;</description><author>Donat Studios</author><pubDate>Thu, 07 Nov 2013 04:36:45 GMT</pubDate><guid isPermaLink="true">https://donatstudios.com/Load-Gist-With-Composer</guid></item><item><title>Embedded Linux Conference Europe 2013 notes</title><link>https://anisse.astier.eu/embedded-linux-conference-europe-2013-notes.html</link><description>&lt;p&gt;So I was in Edinburgh this year, and I took notes as I usually do. These are intended for personal consumption (do no expect LWN-style reports), but as more people were asking me to share them, I thought why not do it in public ?&lt;/p&gt;
&lt;h1&gt;Embedded Linux timeline&lt;/h1&gt;
&lt;p&gt;by Chris Simmonds …&lt;/p&gt;</description><author>Linux Engineer's random thoughts</author><pubDate>Wed, 30 Oct 2013 01:00:00 GMT</pubDate><guid isPermaLink="true">https://anisse.astier.eu/embedded-linux-conference-europe-2013-notes.html</guid></item><item><title>Installing the PHP "memcached" Extension</title><link>https://donatstudios.com/OS-X-Mavericks-Memcached-PHP-Extension-Installation</link><description>&lt;h3&gt;⚠️ Warning: These instructions are outdated&lt;/h3&gt;
&lt;p&gt;This article was written for OS X Mavericks and should not be followed on modern versions of macOS. It remains available for historical reference only.&lt;/p&gt;
&lt;p&gt;For development on modern macOS, use a Homebrew-installed version of PHP. &lt;a href="https://github.com/shivammathur/homebrew-php"&gt;shivammathur/php&lt;/a&gt; is a good place to start.&lt;/p&gt;
&lt;p&gt;Do not disable System Integrity Protection. Doing so weakens macOS security and may leave your system vulnerable to malware.&lt;/p&gt;
&lt;h3&gt;Please Note&lt;/h3&gt;
&lt;p&gt;This tutorial only covers the &lt;strong&gt;default PHP installation&lt;/strong&gt; that ships with Mac OS X / macOS. If you have installed a new installation this does not cover you. &lt;/p&gt;
&lt;p&gt;For those using brew, you can simply &lt;code&gt;brew install&lt;/code&gt; the &lt;a href="https://github.com/Homebrew/homebrew-php/blob/49cb525f2dd836ac1fe79905d9f094931d7b02ba/Formula/php55-memcached.rb"&gt;appropriate brew formula&lt;/a&gt; for the extension.&lt;/p&gt;
&lt;p&gt;These directions are verified to work with Mac OS X 10.9 Mavericks through macOS 10.14 Mojave.&lt;/p&gt;
&lt;fieldset&gt;&lt;legend&gt;System Integrity Protection&lt;/legend&gt;

&lt;p&gt;If you are running OS X El Capitan or &lt;strong&gt;newer&lt;/strong&gt; you will need to disable System Integrity Protection to modify system files and directories.&lt;/p&gt;
&lt;p&gt;To disable System Integrity Protection, boot into recovery mode by restarting and then holding &lt;code&gt;⌘R&lt;/code&gt; as you hear the startup chime. Then start the Terminal from the Utilities menu.&lt;/p&gt;
&lt;p&gt;Run the following command&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-console"&gt;$ csrutil disable&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then reboot. You are good to go.&lt;/p&gt;
&lt;p&gt;If you wish to turn it back on, follow the instructions above but instead use:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-console"&gt;$ csrutil enable&lt;/code&gt;&lt;/pre&gt;

Click &lt;a href="https://donatstudios.com/Disable-macOS-System-Integrity-Protection"&gt;here&lt;/a&gt; for a full post on the topic.

&lt;/fieldset&gt;
&lt;p&gt;The first step is to install the latest version of Xcode. On modern versions of MacOS this can be done from the App Store.&lt;/p&gt;
&lt;p&gt;After this, install the Xcode developer tools. &lt;/p&gt;
&lt;pre&gt;&lt;code class="language-console"&gt;$ xcode-select --install&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Special note to Mojave users&lt;/strong&gt; if the following steps fail, you may need to reinstall your Xcode header files. Instructions for that can be found &lt;a href="https://donatstudios.com/MojaveMissingHeaderFiles"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Next we will need to install the required dependencies before we can build the extension. If you are not already using &lt;a href="https://brew.sh/"&gt;Homebrew&lt;/a&gt; you should be. &lt;/p&gt;
&lt;pre&gt;&lt;code class="language-console"&gt;$ brew install wget autoconf pkg-config libmemcached&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You will also want to make sure you have PEAR installed; full instructions can be found &lt;a href="https://pear.php.net/manual/en/installation.getting.php"&gt;here&lt;/a&gt; but can be summarized as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-console"&gt;$ wget http://pear.php.net/go-pear.phar
$ php go-pear.phar&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then in the &lt;code&gt;/tmp&lt;/code&gt; directory we will create a folder we can work in.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-console"&gt;$ cd /tmp
$ mkdir memcached-work
$ cd memcached-work&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next we will use pecl, part of pear, to fetch the current version of the extension.  &lt;/p&gt;
&lt;p&gt;The version of the memcached extension will vary so you will need to update the paths accordingly.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-console"&gt;$ pecl download memcached
$ open memcached-{{version}}.tgz
$ cd memcached-{{version}}/memcached-{{version}}
$ phpize
$ ./configure
$ make
$ sudo make install&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally you will need to add the following line to your php.ini&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;extension = memcached.so&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can verify your installation with the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-console"&gt;$ php --info | grep memcached\\.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Depending on your setup now you may want to restart apache. &lt;/p&gt;
&lt;pre&gt;&lt;code class="language-console"&gt;$ sudo apachectl restart&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You should be all set to go!&lt;/p&gt;</description><author>Donat Studios</author><pubDate>Wed, 23 Oct 2013 13:42:40 GMT</pubDate><guid isPermaLink="true">https://donatstudios.com/OS-X-Mavericks-Memcached-PHP-Extension-Installation</guid></item><item><title>How to: Disable macOS System Integrity Protection</title><link>https://donatstudios.com/Disable-macOS-System-Integrity-Protection</link><description>&lt;p&gt;If you are running OS X El Capitan or &lt;strong&gt;newer&lt;/strong&gt; you will need to disable System Integrity Protection to modify system files and directories.&lt;/p&gt;
&lt;p&gt;To disable System Integrity Protection, boot into recovery mode by restarting and then holding &lt;code&gt;⌘R&lt;/code&gt; as you hear the startup chime. Then start the Terminal from the Utilities menu.&lt;/p&gt;
&lt;p&gt;Run the following command&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-console"&gt;$ csrutil disable&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then reboot. You are good to go.&lt;/p&gt;
&lt;p&gt;If you wish to turn it back on, follow the instructions above but instead use:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-console"&gt;$ csrutil enable&lt;/code&gt;&lt;/pre&gt;</description><author>Donat Studios</author><pubDate>Wed, 23 Oct 2013 13:42:40 GMT</pubDate><guid isPermaLink="true">https://donatstudios.com/Disable-macOS-System-Integrity-Protection</guid></item><item><title>My wishlist for a Star Wars Galaxies 2</title><link>https://benovermyer.com/blog/2013/10/my-wishlist-for-a-star-wars-galaxies-2/</link><description>&lt;p&gt;Star Wars Galaxies 2 will most likely never happen, owing to the fact that SOE has publicly stated that it will never work on an MMO based on an external IP again. Whether that’s true or not, if somehow this project were to be conceived, here are the changes from the original that I’d personally want to see in a sequel.&lt;/p&gt;
&lt;h1 id="modern-graphics-engine"&gt;Modern Graphics Engine&lt;/h1&gt;
&lt;p&gt;This is the most obvious one. My personal choice for SWG2’s engine would be Unreal Engine 4, owing to its extremely impressive technology. The “Infiltrator” demo video for UE4 shows that it would be perfect for the realistic art style of the Star Wars: Galaxies franchise, as compared to the more stylized art direction of Star Wars: The Old Republic.&lt;/p&gt;
&lt;h1 id="a-return-to-skill-based-character-progression"&gt;A Return to Skill-Based Character Progression&lt;/h1&gt;
&lt;p&gt;Like most SWG veterans, I lamented the loss of the skill tree progression system when the New Game Experience took over. With SWG2, I’d want to see it return with a vengeance. Particularly, I’d like to see the depth and breadth of trees expand, allowing for greater freedom than even the original. Several aspects of the Star Wars universe were never fully explored in the first game, or were only given light treatment, such as slicing or smuggling.&lt;/p&gt;
&lt;h1 id="more-player-races"&gt;More Player Races&lt;/h1&gt;
&lt;p&gt;The selection of available races in the original was good, allowing for most of the popular choices. I would like to see the Togruta, Chiss, and Devaronian races make a core appearance in SWG2. I’d also like to see droids as a player race, though that might be too complicated to balance against other races’ gameplay.&lt;/p&gt;
&lt;h1 id="bigger-worlds"&gt;Bigger Worlds&lt;/h1&gt;
&lt;p&gt;The original SWG planets were pretty big, but they frequently felt empty. The expansion worlds had stronger design, but felt smaller. I’d want to see planets in SWG2 be more expansive than the original SWG planets, but have more points of interest throughout each. For example, the planets in the first SWG felt a lot like the same planet given a different skin each time. While I don’t want quests to provide a meta plot, I would like to see localized content that gives each world its own character.&lt;/p&gt;
&lt;h1 id="greater-freedom-of-movement"&gt;Greater Freedom of Movement&lt;/h1&gt;
&lt;p&gt;Let’s face it, movement was pretty horrible in the original SWG. You couldn’t jump or fly over obstacles, and it was completely possible to walk straight up a sheer cliff face. While we don’t necessarily need the full parkour aspects of EverQuest Next, I do want the ability to jump over things, be prevented from walking up impossible surfaces, and be able to climb, swim, and otherwise move as a person should be able to move.&lt;/p&gt;
&lt;h1 id="atmospheric-vehicles"&gt;Atmospheric Vehicles&lt;/h1&gt;
&lt;p&gt;I know these were added to the original SWG, but it came only in the last few months of the game’s life. Given how important they are to the Star Wars universe in general, it would be a crime not to include these from the beginning in SWG2. I want to be able to bullseye womp rats in a T-16, over take a cruise over the highlands of Corellia in my own YT-1300. I also want shuttle transports to actually have real transit time, and not be a wait-and-teleport affair like they were in the original.&lt;/p&gt;
&lt;h1 id="player-factions"&gt;Player Factions&lt;/h1&gt;
&lt;p&gt;While it was great to have Imperial and Rebel affiliations in the original, to really ramp up the sandboxy nature of Star Wars Galaxies, the sequel would need to have player factions. This is particularly important if the game is set in a time period after the fall of the Empire, where a billion new factions would be vying for power. I see player factions being akin to massive alliances of Player Associations, rather than just an enormous PA. They’d also have to be able to influence planetary politics, like how Imperial and Rebel players were able to influence things towards the end of the original SWG.&lt;/p&gt;
&lt;h1 id="a-player-exchange"&gt;A Player Exchange&lt;/h1&gt;
&lt;p&gt;Unlike the auction house format of current MMORPGs, I’d like to see something closer to the original vendor system in SWG. You’d be able to search available items on a local, planetary, or galactic level, as well as purchase them, but you’d need to physically travel to the vendor in order to pick up the item or items. This is important for two reasons: to encourage exploration and travel, and to support the possibility of player deliverymen - “FedEx” quests that actually matter. I want to have to travel to Tosche station to pick up some power converters.&lt;/p&gt;
&lt;h1 id="greater-visual-customization-of-crafted-items"&gt;Greater Visual Customization of Crafted Items&lt;/h1&gt;
&lt;p&gt;I liked that crafters could dye armor in SWG, but a sequel would need to take it to a new level. Item appearance should be further separated from item game mechanics than in the original. There is a vast variety of armor appearances in Star Wars, for example, and that should be reflected in SWG2. I don’t want just a dozen different kinds of armor in different colors. I want several dozen, if not more. If World of Warcraft can do it, so can SWG2. Involving the Player Studio could help defray the cost of developing new models and textures for components of items.&lt;/p&gt;
&lt;h1 id="vehicular-combat"&gt;Vehicular Combat&lt;/h1&gt;
&lt;p&gt;If we can fly in the atmosphere, we should be able to fight in it also. I’d want to see hover tanks finally make an appearance, among other things. If PlanetSide 2 can balance heavy armor against infantry, Star Wars Galaxies can do it too. This list could be even longer, but the above are my biggest wishes for a Star Wars Galaxies 2. How about yours? What would you want to see in a sequel?&lt;/p&gt;</description><author>Ben Overmyer's Site</author><pubDate>Sun, 20 Oct 2013 03:00:00 GMT</pubDate><guid isPermaLink="true">https://benovermyer.com/blog/2013/10/my-wishlist-for-a-star-wars-galaxies-2/</guid></item><item><title>Hello, World!</title><link>https://anisse.astier.eu/hello-world.html</link><description>&lt;p&gt;&lt;strong&gt;&lt;em&gt;Update&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;: I updated the information below with the &lt;a href="blog-update.html"&gt;2020 tech&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;So I finally did it. You're reading it right now. My personnal website/blog.&lt;/p&gt;
&lt;p&gt;I should be posting here about things that cross my mind as well as various projects I've been working on. And maybe even new projets I …&lt;/p&gt;</description><author>Linux Engineer's random thoughts</author><pubDate>Sun, 20 Oct 2013 01:00:00 GMT</pubDate><guid isPermaLink="true">https://anisse.astier.eu/hello-world.html</guid></item><item><title>Post-mortem of a Dead-on-Arrival SaaS Product</title><link>https://www.petekeen.net/marginalia-postmortem/</link><description>&lt;p&gt;A little over a year ago I &lt;a href="/announcing-marginalia"&gt;announced the launch&lt;/a&gt; of my latest (at the time) product named Marginalia. The idea was to be a sort of online journal. A cheaper, more programmer friendly alternative to Evernote. It never took off, despite my best intentions, and so a few months ago I told the only active user that I was going to shut it down, and today I finally took that sad action. This post is a short history of the project and a few lessons learned.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;A Brief History&lt;/h2&gt;
&lt;img src="https://d2s7foagexgnc2.cloudfront.net/files/e36d73e5075e0eb8456f/create_note.png" /&gt;
&lt;p&gt;Marginalia actually started out pretty humbly. See, for a very long time I've been emailing ideas to myself and then forgetting that I did that and thus those ideas were just completely lost. I needed a better way to capture all of this so I could get it out of my head and into something more permanent. In late January 2012 I finally put together a simple Rails application that used a &lt;a href="http://www.mailgun.com"&gt;Mailgun&lt;/a&gt; incoming account to parse emails sent to a special address and add the Markdown-formatted body content to a new page. The app would reply to that email with the &lt;code&gt;From:&lt;/code&gt; address set to a unique address. Anything emailed to that address would get appended to the note with a timestamp.&lt;/p&gt;
&lt;p&gt;I used this simple app called &amp;quot;Ideas&amp;quot; for seven months and loved it to pieces. I captured a lot of ideas in there, along with daily work notes and other various things. At some point I started talking about it with other people and they encouraged me to try selling it, and after a lot of consideration I added subscriptions and billing and all of the other little things a real SaaS app needs. Then I wrote a blog post and got it on the front page of HN briefly, and then... nothing. A few signups for the original &amp;quot;pay once&amp;quot; subscription model, a handful of people signing up for the $5/mo plan and then never coming back, and otherwise no other traffic. I kept using it for awhile and then &lt;a href="/git-backed-personal-markdown-wiki"&gt;built a better mousetrap&lt;/a&gt; and moved on.&lt;/p&gt;
&lt;h2&gt;Lessons Learned&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;People want their privacy.&lt;/strong&gt; I originally tried pitching Marginalia as a way to keep a programming journal, which ended up being a losing proposition. First of all, software developers don't want to spend money on tools when their free or already-paid-for editor is just as good. Second, keeping journals of any kind at an external service is something that most developers are not at all into because of the privacy implications.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Itched scratches frequently bleed money.&lt;/strong&gt; Every month Heroku charged me $29 for SSL and a non-development-level database. Every month for more than a year, with zero income. It's not a lot of money but it's money from my web hosting budget that could have been going toward something more productive. I also spent an &lt;em&gt;awful&lt;/em&gt; lot of money on paid advertising trying to get an audience with zero return.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Launching without an audience means nobody shows up.&lt;/strong&gt; Building a product without an excited, engaged audience is one of those things that software developers tend to do, often and with gusto. It's so easy to build up this idea in your head and in your editor and just expect people to show up after you're done. It's something that I've done three times and it's something I will hopefully never repeat. For my &lt;a href="/mastering-modern-payments"&gt;latest product&lt;/a&gt; I started with a simple landing page with a Mailchimp signup form. Only after actually determining interest did I move forward with the plan.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;The source is &lt;a href="https://github.com/peterkeen/marginalia"&gt;up on GitHub&lt;/a&gt; if you want to take a look at how it came together. There's some features, including on-page javascript evaluation and data blocks, that never got announced but which I still think were a good idea.&lt;/p&gt;</description><author>Pete Keen</author><pubDate>Sat, 19 Oct 2013 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.petekeen.net/marginalia-postmortem/</guid></item><item><title>First post</title><link>https://nindalf.com/posts/first-post/</link><description>A personal blog about mundane observations on reading, programming, and gaming, with occasional updates and vain attempts to drive traffic to the blog.</description><author>Krishna's blog</author><pubDate>Sat, 19 Oct 2013 03:00:00 GMT</pubDate><guid isPermaLink="true">https://nindalf.com/posts/first-post/</guid></item><item><title>Adding tail-call optimization to Python</title><link>https://tomforb.es/blog/adding-tail-call-optimization-to-python/</link><description>Tail-call optimization is a trick many languages and compilers use to avoid creating excess stack frames when dealing with recursive code like this:...</description><author>Tom Forbes</author><pubDate>Tue, 15 Oct 2013 22:04:05 GMT</pubDate><guid isPermaLink="true">https://tomforb.es/blog/adding-tail-call-optimization-to-python/</guid></item><item><title>Tooling for Simple but Informative Emails</title><link>/2013/10/13/Tooling-for-Simple-but-Informative-Emails/</link><description>&lt;p&gt;Emails are one of my favorite methods of communicating with users. Its works as a quick test for product validation. It works well at one-&amp;gt;some-&amp;gt;many-&amp;gt; all. Its still highly effective even as much noise as we receive in our inboxes. Over the years I&amp;rsquo;ve tried a lot of email tools from custom built solutions, to newer entrants that help around drip actions (&lt;a href="http://www.intercom.io"&gt;intercom.io&lt;/a&gt; and &lt;a href="http://www.customer.io"&gt;customer.io&lt;/a&gt;), to more &amp;ldquo;enterprise&amp;rdquo; tools such as Marketo. While I have varying opinions on all of those, I still find myself coming back to a simple one off script setup to deliver clear concise emails.&lt;/p&gt;
&lt;h3 id="getting-the-data"&gt;
&lt;div&gt;
Getting the Data
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;The first step of any email is deciding what you want to do, but hopefully you know that already. The part that is usually a bit more effort is actually getting the list to send it to and formatting it appropriately. I usually opt for SQL. While the specifics of the query of course always vary it common follows a general structure:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;WITH initial_data AS (
SELECT
email,
app_name,
information_about_app
FROM
users,
apps
WHERE users.id = apps.user_id
AND some_filter_to_limit_data
),
candidates_for_email AS ... --- likely to have additional CTEs
--- Finally I build up the list
SELECT email,
array_to_string(array_agg(data_for_email), '
') --- an important note is to add a newline or not here depending on how you wish to format it
FROM candidates_for_email
GROUP BY email;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The query structure you&amp;rsquo;ll want is first column email, second column whatever data you want to include in your email.&lt;/p&gt;
&lt;p&gt;From here I usually create a dataclip of it. This makes it easy to allow my data to change over time. If I&amp;rsquo;m testing an email for data over the last 7 days I just come back in 7 days and I have new data. It also lets me easily share and iterate on the data. The nice part is there&amp;rsquo;s an easy way to click a button and get the data as a CSV which is what you want for sending.&lt;/p&gt;
&lt;p&gt;Once you download the CSV you&amp;rsquo;ll want to remove the header line as its not needed for the script.&lt;/p&gt;
&lt;h3 id="sending-the-mail"&gt;
&lt;div&gt;
Sending the Mail
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;To actually send the email you&amp;rsquo;ll need this script, which is largely credited to &lt;a href="http://www.twitter.com/leinweber"&gt;@leinweber&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;require 'mail'
require 'csv'
FILE = ARGV[0]
Mail.defaults do
delivery_method :smtp, {
address: 'smtp address',
port: 587,
domain: 'gmail.com',
user_name: 'craig.kerstiens@gmail.com',
password: ENV.fetch('EMAIL_PASSWORD'),
authentication: :plain,
enable_starttls_auto: true
}
end
def send_email(address, app)
mail = Mail.new do
to address
from 'Craig Kerstiens &amp;lt;craig.kerstiens@gmail.com&amp;gt;'
subject &amp;quot;Your email subject in here&amp;quot;
body generate_body(app)
end
end
def generate_body(app)
%Q(
Hi,
Your list of apps:
#{app}
Various email content in here...
)
end
CSV.parse(File.read(FILE)).each do |line|
address = line[0]
app = line[1]
m = send_email(address, app)
puts m.to_s
p m.deliver!
puts
puts
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;You&amp;rsquo;ll want to make sure to export the PW of your email provider with EXPORT EMAIL_PASSWORD=pw_here&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;You can easily download this script from off of &lt;a href="https://gist.github.com/craigkerstiens/6922897"&gt;Github&amp;rsquo;s Gist&lt;/a&gt;. I&amp;rsquo;d recommend using an email service provider other than Gmail in sending your emails such as &lt;a href="http://www.mailgun.com"&gt;mailgun&lt;/a&gt; as they&amp;rsquo;re built to handle sending a large amount of emails. Finally send your emails:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ruby email.rb nameofyourfile.csv
&lt;/code&gt;&lt;/pre&gt;</description><author>CRAIG KERSTIENS</author><pubDate>Sun, 13 Oct 2013 23:55:56 GMT</pubDate><guid isPermaLink="true">/2013/10/13/Tooling-for-Simple-but-Informative-Emails/</guid></item><item><title>My Uni's timetable system sucks, so I built a better one.</title><link>https://tomforb.es/blog/my-uni-s-timetable-system-sucks-so-i-built-a-better-one-1/</link><description>tl;dr The timetable system sucks, so I made one that works Getting your timetable sorted at Uni has never been fun. In years 1 and 2 of my study the department posted a timetable for each year showing all modules and students were expected to remove the classes they did not take, which while not the...</description><author>Tom Forbes</author><pubDate>Sat, 12 Oct 2013 02:05:09 GMT</pubDate><guid isPermaLink="true">https://tomforb.es/blog/my-uni-s-timetable-system-sucks-so-i-built-a-better-one-1/</guid></item><item><title>Objective-C</title><link>https://danpalmer.me/2013-10-12-objective-c/</link><description>&lt;p&gt;If I mentioned that I like C, C++ or Python to other students on my course, or colleagues, there would be no reaction. There are things you can criticise about each one, but they are all very safe bets. When I tell people that I enjoy writing Objective-C however, they are confused and often quite hostile towards the language.&lt;/p&gt;
&lt;p&gt;I am by no means an Objective-C expert, but I&amp;rsquo;ve been thinking through the reasons &lt;em&gt;why&lt;/em&gt; I like it, so this is a random collection of reasons why I enjoy using the language.&lt;/p&gt;</description><author>Dan Palmer</author><pubDate>Sat, 12 Oct 2013 02:00:00 GMT</pubDate><guid isPermaLink="true">https://danpalmer.me/2013-10-12-objective-c/</guid></item><item><title>Purchasing a £30,000 numberplate for the price of a bus ticket</title><link>https://tomforb.es/blog/purchasing-a-30000-numberplate-for-the-price-of-a-bus-ticket/</link><description>Regtransfers.co.uk is a website that allows you to purchase customized numberplates for your car or motorbike. They boast a large number of famous clients and short numberplates are often on sale for upwards of £20,000 (the plate ABC 4 is up for £30,000). While playing with their site I discovered a...</description><author>Tom Forbes</author><pubDate>Mon, 07 Oct 2013 04:40:33 GMT</pubDate><guid isPermaLink="true">https://tomforb.es/blog/purchasing-a-30000-numberplate-for-the-price-of-a-bus-ticket/</guid></item><item><title>Breaking out of secured Python environments</title><link>https://tomforb.es/blog/breaking-out-of-secured-python-environments/</link><description>A week or so ago I was browsing /r/Python and I saw a link to a website called rise4fun.com , which is a Microsoft Research project that contains a lot of cool demos and tools that you can run in your browser. The demo I was linked to was a restricted Python shell that could be used to experiment wi...</description><author>Tom Forbes</author><pubDate>Wed, 25 Sep 2013 23:02:33 GMT</pubDate><guid isPermaLink="true">https://tomforb.es/blog/breaking-out-of-secured-python-environments/</guid></item><item><title>DRY your Rails CRUD with Simple Form and Inherited Resources</title><link>https://www.petekeen.net/dry-your-rails-crud-with-simple-form-and-inherited-resources/</link><description>&lt;p&gt;When you're writing a Rails application you usually end up with a lot of CRUD-only controllers and views just for managing models as an admin. Your user-facing views and controllers should of course have a lot of thought and care put into their design, but for admin stuff you just want to put data in the database as simply as possible. Rails of course gives you scaffolds, but that's quite a bit of duplicated code. Instead, you could use the one-two-three combination of &lt;a href="https://github.com/plataformatec/simple_form"&gt;Simple Form&lt;/a&gt;,  &lt;a href="https://github.com/josevalim/inherited_resources"&gt;Inherited Resources&lt;/a&gt;, and Rails' built-in template inheritance to DRY up most of the scaffolding while still preserving your ability to customize where appropriate. This lets you build your admin interface without having to resort to something heavy like &lt;a href="https://github.com/sferik/rails_admin"&gt;Rails Admin&lt;/a&gt; or &lt;a href="https://github.com/gregbell/active_admin"&gt;ActiveAdmin&lt;/a&gt; while also not having to build from scratch every time.&lt;/p&gt;
&lt;!--more--&gt;
&lt;p&gt;Inherited Resources consists of a base controller you can inherit from that implements all of the standard resourceful actions, plus a few convenience things for working with this controller. If you have a &lt;code&gt;Book&lt;/code&gt; model, you could create a complete resourceful controller for it with the following code:&lt;/p&gt;
&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BooksController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;InheritedResources&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="kp"&gt;protected&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;permitted_params&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;permit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;book&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="ss"&gt;:author&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:isbn&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;As you can see, Inherited Resources has automatic integration with Rails 4's Strong Parameters.&lt;/p&gt;
&lt;h2&gt;DRY up your CRUD&lt;/h2&gt;
&lt;p&gt;Rails 3.1 and further shipped with a thing called 'template inheritance'. This simply means that there's a default search path for templates that Rails will hunt through to find a suitable template. If there's no &lt;code&gt;index.html.erb&lt;/code&gt; in &lt;code&gt;app/views/books&lt;/code&gt;, for example, Rails will look in &lt;code&gt;app/views/application&lt;/code&gt; because that's &lt;code&gt;BooksController&lt;/code&gt;'s base class. We can use this to construct default views for our CRUD controllers. First, let's make a base class for our controllers to inherit from:&lt;/p&gt;
&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CrudController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;InheritedResources&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;attrs_for_index&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="o"&gt;[]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;attrs_for_form&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="o"&gt;[]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;helper_method&lt;/span&gt; &lt;span class="ss"&gt;:attrs_for_index&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;helper_method&lt;/span&gt; &lt;span class="ss"&gt;:attrs_for_form&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And now we can set up default views. First, &lt;code&gt;app/views/crud/index.html.erb&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;resource_class&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pluralize&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="ni"&gt;&amp;amp;nbsp;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;small&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;link_to&lt;/span&gt; &lt;span class="s1"&gt;'New'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:new&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resource_class&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;downcase&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_sym&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;table&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;thead&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="n"&gt;attrs_for_index&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="kp"&gt;attr&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;th&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="kp"&gt;attr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;titlecase&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;th&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;th&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;th&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;thead&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;tbody&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="n"&gt;collection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="n"&gt;attrs_for_index&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="kp"&gt;attr&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;link_to&lt;/span&gt; &lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attributes&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kp"&gt;attr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_s&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resource&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;link_to&lt;/span&gt; &lt;span class="s1"&gt;'Edit'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:edit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;tbody&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;table&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Inherited Resources gives you a few helpers that make these views really easy:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;collection&lt;/code&gt; maps to the collection of objects in your controller. If it was &lt;code&gt;BooksController&lt;/code&gt;, &lt;code&gt;collection&lt;/code&gt; would return &lt;code&gt;@books&lt;/code&gt;. This is only present in the index view.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;resource_class&lt;/code&gt; returns the class of the resource your controller is managing&lt;/li&gt;
&lt;li&gt;&lt;code&gt;resource&lt;/code&gt; maps to &lt;code&gt;@book&lt;/code&gt; and is available in every view except index.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now we need a &lt;code&gt;show&lt;/code&gt; view. Most of the time, all you want to see is a dump of the resource's attribute, which is exactly what we're going to do in &lt;code&gt;app/views/crud/show.html.erb&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;resource_class&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;resource&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;small&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;link_to&lt;/span&gt; &lt;span class="s1"&gt;'Edit'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:edit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;small&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;table&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;th&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Key&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;th&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;th&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Value&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;th&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attributes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sort&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;table&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The edit and new views are super simple:&lt;/p&gt;
&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;New &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;resource_class&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;titlecase&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="s1"&gt;'form'&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Editing &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;resource_class&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;titlecase&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;resource&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="s1"&gt;'form'&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Let's look at &lt;code&gt;app/views/crud/_form.html.erb&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;simple_form_for&lt;/span&gt; &lt;span class="n"&gt;resource&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="n"&gt;attrs_for_form&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="kp"&gt;attr&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="kp"&gt;attr&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="ss"&gt;:submit&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&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&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Here's where Simple Form really starts to shine. It auto-detects the proper form input to display by inspecting the attribute, so we don't have to do any work for a basic form. Of course, because we're using view inheritance, if you want to make a more complicated for you can just drop what you want into &lt;code&gt;app/views/&amp;lt;controller&amp;gt;/_form.html.erb&lt;/code&gt; and it'll get picked up automatically. That also goes the same for any of the templates.&lt;/p&gt;
&lt;p&gt;We should flesh out &lt;code&gt;BooksController&lt;/code&gt; with overrides for those &lt;code&gt;attr&lt;/code&gt; methods:&lt;/p&gt;
&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BooksController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;CrudController&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;attrs_for_index&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:author&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:isbn&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;attrs_for_form&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:author&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:isbn&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;Sorting, Paging, and Whatnot&lt;/h2&gt;
&lt;p&gt;You're probably thinking that this whole thing is great, but what if you need to, for example, sort or paginate the results on the index? Inherited Resources has you covered. Just override the &lt;code&gt;collection&lt;/code&gt; method, like this:&lt;/p&gt;
&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BooksController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;CrudController&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;collection&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="vi"&gt;@books&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="n"&gt;end_of_resource_chain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'created_at DESC'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The &lt;code&gt;end_of_resource_chain&lt;/code&gt; method gives you your resource relation after applying all of the other neat things that Inherited Resources can do. Check out &lt;a href="https://github.com/josevalim/inherited_resources"&gt;the README&lt;/a&gt; for more details on that.&lt;/p&gt;
&lt;p&gt;In the same vein, what if you want to limit the objects created and accessed by the rest of the CRUD actions? For example, let's say you want to limit the books available to the current user:&lt;/p&gt;
&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BooksController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;CrudController&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;begin_of_association_chain&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;current_user&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;begin_of_association_chain&lt;/code&gt; is where Inherited Resources starts out building objects. If you don't provide it, it defaults to the resource class.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;There's a lot more you can do with Inherited Resources and Simple Form, like build controllers that deal with multiple nested resources and forms that have automatically populated select dropdowns. You should check them out.&lt;/p&gt;</description><author>Pete Keen</author><pubDate>Sun, 22 Sep 2013 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.petekeen.net/dry-your-rails-crud-with-simple-form-and-inherited-resources/</guid></item><item><title>Aboard the Nautilus</title><link>https://captnemo.in/blog/2013/09/21/aboard-the-nautilus/</link><description>&lt;p&gt;I’d done a &lt;a href="http://captnemo.in/blog/2009/12/04/nautilus-behind-the-curtains/"&gt;post&lt;/a&gt; on this a long time back (2009), detailing
what all softwares I use on a daily basis. This
is an update to that post.&lt;/p&gt;

&lt;p&gt;Since the last post, I’ve moved on to using Linux, using
Elementary OS as my primary OS. Over the time period this post was written,
I’ve shifted from using Cinnamon to Openbox and finally
to elementaryOS’s pantheon as my Desktop Manager. I’m thinking of switching
to Arch Linux, just to get a faster experience. I use 
Synapse as my application launcher, because its much faster
than anything else out there.&lt;/p&gt;

&lt;p&gt;For most of my web browsing needs, I rely on Google Chrome Stable
and a daily build of Chromium (v31 as of now) for most of my work.
 I switch between them all the time. I use Firefox (stable) only to
test out my projects from time to time.&lt;/p&gt;

&lt;p&gt;The current editor I use is Sublime Text. It is
everything you need, and much more. I’m still to
get started using its build system, and its
plethora of packages; but its still an excellent
choice for a daily use editor. On the command line, I use
Vim, git (with &lt;a href="https://github.com/ndbroadbent/scm_breeze" title="Faster Git Shortcuts"&gt;SCM Breeze&lt;/a&gt;), &lt;a href="https://github.com/clvv/fasd" title="Command Line Booster"&gt;fasd&lt;/a&gt; and &lt;a href="https://github.com/jonas/tig" title="Excellent Git CLI"&gt;tig&lt;/a&gt;, 
which is an excellent git interface on the command line.&lt;/p&gt;

&lt;p&gt;I listen to music on my own browser-based music
player, called &lt;a href="https://sdslabs.co.in/muzi" title="Link works only inside IITR"&gt;Muzi&lt;/a&gt;, YouTube and GrooveShark.&lt;/p&gt;

&lt;p&gt;For my terminal needs, I use Gnome-Terminal. I use
&lt;a href="http://byobu.co/"&gt;Byobu&lt;/a&gt; to manage my session, and often
connect to it from other computers as well. Its an
excellent multiplexer that fits in my workflow
really well.&lt;/p&gt;

&lt;p&gt;I use &lt;a href="http://imo.im/"&gt;Imo.im&lt;/a&gt; on both the
Desktop and my tablet to chat. I occasionally use &lt;a href="http://sourceforge.net/projects/retext/"&gt;ReText&lt;/a&gt;
for editing markdown files. I use &lt;a href="http://jonls.dk/redshift/"&gt;RedShift&lt;/a&gt; on my laptop and &lt;a href="http://justgetflux.com/"&gt;f.lux&lt;/a&gt;
on my iPad to help me sleep better. I recommend it to everyone who is
suffering from eye-strain or wants to sleep better.&lt;/p&gt;

&lt;p&gt;On the browser, my most visited sites would be
&lt;a href="https://news.ycombinator.com/"&gt;Hacker News&lt;/a&gt; (via hckrnews.com), &lt;a href="http://workflowy.com/"&gt;WorkFlowy&lt;/a&gt; for managing
my to-do list and GitHub on a daily basis for most of my projects.&lt;/p&gt;

&lt;h2 id="hardware"&gt;Hardware&lt;/h2&gt;
&lt;p&gt;I own a old Nokia X3-02, and will be upgrading to a &lt;a href="https://www.mozilla.org/en-US/firefox/os/"&gt;Firefox OS Phone&lt;/a&gt; soon enough. I use a Dell Inspiron 1545 as my personal machine. I also use an iPad 2 (with 3G) on a daily basis (mostly for reading). I also own a Dayan Zhanchi 3x3 and a 5x5 shengshou 
speed cube.&lt;/p&gt;

&lt;h2 id="ipad-apps"&gt;iPad Apps&lt;/h2&gt;
&lt;p&gt;The must have iPad apps for me are Chrome, &lt;a href="https://imo.im/iphone/"&gt;imo&lt;/a&gt;,  and iBooks. I have installed &lt;a href="http://www.mailboxapp.com/"&gt;Mailbox&lt;/a&gt; alongside GMail, and haven’t used GMail since
I installed it. I sometimes write stuff using &lt;a href="http://www.hogbaysoftware.com/products/plaintext/"&gt;Plaintext&lt;/a&gt;, and sketch using &lt;a href="http://www.fiftythree.com/paper"&gt;Paper&lt;/a&gt;. I read my RSS feeds using Newsify and Feedly.&lt;/p&gt;

&lt;h2 id="extensions"&gt;Extensions&lt;/h2&gt;
&lt;p&gt;Chrome Extensions that I use on a daily basis include &lt;a href="http://chimeapp.com/"&gt;Chime&lt;/a&gt; for 
wonderful notifications (highly recommended), &lt;a href="http://www.ghostery.com"&gt;Ghostery&lt;/a&gt; for getting a tracker-free internet
, &lt;a href="https://www.eff.org/https-everywhere"&gt;HTTPS Everywhere&lt;/a&gt; to keep me secure, &lt;a href="http://lastpass.com/"&gt;LastPass&lt;/a&gt; to manage passwords and &lt;a href="https://chrome.google.com/webstore/detail/stylish/fjnbnpbmkenffdnngjfgmeleoegfcffe"&gt;Stylish&lt;/a&gt;
 for &lt;a href="http://userstyles.org/users/183835"&gt;customizing&lt;/a&gt; the looks of various websites.&lt;/p&gt;

&lt;h2 id="dream-setup"&gt;Dream Setup&lt;/h2&gt;
&lt;p&gt;My dream setup would consist of a lightweight Ubuntu Laptop that I can carry around
that still has lots of processing power and battery life. I’ll prety much be satisfied
by any high-end Android phone as long as it has a decent battery life.&lt;/p&gt;</description><author>Nemo's Home</author><pubDate>Sat, 21 Sep 2013 03:00:00 GMT</pubDate><guid isPermaLink="true">https://captnemo.in/blog/2013/09/21/aboard-the-nautilus/</guid></item><item><title>Inspecting .NET applications with ILSpy</title><link>https://tomforb.es/blog/inspecting-net-applications-with-ilspy/</link><description>Every once in a while I come across an application that is so comically insecure that I feel the urge to blog about it. The application in question is a .NET application to manage care homes and provide a Medical Administration Record for residents. Staff login to the app using a username and passwo...</description><author>Tom Forbes</author><pubDate>Thu, 19 Sep 2013 15:40:40 GMT</pubDate><guid isPermaLink="true">https://tomforb.es/blog/inspecting-net-applications-with-ilspy/</guid></item><item><title>Introducing SingleBugs the Bug Tracker for Single Developers</title><link>https://boyter.org/2013/09/introducing-singlebugs-bug-tracker-single-developers/</link><description>&lt;p&gt;Introducing the first beta release of SingleBugs, the bug tracker aimed at single/solo developers. Are you a solo developer? Do you find setting up Mantis/Fogbugs/Bugs.net/et.al too complex and a waste of your time? Do you want a single solution that easily syncs and backs up between your machines? Try SingleBugs.&lt;/p&gt;
&lt;p&gt;Guaranteed to save you time and money setting up a bug tracker. Guaranteed to be the fastest bug tracker you have ever used.&lt;/p&gt;</description><author>Ben E. C. Boyter</author><pubDate>Mon, 16 Sep 2013 04:50:08 GMT</pubDate><guid isPermaLink="true">https://boyter.org/2013/09/introducing-singlebugs-bug-tracker-single-developers/</guid></item><item><title>Essential Tools for Starting a Rails App in 2013</title><link>https://www.petekeen.net/essential-tools-for-starting-a-rails-app-in-2013/</link><description>&lt;p&gt;Over the past few years I've written a number of Rails applications. It's become my default &amp;quot;scratch an itch&amp;quot; tool for when I need to build an app quickly to do a task. Even though Rails is mostly batteries-included, there are a few tools that make writing new applications so much easier. This is my list of tools that I use for pretty much every new Rails project.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Edit: The discussion on &lt;a href="https://news.ycombinator.com/item?id=6393242"&gt;Hacker News&lt;/a&gt; has some great gems that you should consider using as well.&lt;/em&gt;&lt;/p&gt;
&lt;!--more--&gt;
&lt;h3&gt;&lt;a href="https://github.com/bkeepers/dotenv"&gt;Dotenv&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://github.com/bkeepers/dotenv"&gt;Dotenv&lt;/a&gt; is a simple gem that loads environment variables from a file named &lt;code&gt;.env&lt;/code&gt; in your project root into the &lt;code&gt;ENV&lt;/code&gt; hash within Ruby. Getting configuration from the environment is one of the factors in &lt;a href="http://12factor.net"&gt;12 Factor Applications&lt;/a&gt;, and using a &lt;code&gt;.env&lt;/code&gt; file for development eases the transition to deploying on Heroku. Or, if you're crazy like me, deploying on your own hardware using a nasty brew of Capistrano and Foreman.&lt;/p&gt;
&lt;h3&gt;&lt;a href="https://github.com/plataformatec/devise"&gt;Devise&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Most Rails apps are going to need a way to authenticate users. You could write something yourself, but there are a lot of subtle security concerns that you have to take into account. By using an off the shelf product like &lt;a href="https://github.com/plataformatec/devise"&gt;Devise&lt;/a&gt; you're insulated from having to worry about that. Some people use &lt;a href="https://github.com/binarylogic/authlogic"&gt;AuthLogic&lt;/a&gt;, which is also perfectly fine.&lt;/p&gt;
&lt;h3&gt;&lt;a href="http://brakemanscanner.org"&gt;Brakeman&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;There have been quite a few security vulnerabilities over the past year or so inside Rails, some of which are due to Rails themselves, but many are coding errors or best practices that, over time, have turned out to be not the best. &lt;a href="http://brakemanscanner.org"&gt;Brakeman&lt;/a&gt; is a security scanner that looks at your code base for both categories of error and tells you if you're doing something wrong. I run Brakeman over my codebase as part of my test suite so I know immediately when I'm doing something that isn't quite right.&lt;/p&gt;
&lt;h3&gt;&lt;a href="https://github.com/railsbp/rails_best_practices"&gt;Rails Best Practices&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;In a similar vein to Brakeman, &lt;a href="https://github.com/railsbp/rails_best_practices"&gt;Rails Best Practices&lt;/a&gt; is a list of best practices that anyone can add to, vote on, and modify. They provide a scanner that looks for violations of these best practices and tells you about them. I also run this as part of my test suite, not because they're necessarily security focused, but hard-won experience has taught me that doing (most of) the things that RBP says to do leads to a more maintainable codebase. They provide a configuration file that you can tweak, in case the scanner starts warning on something that you don't think it should.&lt;/p&gt;
&lt;h3&gt;&lt;a href="https://github.com/plataformatec/simple_form"&gt;Simple Form&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Much of what we do as Rails developers boils down to making simple CRUD forms to work with models. Much of this is going to be inside an admin interface that users never actually see so we want to get the job done as quickly as possible. &lt;a href="https://github.com/plataformatec/simple_form"&gt;Simple Form&lt;/a&gt; lets you write the simplest form declaration possible and bakes in a lot of useful things like error and validation handling. It's also compatible with a number of CSS frameworks like Zurb Foundation and Bootstrap. I tend to use Simple Form in lieu of an admin interface generator like ActiveAdmin, mostly because I haven't had much luck getting those to play with Rails 4.&lt;/p&gt;
&lt;h3&gt;&lt;a href="http://sidekiq.org"&gt;Sidekiq&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;At some point every Rails application is going to need to do some background processing, especially if you're making server-side calls to other web services. These should &lt;em&gt;always&lt;/em&gt; be done outside of a web request because Rule Number 1 is &lt;a href="http://en.wikipedia.org/wiki/Fallacies_of_Distributed_Computing"&gt;The network is unreliable&lt;/a&gt; (the PDF in the sources block is a great explanation of the problems of distributed computing, btw). I've explored a number of different background processing systems for Rails and the best that I've found is named &lt;a href="http://sidekiq.org"&gt;Sidekiq&lt;/a&gt;. It uses less resources per worker than any of the rest and it is super easy to manage.&lt;/p&gt;</description><author>Pete Keen</author><pubDate>Sun, 15 Sep 2013 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.petekeen.net/essential-tools-for-starting-a-rails-app-in-2013/</guid></item><item><title>Disabling muting while typing in Google hangouts</title><link>/2013/09/12/Disabling-muting-while-typing-in-Google-hangouts/</link><description>&lt;p&gt;Google hangouts is awesome, its my preferred method for most audio/video calls these days. When running a group call I often dial into a separate phone if I have a better phone available for the group. It also got around the annoyance that when you are typing google automatically mutes you. This for most people is pretty subpar. While dialing in to the hangout can still be nice, you don&amp;rsquo;t have to do so to get rid of the annoying muting while typing. To fix such simply open up your terminal and run:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; defaults write com.google.googletalkplugind exps -string [\&amp;quot;-tm\&amp;quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;This clever hack discovered courtesy of &lt;a href="http://www.twitter.com/timtyrrell"&gt;@timtyrrell&lt;/a&gt; passed along to me by &lt;a href="http://www.twitter.com/mattmanning"&gt;@mattmanning&lt;/a&gt; and &lt;a href="http://www.twitter.com/blakegentry"&gt;@blakegentry&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</description><author>CRAIG KERSTIENS</author><pubDate>Thu, 12 Sep 2013 23:55:56 GMT</pubDate><guid isPermaLink="true">/2013/09/12/Disabling-muting-while-typing-in-Google-hangouts/</guid></item><item><title>Diving into Postgres JSON operators and functions</title><link>/2013/09/11/Diving-into-Postgres-JSON-operators-and-functions/</link><description>&lt;p&gt;Just as &lt;a href="https://postgres.heroku.com/blog/past/2013/9/9/postgres_93_now_available/"&gt;PostgreSQL 9.3&lt;/a&gt; was coming out I had a need to take advantage of the JSON datatype and some of the &lt;a href="http://www.postgresql.org/docs/9.3/static/functions-json.html"&gt;operators and functions&lt;/a&gt; within it. The use case was pretty simple, run a query across a variety of databases, then take the results and store them. We explored doing something more elaborate with the columns/values, but in the end just opted to save the entire result set as JSON then I could use the operators to explore it as desired.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s the general idea in code (using sequel):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;result = r.connection { |c| c.fetch(self.query).all }
mymodel.results = result.to_json
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As the entire dataset was stored as some compressed JSON I needed to do a bit of manipulation to get it back into a form that was workable. Fortunately all the steps were fairly straightforward.&lt;/p&gt;
&lt;p&gt;First you want to unnest each result from the json array, in my case this looked like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT json_array_elements(result)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The above will unnest all of the array elements so I have an individual result as JSON. A real world example would look something similar to:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT json_array_elements(result)
FROM query_results
LIMIT 2;
json_array_elements
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;p&gt;{&amp;ldquo;column_name&amp;rdquo;:&amp;ldquo;data_in_here&amp;rdquo;}
{&amp;ldquo;column_name_2&amp;rdquo;:&amp;ldquo;other_data_in_here&amp;rdquo;}
(2 rows)&lt;/p&gt;
&lt;p&gt;From here based on the query I would want to get some specific value. In this case I&amp;rsquo;m going to search for the text key column_name_2:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT json_array_elements(result)-&amp;gt;'column_name_2'
FROM query_results
LIMIT 1;
json_array_elements
-----------------------
&amp;quot;other_data_in_here&amp;quot;
(1 rows)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;One gotcha I encountered was when I wanted to search for some value or exclude some value&amp;hellip; Expecting I could just compare the result of the above in a where statement I was sadly mistaken because the equals operator didn&amp;rsquo;t translate.&lt;/em&gt; My first attempt at fixing this was to cast in this form:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT json_array_elements(result)-&amp;gt;'column_name_2'::text
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The sad part is because of the operator the cast doesn&amp;rsquo;t get applied as I&amp;rsquo;d expect. Instead you&amp;rsquo;ll want to do:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT (json_array_elements(result)-&amp;gt;'column_name_2')::text
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Of course theres plenty more you can do with the &lt;a href="http://www.postgresql.org/docs/9.3/static/functions-json.html"&gt;JSON operators in the new Postgres 9.3&lt;/a&gt;. If you&amp;rsquo;ve already got JSON in your application give them a look today. And while slightly worse, if you&amp;rsquo;ve got JSON stored in a text field simply cast it with &lt;code&gt;::json&lt;/code&gt; to begin using the operators.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;If you&amp;rsquo;re looking for a deeper resource on Postgres I recommend the book &lt;a href="https://theartofpostgresql.com/?affiliate=cek"&gt;The Art of PostgreSQL&lt;/a&gt;. It is by a personal friend that has aimed to create the definitive guide to Postgres, from a developer perspective. If you use code CRAIG15 you&amp;rsquo;ll receive 15% off as well.&lt;/em&gt;&lt;/p&gt;</description><author>CRAIG KERSTIENS</author><pubDate>Wed, 11 Sep 2013 23:55:56 GMT</pubDate><guid isPermaLink="true">/2013/09/11/Diving-into-Postgres-JSON-operators-and-functions/</guid></item><item><title>The Fizzbuzz Bug Tracker A Bug Tracker for Single Developers</title><link>https://boyter.org/2013/09/fizzbuzz-bug-tracker/</link><description>&lt;p&gt;This serves as the announcement of my new bug tracker product I am working on.&lt;/p&gt;
&lt;p&gt;My needs are pretty specific and none of the existing bug trackers I have tried have meet the following goals.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Speed. Searching, adding projects/issues/comments should be instant. Any time waiting on the bug tracker is wasted time.&lt;/li&gt;
&lt;li&gt;Outlook style view of projects/issues/comments. This should allow me to get an overview of how I am tracking.&lt;/li&gt;
&lt;li&gt;Not require me to spin up a full web-server. I don&amp;rsquo;t want to have to install a full stack web-server just to track bugs.&lt;/li&gt;
&lt;li&gt;Sync across all devices. I want it to just appear on every device I own without me having to worry about backups or keeping things in sync.&lt;/li&gt;
&lt;li&gt;Be single user. I don&amp;rsquo;t want to have to login or perform user management.&lt;/li&gt;
&lt;li&gt;Support multiple projects. I also want to be able to have multiple issues per project.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With that in mind let me introduce the Fizzbuzz Bug Tracker. Fizzbuzz is a single executable that works on Windows, Linux and OSX (once I have a machine to compile it on). A suggested workflow is to copy it onto your Dropbox folder (or equivalent) where it will be synced across all devices. Fizzbuzz has a strong emphasis on speed. Adding projects or issues, searching across projects and issues, drilling into projects or issues is near instant.&lt;/p&gt;</description><author>Ben E. C. Boyter</author><pubDate>Fri, 06 Sep 2013 08:22:26 GMT</pubDate><guid isPermaLink="true">https://boyter.org/2013/09/fizzbuzz-bug-tracker/</guid></item><item><title>Collection of Letters for Neural Network OCR Training</title><link>https://boyter.org/2013/09/collection-letters-training/</link><description>&lt;p&gt;I was looking for this on Google the other day and unable to find it. Essentially what I needed was a collection of images which are all the same size, but of different fonts so that I use them for training Neural Networks and test other OCR techniques. Since I couldn&amp;rsquo;t find any I thought I would upload my own collection.&lt;/p&gt;
&lt;p&gt;I used the below images when working on my thesis. From memory over 20 different fonts and sizes were used to create about 200 examples of each letter. The full data set proved to be pretty accurate when it came to recognizing most examples of text I found on the web.&lt;/p&gt;</description><author>Ben E. C. Boyter</author><pubDate>Tue, 03 Sep 2013 08:59:42 GMT</pubDate><guid isPermaLink="true">https://boyter.org/2013/09/collection-letters-training/</guid></item><item><title>Adventures in Self Publishing</title><link>https://www.petekeen.net/adventures-in-self-publishing/</link><description>&lt;p&gt;Three months ago I decided to write my &lt;a href="https://www.petekeen.net/mastering-modern-payments"&gt;first technical book&lt;/a&gt; and it's earned me over $5,000 in the two weeks since launch day, so I thought I decided to share what I've learned.&lt;/p&gt;
&lt;p&gt;I had been reading &lt;a href="http://nathanbarry.com"&gt;Nathan Barry&lt;/a&gt;'s excellent book &lt;a href="http://nathanbarry.com/authority/"&gt;Authority&lt;/a&gt; and something about it inspired me. I started throwing around ideas, things that I knew well and that weren't well covered already, and I turned up &lt;a href="https://stripe.com"&gt;Stripe&lt;/a&gt;. I know Stripe very well having used it for a bunch of projects in the past few years. I also know &lt;a href="http://rubyonrails.org"&gt;Rails&lt;/a&gt;, using it in most of those projects plus at my day job. I knew for sure that there were things about payment processing that weren't really talked about much in the 10 minute Stripe tutorials. Thus began my five month journey of writing and self-publishing &lt;a href="https://www.petekeen.net/mastering-modern-payments"&gt;Mastering Modern Payments: Using Stripe with Rails&lt;/a&gt;.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;Why self publish?&lt;/h2&gt;
&lt;p&gt;For me, it wasn't even really a question. There's no way a traditional publisher would be interested in a tiny niche topic like this, and even if they were I wanted as much control as I could get for my first major writing project. Not to mention, the amount of money I would get from direct sales is vastly more than I would get from the same number of sales if I were getting royalties.&lt;/p&gt;
&lt;p&gt;So, why not something like &lt;a href="https://leanpub.com"&gt;Leanpub&lt;/a&gt;? Again, it comes down to control. Leanpub provides a valuable service but I wanted to control the entire experience, from building the book files to the landing page all the way through to the sales experience. A great part of the value of the guide is that the Guide + Code edition comes with the code for the application that actually sells the book, and with Leanpub that wouldn't be possible.&lt;/p&gt;
&lt;h2&gt;Process and Tools&lt;/h2&gt;
&lt;p&gt;My writing process is pretty simple. I make an outline, at first just broad themes for chapters or sections. I step back, play with the order a little maybe, and then start adding more detail to the outline. At some point I get bored with that and start filling in chunks with prose. This is the same process I've used for most of my blog posts and it's been pretty successful.&lt;/p&gt;
&lt;p&gt;Editing was a much bigger job for the guide than it has been for my blog, of course. I did a first pass by hand on actual paper with an actual red pen and then followed that up with another pass on the computer. Shortly after that I released the guide as preorders to my mailing list (more on that below) and they provided vast quantities of feedback, ranging from typos to bug fixes all the way through to high level feedback on the shape and intent of the guide. I can't thank those reviewers enough. They made it possible to have a polished product on launch day.&lt;/p&gt;
&lt;p&gt;The tools I used for most of the writing process:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Emacs and Markdown mode&lt;/strong&gt;. The book is entirely written in &lt;a href="http://daringfireball.net/projects/markdown/"&gt;Markdown&lt;/a&gt;, except for a very few sections written in raw HTML. I use the excellent &lt;a href="http://jblevins.org/projects/markdown-mode/"&gt;Markdown Mode&lt;/a&gt; for basically everything I write, since it basically just gets everything right.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Docverter&lt;/strong&gt;. I use Pandoc and Flying Saucer via a local instance of &lt;a href="http://www.docverter.com"&gt;Docverter&lt;/a&gt; to build the various formats of the guide.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rake and Redcarpet&lt;/strong&gt;. I wrote a few different custom Markdown renderers using &lt;a href="https://github.com/vmg/redcarpet"&gt;Redcarpet&lt;/a&gt; that do things like highlight code samples with &lt;a href="https://github.com/tmm1/pygments.rb"&gt;Pygments&lt;/a&gt;, check the syntax of all of the Ruby code samples, spider all of the links in the book, and finally render out the table of contents how I prefer it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Gimp&lt;/strong&gt; for the cover design.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I've &lt;a href="https://github.com/peterkeen/mmp-builder"&gt;open sourced the code&lt;/a&gt; that I used to build the book. It's very specific for Mastering Modern Payments and the process that I eventually settled on, but I'm hopeful that it will provide inspiration for others that might be contemplating the same kind of self-publishing venture. It includes the &lt;a href="https://github.com/peterkeen/mmp-builder/blob/master/template.erb"&gt;pdf template and style sheet&lt;/a&gt; I use, along with the &lt;a href="https://github.com/peterkeen/mmp-builder/blob/master/Rakefile"&gt;Rakefile&lt;/a&gt; that contains all of the logic for building the guide in it's various formats.&lt;/p&gt;
&lt;h2&gt;Preorders&lt;/h2&gt;
&lt;p&gt;The first thing I did after actually deciding to write MMP was to put up a landing page and start collecting email addresses. On July 15th I started selling preorders, which in this case consisted of 30% off the final purchase price in exchange for an advanced copy of the guide along with regular weekly updates as people sent in feedback. This turns out to have been &lt;em&gt;highly lucrative&lt;/em&gt; and &lt;em&gt;extremely motivational&lt;/em&gt;. Preorder sales totaled over $3,000. Dozens of people were interested enough in this project to pay early. If you're developing an info product you should definitely consider some sort of preorder arrangement.&lt;/p&gt;
&lt;h2&gt;Numbers&lt;/h2&gt;
&lt;p&gt;I kept a simple journal throughout the main writing period and adding up all of that I wrote for about 70 hours. The total hours spent on the whole project is probably double that because I didn't count much of the development of the companion Rails application, editing the guide, writing emails to customers, nor of all of the other little tasks that go into a project like this.&lt;/p&gt;
&lt;p&gt;As of yesterday, the guide has grossed &lt;strong&gt;$8,603&lt;/strong&gt; (this includes preorders):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;99&lt;/strong&gt; copies of Just the Guide for $2,485&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;108&lt;/strong&gt; copies of Guide + Code for $5,341&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;3&lt;/strong&gt; copies of the Team license for $777&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;(note that these numbers don't add up to the straight &lt;code&gt;price * copies&lt;/code&gt; amount because I've had both 10% and 30% discounts going at various times)&lt;/p&gt;
&lt;div style="margin-bottom: 2em;"&gt;
&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;
&lt;/div&gt;
&lt;div style="margin-bottom: 2em;"&gt;
&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;
&lt;/div&gt;
&lt;p&gt;The biggest driver by far was HN traffic from &lt;a href="https://news.ycombinator.com/item?id=6217792"&gt;two&lt;/a&gt; &lt;a href="https://news.ycombinator.com/item?id=6220724"&gt;posts&lt;/a&gt; on launch day, August 15th. The second biggest driver was a link in &lt;a href="http://rubyweekly.com/archive/159.html"&gt;Ruby Weekly&lt;/a&gt;. I purchased an ad on &lt;a href="http://www.reddit.com/r/rails"&gt;/r/rails&lt;/a&gt; and sponsored &lt;a href="http://thechangelog.com/101/"&gt;The Changelog&lt;/a&gt; podcast, neither of which directly generated sales but may have driven direct site visits and sales later on. There wasn't a whole lot of time to put thought into tracking visits from The Changelog so I don't have any direct numbers.&lt;/p&gt;
&lt;p&gt;Since launch day the landing page has gotten 6,084 unique page views driving 136 sales for an overall conversion rate of 2.2%. Prior to launch day, the mailing list converted over 30% for preorders.&lt;/p&gt;
&lt;h2&gt;Lessons Learned&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Get help sooner in the process&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;When I started this project I was all by myself which hampered editing and reviewing a lot. I didn't get my first comprehensive external review of the thing until after I had sold the first few copies. Next time I'll assemble a few reliable technical reviewers ahead of time so that they can start reading drafts before publication.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Write more guest blog posts&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This is something that Nathan espouses in Authority and I just didn't have time to get to. I only had one guest blog post on launch day and it didn't generate much traffic at all, let alone sales. Next time I'll have at least three lined up, as well as having coverage in the relevant weekly newsletters and podcasts set up beforehand.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Write more relevant content for my email list&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Early on I was sending interesting little Stripe and Rails things to my mailing list, but after preorders started it became product updates every time. I think at some point I started overselling them. For my next product I'm going to try to alternate product updates with interesting, relevant information that isn't directly related to the product.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Don't have so much going on while developing and selling&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I had far too many things going on when I decided to launch. Here's an abbreviated schedule for launch day and the following:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;2013-08-12&lt;/em&gt; — Start packing for move&lt;br /&gt;
&lt;em&gt;2013-08-15&lt;/em&gt; — Launch day AND travel day on airplanes&lt;br /&gt;
&lt;em&gt;2013-08-16&lt;/em&gt; — Out of internet range, getting things set up   for the wedding&lt;br /&gt;
&lt;em&gt;2013-08-17&lt;/em&gt; — Wedding Day&lt;br /&gt;
&lt;em&gt;2013-08-18&lt;/em&gt; — Travel back to Portland, get ready to move&lt;br /&gt;
&lt;em&gt;2013-08-23&lt;/em&gt; — Pack everything we own and start driving across the country&lt;br /&gt;
&lt;em&gt;2013-08-28&lt;/em&gt; — Attend a funeral&lt;br /&gt;
&lt;em&gt;2013-08-29&lt;/em&gt; — Sign a new lease in Michigan&lt;/p&gt;
&lt;p&gt;I knew about the wedding and moving well ahead of time, so why did I pick August 15th? Lack of critical thinking, clearly.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I'd like to take the opportunity to thank my wife, who has put up with not only this project but all of my projects over the last two years. She's the most amazing, understanding person that I've ever met. I'd also like to thank &lt;a href="http://www.buzzwordcompliant.net/"&gt;Michael Buckbee&lt;/a&gt;, &lt;a href="http://www.andrewculver.net/"&gt;Andrew Culver&lt;/a&gt;, and a whole host of other people who kept me motivated and on track, right to the end.&lt;/p&gt;
&lt;p&gt;This project has been an amazing experience. I've learned so much about how to build a product, how to build an audience, and how to make a success, and it's not even over. I'll be releasing periodic updates with corrections and new information since both Stripe and Rails are both active, changing products.&lt;/p&gt;</description><author>Pete Keen</author><pubDate>Tue, 03 Sep 2013 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.petekeen.net/adventures-in-self-publishing/</guid></item><item><title>Saving Resources for Humans in PHP</title><link>https://boyter.org/2013/08/saving-resources-humans-php/</link><description>&lt;p&gt;One of the issues I have run into running searchcode.com is that a huge amount of time spent serving pages is serving them to bots (about 900,000 hits a day are from bots). I have been watching the load averages and they can spike to over 30.00 occasionally which is pretty bad for a 4 core/ht system. I don&amp;rsquo;t have any problems with bot&amp;rsquo;s really other then the fact that I cannot really control them. Sure you can specify crawl delays but its of limited use if the bot chooses to ignore it.&lt;/p&gt;</description><author>Ben E. C. Boyter</author><pubDate>Tue, 27 Aug 2013 03:23:43 GMT</pubDate><guid isPermaLink="true">https://boyter.org/2013/08/saving-resources-humans-php/</guid></item><item><title>Mastering Modern Payments Is Out Today!</title><link>https://www.petekeen.net/mastering-modern-payments-launch-day/</link><description>&lt;p&gt;
&lt;img src="https://d2s7foagexgnc2.cloudfront.net/files/a8dab64c9e6402ee7b16/stripe_rails.png" style="float: right; margin-left: 20px;" /&gt;
I'm so proud to announce that &lt;em&gt;&lt;a href="/mastering-modern-payments"&gt;Mastering Modern Payments: Using Stripe with Rails&lt;/a&gt;&lt;/em&gt; is officially launching this morning. &lt;em&gt;Mastering Modern Payments&lt;/em&gt; is your guide to integrating Stripe with your Rails application and is packed with sample code and best practices that will make sure your integration works now and in the future.
&lt;/p&gt;
&lt;!--more--&gt;
&lt;p&gt;I've collected for you the best resources from around the web and added my own experiences and stories to help you make your Stripe integration as robust as it can be. Click below to check it out!&lt;/p&gt;
&lt;div class="well" style="text-align: center;"&gt;&lt;a class="btn btn-large btn-success" href="/mastering-modern-payments"&gt;Find Out More about Mastering Modern Payments&lt;/a&gt;&lt;/div&gt;</description><author>Pete Keen</author><pubDate>Thu, 15 Aug 2013 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.petekeen.net/mastering-modern-payments-launch-day/</guid></item><item><title>The Rule of Thirds - followup</title><link>/2013/08/13/The-Rule-of-Thirds-followup/</link><description>&lt;p&gt;Several months back I wrote about how we do &lt;a href="http://www.craigkerstiens.com/2013/03/13/planning-and-prioritizing/"&gt;higher level, long term planning within the Heroku Postgres team&lt;/a&gt;. If you haven&amp;rsquo;t read the previous article please start there.&lt;/p&gt;
&lt;p&gt;The exercise or rule of thirds is intended to be approximate prioritization and not a perfect science. Since that time I&amp;rsquo;m familiar with some teams both in and out of Heroku who have attempted this exercise with varying levels of success. We&amp;rsquo;ve now done this process 4 times within the team and after the most recent exercise attempted to take some time to internalize why its worked well, creating some more specifics about the process. Heres an attempt to provide even more clarity:&lt;/p&gt;
&lt;h2 id="gather-data-ahead-of-time"&gt;
&lt;div&gt;
Gather data ahead of time
&lt;/div&gt;
&lt;/h2&gt;
&lt;p&gt;Its really common to have a list things to work on, but knowing the impact of those is commonly pure speculation. There may be some people that talk to customers, but even then its a subset of your actual customer base. Going into the exercise as much data you can have ahead of time on impact of features and specific problems helps. In our case we do this by:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Surveying current customers and users&lt;/li&gt;
&lt;li&gt;Surveying attriters&lt;/li&gt;
&lt;li&gt;Engaging with customer facing teams to hear trends&lt;/li&gt;
&lt;li&gt;Input from external parties such as analysts on trends&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="allow-for-casual-discussion"&gt;
&lt;div&gt;
Allow for casual discussion
&lt;/div&gt;
&lt;/h2&gt;
&lt;p&gt;We typically conduct our planning exercise at an offsite, this is a multi-day time of team bonding, planning, hacking. We intentionally schedule our planning excercise towards the end of the offsite. This allows us to have updates/presentations frmo the data we&amp;rsquo;ve gathered and from those that are customer facing. Presentations are meant to be short and direct, discussion can flow casually after. This gets a lot of people on the same page at a smaller level and reduces the problem of too many cooks in the kitchen come time for the actual exercise.&lt;/p&gt;
&lt;h2 id="the-rule-of-thirds"&gt;
&lt;div&gt;
The rule of thirds
&lt;/div&gt;
&lt;/h2&gt;
&lt;h3 id="creating-the-list"&gt;
&lt;div&gt;
Creating the list
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;Coming to the exercise itself&amp;hellip; We begin by everyone writing a list of their ideas individually, this is meant to be a list of the features we want to place on the grid. At this point theres no prioritizing of difficulty or impact. In addition each list while individually created does not have to contain items that only pertain to you, its more a comprehensive list of all the things you can think of that may be important to do.&lt;/p&gt;
&lt;h3 id="bucketing-part-1"&gt;
&lt;div&gt;
Bucketing part 1
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;Once individual lists are created you can then collectively or designate one or two people to clean it up. We do this in two forms:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Removing duplicate items, which there should be several of.&lt;/li&gt;
&lt;li&gt;Bucketing my a common/theme idea, this simply makes things more digestable&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If you&amp;rsquo;re a big group of greater than 7 then it may be advisable to designate two people to do this exercise together. If a smaller group it can be manageable to coordinate collectively.&lt;/p&gt;
&lt;h3 id="bucketing-part-2"&gt;
&lt;div&gt;
Bucketing part 2
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;Once you&amp;rsquo;ve removed dupes, identified themes, and removed excess items (depending on your team size you&amp;rsquo;ll find how many feels right - we aim an average of 5-6 per square for a team of 10) its then on to actually putting them on the grid. In the past we&amp;rsquo;ve done this a variety of ways but our most recent process seemed to be quiet efficient. We gave each item 60 seconds, at the end of that minute wherever the item was it was left there. This forced some quick discussion on impact and difficulty but in the end left us at a very good hit rate without taking multiple hours to complete the exercise.&lt;/p&gt;
&lt;h3 id="final-pass"&gt;
&lt;div&gt;
Final pass
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;We intentionally design it so that low effort and high impact is on the top right corner. Finally once everything is on there we allocate names to the tasks, and put boxes around items we&amp;rsquo;re planning to do in the coming months. With boxes make it very clear of what we are doing as well as explicitly things we are not. The initials or names make it clear of how loaded down people are. If your name is on 3 tasks that are high difficulty, then you&amp;rsquo;re likely over allocated.&lt;/p&gt;
&lt;p&gt;At this point things usually fall out pretty quickly and we emerge with some rough roadmap that in retrospect we&amp;rsquo;ve followed pretty accuately.&lt;/p&gt;</description><author>CRAIG KERSTIENS</author><pubDate>Tue, 13 Aug 2013 23:55:56 GMT</pubDate><guid isPermaLink="true">/2013/08/13/The-Rule-of-Thirds-followup/</guid></item><item><title>C# Vector Space Implementation</title><link>https://boyter.org/2013/08/c-vector-space-implementation/</link><description>&lt;p&gt;Since I am writing lots of Vector Space implementations in Go, Python etc… I thought I would add another one in C#. This one is a little more verbose then either the Python or Go implementations. The verbosity is mostly due to not using any of the nice C# LINQ functionality which would really reduce the size.&lt;/p&gt;
&lt;p&gt;In any case here it is in case you are looking for a simple implementation of this useful class.&lt;/p&gt;</description><author>Ben E. C. Boyter</author><pubDate>Tue, 13 Aug 2013 08:59:09 GMT</pubDate><guid isPermaLink="true">https://boyter.org/2013/08/c-vector-space-implementation/</guid></item><item><title>GoLang Vector Space Implementation</title><link>https://boyter.org/2013/08/golang-vector-space-implementation/</link><description>&lt;p&gt;UPDATE – This is now actually available as a real Golang import with tests. Get it at &lt;a href="https://github.com/boyter/golangvectorspace"&gt;https://github.com/boyter/golangvectorspace&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I have mentioned this before somewhere but one of the first things I usually attempt to implement in any programming language I want to play with is a vector space. Its my own personal FizzBuzz implementation. It usually covers everything I need to know in a language (imports, functions, string manipulation, math functions, iteration, maps etc…) so I consider it a good thing to get started with.&lt;/p&gt;</description><author>Ben E. C. Boyter</author><pubDate>Mon, 12 Aug 2013 09:51:30 GMT</pubDate><guid isPermaLink="true">https://boyter.org/2013/08/golang-vector-space-implementation/</guid></item><item><title>The missing PostgreSQL documentation</title><link>/2013/08/07/The-missing-PostgreSQL-documentation/</link><description>&lt;p&gt;For a couple of years I&amp;rsquo;ve complained about the Postgres documentation and at the same time paraded it as one of the best sets of documentation I&amp;rsquo;ve encountered. In many ways the reason I veer towards &lt;a href="http://www.postgresql.org"&gt;Postgres&lt;/a&gt; as well as &lt;a href="http://www.python.org"&gt;Python&lt;/a&gt; and &lt;a href="http://www.djangoproject.com"&gt;Django&lt;/a&gt; is the quality of their documentation. If you need to find details about something its documented, and more importantly well and thoroughly documented.&lt;/p&gt;
&lt;p&gt;In large part I came to Python by happenstance through &lt;a href="http://www.djangoproject.com"&gt;Django&lt;/a&gt;, and Postgres through happenstance of an employer. Yet, Django was very little of an accident. The Django Tutorial got me a large part of what I needed to know and more excited about development than I had been in some time. Python has done some work at adding docs to make this even better, sadly its still very much needed for PostgreSQL.&lt;/p&gt;
&lt;h3 id="whats-missing-in-the-postgres-docs"&gt;
&lt;div&gt;
Whats Missing in the Postgres Docs
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;Theres a huge variety of types of documentation, off the top of my head theres:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Reference docs (Postgres excels at this)&lt;/li&gt;
&lt;li&gt;Onboarding (Postgres tutorial huh?)&lt;/li&gt;
&lt;li&gt;Tailored guides (Postgres? I can haz? Nope&amp;hellip; We don&amp;rsquo;t understand&amp;hellip;.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Postgres is great if you know the name of what you&amp;rsquo;re looking for, but if you don&amp;rsquo;t you&amp;rsquo;re entirely left in the dark.&lt;/p&gt;
&lt;h3 id="understanding-the-power-of-postgres"&gt;
&lt;div&gt;
Understanding the power of Postgres
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;Postgres is good enough at performance, good enough at usability, and awesome at how powerful and flexible it can be. But all of this is entirely lost if you have to know the esoteric name of what you&amp;rsquo;re looking for.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;What the hell is an hstore&amp;hellip; In so many ways KVstore makes infintely more sense. In the same sense PLV8, I have to know not only what PL stands for but V8 as well, versus the JavaScript extension for Postgres.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I understand there are plenty of reasons why some of these things are the way they are, but its also limiting how great the broader perception is. Postgres externally is this hard to use DB, that well is just a database, versus giving developers a set of powerful and useful functions to make their lives better.&lt;/p&gt;
&lt;h3 id="the-solution"&gt;
&lt;div&gt;
The Solution
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;Lets fix things, there are a ton of people that would love to know more about all things Postgres. This ranges from a good set of onboarding docs, to specific blog posts on topics that people are curious about. Just last week I got an email about improving &lt;strong&gt;the&lt;/strong&gt; Postgres tutorial&amp;hellip; Yes theres a tutorial hidden in the &lt;a href="http://www.postgresql.org/docs/9.2/static/tutorial.html"&gt;2000 page set of documentation for Postgres&lt;/a&gt;. Its simply old, mostly uninteresting, and well just needs to be completely recreated. A great alternative would be a few tutorials/guides for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Noobs to databases in general (Total 101 guide)&lt;/li&gt;
&lt;li&gt;Building and architecting your application with Postgres (App Devs)&lt;/li&gt;
&lt;li&gt;Administering and maintaining Postgres (DBAs)&lt;/li&gt;
&lt;li&gt;SQL and reporting in Postgres (consumers of data, analysts, product people, marketing, etc.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If jumping in and contributing to fixing the core tutorial isn&amp;rsquo;t your cup of tea because you don&amp;rsquo;t want to learn and write in &lt;a href="http://www-sul.stanford.edu/tools/tutorials/html2.0/gentle.html"&gt;SGML&lt;/a&gt;, send a pull request to &lt;a href="http://postgresguide.com"&gt;postgresguide.com&lt;/a&gt; or do a [guest post on my blog](mailto:craig.kerstiens@gmail.com]. If thats too much effort please just let us know, what do you want to see - &lt;a href="mailto:craig.kerstiens@gmail.com"&gt;craig.kerstiens at gmail.com&lt;/a&gt;&lt;/p&gt;</description><author>CRAIG KERSTIENS</author><pubDate>Wed, 07 Aug 2013 23:55:56 GMT</pubDate><guid isPermaLink="true">/2013/08/07/The-missing-PostgreSQL-documentation/</guid></item><item><title>A look at Foreign Data Wrappers</title><link>/2013/08/05/A-look-at-Foreign-Data-Wrappers/</link><description>&lt;p&gt;There are two particular sets of features that continue to keep me very excited about the momentum of Postgres. And while PostgreSQL has had some great momentum in the past few years these features may give it an entirely new pace all together. One is extensions, which is really its own category. Dimitri Fontaine was talking about doing a full series just on extensions, so here&amp;rsquo;s hoping he does so I dont have to :)&lt;/p&gt;
&lt;p&gt;One subset of extensions which I consider entirely separate is the other thing, which is foreign data wrappers or FDWs. FDWs allow you to connect to other data sources from within Postgres. From there you can query them with SQL, join across disparate data sets, or join across different systems. Recently I had a good excuse to give the &lt;code&gt;postgres_fdw&lt;/code&gt; a try. And while I&amp;rsquo;ve blogged about the Redis FDW previously, the Postgres one is particularly exciting because with PostgreSQL 9.3 it will ship as a contrib module, which means all Postgres installers should have it&amp;hellip; you just have to turn it on.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s take a look at getting it setup and then dig into it a bit. First, because I don&amp;rsquo;t have Postgres 9.3 sitting around on my system I&amp;rsquo;m going to provision one from Heroku Postgres:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ heroku addons:add heroku-postgresql:crane --version 9.3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once it becomes available I&amp;rsquo;m going to connect to it then enable the extension:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ heroku pg:psql BLACK -acraig
# CREATE EXTENSION postgres_fdw;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now its there, so we can actually start using it. To use the FDW there&amp;rsquo;s four basic things you&amp;rsquo;ll want to do:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create the remote server&lt;/li&gt;
&lt;li&gt;Create a user mapping for the remote server&lt;/li&gt;
&lt;li&gt;Create your foreign tables&lt;/li&gt;
&lt;li&gt;Start querying some things&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="the-setup"&gt;
&lt;div&gt;
The setup
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;You&amp;rsquo;ll only need to do each of the following once, once you&amp;rsquo;re server, user and foreign table are all setup you can simply query away. This is a nice advantage over db_link which only exists for the set session. &lt;em&gt;One downside I did find was that you can&amp;rsquo;t use a full Postgres connection string, which would make setting it up much simpler&lt;/em&gt;. So onto setting up our server:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# CREATE SERVER app_db
FOREIGN DATA WRAPPER postgres_fdw
OPTIONS (dbname 'dbnamehere', host 'hostname-here);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next we&amp;rsquo;ll actually create our user mapping. In this case we&amp;rsquo;ll take the remote username and password and map it to our current user we&amp;rsquo;re already connected with.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# CREATE USER MAPPING for user_current
SERVER app_db
OPTIONS (user 'remote_user', password 'remote_password');
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And finally we&amp;rsquo;re going to configure our tables. &lt;em&gt;There were some additional pains here as there wasn&amp;rsquo;t a perfectly clean way to generate the &lt;code&gt;CREATE TABLE&lt;/code&gt;. Sure you could pg_dump just that table, but overall it felt a bit cludgey.&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# CREATE FOREIGN TABLE users
(
id integer,
email text,
created_at timestamp,
first_name text,
last_name text
)
SERVER app_db OPTIONS (table_name 'users')
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we&amp;rsquo;ve got all of our local data, as well as remote data. For that report against two databases where you previously wrote a ruby or python script, ran a query, constructed another query, then executed it you can directly do in your database. We can simply query our new table - &lt;code&gt;SELECT * FROM users LIMIT 5;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;But the real power of foreign data wrappers goes well beyond just Postgres to Postgres. Having a defined contract in translating from one system to another, will really allow reinventing the way we work with data. This is especially true in large datasets where doing ETL on terrabytes of data takes longer than asking the questions of it.&lt;/p&gt;
&lt;p&gt;While we&amp;rsquo;re waiting for more FDWs to be ready to use in production situations the Postgres FDW is a great start, &lt;em&gt;though the Redis one is on its way&lt;/em&gt;. Even better is that it ships with standard installs of Postgres, meaning it will see more usage and help push them to advance further.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;One final nicety, you&amp;rsquo;re not required to have ALL Postgres 9.3 DBs, just one that can then connect to the others, so go ahead and give it try :)&lt;/em&gt;&lt;/p&gt;</description><author>CRAIG KERSTIENS</author><pubDate>Mon, 05 Aug 2013 23:55:56 GMT</pubDate><guid isPermaLink="true">/2013/08/05/A-look-at-Foreign-Data-Wrappers/</guid></item><item><title>EverQuest Next Revealed</title><link>https://benovermyer.com/blog/2013/08/everquest-next-revealed/</link><description>&lt;img class="photo" src="https://benovermyer.com/processed_images/EQN_logo_color1.ed82b71ca4d9435a.jpg" /&gt;
&lt;p&gt;I’m writing this just a couple minutes after watching the EverQuest Next reveal video on Twitch. I couldn’t watch it live yesterday, but it was totally worth the wait. For an hour and a half, I was on the edge of my chair. The introduction was a beautiful piece of sand performance art, which I otherwise won’t talk about. The rest of the video was an introduction into what will probably be the single most revolutionary MMORPG to come out since 2003.&lt;/p&gt;
&lt;p&gt;Dave Georgeson, director of development for EverQuest Next, started things off with a reveal of the art style. Let me tell you, this is a gorgeous game. The whole thing is very painterly, and is rendered using a modified version of the engine powering Planetside 2.&lt;/p&gt;
&lt;p&gt;All of the races have been redone, and we were treated to concept art for several: the High Elf, the Dark Elf, the Human, the Dwarf, and the Kerran. The Dark Elf and the Kerran were probably the biggest departure from previous EQ designs. Kerrans are distinctly more lionesque, with pronounced lion’s heads instead of the old cat faces and huge builds. Dark elves look like standard dark elves, except they have draconic eyes and short horns. There’s a story behind that that I really want to hear.&lt;/p&gt;
&lt;p&gt;The art style for characters is exaggerated, even slightly cartoony. I’m really OK with that, especially since they’re building SOEmote into the game from the beginning. It’s easy to see the expressions of the players themselves on their characters, and that’s marvelous for a roleplayer, but also for immersion and socializing in general.&lt;/p&gt;
&lt;img class="photo" src="https://benovermyer.com/processed_images/everquestnext_07.40655afe3d5594f3.jpg" /&gt;
&lt;p&gt;Movement is fantastic. Characters slide, leap, vault, grab, and otherwise interact with the terrain in a very natural way. With EQN’s “active movement system,” players are free to really enjoy moving around in the world. There are items and spells that allow additional ways to move, like the Boots of the Zephyr in the video that allowed the players to float gently downward. Brilliant stuff.&lt;/p&gt;
&lt;p&gt;Then, we got into the Four Grails, as Dave put it.&lt;/p&gt;
&lt;h1 id="the-core-game"&gt;The Core Game&lt;/h1&gt;
&lt;p&gt;Grail 1 is the “Core Game.” This was the least impressive of the Grails, but it was still nice to see. Character evolution from a mechanical standpoint is based on collecting classes from around the world and mixing and matching abilities from them. It sounds like a hybrid of Guild Wars 2’s system and Rift’s system. Weapon choice matters, like it does in GW2.&lt;/p&gt;
&lt;h1 id="destructibility"&gt;Destructibility&lt;/h1&gt;
&lt;p&gt;Grail 2 is “Destructibility,” and this was the first mind-blowing revelation. The entire game world is built of voxels, despite looking natural and perfectly designed. It’s fully destructible, even though it heals over time. It’s like if SOE took a look at Minecraft and applied that on a grand scale. The way it works, too, is gorgeous. You can definitely see the aftermath of epic battles in the environment that it occurred in.&lt;/p&gt;
&lt;h1 id="a-life-of-consequence"&gt;A Life of Consequence&lt;/h1&gt;
&lt;p&gt;Grail 3 is “A Life of Consequence.” Besides the standard “every action you take matters” spiel, we were introduced to the idea of emergent AI in an MMO. SOE will create mob behavioral patterns - likes, dislikes, etc. - and then release mobs of that type into the world. They will live, react, and die according to how they’re programmed to behave. This means it’s entirely possible to have roving bands of orcs that actually decide to set up camp in a particular area - choose their own spawn point, as it were. It’s also possible to drive them out.&lt;/p&gt;
&lt;h1 id="permanent-change"&gt;Permanent Change&lt;/h1&gt;
&lt;p&gt;Grail 4 is “Permanent Change.” For the most part, Dave talked about the Rallying Cry system, which is a little like a server-wide public quest that lasts 2-3 months each… and has permanent effect on the game world. These Rallying Cries are promised to be different on every server, and that along with the Life of Consequence and Destructibility Grails ensure that no two servers will have the same environment and history, let alone community.&lt;/p&gt;
&lt;h1 id="everquest-next-landmark"&gt;EverQuest Next Landmark&lt;/h1&gt;
&lt;p&gt;As if all this wasn’t enough, then they got into the EverQuest Next Landmark game that’s coming out later in 2013. Landmark sounds basically like Minecraft on an epic scale, except that one continent in each Landmark server will have EverQuest Next art direction enforced… and creations from those areas will make it into EverQuest Next as real, lasting landmarks.&lt;/p&gt;
&lt;h1 id="player-studio"&gt;Player Studio&lt;/h1&gt;
&lt;p&gt;On top of that, SOE’s Player Studio will find a very interesting real money economy implementation for people who enjoy building things in Landmark; you’ll be able to sell not only finished works, but components. The example that Dave gave was of a player creating a nice crenelated tower. People could buy that tower, then make castles from it and sell those castles, and the player who created the tower would get royalties based on how many of that tower were used in the castle design for every castle sold. It’s a fascinating system.&lt;/p&gt;
&lt;p&gt;Basically, EverQuest Next sounds like the game I’ve been waiting for my entire gaming life. I really want to hear about how the crafting system works, and more about all the races, but… it sounds awe-inspiring. EverQuest Next takes player involvement in the game to a level never before seen.&lt;/p&gt;
&lt;h1 id="related-articles"&gt;Related articles&lt;/h1&gt;
&lt;p&gt;EverQuest Next - Landmark Focuses on Construction and Exploration, Is Free-to-Play&lt;/p&gt;
&lt;p&gt;EverQuest Next + Jeremy Soule = Geek Delight&lt;/p&gt;
&lt;p&gt;EverQuest Next Redefines Next-Gen MMOs Enhanced by Zemanta&lt;/p&gt;</description><author>Ben Overmyer's Site</author><pubDate>Sat, 03 Aug 2013 03:00:00 GMT</pubDate><guid isPermaLink="true">https://benovermyer.com/blog/2013/08/everquest-next-revealed/</guid></item><item><title>Postgres Dollar Quoting</title><link>/2013/08/02/Postgres-Dollar-Quoting/</link><description>&lt;p&gt;After my most recent post on &lt;a href="/2013/07/29/documenting-your-postgres-database/"&gt;documenting your database&lt;/a&gt; I had a colleague and friend chime in:&lt;/p&gt;
&lt;p&gt;{% blockquote @danfarina &lt;a href="https://twitter.com/danfarina/status/362007008079126528"&gt;https://twitter.com/danfarina/status/362007008079126528&lt;/a&gt; %}
@craigkerstiens You may want to mention for another post the generality of dollar quoting: it&amp;rsquo;s not just for CREATE FUNCTION.
{% endblockquote %}&lt;/p&gt;
&lt;p&gt;Luckily I was able to convince him to create the post. You can read a bit more on him below, but without further adieu here&amp;rsquo;s a bit on dollar quoting within Postgres:&lt;/p&gt;
&lt;p&gt;Postgres supports two forms of entry of data literals into the system.
One is the familiar single-quote:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;=&amp;gt; SELECT 'hello';
?column?
----------
hello
(1 row)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This format is problematic when one might be using single quotes in
the textual string.&lt;/p&gt;
&lt;p&gt;Postgres also supports another way to enter data
literals, most often seen in &lt;code&gt;CREATE FUNCTION&lt;/code&gt;, but can be profitably
used anywhere. This is called &amp;ldquo;dollar quoting,&amp;rdquo; and it looks like
this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;=&amp;gt; SELECT $$hello's the name of the game$$;
?column?
------------------------------
hello's the name of the game
(1 row)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If one needs nested dollar quoting, one can specify a string, much
like the &amp;lsquo;heredoc&amp;rsquo; feature seen in some programming languages:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;=&amp;gt; SELECT $goodbye$hello's the name of the $$ game$goodbye$;
?column?
---------------------------------
hello's the name of the $$ game
(1 row)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This can appear anywhere where single quotes would otherwise be,
simplifying tasks like using contractions in database object comments,
for example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;=&amp;gt; CREATE TABLE described(a int);
=&amp;gt; COMMENT ON TABLE described IS $$I'm describing this,
including newlines and an apostrophe in the contraction &amp;quot;I'm.&amp;quot;$$;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or, alternatively, entry of literals for types that may include
apostrophes in their serialization, such as &amp;rsquo;text&amp;rsquo; or &amp;lsquo;json&amp;rsquo;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;=&amp;gt; CREATE TABLE json(data json);
=&amp;gt; INSERT INTO json(data) VALUES
($${&amp;quot;quotation&amp;quot;: &amp;quot;'there is no time like the present'&amp;quot;}$$);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="security"&gt;
&lt;div&gt;
Security
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;Even though dollar quotes can be used to reduce the pain of many
quoting problems, don&amp;rsquo;t be tempted to use them to avoid SQL injection:
an adversary that knows one is using dollar quoting can still mount
exactly the same kind of attacks as if one were using single quotes.&lt;/p&gt;
&lt;p&gt;There is also no need, because any place a data literal can appear can
also be used with parameter binding (e.g. &lt;code&gt;$1&lt;/code&gt;, &lt;code&gt;$2&lt;/code&gt;, &lt;code&gt;$3&lt;/code&gt;&amp;hellip;), which one&amp;rsquo;s
Postgres driver should support. Nevertheless, for data or scripts one
is working with by hand, dollar quoting can make things much easier to
read.&lt;/p&gt;
&lt;h3 id="about-the-author"&gt;
&lt;div&gt;
About the Author
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;Daniel Farina is a long time colleague and friend, having worked together at 5 different companies. He&amp;rsquo;s part of the &lt;a href="https://twitter.com/danfarina/status/362007008079126528"&gt;Heroku Postgres&lt;/a&gt; team as the resident tuple groomer, and the creator of &lt;a href="https://github.com/wal-e/wal-e"&gt;WAL-E&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;As is always the case if you have articles you&amp;rsquo;d like to see created or if you&amp;rsquo;re interested in doing a guest post please feel free to drop me a line &lt;a href="mailto:craig.kerstiens@gmail.com"&gt;craig.kerstiens at gmail.com&lt;/a&gt;. And if you have articles you feel are helpful to others in the Postgres world drop me a note as well for including them in &lt;a href="http://www.postgresweekly.com"&gt;Postgres Weekly&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;</description><author>CRAIG KERSTIENS</author><pubDate>Fri, 02 Aug 2013 23:55:56 GMT</pubDate><guid isPermaLink="true">/2013/08/02/Postgres-Dollar-Quoting/</guid></item><item><title>Automatically inline Python function calls</title><link>https://tomforb.es/blog/automatically-inline-python-function-calls/</link><description>Edit: Code is here on GitHub Calling functions in Python can be expensive. Consider this example: there are two statements that are being timed, the first one calls a function that returns an integer while the second one calls a function that returns the result of a second function call which return...</description><author>Tom Forbes</author><pubDate>Thu, 01 Aug 2013 18:54:18 GMT</pubDate><guid isPermaLink="true">https://tomforb.es/blog/automatically-inline-python-function-calls/</guid></item><item><title>Clean Repository Data Access in C#</title><link>https://boyter.org/2013/07/clean-repository-data-access-c/</link><description>&lt;p&gt;Mostly as a self reference here is an extremely clean data access pattern possible using C# and Entity Framework. It saves you the effort of mocking the database context as the code you end up writing is so simple it is all compile time checked.&lt;/p&gt;
&lt;p&gt;Essentially you define a very simple class which provides a single method for getting data (although you may want a save data method too) and make sure you add an interface to make unit testing/mocking easier.&lt;/p&gt;</description><author>Ben E. C. Boyter</author><pubDate>Thu, 01 Aug 2013 02:52:14 GMT</pubDate><guid isPermaLink="true">https://boyter.org/2013/07/clean-repository-data-access-c/</guid></item><item><title>Documenting your PostgreSQL database</title><link>/2013/07/29/Documenting-your-PostgreSQL-database/</link><description>&lt;p&gt;Just a few days ago I was surprised by what someone was doing with their database, and not in the typical horrifying travesty against mankind. Rather, it was a feature that while familiar with I&amp;rsquo;d never seen anyone fully take proper advantage of - &lt;code&gt;COMMENT&lt;/code&gt; or describing tables. Postgres has a nice facility for you to provide a description for just about anything:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Table&lt;/li&gt;
&lt;li&gt;Column&lt;/li&gt;
&lt;li&gt;Function&lt;/li&gt;
&lt;li&gt;Schema&lt;/li&gt;
&lt;li&gt;View&lt;/li&gt;
&lt;li&gt;Index&lt;/li&gt;
&lt;li&gt;Etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The specific use case was a database acting as a datamart pulling in data from multiple sources to be able to report against disparate data. Over the years I&amp;rsquo;ve seen this occur really one three ways, the first is that a limited set of people, typically one person, have knowledge over all the datasources and thus far the sole individual responsible for creating reports and answering questions of the data. The second, is wide open access to anyone that wishes for it. In this case you often have people asking questions of the data, and because they don&amp;rsquo;t understand the relationships coming up to entirely wrong conclusions. The final approach is to create some external documentation, entity relationship diagrams, data dictionaries, etc. This last one often works okay enough, but often suffers from lack of updates and being too heavyweight.&lt;/p&gt;
&lt;p&gt;A better solution, and all around good process is simply documenting clearly within the database itself. Simply comment each table and column, just as you would outside of your DB then it can be quite clear when inside the database working interactivly:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;COMMENT ON TABLE products IS 'Products catalog';
COMMENT ON COLUMN products.price is 'Current price of a single item purchased';
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;While an obvious example above naming even the most mundance columns can help create more accurate reports. Then of course when you want to inspect your DB its quite clear:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;\d+ users
# \d+ users
Table &amp;quot;public.users&amp;quot;
Column | Type | ... | Description
------------+-----------------------------+-...-+-----------------------------------------
id | integer | ... | auto serial pk
first_name | character varying(50) | ... | required first name of user
last_name | character varying(50) | ... | required first name of user
email | character varying(255) | ... | email address of account
data | hstore | ... | mix of data, city, state, gender
created_at | timestamp without time zone | ... | when account was created, not confirmed
updated_at | timestamp without time zone | ... | time any details were last updated
Indexes:
&amp;quot;idx_user_created&amp;quot; btree (date_trunc('day'::text, created_at))
Has OIDs: no
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But it doesn&amp;rsquo;t necessarily have to stop there. Which actually brings me to one other item, you should be commenting your SQL just the same. SQL comments can be done easily by just starting a line with &lt;code&gt;--&lt;/code&gt;, or you can have it at the end of the line with further info. Here&amp;rsquo;s a nice example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-- Query aggregates all project names that have open past due tasks grouped by email
SELECT
users.email,
array_to_string(array_agg(projects.name), ',')) as projects # Aggregate all projects and separate by comma
FROM
projects,
tasks,
users
-- A user has a project, which has tasks
WHERE projects.id = tasks.project_id
-- Check for tasks that are due before now and not done yet
AND tasks.due_at &amp;gt; tasks.completed_at
AND tasks.due_at &amp;lt; now()
AND users.id = projects.user_id
GROUP BY
users.email
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You comment your code, why shouldn&amp;rsquo;t you comment your database?&lt;/p&gt;</description><author>CRAIG KERSTIENS</author><pubDate>Mon, 29 Jul 2013 23:55:56 GMT</pubDate><guid isPermaLink="true">/2013/07/29/Documenting-your-PostgreSQL-database/</guid></item><item><title>Gigablast Aquired and Code Posted</title><link>https://boyter.org/2013/07/gigablast-aquired-code-posted/</link><description>&lt;p&gt;Interestingly it seems that Matt Well&amp;rsquo;s search engine Gigablast has been acquired by Yippy.com [&lt;a href="http://www.prnewswire.com/news-releases/yippy-inc-yipi-to-acquire-gigablast-inc-and-web-research-properties-llc-to-expand-consumer-search-enterprise-and-ediscovery-products-213120401.html"&gt;1&lt;/a&gt;]&lt;a href="http://www.prnewswire.com/news-releases/yippy-inc-yipi-to-acquire-gigablast-inc-and-web-research-properties-llc-to-expand-consumer-search-enterprise-and-ediscovery-products-213120401.html"&gt;1&lt;/a&gt; [&lt;a href="http://arnoldit.com/wordpress/2013/07/19/acquisition-of-gigablast-by-yippy-inc-leaves-some-questions-unanswered/"&gt;2&lt;/a&gt;]&lt;a href="http://arnoldit.com/wordpress/2013/07/19/acquisition-of-gigablast-by-yippy-inc-leaves-some-questions-unanswered/"&gt;2&lt;/a&gt; [&lt;a href="http://ca.finance.yahoo.com/news/yippy-inc-yipi-acquire-gigablast-130000100.html"&gt;3&lt;/a&gt;]&lt;a href="http://ca.finance.yahoo.com/news/yippy-inc-yipi-acquire-gigablast-130000100.html"&gt;3&lt;/a&gt; (&lt;a href="http://demo.yippy.com/"&gt;demo here http://demo.yippy.com/&lt;/a&gt;). Gigablast has always been one of my favorite search engines simply because it is so interesting. Started by a single guy, with an interesting blog and being one of the last true new indexes of the web it was always worth a look. While its sad to see it go this way I am happy that Matt presumably has been able to cash out on his creation. Well done to him. I must admit Gigablast had been going downhill for a while and this might explain why ProCog appeared and then vanished so quickly.&lt;/p&gt;</description><author>Ben E. C. Boyter</author><pubDate>Mon, 29 Jul 2013 08:58:30 GMT</pubDate><guid isPermaLink="true">https://boyter.org/2013/07/gigablast-aquired-code-posted/</guid></item><item><title>Reawakening the Starborn</title><link>https://benovermyer.com/blog/2013/07/reawakening-the-starborn/</link><description>&lt;p&gt;About a year and a half ago, I started a project to create a sci-fantasy sandbox MMORPG. It was called Starborn: Universe. I went through the trouble of getting a HeroEngine/HeroCloud environment and began learning the toolset. Design documents popped up in my Documents folder and on the dev blog I started for the project. Then, I got busy with other things, and Starborn: Universe went dark. Until now. I’m dusting off the design documents, but Starborn: Universe is as much of an experiment as it is a game. See, my theory is that online player-driven games can start with very little and grow over time. The idea of the “Lean Startup” can apply just as much to an MMORPG as it can to an iOS app. The trick is that MMORPGs are hard to produce. They have a lot of moving parts and require a lot of resources on the part of the developer and operator. So how does one or two people accomplish this monumental feat? By starting small, and iterating quickly. If you’re curious as to what I have in mind, head over to the &lt;a href="https://starbornuniverse.wordpress.com/" rel="external"&gt;Starborn: Universe devlog&lt;/a&gt;. That’s where I’ll be writing about Starborn.&lt;/p&gt;</description><author>Ben Overmyer's Site</author><pubDate>Sat, 27 Jul 2013 03:00:00 GMT</pubDate><guid isPermaLink="true">https://benovermyer.com/blog/2013/07/reawakening-the-starborn/</guid></item><item><title>CoffeeScript's Scoping is Madness</title><link>https://donatstudios.com/CoffeeScript-Madness</link><description>&lt;p&gt;&lt;a href="https://coffeescript.org/"&gt;CoffeeScript&lt;/a&gt; is a programming language that compiles to JavaScript. Its mission is to &amp;quot;expose the good parts of JavaScript in a simple way&amp;quot;. I've  heard a lot of interest in it lately from various channels. &lt;/p&gt;
&lt;p&gt;After a coworkers departure, I inherited maintenance on our deployment bot, and it was written in CoffeeScript. While trying to make some small modifications, I ended up getting unexpected issues I didn't understand. Things I did not touch were suddenly breaking. I did not understand until I investigated the generated JavaScript.&lt;/p&gt;
&lt;p&gt;It ends up CoffeeScripts approach to scoping of variables is to not scope variables. There is no shadowing. There is no &lt;code&gt;var&lt;/code&gt;, &lt;code&gt;let&lt;/code&gt; nor &lt;code&gt;const&lt;/code&gt; equivalent, there is simply no way to explicitly define scope. &lt;/p&gt;
&lt;p&gt;Variables in CoffeeScript are scoped to their outermost definition, regardless of whether the name was already in use elsewhere. When you use a variable of a name, its scope now includes all inner scopes, trampling any existing definitions of the variable in those scopes.&lt;/p&gt;
&lt;p&gt;Let's examine the following examples:&lt;/p&gt;
&lt;div style="display: grid;"&gt;
&lt;strong&gt;CoffeeScript&lt;/strong&gt;
&lt;pre&gt;&lt;code&gt;test = (x) -&amp;gt;
  y = 10
  x + y;

alert test 5&lt;/code&gt;&lt;/pre&gt;
&lt;strong&gt;Generated JavaScript&lt;/strong&gt;
&lt;pre&gt;&lt;code&gt;var test;

test = function(x) {
  var y;
  y = 10;
  return x + y;
};

alert(test(5));&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Now let's make one small change and define a variable &lt;code&gt;y&lt;/code&gt; in the outer scope, outside the function.&lt;/p&gt;
&lt;div style="display: grid;"&gt;
&lt;strong&gt;CoffeeScript&lt;/strong&gt;
&lt;pre&gt;&lt;code&gt;y = 0;
test = (x) -&amp;gt;
  y = 10
  x + y;

alert test 5&lt;/code&gt;&lt;/pre&gt;
&lt;strong&gt;Generated JavaScript&lt;/strong&gt;
&lt;pre&gt;&lt;code&gt;var test, y;

y = 0;

test = function(x) {
  y = 10;
  return x + y;
};

alert(test(5));&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h2&gt;The Problem&lt;/h2&gt;
&lt;p&gt;Notice the highlighting indicating the scope of the &lt;code&gt;y&lt;/code&gt; variable. By defining &lt;code&gt;y&lt;/code&gt; in the global scope all subsequent &lt;code&gt;y&lt;/code&gt;'s are also now that global. You may no longer have a variable named &lt;code&gt;y&lt;/code&gt; in your code, at any deeper scope. The outer &lt;code&gt;y&lt;/code&gt; is the only &lt;code&gt;y&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Creating a global of a name given overrides all subsequent definitions.&lt;/p&gt;
&lt;p&gt;In JavaScript &lt;code&gt;var&lt;/code&gt;, &lt;code&gt;let&lt;/code&gt;, and &lt;code&gt;const&lt;/code&gt; keywords define the scope of a variable. Leave it out and it scopes downward until it hits a &lt;code&gt;var&lt;/code&gt; definition of that variable &lt;em&gt;or&lt;/em&gt; the global level. This means that inner scopes inherits from outer scopes &lt;em&gt;unless&lt;/em&gt; it defines its own scope of the variable.  CoffeeScript on the other hand offers you no method to scope a variable. Scoping is entirely left to the lowest level use of a &lt;em&gt;variable name&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;This behavior is dangerous and makes code very difficult to maintain. The problem is twofold. Firstly, when writing a function, you need to be aware of all shallower scopes variable definitions, as not to trample them. Secondly, when altering code at any scope but the outermost, you need to be aware of all deeper scopes variables as to not trample any of them. &lt;/p&gt;
&lt;p&gt;The only &amp;quot;safe&amp;quot; way to write CoffeeScript is to assume all variable names are global. In practice they are.&lt;/p&gt;
&lt;p&gt;There have been proposed solutions, such as a Go-lang style &lt;code&gt;:=&lt;/code&gt; for inner scoping, but these have not been accepted as of this writing. The creator of CoffeeScript, Jeremy Ashkenas, when questioned about it by Armin Ronacher on Twitter replied:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Not gonna happen ;) Forbidding shadowing altogether is a huge win, and a huge conceptual simplification.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is not true for applications of any scale larger than toy. It makes development more complex, having to check every inner scope before using a new variable name. This is not simpler. The entire behaviour of all inner scopes can change with a single bad variable defintion.&lt;/p&gt;
&lt;h2&gt;Finally&lt;/h2&gt;
&lt;p&gt;CoffeeScripts goal of &amp;quot;expos[ing] the good parts of JavaScript in a simple way&amp;quot; was &lt;strong&gt;missed&lt;/strong&gt;.  Instead of improving JavaScript's scoping which can be kind of daunting to learn, they actually made it worse. If you want to improve JavaScript make every variable &lt;em&gt;local&lt;/em&gt; until explicitly defined global, as is the behavior in the vast majority of languages.&lt;/p&gt;
&lt;p&gt;JavaScripts &amp;quot;&lt;em&gt;global unless defined as local&lt;/em&gt;&amp;quot; behavior is weird, but manageable. CoffeeScript's &amp;quot;&lt;em&gt;local until defined &lt;strong&gt;elsewhere&lt;/strong&gt; globally&lt;/em&gt;&amp;quot; behavior on the other hand is simply difficult to predict and makes it easy to accidentally change the entire meaning of code even when you entirely understand the behavior.&lt;/p&gt;
&lt;p&gt;It is a &lt;strong&gt;bad&lt;/strong&gt; behavior, period.&lt;/p&gt;
&lt;h2&gt;Further Reading&lt;/h2&gt;
&lt;p&gt;I'd like to also suggest reading Armin Ronacher's &lt;a href="https://lucumr.pocoo.org/2011/12/22/implicit-scoping-in-coffeescript/"&gt;The Problem with Implicit Scoping in CoffeeScript&lt;/a&gt;.  It eloquently describes the problem from a slightly different angle.&lt;/p&gt;
&lt;h2&gt;Update&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;I've a fight on Reddit &lt;a href="https://www.reddit.com/r/programming/comments/1j1cw7/coffeescripts_scoping_is_madness/"&gt;here&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;I've irked Reginald Braithwaite, the author of &lt;a href="https://leanpub.com/coffeescript-ristretto"&gt;CoffeeScript Ristretto&lt;/a&gt;, so badly he's giving away &lt;a href="https://twitter.com/raganwald/status/360501252162981888"&gt;100 copies of his book for free&lt;/a&gt;!
&lt;ul&gt;
&lt;li&gt;The author has upgraded me to &lt;em&gt;&lt;a href="https://web.archive.org/web/20221122115820/https://twitter.com/raganwald/status/360514948956303361"&gt;Not Wrong&lt;/a&gt;&lt;/em&gt;™!&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;</description><author>Donat Studios</author><pubDate>Fri, 26 Jul 2013 01:19:44 GMT</pubDate><guid isPermaLink="true">https://donatstudios.com/CoffeeScript-Madness</guid></item><item><title>SSDs are awesome, buy one.</title><link>https://tomforb.es/blog/ssd-s-are-awesome/</link><description>I recently brought a Samsung 840 Series Pro 256GB 2.5 inch SATA Solid State Drive and its easily the best PC hardware purchase I have ever made. Before I purchased it I was thinking about replacing my laptop as it was getting pretty sluggish, despite having decent specs even 2 years after I purchase...</description><author>Tom Forbes</author><pubDate>Thu, 25 Jul 2013 22:54:58 GMT</pubDate><guid isPermaLink="true">https://tomforb.es/blog/ssd-s-are-awesome/</guid></item><item><title>Why I still recommend Windows</title><link>https://captnemo.in/blog/2013/07/21/why-i-still-recommend-windows/</link><description>&lt;p&gt;Even though I am a long time Linux user, and a big time fan of the many Linux distros that I’ve tried out over time, I still go around recommending Windows to people who ask me for advice. The only exception I make is when the person in question is a developer, in which case I try to convert them to the Church of Linux.&lt;/p&gt;

&lt;p&gt;The main reason I recommend windows to non-developers is because it is a far better operating system than most Linux distributions (for general public). Now, before you bring out your pitchforks, hear me out.&lt;/p&gt;

&lt;p&gt;The first and foremost reason that I give is that Windows sports a far better integration across all its services. Nautilus/Nemo in the Linux world do not reach same level of integration that Windows Explorer does. For instance, just look at the way the “Send To” feature works in explorer. To add a folder to the send to entry, you just have to add a shortcut to that folder inside the special “Send To” folder. On nautilus, the equivalent would be going about installing an extension, and editing a configuration file by hand.&lt;/p&gt;

&lt;p&gt;Or take a look at how the “Play All” feature in Explorer. Or the “Libraries” feature in Windows 7. Or the simple way that you handle file sharing in Windows. Even though Linux has (arguably better) Samba support for files sharing, you have to go about editing a handful of files to make it work. I personally find apache easier to configure to just share files one way.&lt;/p&gt;

&lt;p&gt;Games are another reason. Even though Steam is available on Linux, all of the non-Valve triple-A titles are missing on Linux. Even though I continue to buy and play the Humble Bundles that offer Linux as a platform, I’m reminded of the stark reality every day when my friends ask me if I’ve played a recent title such as Call of Duty, Metro, NFS or even Swapper.&lt;/p&gt;

&lt;p&gt;The next peeve that comes to my mind is the ridiculous driver support. It has been improving since a long time, but its still &lt;em&gt;not there&lt;/em&gt;. Even Ubuntu needs to fetch proprietary drivers for my WiFi support on Broadcom, which needs an internet connection in the first place. This means I need to find out a LAN network connection to even start using Ubuntu. Similarly the pain I’d to go through to install drivers for Ralink network drivers on a friend’s laptop was immense. I can never use circular scrolling or touchpad zoom on my laptop in Linux because there are still no drivers available anywhere for it. And don’t get me started on UEFI boot issues. No matter what people believe or pretend, hardware support is just not good enough to be relied on in the Linux world. As a side note, I haven’t synced my iPad in Ubuntu since I shifted to iOS 5, and apple driver support on Linux will remain abysmal forever just because iTunes will never be released for Linux. The last version of iOS that had music sync support (via libimobiledevice) was iOS 4.0 (released 3 years ago in June 2010).&lt;/p&gt;

&lt;p&gt;Next I want to point put out the upgrade pain that everyone has to go through. It’s like a constant rite of passage, which turns a Linux noob into an actual user. I am yet to do an Ubuntu upgrade which went smoothly and didn’t break a thing; and I’ve been upgrading my Ubuntu since 10.04 was released. The &lt;a href="http://askubuntu.com/questions/tagged/upgrade"&gt;upgrade tag&lt;/a&gt; on askubuntu is chock full of horror stories.&lt;/p&gt;

&lt;p&gt;Another thing that frustrates me to no end is that the Ubuntu Dash, and the GNOME Overview are both slow as hell. I’m currently using Cinnamon, which is faster than both of these, but still an order of magnitude slower than the Windows Start Menu. Synpase is better, but &lt;a href="http://askubuntu.com/questions/174838/can-i-change-synapse-shortcut-to-super-windows-key-alone"&gt;cannot be set as the deafult&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I was using Windows 7 on my cousin’s laptop these last few days and I remembered the favourite app that I used to no end: Everything. It is the quickest file search I’ve ever used. The alternatives, in the Linux world are synapse, zeitgeist, and plain old locate command. The only issue is that I’ve to manually run updatedb manually, while Everything was always up to date, using the NTFS File Journal. To this date, I am yet to find a good enough alternative to Everything.&lt;/p&gt;

&lt;p&gt;It is true that Windows lacks many of the good things that Linux distros provide, such as the excellent package management support, POSIX compatibility, and the plethora of tools we get on the command line; but at the same time, it is also a better operating system for most of the masses. I’ll continue to recommend Windows to all my non-developer friends till “The year of Desktop Linux” arrives.&lt;/p&gt;</description><author>Nemo's Home</author><pubDate>Sun, 21 Jul 2013 03:00:00 GMT</pubDate><guid isPermaLink="true">https://captnemo.in/blog/2013/07/21/why-i-still-recommend-windows/</guid></item><item><title>Returning to Skyrim</title><link>https://benovermyer.com/blog/2013/07/returning-to-skyrim/</link><description>&lt;p&gt;The last time I posted about Skyrim, it was shortly after release. You can read the first in the series here. At the time, I was playing on the Xbox 360 version. This time around, I’ve purchased the Legendary Edition for PC. I’m also playing a Destruction mage instead of the heavily armed and armored warrior I focused on in the 360 version.&lt;/p&gt;
&lt;p&gt;Besides taking a completely different character route this time around, I’m also focusing much more on quests other than the main line. I own a small house that I’ve built but not yet furnished, gotten a fair ways through the College of Winterhold quest line, and continue to light up vampires that seem to crop up all the time in areas I’m traveling to.&lt;/p&gt;
&lt;p&gt;I heard so many awesome things about mods for the game that I just had to get the PC version. That, and I couldn’t take screenshots in the 360 version, which was infuriating for someone who blogs about games. Today, I read an article someone reblogged on Reddit about &lt;a href="http://www.pcgamer.com/behind-falskaar-a-massive-new-skyrim-mod-and-the-19-year-old-who-spent-a-year-building-it/" rel="external"&gt;a mod called Falskaar&lt;/a&gt;. Falskaar is unique in that it was produced chiefly by a 19 year old who declined to go to university in order to produce the mod. It took him a full year, and he’s aiming to get a job at Bethesda by proving his dedication.&lt;/p&gt;
&lt;p&gt;By all accounts, it’s a damned good mod. I look forward to installing it when I finish with vanilla Skyrim.&lt;/p&gt;
&lt;p&gt;If you’re interested in hearing further about my adventures in Skyrim, by all means, let me know in the comments. Otherwise, next week I’ll be writing about a topic near and dear to my heart…. sci-fantasy sandbox MMORPGs.&lt;/p&gt;</description><author>Ben Overmyer's Site</author><pubDate>Sat, 20 Jul 2013 03:00:00 GMT</pubDate><guid isPermaLink="true">https://benovermyer.com/blog/2013/07/returning-to-skyrim/</guid></item><item><title>DNS: The Good Parts</title><link>https://www.petekeen.net/dns-the-good-parts/</link><description>&lt;p&gt;Frequently I come across confusion with domain names. &lt;em&gt;Why doesn't my website work? Why is this stupid thing broken, everything I try fails, I just want it to work!!&lt;/em&gt; Invariably the question asker either doesn't know what DNS is or doesn't understand how something fundamental works. More generally, people think that DNS is scary or complicated. This article is an attempt at quelling that fear. &lt;em&gt;DNS is easy&lt;/em&gt; once you understand a few basic concepts.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;What is DNS&lt;/h2&gt;
&lt;p&gt;First things first. &lt;em&gt;DNS&lt;/em&gt; stands for &lt;em&gt;Domain Name System&lt;/em&gt;. Fundamentally it's a globally distributed key value store. Servers around the world can give you the value associated with a key, and if they don't know they'll ask other servers for the answer.&lt;/p&gt;
&lt;p&gt;That's it. That's all there is to it. You (or your web browser) ask for the value associated with the key &lt;code&gt;www.example.com&lt;/code&gt; and get back &lt;code&gt;1.2.3.4&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Basic Exploration and Fundamental Types&lt;/h2&gt;
&lt;p&gt;The great thing about the DNS is that it's completely public and open so it's easy to poke around. Let's do a little exploring, starting with this domain, &lt;code&gt;petekeen.net&lt;/code&gt; which I am hosting on a machine named &lt;code&gt;web01.bugsplat.info&lt;/code&gt;. Note that you can run all of these examples from an OS X or linux command line.&lt;/p&gt;
&lt;p&gt;First, let's look at a simple domain name to IP address mapping:&lt;/p&gt;
&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ dig web01.bugsplat.info
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The &lt;code&gt;dig&lt;/code&gt; command is a veritable Swiss Army knife for querying DNS servers and we'll be using it quite a bit. Here's the first part of the response:&lt;/p&gt;
&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;;&lt;/span&gt; &amp;lt;&amp;lt;&amp;gt;&amp;gt; DiG 9.7.6-P1 &amp;lt;&amp;lt;&amp;gt;&amp;gt; web01.bugsplat.info
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;;;&lt;/span&gt; global options: +cmd
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;;;&lt;/span&gt; Got answer:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;;;&lt;/span&gt; -&amp;gt;&amp;gt;HEADER&lt;span class="s"&gt;&amp;lt;&amp;lt;- opco&lt;/span&gt;de: QUERY, status: NOERROR, id: &lt;span class="m"&gt;51539&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;;;&lt;/span&gt; flags: qr rd ra&lt;span class="p"&gt;;&lt;/span&gt; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: &lt;span class="m"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;There's only one interesting thing in here. We asked for one record and got exactly one response. Here's the question we asked:&lt;/p&gt;
&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;;;&lt;/span&gt; QUESTION SECTION:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;;&lt;/span&gt;web01.bugsplat.info.		IN	A
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;dig&lt;/code&gt; defaults to asking for &lt;code&gt;A&lt;/code&gt; records. &lt;code&gt;A&lt;/code&gt; stands for &lt;em&gt;address&lt;/em&gt; and is one of the basic fundamental types of records in the DNS. An &lt;code&gt;A&lt;/code&gt; record holds exactly one &lt;code&gt;IPv4&lt;/code&gt; address. There's an equivalent record for &lt;code&gt;IPv6&lt;/code&gt; addresses named &lt;code&gt;AAAA&lt;/code&gt;. Next, let's look at the answer our DNS server gave us:&lt;/p&gt;
&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;;;&lt;/span&gt; ANSWER SECTION:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;web01.bugsplat.info.	300	IN	A	192.241.250.244
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This says the host &lt;code&gt;web01.bugsplat.info.&lt;/code&gt; has exactly one &lt;code&gt;A&lt;/code&gt; address: &lt;code&gt;192.241.250.244&lt;/code&gt;. The &lt;code&gt;300&lt;/code&gt; is called the &lt;code&gt;TTL&lt;/code&gt; value, or &lt;em&gt;time to live&lt;/em&gt;. It's the number of seconds that this record can be cached before it needs to be checked again. The &lt;code&gt;IN&lt;/code&gt; component stands for &lt;code&gt;Internet&lt;/code&gt; and is meant to disambiguate between the various types of networks that the DNS historically was responsible for. You can read about those in &lt;a href="https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-2"&gt;IANA's DNS Parameters document&lt;/a&gt; (thanks for the correction, &lt;a href="https://news.ycombinator.com/item?id=6075525"&gt;mcmatterson&lt;/a&gt;!)&lt;/p&gt;
&lt;p&gt;The rest of the response tells you things about the response itself:&lt;/p&gt;
&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;;;&lt;/span&gt; Query time: &lt;span class="m"&gt;20&lt;/span&gt; msec
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;;;&lt;/span&gt; SERVER: 192.168.1.1#53&lt;span class="o"&gt;(&lt;/span&gt;192.168.1.1&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;;;&lt;/span&gt; WHEN: Fri Jul &lt;span class="m"&gt;19&lt;/span&gt; 20:01:16 &lt;span class="m"&gt;2013&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;;;&lt;/span&gt; MSG SIZE  rcvd: &lt;span class="m"&gt;56&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Specifically, it tells you how long it took for your server to respond, what that server's IP address is (&lt;code&gt;192.168.1.1&lt;/code&gt;), what port &lt;code&gt;dig&lt;/code&gt; asked (&lt;code&gt;53&lt;/code&gt;, the default DNS port), when the query completed, and how many bytes the response contained.&lt;/p&gt;
&lt;p&gt;As you can see, there's an awful lot going on in a single DNS query. Every time you open a web page your browser makes literally dozens of these queries to resolve the web host, all of the hosts where external resources like images and scripts are located, etc. Every single resource involves at least one DNS query, which would involve an awful lot of traffic if DNS wasn't designed to be heavily cached.&lt;/p&gt;
&lt;p&gt;What you probably can't see, however, is that the DNS server at &lt;code&gt;192.168.1.1&lt;/code&gt; contacted a whole chain of other servers in order to answer that simple question of what address does &lt;code&gt;web01.bugsplat.info&lt;/code&gt; map to. Let's run a trace to see all of the servers that &lt;code&gt;dig&lt;/code&gt; would have to contact if they weren't already cached:&lt;/p&gt;
&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ dig +trace web01.bugsplat.info
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;;&lt;/span&gt; &amp;lt;&amp;lt;&amp;gt;&amp;gt; DiG 9.7.6-P1 &amp;lt;&amp;lt;&amp;gt;&amp;gt; +trace web01.bugsplat.info
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;;;&lt;/span&gt; global options: +cmd
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;.			137375	IN	NS	l.root-servers.net.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;.			137375	IN	NS	m.root-servers.net.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;.			137375	IN	NS	a.root-servers.net.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;.			137375	IN	NS	b.root-servers.net.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;.			137375	IN	NS	c.root-servers.net.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;.			137375	IN	NS	d.root-servers.net.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;.			137375	IN	NS	e.root-servers.net.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;.			137375	IN	NS	f.root-servers.net.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;.			137375	IN	NS	g.root-servers.net.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;.			137375	IN	NS	h.root-servers.net.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;.			137375	IN	NS	i.root-servers.net.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;.			137375	IN	NS	j.root-servers.net.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;.			137375	IN	NS	k.root-servers.net.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;;;&lt;/span&gt; Received &lt;span class="m"&gt;512&lt;/span&gt; bytes from 192.168.1.1#53&lt;span class="o"&gt;(&lt;/span&gt;192.168.1.1&lt;span class="o"&gt;)&lt;/span&gt; in &lt;span class="m"&gt;189&lt;/span&gt; ms
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;info.			172800	IN	NS	c0.info.afilias-nst.info.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;info.			172800	IN	NS	a2.info.afilias-nst.info.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;info.			172800	IN	NS	d0.info.afilias-nst.org.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;info.			172800	IN	NS	b2.info.afilias-nst.org.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;info.			172800	IN	NS	b0.info.afilias-nst.org.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;info.			172800	IN	NS	a0.info.afilias-nst.info.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;;;&lt;/span&gt; Received &lt;span class="m"&gt;443&lt;/span&gt; bytes from 192.5.5.241#53&lt;span class="o"&gt;(&lt;/span&gt;192.5.5.241&lt;span class="o"&gt;)&lt;/span&gt; in &lt;span class="m"&gt;1224&lt;/span&gt; ms
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bugsplat.info.		86400	IN	NS	ns-1356.awsdns-41.org.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bugsplat.info.		86400	IN	NS	ns-212.awsdns-26.com.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bugsplat.info.		86400	IN	NS	ns-1580.awsdns-05.co.uk.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bugsplat.info.		86400	IN	NS	ns-911.awsdns-49.net.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;;;&lt;/span&gt; Received &lt;span class="m"&gt;180&lt;/span&gt; bytes from 199.254.48.1#53&lt;span class="o"&gt;(&lt;/span&gt;199.254.48.1&lt;span class="o"&gt;)&lt;/span&gt; in &lt;span class="m"&gt;239&lt;/span&gt; ms
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;web01.bugsplat.info.	300	IN	A	192.241.250.244
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bugsplat.info.		172800	IN	NS	ns-1356.awsdns-41.org.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bugsplat.info.		172800	IN	NS	ns-1580.awsdns-05.co.uk.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bugsplat.info.		172800	IN	NS	ns-212.awsdns-26.com.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bugsplat.info.		172800	IN	NS	ns-911.awsdns-49.net.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;;;&lt;/span&gt; Received &lt;span class="m"&gt;196&lt;/span&gt; bytes from 205.251.195.143#53&lt;span class="o"&gt;(&lt;/span&gt;205.251.195.143&lt;span class="o"&gt;)&lt;/span&gt; in &lt;span class="m"&gt;15&lt;/span&gt; ms
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The DNS is arranged in a hierarchy. Remember how &lt;code&gt;dig&lt;/code&gt; inserted a single &lt;code&gt;.&lt;/code&gt; after the hostname we asked for before, &lt;code&gt;web01.bugsplat.info&lt;/code&gt;? Well, that &lt;code&gt;.&lt;/code&gt; is pretty important and stands for the root of the hierarchy. The root DNS servers are run by various companies and governments around the world. Originally there were only a handful of these servers but as the Internet has grown more have been added, so that now there are notionally 13. Each one of these servers, however, has dozens or hundreds of physical machines hiding behind a single IP.&lt;/p&gt;
&lt;p&gt;So, at the top of the trace we see the root servers, each represented by an &lt;code&gt;NS&lt;/code&gt; record. An &lt;code&gt;NS&lt;/code&gt; record maps a domain name, in this case the root, to a DNS server. When you register a domain name with a registrar like Namecheap or Godaddy they create &lt;code&gt;NS&lt;/code&gt; records for you.&lt;/p&gt;
&lt;p&gt;In the next block you can see that &lt;code&gt;dig&lt;/code&gt; randomly picked one of the root server responses and asked it for the &lt;code&gt;A&lt;/code&gt; record  &lt;code&gt;web01.bugsplat.info&lt;/code&gt;. Which root server? Let's ask!&lt;/p&gt;
&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ dig -x 192.5.5.241
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;;&lt;/span&gt; &amp;lt;&amp;lt;&amp;gt;&amp;gt; DiG 9.8.3-P1 &amp;lt;&amp;lt;&amp;gt;&amp;gt; -x 192.5.5.241
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;;;&lt;/span&gt; global options: +cmd
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;;;&lt;/span&gt; Got answer:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;;;&lt;/span&gt; -&amp;gt;&amp;gt;HEADER&lt;span class="s"&gt;&amp;lt;&amp;lt;- opco&lt;/span&gt;de: QUERY, status: NOERROR, id: &lt;span class="m"&gt;2862&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;;;&lt;/span&gt; flags: qr rd ra&lt;span class="p"&gt;;&lt;/span&gt; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: &lt;span class="m"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;;;&lt;/span&gt; QUESTION SECTION:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;;&lt;/span&gt;241.5.5.192.in-addr.arpa.	IN	PTR
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;;;&lt;/span&gt; ANSWER SECTION:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;241.5.5.192.in-addr.arpa. 3261	IN	PTR	f.root-servers.net.
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The &lt;code&gt;-x&lt;/code&gt; flag tells &lt;code&gt;dig&lt;/code&gt; to do a reverse lookup on the given IP address. The DNS responds with a &lt;code&gt;PTR&lt;/code&gt; record which maps an IP with a hostname, in this case &lt;code&gt;f.root-servers.net&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Getting back to our original query, the &lt;code&gt;F&lt;/code&gt; root server responded with another set of &lt;code&gt;NS&lt;/code&gt; servers, this time the ones responsible for the &lt;code&gt;info&lt;/code&gt; top level domain. &lt;code&gt;dig&lt;/code&gt; asks one of these servers for the &lt;code&gt;A&lt;/code&gt; record for &lt;code&gt;web01.bugsplat.info&lt;/code&gt;, gets back another set of &lt;code&gt;NS&lt;/code&gt; servers, and then asks one of &lt;em&gt;those&lt;/em&gt; servers for the &lt;code&gt;A&lt;/code&gt; record for &lt;code&gt;web01.bugsplat.info.&lt;/code&gt; and finally receives an actual answer. (thanks for the corrections, &lt;a href="https://news.ycombinator.com/item?id=6075556"&gt;colmmacc&lt;/a&gt;!)&lt;/p&gt;
&lt;p&gt;Whew! That would be a heck of a lot of traffic, except that almost all of these entries are cached for a long time by every server in the chain. Your computer caches too, as does your browser. Most of the time DNS resolution will never touch the root servers because their IP addresses hardly ever change. The top level domains &lt;code&gt;com&lt;/code&gt;, &lt;code&gt;net&lt;/code&gt;, &lt;code&gt;org&lt;/code&gt;, etc, are also generally heavily cached.&lt;/p&gt;
&lt;h3&gt;Other Types&lt;/h3&gt;
&lt;p&gt;There are a few other types that you should be aware of. The first is &lt;code&gt;MX&lt;/code&gt;, which maps a domain name to one or more email servers. Email is so important to the functioning of the Internet that it gets its own record type. Here are the &lt;code&gt;MX&lt;/code&gt; records for &lt;code&gt;petekeen.net&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ dig petekeen.net mx
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;;&lt;/span&gt; &amp;lt;&amp;lt;&amp;gt;&amp;gt; DiG 9.7.6-P1 &amp;lt;&amp;lt;&amp;gt;&amp;gt; petekeen.net mx
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;;;&lt;/span&gt; global options: +cmd
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;;;&lt;/span&gt; Got answer:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;;;&lt;/span&gt; -&amp;gt;&amp;gt;HEADER&lt;span class="s"&gt;&amp;lt;&amp;lt;- opco&lt;/span&gt;de: QUERY, status: NOERROR, id: &lt;span class="m"&gt;18765&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;;;&lt;/span&gt; flags: qr rd ra&lt;span class="p"&gt;;&lt;/span&gt; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: &lt;span class="m"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;;;&lt;/span&gt; QUESTION SECTION:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;;&lt;/span&gt;petekeen.net.			IN	MX
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;;;&lt;/span&gt; ANSWER SECTION:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;petekeen.net.		86400	IN	MX	&lt;span class="m"&gt;60&lt;/span&gt; web01.bugsplat.info.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;;;&lt;/span&gt; Query time: &lt;span class="m"&gt;272&lt;/span&gt; msec
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;;;&lt;/span&gt; SERVER: 192.168.1.1#53&lt;span class="o"&gt;(&lt;/span&gt;192.168.1.1&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;;;&lt;/span&gt; WHEN: Fri Jul &lt;span class="m"&gt;19&lt;/span&gt; 20:33:43 &lt;span class="m"&gt;2013&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;;;&lt;/span&gt; MSG SIZE  rcvd: &lt;span class="m"&gt;93&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Note that an &lt;code&gt;MX&lt;/code&gt; record points at a name and not an IP address.&lt;/p&gt;
&lt;p&gt;The other record type that you should be familiar with is &lt;code&gt;CNAME&lt;/code&gt; which stands for &lt;em&gt;Canonical Name&lt;/em&gt; and maps one name onto another. Let's look at the response we get for a &lt;code&gt;CNAME&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ dig www.petekeen.net
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;;&lt;/span&gt; &amp;lt;&amp;lt;&amp;gt;&amp;gt; DiG 9.7.6-P1 &amp;lt;&amp;lt;&amp;gt;&amp;gt; www.petekeen.net
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;;;&lt;/span&gt; global options: +cmd
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;;;&lt;/span&gt; Got answer:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;;;&lt;/span&gt; -&amp;gt;&amp;gt;HEADER&lt;span class="s"&gt;&amp;lt;&amp;lt;- opco&lt;/span&gt;de: QUERY, status: NOERROR, id: &lt;span class="m"&gt;16785&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;;;&lt;/span&gt; flags: qr rd ra&lt;span class="p"&gt;;&lt;/span&gt; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: &lt;span class="m"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;;;&lt;/span&gt; QUESTION SECTION:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;;&lt;/span&gt;www.petekeen.net.		IN	A
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;;;&lt;/span&gt; ANSWER SECTION:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;www.petekeen.net.	86400	IN	CNAME	web01.bugsplat.info.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;web01.bugsplat.info.	300	IN	A	192.241.250.244
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;;;&lt;/span&gt; Query time: &lt;span class="m"&gt;63&lt;/span&gt; msec
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;;;&lt;/span&gt; SERVER: 192.168.1.1#53&lt;span class="o"&gt;(&lt;/span&gt;192.168.1.1&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;;;&lt;/span&gt; WHEN: Fri Jul &lt;span class="m"&gt;19&lt;/span&gt; 20:36:58 &lt;span class="m"&gt;2013&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;;;&lt;/span&gt; MSG SIZE  rcvd: &lt;span class="m"&gt;86&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The first thing to notice is that we get back two answers. The first says that &lt;code&gt;www.petekeen.net&lt;/code&gt; maps to &lt;code&gt;web01.bugsplat.info&lt;/code&gt;. The second gives the &lt;code&gt;A&lt;/code&gt; record for that server. One way to think about a &lt;code&gt;CNAME&lt;/code&gt; is as an &lt;em&gt;alias&lt;/em&gt; for another domain name.&lt;/p&gt;
&lt;h3&gt;Why CNAME is Messed Up&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;CNAME&lt;/code&gt;s are incredibly useful, but they have one very important gotcha: if a &lt;code&gt;CNAME&lt;/code&gt; exists for a particular name, that is the &lt;em&gt;only&lt;/em&gt; record allowed for that name. No &lt;code&gt;MX&lt;/code&gt;, no &lt;code&gt;A&lt;/code&gt;, no &lt;code&gt;NS&lt;/code&gt;, no nothing. This is because the DNS substitutes the &lt;code&gt;CNAME&lt;/code&gt;'s target for its own value, so every record valid for the target is also valid for the &lt;code&gt;CNAME&lt;/code&gt;. This is why you can't have a &lt;code&gt;CNAME&lt;/code&gt; on a root domain like &lt;code&gt;petekeen.net&lt;/code&gt;, because you generally have to have other records for that domain like &lt;code&gt;MX&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Querying Other Servers&lt;/h3&gt;
&lt;p&gt;Let's say for sake of argument that you messed up a DNS configuration. You think you've fixed the problem, but you don't want to wait for the cache to expire to see. With &lt;code&gt;dig&lt;/code&gt; you can actually query one of a number of public DNS servers instead of your default server like this:&lt;/p&gt;
&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ dig www.petekeen.net @8.8.8.8
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The &lt;code&gt;@&lt;/code&gt; symbol followed by an IP address or hostname tells &lt;code&gt;dig&lt;/code&gt; to query that server on the default DNS port. I use this a lot to query &lt;a href="https://developers.google.com/speed/public-dns/"&gt;Google's public DNS servers&lt;/a&gt; or &lt;a href="http://www.tummy.com/articles/famous-dns-server/"&gt;Level 3's sort-of-public servers&lt;/a&gt; at &lt;code&gt;4.2.2.2&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Common Situations&lt;/h2&gt;
&lt;p&gt;In this last section we'll talk about some common situations that web developers find themselves in.&lt;/p&gt;
&lt;h3&gt;Redirect bare domain to www&lt;/h3&gt;
&lt;p&gt;Almost always you'll want to redirect a bare domain like &lt;code&gt;iskettlemanstillopen.com&lt;/code&gt; to &lt;code&gt;www.iskettlemanstillopen.com&lt;/code&gt;. Registrars like Namecheap and DNSimple call this a &lt;em&gt;URL Redirect&lt;/em&gt;. In Namecheap you would set up a URL Redirect like this:&lt;/p&gt;
&lt;img alt="Namecheap URL redirect setup" class="thumbnail" src="https://d2s7foagexgnc2.cloudfront.net/files/3abc3ac12462e5a92ae7/Screen%20Shot%202013-07-19%20at%208.48.36%20PM.png" /&gt;
&lt;p&gt;The &lt;code&gt;@&lt;/code&gt; stands for the root domain &lt;code&gt;iskettlemanstillopen.com&lt;/code&gt;. Let's look at the &lt;code&gt;A&lt;/code&gt; record for that domain:&lt;/p&gt;
&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ dig iskettlemanstillopen.com
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;;;&lt;/span&gt; QUESTION SECTION:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;;&lt;/span&gt;iskettlemanstillopen.com.	IN	A
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;;;&lt;/span&gt; ANSWER SECTION:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;iskettlemanstillopen.com. 500	IN	A	192.64.119.118
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;That IP is owned by Namecheap and is running a small web server that just serves up an HTTP-level redirect to &lt;code&gt;http://www.iskettlemanstillopen.com&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ curl -I iskettlemanstillopen.com
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -I iskettlemanstillopen.com
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;HTTP/1.1 &lt;span class="m"&gt;302&lt;/span&gt; Moved Temporarily
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Server: nginx
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Date: Fri, &lt;span class="m"&gt;19&lt;/span&gt; Jul &lt;span class="m"&gt;2013&lt;/span&gt; 23:53:21 GMT
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Content-Type: text/html
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Connection: keep-alive
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Content-Length: &lt;span class="m"&gt;154&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Location: http://www.iskettlemanstillopen.com/
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;CNAME to Heroku or Github&lt;/h3&gt;
&lt;p&gt;Notice in the screenshot above that there's a second row defining a &lt;code&gt;CNAME&lt;/code&gt;. In this case &lt;code&gt;www.iskettlemanstillopen.com&lt;/code&gt; maps to an application running on Heroku. You'll have to set up Heroku with a similar domain mapping, of course:&lt;/p&gt;
&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ heroku &lt;span class="nv"&gt;domains&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;===&lt;/span&gt; warm-journey-3906 Domain Names
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;warm-journey-3906.herokuapp.com
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;www.iskettlemanstillopen.com
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Github is similar, except that the mapping lives in a file called &lt;code&gt;CNAME&lt;/code&gt; at the root of your pages, as described &lt;a href="https://help.github.com/articles/setting-up-a-custom-domain-with-pages"&gt;in their documentation&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Wildcards&lt;/h3&gt;
&lt;p&gt;Most DNS servers allow you to set up DNS wildcards. For example, I have a wildcard &lt;code&gt;CNAME&lt;/code&gt; set up for &lt;code&gt;*.web01.bugsplat.info&lt;/code&gt; that maps to &lt;code&gt;web01.bugsplat.info&lt;/code&gt;. That way I can host arbitrary things on &lt;code&gt;web01&lt;/code&gt; and not have to create new DNS entries for them every time:&lt;/p&gt;
&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ dig randomapp.web01.bugsplat.info
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;;;&lt;/span&gt; QUESTION SECTION:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;;&lt;/span&gt;randomapp.web01.bugsplat.info. IN	A
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;;;&lt;/span&gt; ANSWER SECTION:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;randomapp.web01.bugsplat.info. &lt;span class="m"&gt;300&lt;/span&gt; IN CNAME	web01.bugsplat.info.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;web01.bugsplat.info.	15	IN	A	192.241.250.244
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;Wrap Up&lt;/h2&gt;
&lt;p&gt;Hopefully this gives you a good beginning understanding of what DNS is and how to about exploring and verifying your configuration. Just remember that you can always ask the DNS questions and generally get back answers. The Internet standards (RFCs) that define DNS are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.ietf.org/rfc/rfc1034.txt"&gt;RFC 1034: Domain Names - Concepts and Facilities&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.ietf.org/rfc/rfc1035.txt"&gt;RFC 1035: Domain Names - Implementation and Specification&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There are a few more interesting RFCs as well, including &lt;a href="http://www.ietf.org/rfc/rfc4034.txt"&gt;4034&lt;/a&gt; about a standard named &lt;code&gt;DNSSEC&lt;/code&gt; and &lt;a href="http://www.ietf.org/rfc/rfc5321.txt"&gt;5321&lt;/a&gt; which talks about DNS as it relates to email. These are all fascinating reads if you want more background information&lt;/p&gt;
&lt;p&gt;&lt;em&gt;This article is featured in &lt;a href="http://hackermonthly.com/issue-42.html"&gt;Hacker Monthly issue 42&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;</description><author>Pete Keen</author><pubDate>Fri, 19 Jul 2013 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.petekeen.net/dns-the-good-parts/</guid></item><item><title>List of useful CAPTCHA Decoding Articles</title><link>https://boyter.org/2013/07/list-captcha-decoding-articles/</link><description>&lt;p&gt;This website ranks quite high in most search engines for the search term &amp;ldquo;captcha decoding&amp;rdquo; or some permutation of it. As such here are a collection of useful links if you are looking into doing such a thing. If any more come up I will be sure to update this post.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://www.boyter.org/decoding-captchas/"&gt;http://www.boyter.org/decoding-captchas/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Shameless self promotion but this link is why this page ranks so highly. Its an article I wrote some time ago about how to go about decoding a simple CAPTCHA. There is full source code and the principles can be applied to 90% of CAPTCHA&amp;rsquo;s out there. For the record it only came about because a colleague bet me that I couldn&amp;rsquo;t decode his websites CAPTCHA which was the one used in the article. Of course I waited till he changed it before publishing.&lt;/p&gt;</description><author>Ben E. C. Boyter</author><pubDate>Fri, 19 Jul 2013 02:46:43 GMT</pubDate><guid isPermaLink="true">https://boyter.org/2013/07/list-captcha-decoding-articles/</guid></item><item><title>Displaying a processes output on a web page with Websockets and Python</title><link>https://tomforb.es/blog/displaying-a-processes-output-on-a-web-page-with-websockets-and-python/</link><description>A few days ago a colleague of mine asked me how you would pipe the standard output of a process into a browser. I hacked around for a few hours and came up with a websockets based solution (using Twisted and Autobahn.ws ) that you can see below ( Your browser needs to support WebSockets, sorry IE9 a...</description><author>Tom Forbes</author><pubDate>Mon, 15 Jul 2013 21:40:38 GMT</pubDate><guid isPermaLink="true">https://tomforb.es/blog/displaying-a-processes-output-on-a-web-page-with-websockets-and-python/</guid></item><item><title>Announcing: Mastering Modern Payments: Using Stripe with Rails</title><link>https://www.petekeen.net/announcing-mastering-modern-payments-using-stripe-with-rails/</link><description>&lt;p&gt;Over the past few years I've put together a &lt;a href="/projects"&gt;number of projects&lt;/a&gt; that use &lt;a href="https://stripe.com"&gt;Stripe&lt;/a&gt; and their &lt;a href="https://stripe.com/docs/api/ruby"&gt;Ruby API&lt;/a&gt; to collect payments and manage subscriptions. I've learned quite a bit about how to effectively use the things that Stripe provides to my best advantage. Two months ago I decided that I would like to share that knowledge and so I started working on a guide to integrating Stripe with Rails and today I'd like to announce that &lt;a href="/mastering-modern-payments"&gt;&lt;em&gt;Mastering Modern Payments: Using Stripe with Rails&lt;/em&gt;&lt;/a&gt; will be available on &lt;strong&gt;August 15th, 2013&lt;/strong&gt;.&lt;/p&gt;
&lt;!--more--&gt;
&lt;p&gt;&lt;em&gt;Mastering Modern Payments&lt;/em&gt; covers the gamut of Stripe integrations, from the simplest using &lt;code&gt;checkout.js&lt;/code&gt; all the way through custom payment forms, subscriptions, background workers and more. It's my first ebook and I couldn't be more excited.&lt;/p&gt;
&lt;p&gt;My intended audience is Ruby developers who know Rails well but need some help with their first Stripe integration or want help figuring out how to make their existing integration better. If that's you, you should &lt;a href="/mastering-modern-payments"&gt;sign up for the mailing list&lt;/a&gt; to get updates and announcements leading up to launch day as well as a 10% off coupon code and a free chapter named &lt;em&gt;&lt;strong&gt;State and History&lt;/strong&gt;&lt;/em&gt; which covers how to easily keep an audit trail on your sales records.&lt;/p&gt;
&lt;div class="well" style="text-align: center;"&gt;&lt;a class="btn btn-large btn-success" href="/mastering-modern-payments"&gt;Find Out More about Mastering Modern Payments&lt;/a&gt;&lt;/div&gt;</description><author>Pete Keen</author><pubDate>Mon, 15 Jul 2013 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.petekeen.net/announcing-mastering-modern-payments-using-stripe-with-rails/</guid></item><item><title>About</title><link>https://boyter.org/about/</link><description>&lt;p&gt;I&amp;rsquo;m a software developer by trade who after years of leaving articles and blog posts all over the place has finally gotten around to consolidating them all into this single site.&lt;/p&gt;
&lt;p&gt;I am a professional software engineer who has been working in development with a focus on testing for over 15 years. In that time I have played an integral part in developing and implementing testing methodologies for all sorts of projects including,&lt;/p&gt;</description><author>Ben E. C. Boyter</author><pubDate>Thu, 11 Jul 2013 07:02:47 GMT</pubDate><guid isPermaLink="true">https://boyter.org/about/</guid></item><item><title>Decoding CAPTCHA’s</title><link>https://boyter.org/decoding-captchas/</link><description>&lt;p&gt;&lt;img alt="Decoding CAPTCHA&amp;rsquo;s Book" src="https://boyter.org/static/decoding-captchas/booksmall.jpeg" /&gt;&lt;/p&gt;
&lt;p&gt;Looking for a practical guide to CAPTCHA decoding? All About CAPTCHA’s. This eBook will teach you how to identify weaknesses and exploit CAPTCHA’s from beginning to end. &lt;a href="https://leanpub.com/decodingcaptchas/"&gt;Buy now using Leanpub&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Most people don’t know this but my honors thesis was about using a computer program to read text out of web images. My theory was that if you could get a high level of successful extraction you could use it as another source of data which could be used to improve search engine results. I was even quite successful in doing it, but never really followed my experiments up. My honors adviser &lt;a href="http://csusap.csu.edu.au/~jbgao/"&gt;Dr Junbin Gao http://csusap.csu.edu.au/~jbgao/&lt;/a&gt; had suggested the following writing my thesis I should write some form of article on what I had learnt. Well I finally got around to doing it. While what follows is not exactly what I was studying it is something I wish had existed when I started looking around.&lt;/p&gt;</description><author>Ben E. C. Boyter</author><pubDate>Thu, 11 Jul 2013 06:00:02 GMT</pubDate><guid isPermaLink="true">https://boyter.org/decoding-captchas/</guid></item><item><title>hstore vs. JSON - Which to use in Postgres</title><link>/2013/07/03/hstore-vs.-JSON-Which-to-use-in-Postgres/</link><description>&lt;p&gt;If you&amp;rsquo;re deciding what to put in &lt;a href="http://www.amazon.com/Seven-Databases-Weeks-Modern-Movement/dp/1934356921?tag=mypred-20"&gt;Postgres and what not to&lt;/a&gt;, consider that Postgres can be a &lt;a href="/2012/06/11/schemaless-django/"&gt;perfectly good schema-less database&lt;/a&gt;. Of course as soon as people realized this then the common comes a question, is hstore or JSON better. Which do I use and in what cases. Well first, if you&amp;rsquo;re not familiar check out some previous material on them:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.postgresql.org/docs/9.2/static/hstore.html"&gt;hstore on PostgresGuide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.postgresql.org/docs/9.2/static/hstore.html"&gt;hstore in Postgres docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.craigkerstiens.com/2012/06/11/schemaless-django/"&gt;hstore with Django&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://wiki.postgresql.org/wiki/What's_new_in_PostgreSQL_9.2#JSON_datatype"&gt;JSON datatype&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://postgres.heroku.com/blog/past/2013/6/5/javascript_in_your_postgres/"&gt;JavaScript support in Postgres&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you&amp;rsquo;re already up to date with both of them, but still wondering which to use lets dig in.&lt;/p&gt;
&lt;h3 id="hstore"&gt;
&lt;div&gt;
hstore
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;hstore is a key value store directly within your database. Its been a common favorite of mine and has been for some time. hstore gives you flexibility when working with your schema, as you don&amp;rsquo;t have to define models ahead of time. Though its two big limitations are that 1. it only deals with text and 2. its not a full document store meaning you can&amp;rsquo;t nest objects.&lt;/p&gt;
&lt;p&gt;Though major benefits of hstore include the ability to index on it, robust support for various operators, and of course the obvious of flexibility with your data. Some of the basic operators available include:&lt;/p&gt;
&lt;p&gt;Return the value from column&lt;code&gt;foo&lt;/code&gt; for key &lt;code&gt;bar&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;foo-&amp;gt;'bar'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Does the specified column &lt;code&gt;foo&lt;/code&gt; contain a key &lt;code&gt;bar&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;foo?'bar'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Does the specified column &lt;code&gt;foo&lt;/code&gt; contain a value of &lt;code&gt;baz&lt;/code&gt; for key &lt;code&gt;bar&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;foo@&amp;gt;'bar-&amp;gt;baz'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Perhaps one of the best parts of hstore is that you can index on it. In particular Postgres &lt;code&gt;gin&lt;/code&gt; and &lt;code&gt;gist&lt;/code&gt; indexes allow you to index all keys and values within an hstore. A talk by &lt;a href="http://www.twitter.com/XoF"&gt;Christophe Pettus&lt;/a&gt; of PgExperts actually highlights some &lt;a href="http://thebuild.com/presentations/pg-as-nosql-pgday-fosdem-2013.pdf"&gt;performance details of hstore with indexes&lt;/a&gt;. To give away the big punchline in several cases hstore with gin/gist beats mongodb in performance.&lt;/p&gt;
&lt;h3 id="json"&gt;
&lt;div&gt;
json
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;JSON in contrast to hstore is a full document datatype. In addition to nesting objects you have support for more than just text (read numbers). As you insert JSON into Postgres it will automatically ensure its valid JSON and error if its well not. JSON gets a lot better come Postgres 9.3 as well with &lt;a href="http://www.postgresql.org/docs/devel/static/functions-json.html"&gt;some built in operators&lt;/a&gt;. Though if you need more functionality in it today you should look at &lt;a href="https://code.google.com/p/plv8js/wiki/PLV8"&gt;PLV8&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="which-to-use"&gt;
&lt;div&gt;
Which to Use
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;So which do you actually want to use in your application? If you&amp;rsquo;re already using JSON and simply want to store it in your database then the JSON datatype is often the correct pick. However, if you&amp;rsquo;re just looking for flexibility with your data model then hstore is likely the path you want to take. hstore will give you much of the flexibility you want as well as a good ability to query your data in a performant manner. Of course much of this starts to change in Postgres 9.3.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;If you&amp;rsquo;re looking for a deeper resource on Postgres I recommend the book &lt;a href="https://theartofpostgresql.com/?affiliate=cek"&gt;The Art of PostgreSQL&lt;/a&gt;. It is by a personal friend that has aimed to create the definitive guide to Postgres, from a developer perspective. If you use code CRAIG15 you&amp;rsquo;ll receive 15% off as well.&lt;/em&gt;&lt;/p&gt;</description><author>CRAIG KERSTIENS</author><pubDate>Wed, 03 Jul 2013 23:55:56 GMT</pubDate><guid isPermaLink="true">/2013/07/03/hstore-vs.-JSON-Which-to-use-in-Postgres/</guid></item><item><title>Shipping with Stripe and EasyPost</title><link>https://www.petekeen.net/shipping-with-stripe-and-easypost/</link><description>&lt;p&gt;Let's say that instead of running a Software as a Service, you're actually building and shipping physical products. Let's say quadcopter kits. People come to your website, buy a quadcopter kit, and then you build it and ship it to them. It takes you a few days to build the kit, though, and you would rather not charge the customer until you ship. Traditionally &lt;a href="https://stripe.com"&gt;Stripe&lt;/a&gt; has been focused on paying for online services but recently they added the ability to &lt;em&gt;authorize&lt;/em&gt; and &lt;em&gt;capture&lt;/em&gt; payments in two steps. In this post we're going to explore billing with Stripe and shipping with &lt;a href="https://www.easypost.com"&gt;EasyPost&lt;/a&gt; with separate charge and capture.&lt;/p&gt;
&lt;!--more--&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://stripe.com/api/ruby"&gt;Stripe API Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.easypost.com/docs/ruby"&gt;EasyPost API Docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Step 1: Calculate Shipping&lt;/h3&gt;
&lt;p&gt;EasyPost makes it really simple to calculate shipping rates. Just take the customer's shipping address and create an &lt;code&gt;EasyPost::Shipment&lt;/code&gt; object:&lt;/p&gt;
&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;from_address&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;EasyPost&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Address&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="s1"&gt;'Pete Keen'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="ss"&gt;street1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'618 NW Glisan Ave'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="ss"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="s1"&gt;'Portland'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="ss"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="s1"&gt;'OR'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="ss"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;     &lt;span class="s1"&gt;'97211'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="ss"&gt;country&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'US'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="ss"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="s1"&gt;'pete@petekeen.net'&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;to_address&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;EasyPost&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Address&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:to_name&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="ss"&gt;street1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:to_street1&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="ss"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:to_city&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="ss"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:to_state&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="ss"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;     &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:to_zip&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="ss"&gt;country&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:to_country&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="ss"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:email&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;parcel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;EasyPost&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Parcel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="ss"&gt;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="ss"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="ss"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="ss"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;shipment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;EasyPost&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Shipment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="ss"&gt;to_address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;to_address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="ss"&gt;from_address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;from_address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="ss"&gt;parcel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;parcel&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="vi"&gt;@rates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;shipment&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'rates'&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Display those rates to the customer, have them pick the one they want, and then move on to the next step.&lt;/p&gt;
&lt;h3&gt;Step 2: Authorize&lt;/h3&gt;
&lt;p&gt;Charging a card and just doing the authorization step are remarkably similar. Just get the user's credit card info with &lt;code&gt;stripe.js&lt;/code&gt; or &lt;code&gt;checkout.js&lt;/code&gt; and then make the charge with the &lt;code&gt;capture&lt;/code&gt; parameter set to &lt;code&gt;false&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;charge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Stripe&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Charge&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="ss"&gt;card&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:stripeToken&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="ss"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@selected_rate&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'rate'&lt;/span&gt;&lt;span class="o"&gt;].&lt;/span&gt;&lt;span class="n"&gt;to_f&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="ss"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'usd'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="ss"&gt;capture&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This will authorize a charge of $100 plus the shipping rate for up to seven days. EasyPost gives back shipping rates as decimal strings, so to give it to Stripe we have to convert it to a number, then to cents, and finally to an integer. Save &lt;code&gt;shipment.id&lt;/code&gt; and &lt;code&gt;charge.id&lt;/code&gt; to your database, you'll need them later.&lt;/p&gt;
&lt;h3&gt;Step 3: Build the Product&lt;/h3&gt;
&lt;p&gt;You're on your own here. Just make sure to get everything done in 7 days, because after that Stripe will release the funds from the customer's credit card and the charge object won't be valid anymore.&lt;/p&gt;
&lt;h3&gt;Step 4: Ship It!&lt;/h3&gt;
&lt;p&gt;Now you're at the point where you're ready to ship. All you have to do now is purchase the shipping label and tell Stripe to capture the charge:&lt;/p&gt;
&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;shipment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Shipment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;retrieve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;saved_shipment_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;shipment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;buy&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;charge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Stripe&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Charge&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;retrieve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;saved_charge_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;charge&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;capture&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;label_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'label_pdf_url'&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# display the url and print it&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now just print off the PDF, tape it to your box, and take the package to the UPS or FedEx store or the post office and drop it off. Wait a few weeks and &lt;a href="http://www.youtube.com/results?search_query=quadcopter&amp;amp;oq=quadcopter&amp;amp;gs_l=youtube.3..0l10.52.1051.0.1171.10.5.0.5.5.0.114.411.2j3.5.0...0.0...1ac.1.11.youtube.kAqJ9C9hPz8"&gt;search YouTube for quadcopter videos&lt;/a&gt; and you'll be sure to see one of your kits, gracefully flying around.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Stripe is awesome for processing credit cards. EasyPost is the easiest way to buy shipping known to man. Combine them and you have yourself a simple way to sell and ship physical products in the US and worldwide.&lt;/p&gt;</description><author>Pete Keen</author><pubDate>Tue, 02 Jul 2013 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.petekeen.net/shipping-with-stripe-and-easypost/</guid></item><item><title>10 Years Ago, in an MMO far, far away</title><link>https://benovermyer.com/blog/2013/06/10-years-ago-in-an-mmo-far-far-away/</link><description>&lt;p&gt;Ten years ago nearly to the day, Star Wars: Galaxies launched to the world. As a remembrance of the adventures I had there, I’m writing now about the game as it was in those days and the community that surrounded it. As you read this, you may wish to listen to the playlist on YouTube that starts with the above video. It’s a wonderful collection of the music from the game, familiar audio glitches included.&lt;/p&gt;
&lt;div class="dress-image"&gt;
&lt;img alt="A screenshot of the features page from the first Star Wars: Galaxies website" src="https://benovermyer.com/processed_images/OriginalFeaturesPage.a5b1887d1fc2374d.jpg" /&gt;
&lt;div class="caption"&gt;The features page for the first SWG website&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;One of the things I remember reading first about Star Wars: Galaxies was an interview with lead world artist John Roy about procedural planet generation, and how he would discover features of the landscape that were new even to the designers. That got me really excited about exploring these worlds that I’d only read about or seen on the big screen. I’m not sure how often this technique is employed in modern MMORPGs. My impression is that the vast majority of current worlds are slowly crafted by hand by artists, which is why we have so many beautiful vistas but so few boring areas to contrast them with. If all we ever see is beauty, we start to tire of it and look for something better.&lt;/p&gt;
&lt;p&gt;I miss the rolling hills of Corellia where my first SWG toon “grew up.” Another thing that I truly miss about Star Wars: Galaxies is the wait times at the starports. You could only travel to another planet if you bought a ticket for a starship and waited at a starport for one. They arrived in ten minute intervals, which means frequently large groups of players would congregate at the main starports while waiting to travel to another planet. It made for some great conversations, and new friends.&lt;/p&gt;
&lt;div class="dress-image"&gt;
&lt;img alt="A screenshot of a starport in Star Wars: Galaxies" src="https://benovermyer.com/processed_images/starport.be230e6bad6d47b0.jpg" /&gt;
&lt;div class="caption"&gt;A starport in Star Wars: Galaxies&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Something else I miss about Star Wars: Galaxies was the grind. As a crafter who played before the concept of “practice mode” was introduced, I would sit there and grind out components to level up my skill for hours. When I ran out of materials or empty space in my inventory, I’d make a backpack, shove the extra items in there, and head out to my personal harvesters for more materials. This was before anyone was able to build medium or heavy harvesters, so there were still vast fields of personal harvesters dotting the landscape.&lt;/p&gt;
&lt;div class="dress-image"&gt;
&lt;img alt="An image of a personal mineral extractor from SWG" src="https://benovermyer.com/processed_images/Personal_Mineral_Extractor.f465e0046c576571.jpg" /&gt;
&lt;div class="caption"&gt;A personal mineral extractor&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The grind, at least for SWG crafting, meant long periods of sitting there doing the exact same motions over and over again. This sounds really boring, but frequently after about five minutes of doing this, I would hit a kind of altered state of mind - a vaguely pleasant feeling of just “being.” The only other time I’ve ever felt that is when practicing Zen meditation, and I sorely miss it.&lt;/p&gt;
&lt;p&gt;Before vehicles were introduced, everyone had to walk everywhere. Shuttles and starports were jammed full of players on their way someplace. Player cities didn’t exist yet, but there were always massive collections of player houses exactly 1 km outside of every major NPC city. It made those cities feel a lot like real cities - you would always be passing by other players on your way either into the city core for transport, into the suburbs for shopping or socializing, or out into the wilderness for adventuring and hunting. Every city had its own distinctive feel, too, on my home server of Chilastra.&lt;/p&gt;
&lt;div class="dress-image"&gt;
&lt;img alt="A screenshot of Corellia" src="https://benovermyer.com/processed_images/correlia1.fc2c83f4d1bf5de1.jpg" /&gt;
&lt;div class="caption"&gt;Corellia had some beautiful vistas&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Coronet on Corellia was where I first landed, and it’s where I spent the first few months of the game. It felt very rugged, like a city on the fringe. Theed, on Naboo, was where the entertainers and image designers hung out. It was definitely a “pretty” city. Mos Eisley was… well, a desert city. Because of all the difficult, unbuildable terrain surrounding Mos Eisley, it wasn’t nearly as dense as the others. I think the players more nostalgic for A New Hope landed there. The other cities, like Restuss on Rori and the Imperial Outpost on Talus, had much smaller and more focused populations. Later, when they allowed players to build on some of the more advanced planets like Dantooine and Lok, more interesting communities started springing up.&lt;/p&gt;
&lt;p&gt;In the end, Star Wars: Galaxies really was the best MMORPG experience I’ve ever had. The early months, even with all the bugs, network issues, and missing content (capes!?), are permanently etched in my memory as a wonderful time. Rest in peace, SWG.&lt;/p&gt;</description><author>Ben Overmyer's Site</author><pubDate>Sat, 29 Jun 2013 03:00:00 GMT</pubDate><guid isPermaLink="true">https://benovermyer.com/blog/2013/06/10-years-ago-in-an-mmo-far-far-away/</guid></item><item><title>Pivoting in Postgres</title><link>/2013/06/27/Pivoting-in-Postgres/</link><description>&lt;p&gt;Earlier today on an internal Heroku group alias there was a &lt;a href="https://postgres.heroku.com/dataclips"&gt;dataclip&lt;/a&gt; shared. The dataclip listed off some data grouped by a category, there was a reply a few minutes later with a modification to the query that used the &lt;code&gt;crosstab&lt;/code&gt; function to pivot directly in SQL. There were immediately several reactions on the list that went something like this:&lt;/p&gt;
&lt;p&gt;&lt;img alt="mindblown" src="https://f.cl.ly/items/1b0G101r0B2W243b2933/Image%202013.06.27%202%3A06%3A23%20PM.gif" /&gt;&lt;/p&gt;
&lt;p&gt;While a mostly simple function in Postgres (there are a few rough edges), it really is all too handy. So here it is in action. Taking some data that looks like&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;row identifier, in this case date&lt;/li&gt;
&lt;li&gt;category grouping, in this case OS&lt;/li&gt;
&lt;li&gt;value&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Given a really basic query that generates some sample data it may look something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT generate_series AS date,
b.desc AS TYPE,
(random() * 10000 + 1)::int AS val
FROM generate_series((now() - '100 days'::interval)::date, now()::date, '1 day'::interval),
(SELECT unnest(ARRAY['OSX', 'Windows', 'Linux']) AS DESC) b;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You get results that look like:&lt;/p&gt;
&lt;!-- raw HTML omitted --&gt;
&lt;p&gt;But of course this isn&amp;rsquo;t overly helpful in comparing day to day overall. You can do so on a OS by OS basis, but its annoying enough as is. The easy solution is to simply use a pivot table on your data. Most people at this point would pull it up into Excel or Google Docs, or you can do it directly in Postgres. To do so you&amp;rsquo;ll first enable the extension &lt;code&gt;tablefunc&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CREATE EXTENSION tablefunc
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then you&amp;rsquo;ll use the crosstab function. The function looks something like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT *
FROM crosstab(
'SELECT row_name, category_grouping, value FROM foo',
'SELECT category_names FROM bar')
AS
ct_result (category_name text, category1 text, category2 text, etc.)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Lets see it an actual action. Given the same query we used to generate fake data we can actually pivot on it now directly in PostgreSQL:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT *
FROM crosstab(
'SELECT
a date,
b.desc AS os,
(random() * 10000 + 1)::int AS value
FROM generate_series((now() - ''100 days''::interval)::date, now()::date, ''1 DAY''::interval) a,
(SELECT unnest(ARRAY[''OSX'', ''Windows'', ''Linux'']) AS DESC) b ORDER BY 1,2
','SELECT unnest(ARRAY[''OSX'', ''Windows'', ''Linux''])'
)
AS ct(date date, OSX int, Windows int, Linux int);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And see some results:&lt;/p&gt;
&lt;!-- raw HTML omitted --&gt;
&lt;p&gt;Have fun analyzing your data directly in your DB now. And as always if you have feedback/questions/requests please feel free to drop me a line &lt;a href="mailto:craig.kerstiens@gmail.com"&gt;craig.kerstiens@gmail.com&lt;/a&gt;&lt;/p&gt;</description><author>CRAIG KERSTIENS</author><pubDate>Thu, 27 Jun 2013 23:55:56 GMT</pubDate><guid isPermaLink="true">/2013/06/27/Pivoting-in-Postgres/</guid></item><item><title>Javascript Functions for PostgreSQL</title><link>/2013/06/25/Javascript-Functions-for-PostgreSQL/</link><description>&lt;p&gt;Javascript in Postgres has gotten a good bit of love lately, part of that is from &lt;a href="http://postgres.herpku.com"&gt;Heroku Postgres&lt;/a&gt; recently &lt;a href="https://postgres.heroku.com/blog/past/2013/6/5/javascript_in_your_postgres/"&gt;adding support for Javascript&lt;/a&gt; and part from a variety of people championing the power of it such as &lt;a href="http://twitter.com/leinweber"&gt;@leinweber&lt;/a&gt; (&lt;a href="http://www.youtube.com/watch?v=fRupMAVdmWA"&gt;Embracing the web with JSON and PLV8&lt;/a&gt;) and &lt;a href="http://twitter.com/selenamarie"&gt;@selenamarie&lt;/a&gt; (&lt;a href="https://speakerdeck.com/selenamarie/schema-liberation-with-json-and-plv8-and-postgres"&gt;schema liberation with JSON and PLV8&lt;/a&gt;). In a recent conversation it was pointed out that it seems a bit of headache to have to create your own functions, or at least having an initial collection would make it that much more powerful. While many can look forward to &lt;a href="http://www.postgresql.org/docs/9.3/static/functions-json.html"&gt;PostgreSQL 9.3&lt;/a&gt; which will have a bit more built in support for JSON a few functions can really help make it more useful today.&lt;/p&gt;
&lt;p&gt;These are courtesy of &lt;a href="http://bitfission.com"&gt;Will Leinweber&lt;/a&gt;. For each of the following functions I&amp;rsquo;ll highlight an example of using it as well. To get an idea of the data its being run on:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;select * from example;
data
--------------------------------------------
{&amp;quot;name&amp;quot;:&amp;quot;Craig Kerstiens&amp;quot;, +
&amp;quot;age&amp;quot;:27, +
&amp;quot;siblings&amp;quot;:1, +
&amp;quot;numbers&amp;quot;:[ +
{&amp;quot;type&amp;quot;:&amp;quot;work&amp;quot;, +
&amp;quot;number&amp;quot;:&amp;quot;123-456-7890&amp;quot;}, +
{&amp;quot;type&amp;quot;:&amp;quot;home&amp;quot;, +
&amp;quot;number&amp;quot;:&amp;quot;456-123-7890&amp;quot;}]}
(1 row)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="get_text"&gt;
&lt;div&gt;
get_text
&lt;/div&gt;
&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;CREATE OR REPLACE FUNCTION
get_text(key text, data json)
RETURNS text AS $$
return data[key];
$$ LANGUAGE plv8 IMMUTABLE STRICT;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then using the function:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;select get_text('name', data) from example;
get_text
----------------
Craig Kerstiens
(1 row)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="get_numeric"&gt;
&lt;div&gt;
get_numeric
&lt;/div&gt;
&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;CREATE OR REPLACE FUNCTION
get_numeric(key text, data json)
RETURNS numeric AS $$
return data[key];
$$ LANGUAGE plv8 IMMUTABLE STRICT;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then using the function:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;select get_numeric('siblings', data) from example;
get_text
----------------
1
(1 row)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="json_select"&gt;
&lt;div&gt;
json_select
&lt;/div&gt;
&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;create or replace function
json_select(selector text, data json)
returns json as $$
exports = {};
(function(a){function z(a){return{sel:q(a)[1],match:function(a){return y(this.sel,a)},forEach:function(a,b){return x(this.sel,a,b)}}}function y(a,b){var c=[];x(a,b,function(a){c.push(a)});return c}function x(a,b,c,d,e,f){var g=a[0]===&amp;quot;,&amp;quot;?a.slice(1):[a],h=[],i=!1,j=0,k=0,l,m;for(j=0;j&amp;lt;g.length;j++){m=w(b,g[j],d,e,f),m[0]&amp;amp;&amp;amp;(i=!0);for(k=0;k&amp;lt;m[1].length;k++)h.push(m[1][k])}if(h.length&amp;amp;&amp;amp;typeof b==&amp;quot;object&amp;quot;){h.length&amp;gt;=1&amp;amp;&amp;amp;h.unshift(&amp;quot;,&amp;quot;);if(u(b))for(j=0;j&amp;lt;b.length;j++)x(h,b[j],c,undefined,j,b.length);else for(l in b)b.hasOwnProperty(l)&amp;amp;&amp;amp;x(h,b[l],c,l)}i&amp;amp;&amp;amp;c&amp;amp;&amp;amp;c(b)}function w(a,b,c,d,e){var f=[],g=b[0]===&amp;quot;&amp;gt;&amp;quot;?b[1]:b[0],h=!0,i;g.type&amp;amp;&amp;amp;(h=h&amp;amp;&amp;amp;g.type===v(a)),g.id&amp;amp;&amp;amp;(h=h&amp;amp;&amp;amp;g.id===c),h&amp;amp;&amp;amp;g.pf&amp;amp;&amp;amp;(g.pf===&amp;quot;:nth-last-child&amp;quot;?d=e-d:d++,g.a===0?h=g.b===d:(i=(d-g.b)%g.a,h=!i&amp;amp;&amp;amp;d*g.a+g.b&amp;gt;=0));if(h&amp;amp;&amp;amp;g.has){var j=function(){throw 42};for(var k=0;k&amp;lt;g.has.length;k++){try{x(g.has[k],a,j)}catch(l){if(l===42)continue}h=!1;break}}h&amp;amp;&amp;amp;g.expr&amp;amp;&amp;amp;(h=p(g.expr,a)),b[0]!==&amp;quot;&amp;gt;&amp;quot;&amp;amp;&amp;amp;b[0].pc!==&amp;quot;:root&amp;quot;&amp;amp;&amp;amp;f.push(b),h&amp;amp;&amp;amp;(b[0]===&amp;quot;&amp;gt;&amp;quot;?b.length&amp;gt;2&amp;amp;&amp;amp;(h=!1,f.push(b.slice(2))):b.length&amp;gt;1&amp;amp;&amp;amp;(h=!1,f.push(b.slice(1))));return[h,f]}function v(a){if(a===null)return&amp;quot;null&amp;quot;;var b=typeof a;b===&amp;quot;object&amp;quot;&amp;amp;&amp;amp;u(a)&amp;amp;&amp;amp;(b=&amp;quot;array&amp;quot;);return b}function u(a){return Array.isArray?Array.isArray(a):b.call(a)===&amp;quot;[object Array]&amp;quot;}function t(a,b,c){var d=b,g={},j=i(a,b);j&amp;amp;&amp;amp;j[1]===&amp;quot; &amp;quot;&amp;amp;&amp;amp;(d=b=j[0],j=i(a,b)),j&amp;amp;&amp;amp;j[1]===f.typ?(g.type=j[2],j=i(a,b=j[0])):j&amp;amp;&amp;amp;j[1]===&amp;quot;*&amp;quot;&amp;amp;&amp;amp;(j=i(a,b=j[0]));for(;;){if(j===undefined)break;if(j[1]===f.ide)g.id&amp;amp;&amp;amp;e(&amp;quot;nmi&amp;quot;,j[1]),g.id=j[2];else if(j[1]===f.psc)(g.pc||g.pf)&amp;amp;&amp;amp;e(&amp;quot;mpc&amp;quot;,j[1]),j[2]===&amp;quot;:first-child&amp;quot;?(g.pf=&amp;quot;:nth-child&amp;quot;,g.a=0,g.b=1):j[2]===&amp;quot;:last-child&amp;quot;?(g.pf=&amp;quot;:nth-last-child&amp;quot;,g.a=0,g.b=1):g.pc=j[2];else{if(j[1]!==f.psf)break;if(j[2]===&amp;quot;:val&amp;quot;||j[2]===&amp;quot;:contains&amp;quot;)g.expr=[undefined,j[2]===&amp;quot;:val&amp;quot;?&amp;quot;=&amp;quot;:&amp;quot;*=&amp;quot;,undefined],j=i(a,b=j[0]),j&amp;amp;&amp;amp;j[1]===&amp;quot; &amp;quot;&amp;amp;&amp;amp;(j=i(a,b=j[0])),(!j||j[1]!==&amp;quot;(&amp;quot;)&amp;amp;&amp;amp;e(&amp;quot;pex&amp;quot;,a),j=i(a,b=j[0]),j&amp;amp;&amp;amp;j[1]===&amp;quot; &amp;quot;&amp;amp;&amp;amp;(j=i(a,b=j[0])),(!j||j[1]!==f.str)&amp;amp;&amp;amp;e(&amp;quot;sex&amp;quot;,a),g.expr[2]=j[2],j=i(a,b=j[0]),j&amp;amp;&amp;amp;j[1]===&amp;quot; &amp;quot;&amp;amp;&amp;amp;(j=i(a,b=j[0])),(!j||j[1]!==&amp;quot;)&amp;quot;)&amp;amp;&amp;amp;e(&amp;quot;epex&amp;quot;,a);else if(j[2]===&amp;quot;:has&amp;quot;){j=i(a,b=j[0]),j&amp;amp;&amp;amp;j[1]===&amp;quot; &amp;quot;&amp;amp;&amp;amp;(j=i(a,b=j[0])),(!j||j[1]!==&amp;quot;(&amp;quot;)&amp;amp;&amp;amp;e(&amp;quot;pex&amp;quot;,a);var k=q(a,j[0],!0);j[0]=k[0],g.has||(g.has=[]),g.has.push(k[1])}else if(j[2]===&amp;quot;:expr&amp;quot;){g.expr&amp;amp;&amp;amp;e(&amp;quot;mexp&amp;quot;,a);var l=o(a,j[0]);j[0]=l[0],g.expr=l[1]}else{(g.pc||g.pf)&amp;amp;&amp;amp;e(&amp;quot;mpc&amp;quot;,a),g.pf=j[2];var m=h.exec(a.substr(j[0]));m||e(&amp;quot;mepf&amp;quot;,a),m[5]?(g.a=2,g.b=m[5]===&amp;quot;odd&amp;quot;?1:0):m[6]?(g.a=0,g.b=parseInt(m[6],10)):(g.a=parseInt((m[1]?m[1]:&amp;quot;+&amp;quot;)+(m[2]?m[2]:&amp;quot;1&amp;quot;),10),g.b=m[3]?parseInt(m[3]+m[4],10):0),j[0]+=m[0].length}}j=i(a,b=j[0])}d===b&amp;amp;&amp;amp;e(&amp;quot;se&amp;quot;,a);return[b,g]}function s(a){if(a[0]===&amp;quot;,&amp;quot;){var b=[&amp;quot;,&amp;quot;];for(var c=c;c&amp;lt;a.length;c++){var d=r(d[c]);b=b.concat(d[0]===&amp;quot;,&amp;quot;?d.slice(1):d)}return b}return r(a)}function r(a){var b=[],c;for(var d=0;d&amp;lt;a.length;d++)if(a[d]===&amp;quot;~&amp;quot;){if(d&amp;lt;2||a[d-2]!=&amp;quot;&amp;gt;&amp;quot;)c=a.slice(0,d-1),c=c.concat([{has:[[{pc:&amp;quot;:root&amp;quot;},&amp;quot;&amp;gt;&amp;quot;,a[d-1]]]},&amp;quot;&amp;gt;&amp;quot;]),c=c.concat(a.slice(d+1)),b.push(c);if(d&amp;gt;1){var e=a[d-2]===&amp;quot;&amp;gt;&amp;quot;?d-3:d-2;c=a.slice(0,e);var f={};for(var g in a[e])a[e].hasOwnProperty(g)&amp;amp;&amp;amp;(f[g]=a[e][g]);f.has||(f.has=[]),f.has.push([{pc:&amp;quot;:root&amp;quot;},&amp;quot;&amp;gt;&amp;quot;,a[d-1]]),c=c.concat(f,&amp;quot;&amp;gt;&amp;quot;,a.slice(d+1)),b.push(c)}break}if(d==a.length)return a;return b.length&amp;gt;1?[&amp;quot;,&amp;quot;].concat(b):b[0]}function q(a,b,c,d){c||(d={});var f=[],g,h;b||(b=0);for(;;){var j=t(a,b,d);f.push(j[1]),j=i(a,b=j[0]),j&amp;amp;&amp;amp;j[1]===&amp;quot; &amp;quot;&amp;amp;&amp;amp;(j=i(a,b=j[0]));if(!j)break;if(j[1]===&amp;quot;&amp;gt;&amp;quot;||j[1]===&amp;quot;~&amp;quot;)j[1]===&amp;quot;~&amp;quot;&amp;amp;&amp;amp;(d.usesSiblingOp=!0),f.push(j[1]),b=j[0];else if(j[1]===&amp;quot;,&amp;quot;)g===undefined?g=[&amp;quot;,&amp;quot;,f]:g.push(f),f=[],b=j[0];else if(j[1]===&amp;quot;)&amp;quot;){c||e(&amp;quot;ucp&amp;quot;,j[1]),h=1,b=j[0];break}}c&amp;amp;&amp;amp;!h&amp;amp;&amp;amp;e(&amp;quot;mcp&amp;quot;,a),g&amp;amp;&amp;amp;g.push(f);var k;!c&amp;amp;&amp;amp;d.usesSiblingOp?k=s(g?g:f):k=g?g:f;return[b,k]}function p(a,b){if(a===undefined)return b;if(a===null||typeof a!=&amp;quot;object&amp;quot;)return a;var c=p(a[0],b),d=p(a[2],b);return l[a[1]][1](c,d)}function o(a,b){function c(a){return typeof a!=&amp;quot;object&amp;quot;||a===null?a:a[0]===&amp;quot;(&amp;quot;?c(a[1]):[c(a[0]),a[1],c(a[2])]}var d=n(a,b?b:0);return[d[0],c(d[1])]}function n(a,b){b||(b=0);var c=m(a,b),d;if(c&amp;amp;&amp;amp;c[1]===&amp;quot;(&amp;quot;){d=n(a,c[0]);var f=m(a,d[0]);(!f||f[1]!==&amp;quot;)&amp;quot;)&amp;amp;&amp;amp;e(&amp;quot;epex&amp;quot;,a),b=f[0],d=[&amp;quot;(&amp;quot;,d[1]]}else!c||c[1]&amp;amp;&amp;amp;c[1]!=&amp;quot;x&amp;quot;?e(&amp;quot;ee&amp;quot;,a+&amp;quot; - &amp;quot;+(c[1]&amp;amp;&amp;amp;c[1])):(d=c[1]===&amp;quot;x&amp;quot;?undefined:c[2],b=c[0]);var g=m(a,b);if(!g||g[1]==&amp;quot;)&amp;quot;)return[b,d];(g[1]==&amp;quot;x&amp;quot;||!g[1])&amp;amp;&amp;amp;e(&amp;quot;bop&amp;quot;,a+&amp;quot; - &amp;quot;+(g[1]&amp;amp;&amp;amp;g[1]));var h=n(a,g[0]);b=h[0],h=h[1];var i;if(typeof h!=&amp;quot;object&amp;quot;||h[0]===&amp;quot;(&amp;quot;||l[g[1]][0]&amp;lt;l[h[1]][0])i=[d,g[1],h];else{i=h;while(typeof h[0]==&amp;quot;object&amp;quot;&amp;amp;&amp;amp;h[0][0]!=&amp;quot;(&amp;quot;&amp;amp;&amp;amp;l[g[1]][0]&amp;gt;=l[h[0][1]][0])h=h[0];h[0]=[d,g[1],h[0]]}return[b,i]}function m(a,b){var d,e=j.exec(a.substr(b));if(e){b+=e[0].length,d=e[1]||e[2]||e[3]||e[5]||e[6];if(e[1]||e[2]||e[3])return[b,0,c(d)];if(e[4])return[b,0,undefined];return[b,d]}}function k(a,b){return typeof a===b}function i(a,b){b||(b=0);var d=g.exec(a.substr(b));if(!d)return undefined;b+=d[0].length;var h;d[1]?h=[b,&amp;quot; &amp;quot;]:d[2]?h=[b,d[0]]:d[3]?h=[b,f.typ,d[0]]:d[4]?h=[b,f.psc,d[0]]:d[5]?h=[b,f.psf,d[0]]:d[6]?e(&amp;quot;upc&amp;quot;,a):d[8]?h=[b,d[7]?f.ide:f.str,c(d[8])]:d[9]?e(&amp;quot;ujs&amp;quot;,a):d[10]&amp;amp;&amp;amp;(h=[b,f.ide,d[10].replace(/\\([^\r\n\f0-9a-fA-F])/g,&amp;quot;$1&amp;quot;)]);return h}function e(a,b){throw new Error(d[a]+(b&amp;amp;&amp;amp;&amp;quot; in '&amp;quot;+b+&amp;quot;'&amp;quot;))}function c(a){try{if(JSON&amp;amp;&amp;amp;JSON.parse)return JSON.parse(a);return(new Function(&amp;quot;return &amp;quot;+a))()}catch(b){e(&amp;quot;ijs&amp;quot;,b.message)}}var b=Object.prototype.toString,d={bop:&amp;quot;binary operator expected&amp;quot;,ee:&amp;quot;expression expected&amp;quot;,epex:&amp;quot;closing paren expected ')'&amp;quot;,ijs:&amp;quot;invalid json string&amp;quot;,mcp:&amp;quot;missing closing paren&amp;quot;,mepf:&amp;quot;malformed expression in pseudo-function&amp;quot;,mexp:&amp;quot;multiple expressions not allowed&amp;quot;,mpc:&amp;quot;multiple pseudo classes (:xxx) not allowed&amp;quot;,nmi:&amp;quot;multiple ids not allowed&amp;quot;,pex:&amp;quot;opening paren expected '('&amp;quot;,se:&amp;quot;selector expected&amp;quot;,sex:&amp;quot;string expected&amp;quot;,sra:&amp;quot;string required after '.'&amp;quot;,uc:&amp;quot;unrecognized char&amp;quot;,ucp:&amp;quot;unexpected closing paren&amp;quot;,ujs:&amp;quot;unclosed json string&amp;quot;,upc:&amp;quot;unrecognized pseudo class&amp;quot;},f={psc:1,psf:2,typ:3,str:4,ide:5},g=new RegExp('^(?:([\\r\\n\\t\\ ]+)|([~*,&amp;gt;\\)\\(])|(string|boolean|null|array|object|number)|(:(?:root|first-child|last-child|only-child))|(:(?:nth-child|nth-last-child|has|expr|val|contains))|(:\\w+)|(?:(\\.)?(\\&amp;quot;(?:[^\\\\\\&amp;quot;]|\\\\[^\\&amp;quot;])*\\&amp;quot;))|(\\&amp;quot;)|\\.((?:[_a-zA-Z]|[^\\0-\\0177]|\\\\[^\\r\\n\\f0-9a-fA-F])(?:[_a-zA-Z0-9\\-]|[^\\u0000-\\u0177]|(?:\\\\[^\\r\\n\\f0-9a-fA-F]))*))'),h=/^\s*\(\s*(?:([+\-]?)([0-9]*)n\s*(?:([+\-])\s*([0-9]))?|(odd|even)|([+\-]?[0-9]+))\s*\)/,j=new RegExp('^\\s*(?:(true|false|null)|(-?\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d+)?)|(&amp;quot;(?:[^\\]|\\[^&amp;quot;])*&amp;quot;)|(x)|(&amp;amp;&amp;amp;|\\|\\||[\\$\\^&amp;lt;&amp;gt;!\\*]=|[=+\\-*/%&amp;lt;&amp;gt;])|([\\(\\)]))'),l={&amp;quot;*&amp;quot;:[9,function(a,b){return a*b}],&amp;quot;/&amp;quot;:[9,function(a,b){return a/b}],&amp;quot;%&amp;quot;:[9,function(a,b){return a%b}],&amp;quot;+&amp;quot;:[7,function(a,b){return a+b}],&amp;quot;-&amp;quot;:[7,function(a,b){return a-b}],&amp;quot;&amp;lt;=&amp;quot;:[5,function(a,b){return k(a,&amp;quot;number&amp;quot;)&amp;amp;&amp;amp;k(b,&amp;quot;number&amp;quot;)&amp;amp;&amp;amp;a&amp;lt;=b}],&amp;quot;&amp;gt;=&amp;quot;:[5,function(a,b){return k(a,&amp;quot;number&amp;quot;)&amp;amp;&amp;amp;k(b,&amp;quot;number&amp;quot;)&amp;amp;&amp;amp;a&amp;gt;=b}],&amp;quot;$=&amp;quot;:[5,function(a,b){return k(a,&amp;quot;string&amp;quot;)&amp;amp;&amp;amp;k(b,&amp;quot;string&amp;quot;)&amp;amp;&amp;amp;a.lastIndexOf(b)===a.length-b.length}],&amp;quot;^=&amp;quot;:[5,function(a,b){return k(a,&amp;quot;string&amp;quot;)&amp;amp;&amp;amp;k(b,&amp;quot;string&amp;quot;)&amp;amp;&amp;amp;a.indexOf(b)===0}],&amp;quot;*=&amp;quot;:[5,function(a,b){return k(a,&amp;quot;string&amp;quot;)&amp;amp;&amp;amp;k(b,&amp;quot;string&amp;quot;)&amp;amp;&amp;amp;a.indexOf(b)!==-1}],&amp;quot;&amp;gt;&amp;quot;:[5,function(a,b){return k(a,&amp;quot;number&amp;quot;)&amp;amp;&amp;amp;k(b,&amp;quot;number&amp;quot;)&amp;amp;&amp;amp;a&amp;gt;b}],&amp;quot;&amp;lt;&amp;quot;:[5,function(a,b){return k(a,&amp;quot;number&amp;quot;)&amp;amp;&amp;amp;k(b,&amp;quot;number&amp;quot;)&amp;amp;&amp;amp;a&amp;lt;b}],&amp;quot;=&amp;quot;:[3,function(a,b){return a===b}],&amp;quot;!=&amp;quot;:[3,function(a,b){return a!==b}],&amp;quot;&amp;amp;&amp;amp;&amp;quot;:[2,function(a,b){return a&amp;amp;&amp;amp;b}],&amp;quot;||&amp;quot;:[1,function(a,b){return a||b}]};a._lex=i,a._parse=q,a.match=function(a,b){return z(a).match(b)},a.forEach=function(a,b,c){return z(a).forEach(b,c)},a.compile=z})(typeof exports==&amp;quot;undefined&amp;quot;?window.JSONSelect={}:exports)
return JSON.stringify(
exports.match(selector,
data));
$$ LANGUAGE plv8 IMMUTABLE STRICT
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then using the function:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;select json_select('.name nth-child(1)', data) as name, json_select('.numbers', data) as phone
from example;
name | phone
--------------------+------------------------------------------------------------------------------------------
[&amp;quot;Craig Kerstiens&amp;quot;] | [[{&amp;quot;type&amp;quot;:&amp;quot;work&amp;quot;,&amp;quot;number&amp;quot;:&amp;quot;456-123-7890&amp;quot;},{&amp;quot;type&amp;quot;:&amp;quot;home&amp;quot;,&amp;quot;number&amp;quot;:&amp;quot;123-456-7890&amp;quot;}]]
(1 row)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="javascript-injection-attack"&gt;
&lt;div&gt;
javascript injection attack
&lt;/div&gt;
&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;create or replace function
js(src text) returns text as $$
return eval(
&amp;quot;(function() { &amp;quot; + src + &amp;quot;})&amp;quot;
)();
$$ LANGUAGE plv8;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Have any others you feel are essential when starting to work with JSON? Let me know &lt;a href="mailto:craig.kerstiens@gmail.com"&gt;craig.kerstiens@gmail.com&lt;/a&gt;. Beyond that give JSON and JavaScript a try inside your database.&lt;/p&gt;</description><author>CRAIG KERSTIENS</author><pubDate>Tue, 25 Jun 2013 23:55:56 GMT</pubDate><guid isPermaLink="true">/2013/06/25/Javascript-Functions-for-PostgreSQL/</guid></item><item><title>The Joys of UNIX Keyboards</title><link>https://donatstudios.com/UNIX-Keyboards</link><description>&lt;h2&gt;I fell in love with a dead keyboard layout.&lt;/h2&gt;
&lt;p&gt;A decade or so ago while helping a friends father clean out an old building, we came across an ancient Sun Microsystems server. We found it curious. &lt;em&gt;Everything&lt;/em&gt; about it was different from what we were used to. The command line was black text on a white background, the connectors strange and foreign, and the keyboard layout was bizarre.&lt;/p&gt;
&lt;p&gt;We never did much with it; turning it on made all the lights in his home dim, and our joint knowledge of UNIX was nonexistent. It sat in his bedroom for years supporting his television at the foot of his bed.&lt;/p&gt;
&lt;p&gt;I never forgot that keyboard though. The thought that there was this alternative layout out there seemed intriguing to me.&lt;/p&gt;
&lt;h2&gt;I am ruined for all other keyboards…&lt;/h2&gt;
&lt;p&gt;Mac is the main platform at my new job. I found myself unhappy with Apple keyboards; they are flat, bland, and completely unsatisfying to type on. I dug out an old AppleDesign keyboard, and used that for several months but I still needed something more.&lt;/p&gt;
&lt;p&gt;I read about Happy Hacking Keyboard, and after some deliberation decided to give it a try. I thought I could learn to accept most of the layout changes, but the lack of arrow keys on the &amp;quot;Professional&amp;quot; model made me squirm a little. Luckily there is a &amp;quot;Lite&amp;quot; version that includes arrow keys.&lt;/p&gt;
&lt;p&gt;I purchased the &lt;a href="https://amzn.com/B0000U1DJ2"&gt;Happy Hacking Keyboard Lite2&lt;/a&gt;. &lt;/p&gt;
&lt;p&gt;The Happy Hacking Keyboard much like the Sun Microsystem keyboard I found years earlier features a UNIX layout. They designed it with typing at a terminal in mind.&lt;/p&gt;
&lt;h2&gt;A Better Layout&lt;/h2&gt;
&lt;p&gt;For those unfamiliar with UNIX keyboards, there are a few distinctions.&lt;/p&gt;
&lt;img alt="Happy Hacking Keyboard" src="https://donatstudios.com/assets/43/hhkb.png" style="display: block; float: right; margin-left: 22px; margin-bottom: 22px;" /&gt; 
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The &lt;strong&gt;Control&lt;/strong&gt; key moved to the &lt;strong&gt;Caps Lock&lt;/strong&gt; keys usual position. This makes it much easier to hit common commands than its usual location, often without ever leaving home row. I can't imagine why anyone thought &lt;strong&gt;Caps Lock&lt;/strong&gt; should have had this prominent of placement.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The &lt;strong&gt;Meta&lt;/strong&gt; (&lt;strong&gt;◆ / ⌘ / Windows&lt;/strong&gt;) key is in its rightful place next to the space bar, as on a Mac keyboard.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The &lt;strong&gt;Backspace&lt;/strong&gt; key moved down a row to be above the &lt;strong&gt;Enter&lt;/strong&gt; key. This change is my favorite change. Having a small reach, normally my hands have to leave home row to hit backspace. but on a UNIX board I have no issue hitting it with my pinky with the rest of my fingers remaining firmly on home row.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Escape&lt;/strong&gt; is also in reach of home row, placed where you would usually find &lt;strong&gt;~&lt;/strong&gt;. I could have done without this change, but it is fine.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The &lt;strong&gt;~&lt;/strong&gt; and &lt;strong&gt;\&lt;/strong&gt; have moved to the usual location of the &lt;strong&gt;Backspace&lt;/strong&gt;. Working exclusively on UNIX systems now, I don't mind the &lt;strong&gt;\&lt;/strong&gt; being harder to reach. I could understand a Windows user finding this irritating.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The HHKB is a minimal version of the UNIX keyboard. To this end the &lt;strong&gt;F Keys&lt;/strong&gt; are mapped to &lt;strong&gt;Fn + 1, 2, 3, etc&lt;/strong&gt;. The HHKB encourages you to stay on home row; you can hit any key on the keyboard without leaving it. I find my typing accuracy and speed to have improved thanks to its use.&lt;/p&gt;
&lt;p&gt;The HHKB features one more change I don't care for. The &lt;strong&gt;Backspace&lt;/strong&gt; key by &lt;em&gt;default&lt;/em&gt; performs &lt;strong&gt;Forward Delete&lt;/strong&gt;. Luckily changing this is easy by flipping a toggle switch on the back of the board.&lt;/p&gt;
&lt;h2&gt;Closing&lt;/h2&gt;
&lt;p&gt;I recommend this keyboard to &lt;em&gt;anyone&lt;/em&gt; who spends a lot of time at the terminal. You are able to do more of what you need without ever leaving home row, every important key is within reach.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Elsewhere&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Discussion on &lt;a href="https://news.ycombinator.com/item?id=21912184"&gt;Hacker News&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Mentioned on BSD Now Podcast: &lt;a href="https://www.bsdnow.tv/333"&gt;Episode 333&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description><author>Donat Studios</author><pubDate>Wed, 19 Jun 2013 02:10:51 GMT</pubDate><guid isPermaLink="true">https://donatstudios.com/UNIX-Keyboards</guid></item><item><title>Making eBooks with Docverter</title><link>https://www.petekeen.net/making-ebooks-with-docverter/</link><description>&lt;p&gt;I've been writing &lt;a href="/mastering-modern-payments"&gt;my guide to integrating Stripe with Rails&lt;/a&gt; using markdown, as with most textual projects that I work on. Every chapter is a markdown-formatted file living in a git repo, synced-on-save to &lt;a href="/hosting-private-git-repositories-with-gitolite"&gt;my git server&lt;/a&gt; and S3 &lt;a href="/git-backed-personal-markdown-wiki"&gt;using SparkleShare&lt;/a&gt;. When I want to peek at the rendered version I use little previewer app running on a VM on my Mac mini that I &lt;a href="/page-viewer-a-simple-markdown-viewer"&gt;talked about previously&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;A good eBook needs a PDF version, of course. Awhile back I wrote an open-source service named &lt;a href="http://www.docverter.com"&gt;Docverter&lt;/a&gt; that can render XHTML to PDF using a library named &lt;a href="http://code.google.com/p/flying-saucer/"&gt;Flying Saucer&lt;/a&gt;, among other things. All you have to do is pipe in the HTML and other related files and you get back a rendered, self-contained PDF file. There are a few non-trivial aspects to this, of course, because HTML is not primarily intended for printable output. The W3C has worked up a &lt;a href="http://www.w3.org/TR/css3-page/"&gt;whole CSS module for page-related styles&lt;/a&gt; but it's not the most readable document. There's a few simple-ish things that you can do to your document to make it look nice, though.&lt;/p&gt;
&lt;!--more--&gt;
&lt;p&gt;Here's the simplest HTML to PDF renderer:&lt;/p&gt;
&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'docverter'&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="no"&gt;Docverter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;base_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'http://c.docverter.com'&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;&amp;lt;HERE
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="no"&gt;Test&lt;/span&gt; &lt;span class="no"&gt;Document&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/title&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sr"&gt;  &amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="no"&gt;Test&lt;/span&gt; &lt;span class="no"&gt;Header&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h1&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sr"&gt;    &amp;lt;p&amp;gt;This is some text&amp;lt;/&lt;/span&gt;&lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/body&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sr"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="no"&gt;HERE&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"out.pdf"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"w+"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Docverter&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Conversion&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'html'&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;      &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'pdf'&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The HTML document is very simple, as is the conversion. &lt;code&gt;Docverter::Conversion.run&lt;/code&gt; takes a block which yields a &lt;code&gt;Docverter::Conversion&lt;/code&gt; object that can be set with any options Docverter supports. Most basically, you have to specify &lt;code&gt;from&lt;/code&gt;, &lt;code&gt;to&lt;/code&gt;, and &lt;code&gt;content&lt;/code&gt;. If you run this program you'll get a file named &lt;code&gt;out.pdf&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Fonts&lt;/h3&gt;
&lt;p&gt;The first non-trivial thing that one would want to do is customize fonts. Flying Saucer knows quite a bit of CSS, including &lt;code&gt;@font-face&lt;/code&gt;. All you have to do to customize fonts is to download the font as a &lt;code&gt;ttf&lt;/code&gt; and modify the above program to look like this:&lt;/p&gt;
&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'docverter'&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="no"&gt;Docverter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;base_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'http://c.docverter.com'&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;&amp;lt;HERE
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="no"&gt;Test&lt;/span&gt; &lt;span class="no"&gt;Document&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/title&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sr"&gt;    &amp;lt;style type="text/&lt;/span&gt;&lt;span class="n"&gt;css&lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;      @font-face {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;        font-family: 'Droid Sans';
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;        font-style: normal;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;        font-weight: 400;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;        src: url('droid_sans.ttf');
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;        -fs-pdf-font-embed: embed;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;        -fs-pdf-font-encoding: Identity-H;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;      }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;      body {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;        font-family: 'Droid Sans';
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;      }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;    &amp;lt;/style&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;  &amp;lt;/head&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;  &amp;lt;body&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;    &amp;lt;h1&amp;gt;Test Header&amp;lt;/h1&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;    &amp;lt;p&amp;gt;This is some text&amp;lt;/p&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;  &amp;lt;/body&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;lt;/html&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;HERE
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;File.open("&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="s2"&gt;", "&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="s2"&gt;") do |f|
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;  f.write(Docverter::Conversion.run do |c|
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;    c.from    = 'html'
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;    c.to      = 'pdf'
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;    c.content = html
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;    c.add_other_file 'droid_sans.ttf'
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;  end)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;end
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;A few interesting things are going on here. First, &lt;code&gt;@font-face&lt;/code&gt; declares the font. &lt;code&gt;font-family&lt;/code&gt; &lt;em&gt;must&lt;/em&gt; match the name the font file specifies. Second, &lt;code&gt;-fs-pdf-font-embed&lt;/code&gt; and &lt;code&gt;-fs-pdf-font-encoding&lt;/code&gt; &lt;em&gt;must&lt;/em&gt; match the values given above or the embedding won't work. &lt;code&gt;src&lt;/code&gt; is the filename of the file, which we add to the conversion using &lt;code&gt;add_other_file&lt;/code&gt;, which takes a path. Drop &lt;code&gt;droid_sans.ttf&lt;/code&gt; in the same directory as the script and run it again. Notice that the PDF is now in Droid Sans, which is pretty pleasant.&lt;/p&gt;
&lt;h3&gt;Footers&lt;/h3&gt;
&lt;p&gt;Most documents longer than a page are going to need page numbers. Adding page numbers to a PDF with Docverter is very much not trivial. You need to combine the powers of CSS Paged Media running elements and &lt;a href="http://dev.w3.org/csswg/css-gcpm/"&gt;generated content&lt;/a&gt; to generate properly formatted footers with page numbers. Here's the HTML source:&lt;/p&gt;
&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Test Document&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;style&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;"text/css"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="k"&gt;font-face&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="nt"&gt;font-family&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'Droid Sans'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="nt"&gt;font-style&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;normal&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="nt"&gt;font-weight&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;400&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="nt"&gt;src&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;url&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'droid_sans.ttf'&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="nt"&gt;-fs-pdf-font-embed&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;embed&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="nt"&gt;-fs-pdf-font-encoding&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;Identity-H&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'Droid Sans'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;page_footer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;block&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;text-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'Droid Sans'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;running&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;footer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;page_footer&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;page_number&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nd"&gt;after&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;page&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="k"&gt;page&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="k"&gt;bottom-center&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="nt"&gt;content&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;element&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;footer&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="p"&gt;}&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;style&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"page_footer"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"page_number"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Test Header&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;This is some text&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Three things to note here. First, the new &lt;code&gt;div&lt;/code&gt; with class &lt;code&gt;page_footer&lt;/code&gt; &lt;em&gt;has&lt;/em&gt; to come before the rest of the body content because it gets moved into place as Flying Saucer renders. If it's at the bottom it won't exist when Flying Saucer tries to render the page and so nothing gets rendered at all.&lt;/p&gt;
&lt;p&gt;Second, notice &lt;code&gt;position: running(footer)&lt;/code&gt;. CSS Paged Media introduces the &lt;code&gt;running()&lt;/code&gt; which tells the renderer to stick the content that's currently selected, in this case &lt;code&gt;div.page_footer&lt;/code&gt;, into a slot named &lt;code&gt;footer&lt;/code&gt;. We set up the page using the &lt;code&gt;@page&lt;/code&gt; selector. Inside there is another selector named &lt;code&gt;@bottom-center&lt;/code&gt;, which specifies the center section at the bottom of the page. The &lt;code&gt;content&lt;/code&gt; attribute with an &lt;code&gt;element()&lt;/code&gt; value tells the renderer to take the content from the slot named &lt;code&gt;footer&lt;/code&gt; and use it to populate the section. Note that we could have named the &lt;code&gt;footer&lt;/code&gt; slot anything. The named slots are in their own namespace separate from ids and classes.&lt;/p&gt;
&lt;p&gt;Finally we get to actually setting up the page number. There's a default counter named &lt;code&gt;page&lt;/code&gt; which we put after the span with class &lt;code&gt;page_number&lt;/code&gt; using CSS generated content.&lt;/p&gt;
&lt;h3&gt;Page Breaks&lt;/h3&gt;
&lt;p&gt;PDFs are effectively pre-printed documents, so CSS Paged Media gives you a few different facilities for controlling page breaks. For example, if I wanted a page break before every &lt;code&gt;H1&lt;/code&gt; element I could say this:&lt;/p&gt;
&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;h1&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;page-break-before&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;always&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If, instead, I want to break after a certain element, like a closing paragraph or something, I could do this:&lt;/p&gt;
&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;closing&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;page-break-after&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;always&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Sometimes you have elements that you want to not break across pages if at all possible. In Mastering Modern Payments there are dozens of code examples that, if I let Flying Saucer break at the natural spots, would have one or two lines on one page and the rest of the 10-line sample on the other. CSS Paged Media lets you control that, too, with &lt;code&gt;page-break-inside&lt;/code&gt;. Here's the rule I use:&lt;/p&gt;
&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;code&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;page-break-inside&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;avoid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;orphans&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;widows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This says to avoid inserting page breaks inside a code block. The &lt;code&gt;orphans&lt;/code&gt; option controls how many lines are allowable at the bottom of a page inside a block and &lt;code&gt;widows&lt;/code&gt; controls how many are allowed at the top of the next. By setting both to &lt;code&gt;0&lt;/code&gt; I'm saying that I don't want any page breaks at all. Flying Saucer will ignore me if it's not possible, if for instance I have a code block that spans more than an entire page.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Making nice-looking PDFs with HTML source is not trivial. It would probably end up being easier to just drop the raw text inside Apple's iBooks Creator and style it that way, but I like a challenge. It's already looking pretty nice, and with a little more work I think I can have professional-grade PDF output. Docverter also knows how to do Markdown to ePub and Mobi so I should be set for book production. Now to finish writing.&lt;/p&gt;</description><author>Pete Keen</author><pubDate>Tue, 18 Jun 2013 15:00:00 GMT</pubDate><guid isPermaLink="true">https://www.petekeen.net/making-ebooks-with-docverter/</guid></item><item><title>Page Viewer, a Simple Markdown Viewer</title><link>https://www.petekeen.net/page-viewer-a-simple-markdown-viewer/</link><description>&lt;p&gt;For various projects including &lt;a href="/mastering-modern-payments"&gt;Mastering Modern Payments&lt;/a&gt; I've found it really useful to be able to view the Markdown source rendered as HTML but I don't really care about editing it online. I put together a little gem named &lt;a href="https://github.com/peterkeen/page_viewer"&gt;page_viewer&lt;/a&gt; which renders Markdown files like this:&lt;/p&gt;
&lt;img src="https://d2s7foagexgnc2.cloudfront.net/files/bc0c9ca20f9b12269192/Screen%20Shot%202013-06-15%20at%2011.04.48%20PM.opti.png" /&gt;
&lt;!--more--&gt;
&lt;p&gt;&lt;code&gt;page_viewer&lt;/code&gt; has some convenience features. First, it dynamically renders the markdown files for each request. This means that if the files change underneath there's no cache to refresh or anything. Second, code blocks are highlighted with Pygments. You'll need a working python installation but that's it. Third, it integrates with &lt;a href="http://www.docverter.com"&gt;Docverter&lt;/a&gt; for on-the-fly PDF conversion.&lt;/p&gt;
&lt;p&gt;It's also easy to subclass to do what you want. For example, the site where I'm developing my guide subclasses &lt;code&gt;PageViewer::App&lt;/code&gt; to add some routes that render the whole guide as one page with a table of contents, and another one that renders the whole thing as a PDF.&lt;/p&gt;
&lt;p&gt;I use an instance &lt;code&gt;page_viewer&lt;/code&gt; to view my &lt;a href="/git-backed-personal-markdown-wiki"&gt;personal wiki&lt;/a&gt; nicely rendered on a web page. My wiki repo is hosted on &lt;a href="/hosting-private-git-repositories-with-gitolite"&gt;my private gitolite instance&lt;/a&gt; with a local clone. Each time I push changes, which happens pretty frequently with Sparkleshare, gitolite clones the repo to a directory somewhere else on the machine. This happens to be the same directory that &lt;code&gt;page_viewer&lt;/code&gt; is pointing at.&lt;/p&gt;
&lt;p&gt;Installation is pretty simple. Create a new project and include the &lt;code&gt;page_viewer&lt;/code&gt; gem in the Gemfile:&lt;/p&gt;
&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="ss"&gt;:rubygems&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'page_viewer'&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Configure and run &lt;code&gt;PageViewer::App&lt;/code&gt; inside &lt;code&gt;config.ru&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'page_viewer'&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="no"&gt;PageViewer&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;App&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="ss"&gt;:page_root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'/path/to/some/markdown/files'&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Rack&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Auth&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Basic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Restricted Area"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;username&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'USERNAME'&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'PASSWORD'&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="no"&gt;PageViewer&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;App&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;:page_root&lt;/code&gt; needs to contain the path to the markdown files you want to render and is required. Every file in that path ending in &lt;code&gt;.md&lt;/code&gt; will be renderable.&lt;/p&gt;
&lt;p&gt;Is this going to be useful for other people? Probably not, since it's really specific to my own needs. If you have a dynamically changing set of markdown documents you want rendered fresh all the time, though, this may be the ticket.&lt;/p&gt;</description><author>Pete Keen</author><pubDate>Sat, 15 Jun 2013 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.petekeen.net/page-viewer-a-simple-markdown-viewer/</guid></item><item><title>Explaining your PostgreSQL data</title><link>/2013/06/13/Explaining-your-PostgreSQL-data/</link><description>&lt;p&gt;I&amp;rsquo;ve written a bit before about &lt;a href="http://www.craigkerstiens.com/2013/01/10/more-on-postgres-performance/"&gt;understanding the output&lt;/a&gt; from &lt;code&gt;EXPLAIN&lt;/code&gt; and &lt;code&gt;EXPLAIN ANALYZE&lt;/code&gt; in PostgreSQL. Though understandably getting a grasp on execution plans could probably use some more guidance. Yet, this time around I&amp;rsquo;m taking a bit of a cop out and highlighting a few tools instead of documenting myself, which I&amp;rsquo;ve done in a talk I&amp;rsquo;ve frequently given &lt;a href="https://speakerdeck.com/craigkerstiens/postgres-demystified-1"&gt;Postgres Demystified&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Explain explained" src="https://f.cl.ly/items/2Y0A0H2B2q3C0622261C/Screenshot_6_13_13_9_57_AM.png" /&gt;&lt;/p&gt;
&lt;h3 id="getting-at-the-data"&gt;
&lt;div&gt;
Getting at the Data
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;The first small thing you can do is actually retrieve the data in JSON form. By adding in &lt;code&gt;(format json)&lt;/code&gt; right after your &lt;code&gt;EXPLAIN&lt;/code&gt; or &lt;code&gt;EXPLAIN ANALYZE&lt;/code&gt; command it&amp;rsquo;ll as you&amp;rsquo;d expect return it in JSON. To give an example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# EXPLAIN SELECT * FROM users LIMIT 1;
QUERY PLAN
--------------------------------------------------------------
Limit (cost=0.00..0.03 rows=1 width=812)
-&amp;gt; Seq Scan on users (cost=0.00..1.50 rows=50 width=812)
(2 rows)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then in JSON format:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;EXPLAIN (format json) SELECT * FROM users LIMIT 1;
QUERY PLAN
------------------------------------------------
[ +
{ +
&amp;quot;Plan&amp;quot;: { +
&amp;quot;Node Type&amp;quot;: &amp;quot;Limit&amp;quot;, +
&amp;quot;Startup Cost&amp;quot;: 0.00, +
&amp;quot;Total Cost&amp;quot;: 0.03, +
&amp;quot;Plan Rows&amp;quot;: 1, +
&amp;quot;Plan Width&amp;quot;: 812, +
&amp;quot;Plans&amp;quot;: [ +
{ +
&amp;quot;Node Type&amp;quot;: &amp;quot;Seq Scan&amp;quot;, +
&amp;quot;Parent Relationship&amp;quot;: &amp;quot;Outer&amp;quot;,+
&amp;quot;Relation Name&amp;quot;: &amp;quot;users&amp;quot;, +
&amp;quot;Alias&amp;quot;: &amp;quot;users&amp;quot;, +
&amp;quot;Startup Cost&amp;quot;: 0.00, +
&amp;quot;Total Cost&amp;quot;: 1.50, +
&amp;quot;Plan Rows&amp;quot;: 50, +
&amp;quot;Plan Width&amp;quot;: 812 +
} +
] +
} +
} +
]
(1 row)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;While its on my list to build some interesting apps by pulling in the JSON input, others may be equally as interested in taking advantage of this data in its JSON form. If you take a shot at building something with this output, as always I&amp;rsquo;d love to hear about it - &lt;a href="mailto:craig.kerstiens@gmail.com"&gt;craig.kerstiens@gmail.com&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="despez"&gt;
&lt;div&gt;
Despez
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;Of course if you&amp;rsquo;re itch isn&amp;rsquo;t in better tools for Postgres, you may just want to have a solution that works today. While its not perfect, one of the best ones out there is &lt;a href="http://explain.depesz.com/"&gt;Dezpez&amp;rsquo;s explain tool&lt;/a&gt;. You can take any execution plan and paste it in and get some better visual representation of the result. You can also &lt;a href="http://explain.depesz.com/s/vL1"&gt;share them as well&lt;/a&gt;.&lt;/p&gt;</description><author>CRAIG KERSTIENS</author><pubDate>Thu, 13 Jun 2013 23:55:56 GMT</pubDate><guid isPermaLink="true">/2013/06/13/Explaining-your-PostgreSQL-data/</guid></item><item><title>Book Review: The Box</title><link>https://www.petekeen.net/book-review-the-box/</link><description>&lt;p&gt;
&lt;a class="thumbnail" href="http://www.amazon.com/gp/product/B003U2TR5O/" style="float: right; margin-left: 2em; margin-bottom: 1em;"&gt;&lt;img border="0" src="http://ecx.images-amazon.com/images/I/51%2BcvYn2lwL._SL160_.jpg" /&gt;&lt;/a&gt;
As a kid something about the &lt;a href="http://en.wikipedia.org/wiki/millennium_falcon"&gt;Millennium Falcon&lt;/a&gt; always bothered me. See, everybody knows that cargo travels around the world, and presumably around the galaxy, in containers. You know, the big steel boxes that you see on TV where murders and drug deals are always happening. If Han Solo's ship is supposedly some awesome super duper fast freighter, where do the containers go? I guess on some level the fact that Solo was a smuggler sort of registered, but still you'd think that the tea and stuff (that's what smugglers move according to the school history books) would just be hidden in the containers.
&lt;p&gt;After reading &lt;a href="http://www.amazon.com/gp/product/B003U2TR5O/"&gt;The Box&lt;/a&gt;) by Marc Levinson I'm relatively certain the Falcon would be considered a &lt;em&gt;breakbulk tramp freighter&lt;/em&gt;. &amp;quot;Breakbulk&amp;quot; meaning traditional non-containerized freight, &amp;quot;tramp&amp;quot; for the fact that Han and Chewie are tramps. Well, and they travel around without a fixed schedule or ports of call, picking up freight wherever and whenever they can. But mostly because they're tramps.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h2&gt;History Lessons&lt;/h2&gt;
&lt;p&gt;The Box presents a comprehensive history of the shipping container and of shipping in general, from the days of sailing ships, gallantly making their way from India to Britain for the tea trade, all the way to modern day when &lt;a href="http://en.wikipedia.org/wiki/Ebba_M%C3%A6rsk"&gt;huge container ships&lt;/a&gt; carrying over fifteen thousand twenty foot long standard containers ply the seas between Asia and Europe.&lt;/p&gt;
&lt;p&gt;One of the most interesting sections of the book for me was the exhaustive description of how, exactly, a breakbulk freighter is crammed to the gills with individual items of every imaginable description. It turns out that longshoreman (people who work the docks and load the ships) used to have to know a lot of details of how to pack a ship and what things can be crammed where in order to maximize the limited amount of storage capacity.&lt;/p&gt;
&lt;h2&gt;The Container&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Malcolm McLean’s fundamental insight, commonplace today but quite radical in the 1950s, was that the shipping industry’s business was moving cargo, not sailing ships.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The origin of the standardized international shipping container is almost, but not entirely, the story of a man named &lt;a href="http://en.wikipedia.org/wiki/Malcolm_McLean"&gt;Malcolm McLean&lt;/a&gt;. Before McLean containers had been tried on and off for decades, both in the United States and in Europe, on trucks and rails and ships. There had never been dedicated through-service, though, where a container could be packed at the shipper's factory and arrive at the destination without ever being opened.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A 25-ton container of coffeemakers can leave a factory in Malaysia, be loaded aboard a ship, and cover the 9,000 miles to Los Angeles in 16 days. A day later, the container is on a unit train to Chicago, where it is transferred immediately to a truck headed for Cincinnati. The 11,000-mile trip from the factory gate to the Ohio warehouse can take as little as 22 days, a rate of 500 miles per day, at a cost lower than that of a single first-class air ticket. More than likely, no one has touched the contents, or even opened the container, along the way&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The road to the standard &lt;a href="http://en.wikipedia.org/wiki/Intermodal_container"&gt;intermodal container&lt;/a&gt; took quite a few twists and turns, however. Every shipper had their own standard at first, none of them compatible with each other. Eventually a committee convened by the ISO decided on dimensions and shippers slowly adopted them. The advent of the giant containerships in the 70's and 80's spelled the end of the non-standard container sizes. Originally there were two sizes, both 8 feet 6 inches high and 8 feet wide, one being 20 feet long and the other 40 feet long. Eventually a few more sizes were added, but the core standards were still the same. The key innovation that standardization added was the method by which containers are connected together to form stacks on ships, the &lt;a href="http://en.wikipedia.org/wiki/Twistlock"&gt;twistlock&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;One thing I was astounded by was just how fast the container changed the shipping industry. Over the course of fifty years, containerization eliminated thousands of jobs worldwide, eliminated entire ports while creating new ones where there was just a tiny dock. It opened up entire countries to new forms of trade and ruined the economies of others. &lt;em&gt;&lt;a href="http://www.amazon.com/gp/product/B003U2TR5O/"&gt;The Box&lt;/a&gt;&lt;/em&gt; tells this whole compelling story, really a story of unintended consequences. Kind of like how Han Solo went from being a tramp to a general in a galaxy-spanning republic.&lt;/p&gt;</description><author>Pete Keen</author><pubDate>Sun, 09 Jun 2013 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.petekeen.net/book-review-the-box/</guid></item><item><title>Build a Better Warrior in Rift</title><link>https://benovermyer.com/blog/2013/06/build-a-better-warrior-in-rift/</link><description>&lt;p&gt;I’ve been playing Rift a lot lately, and the more I play, the better I understand the ins and outs of being a Warrior. This post has a few of my thoughts on the subject of tuning warrior builds. The class is really versatile, but focuses heavily on two aspects - tanking and dealing damage. That much is obvious. What is less obvious is that there are several ways to do each of these. My personal preference is for dealing respectable amounts of damage to multiple enemies while being able to take a lot of punishment before going down. My favorite ability for staying in the fight is &lt;strong&gt;No Permission to Die&lt;/strong&gt;. It’s in the fifth tier of the Warlord soul, and it can be a godsend. If you die within ten seconds of triggering it, you are resurrected at 50% of your maximum health. Properly timed, it can really save your bacon. Also, it’s not on global cooldown, so it’s really hard to NOT time it right if you have a good sense of your combat situation. My next favorite ability is &lt;strong&gt;Cornered Beast&lt;/strong&gt;, which is the 32 point unlock for the Champion soul. It hits up to 6 enemies for a ton of damage over time AND generates Attack Points over time, making it a powerful threat-building ability. Awesome for tanks, and for area effect damage. Along with Warlord and Champion, picking the Reaver soul allows for a ridiculous amount of survivability. It has a number of abilities that boost armor, endurance, and resistances. A balanced mix of the three souls, like the &lt;a href="http://rift.magelo.com/en/stc/zzkBLlckIkrhz00zvl0V0ocMzzvlz0Vhzo" rel="external"&gt;build linked here (Champion (32) / Warlord (23) / Reaver (21))&lt;/a&gt;, will give you a decent AoE tanking playstyle. You won’t win any DPS races, but it will keep you in the fight for a good long time.&lt;/p&gt;</description><author>Ben Overmyer's Site</author><pubDate>Sat, 08 Jun 2013 03:00:00 GMT</pubDate><guid isPermaLink="true">https://benovermyer.com/blog/2013/06/build-a-better-warrior-in-rift/</guid></item><item><title>How and why I'm not running my own DNS</title><link>https://www.petekeen.net/how-and-why-im-not-running-my-own-dns/</link><description>&lt;p&gt;A few months ago I posted about &lt;a href="/how-i-run-my-own-dns"&gt;how I run my own DNS servers&lt;/a&gt; using my virtual private servers and tinydns. Well, it turns out that's not a great idea, for a few reasons. First, because if I mess up I'm entirely shut out of my servers. I tried to turn off a service on them the other day and accidentally turned of the tinydns service instead and it took me ages to get back in. Running DNS on the same machines that handle email and web hosting for almost every piece of my online presence is just way too fragile.&lt;/p&gt;
&lt;!--more--&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;a href="https://gumroad.com/l/MbhM"&gt;Buy the entire collection of DNS articles as a nicely formatted ebook.&lt;/a&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Second, and really more importantly, this happened:&lt;/p&gt;
&lt;img alt="Papertrail has a lot of logs" class="thumbnail" src="https://d2s7foagexgnc2.cloudfront.net/files/35633efcdeb80dd81714/Screen%20Shot%202013-06-07%20at%204.41.49%20PM.png" /&gt;
&lt;p&gt;At some point last week I started seeing a very large number of queries for various domains coming into one of my servers. There were thousands of them a minute, supposedly coming from just a handful of machines. I couldn't figure this out, and the only solution I could reasonably come up with was to turn off logging. That's totally unacceptable, and if I couldn't figure this out I probably shouldn't be running DNS for myself after all. After consulting with a few very helpful people from the Internet we determined that my server was being used as part of a reflection attack originating at a known bad actor used for various command and control botnet nastiness. It was time to switch as soon as possible. Thankfully I had the TTL for my NS records set very low, otherwise this would have been a much more painful process.&lt;/p&gt;
&lt;h2&gt;Route 53&lt;/h2&gt;
&lt;p&gt;Before I decided to go the route of self-hosting I had investigated a few different providers. The most promising of these was Amazon's &lt;a href="http://aws.amazon.com/route53/"&gt;Route53&lt;/a&gt;, with the small snag that there was no easy out of the box solution to dynamic DNS for my home machine. When it came time to switch I figured that a solution would present itself and charged forward with manually switching a few domains over.&lt;/p&gt;
&lt;p&gt;At some point after moving my most important domains and turning the DNS service off for good on my VPSs I came up with this little script:&lt;/p&gt;
&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="ch"&gt;#!/usr/bin/python&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;ZONE_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ZXXXXXXXX"&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;DOMAIN_NAME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"dynamichost.example.com."&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;boto.route53.connection&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Route53Connection&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;boto.route53.record&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ResourceRecordSets&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;requests&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;sys&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;ip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"http://ip.example.com"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;conn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Route53Connection&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_all_rrsets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ZONE_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'A'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DOMAIN_NAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;maxitems&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;old_ip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resource_records&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ip&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;old_ip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;changes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ResourceRecordSets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ZONE_ID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;delete_record&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;changes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_change&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"DELETE"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DOMAIN_NAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"A"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;delete_record&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;old_ip&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;create_record&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;changes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_change&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"CREATE"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DOMAIN_NAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"A"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;create_record&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;changes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This runs every minute on a VM running on the Mac mini in my living room. It uses &lt;a href="http://docs.python-requests.org/en/latest/"&gt;requests&lt;/a&gt; to get my external API from a tiny webservice running on one of my servers, and if it's changed from what Route53 thinks it is, it uses &lt;a href="http://boto.readthedocs.org/en/latest/"&gt;boto&lt;/a&gt; to delete and recreate the A record. There's a bunch of public services out there that provide your external IP but I wanted to run my own. Of course.&lt;/p&gt;
&lt;p&gt;Here's the code for the webservice:&lt;/p&gt;
&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="nb"&gt;lambda&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"Content-Type"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"text/plain"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="no"&gt;Rack&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ip&lt;/span&gt;&lt;span class="o"&gt;]]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I think I learned a valuable lesson about limits with this whole &amp;quot;host everything all the time&amp;quot; exercise. Namely, that DNS is best left to the professionals, just like outgoing email. Route53 isn't the cheapest provider around but they promise 100% uptime and have a very nice easy to work with API. I very much recommend them.&lt;/p&gt;</description><author>Pete Keen</author><pubDate>Fri, 07 Jun 2013 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.petekeen.net/how-and-why-im-not-running-my-own-dns/</guid></item><item><title>Cancer</title><link>https://www.petekeen.net/cancer/</link><description>&lt;p&gt;&lt;em&gt;&amp;quot;Thank you for calling the Bergs and the Keens. We're not home right now...&amp;quot;&lt;/em&gt;. Mom hasn't changed the outgoing voicemail message yet. John's voice is still there, encouraging anyone who wants to leave a message. It's the last vestige of him, other than what we hold in our memories. The last bit of him that's still real, and even that's just an echo that will fade with time.&lt;/p&gt;
&lt;!--more--&gt;
&lt;p&gt;&lt;small&gt;&lt;i&gt;Note: this is a deeply personal entry. If you're here for the programming stuff you may want to skip it.&lt;/i&gt;&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;&lt;small&gt;&lt;i&gt;&lt;a href="#epilogue"&gt;Update August 29th 2013&lt;/a&gt;&lt;/i&gt;&lt;/small&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;em&gt;February 2006&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;A few weeks prior my stepdad John almost ran his pickup truck into the side of a semi-truck on the highway. That's how these things get noticed, you see. The months, years, of waking up at 5am and not being able to sleep because of the pounding headaches just become part of the routine, but as soon as something catastrophic happens, that's when people sit up and take notice. I must have been at school when Mom called and told me that John had to have some tests done because they think he had brain tumor. After reviewing the MRI his brain surgeon announced that John had a large tumor, almost the size of a golf ball, embedded in his cerebrum. He was to get have it removed as soon as possible.&lt;/p&gt;
&lt;p&gt;My sisters and I sat in the uncomfortable waiting area for 12 hours while his surgeon opened up John's skull and removed the tumor. We came back every day for two weeks while he recovered, first in a normal recovery room and then in the ICU when things turned sour for awhile. The first clue was when the nursed asked him if he knew where he was and he said &amp;quot;I'm going to be skating soon&amp;quot;. The winter Olympics were going on in Italy that month and my mom and him were avid fans.&lt;/p&gt;
&lt;p&gt;Eventually, with time, he got better. The MRI scans became less frequent. They talked about having to go to Madison for the day less and less. Things went back to normal. The tumor they pulled out of his head was benign. He never quite talked the same after that, and his temper was maybe a little bit hotter than it used to be, but by and large he was back to the John we knew and loved.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;February 2012&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I was standing in the living room when my phone rang. It was Mom, calling me for our weekly Sunday chat. I could tell she was a little upset, so I asked her what was going on, and she says &amp;quot;Well, John's in the hospital. We went to the ER last night because of some back pain he's been having for a few days and they did a CT scan and found cancer in his kidney. He's having surgery tomorrow. How was your week?&amp;quot;. She was clearly in shock. I don't really remember my reaction, but I know I was crying and couldn't talk and had to yell at her to stop talking for a second.&lt;/p&gt;
&lt;p&gt;My sister was on a plane that afternoon from Pennsylvania and I was on the next available flight out of Portland on Monday morning. I landed in Milwaukee and drove straight to the hospital, where I found Mom and my sisters waiting in the waiting area. The surgery went fine and he was in recovery. They took out one of his kidneys but they weren't sure what was going on. They knew, or at least they had a pretty good idea, but they didn't want to say without running a bunch of tests. Again, we were at the hospital every day, sitting with him, willing him to get better. After a few days we finally got a diagnosis: stage IV metastatic renal carcinoma in his kidneys and lungs. As soon as he was healed from surgery he'd be starting chemo.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Summer 2012&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;John and Mom came to visit my then-girlfriend Emily and me for a few days. John was in between chemo drugs, since the first one stopped working eventually. We went up to Snoqualmie Falls for the afternoon, took some great photos, and had generally a pretty good weekend. They were supposed to take the train back from Seattle to Chicago, but John wasn't really feeling well enough to go that long so they ended up flying back.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;October 31st 2012&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Emily had been looking forward to handing out Halloween candy for about a week. It's a thing that they used to do in her family and she's pretty sentimental about these kind of things, so she roped me into it. She picked up two huge bags of mixed goodies a few days prior, and then at the appointed hour on the appointed day we took the camp chairs down to the front porch and set up, waiting for kids to show up.&lt;/p&gt;
&lt;p&gt;An hour passed. None did. Not a single one.&lt;/p&gt;
&lt;p&gt;Emily's phone rang. It was her aunt, which was pretty weird. Her aunt doesn't normally call, she just sends care packages in the mail filled with candy and bottles of BBQ sauce. Her aunt tells her that her mom is in the hospital, about to undergo emergency surgery to remove part of her colon. She's had a blockage for awhile and the doctors decided that it had to come out, right then and there. They think it's cancer.&lt;/p&gt;
&lt;p&gt;Emily was on a plane the very next day and stayed for more than a week, one of the longest times we've been apart since we started going out in 2009. We talked on the phone every night. She came back but without any news about what kind of cancer her mom has. Eventually we find out it's stage IV metastatic colon cancer.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;November 28th 2012&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;While Emily was visiting family I had started to develop some pain in my groin. I attributed it to over-use while she was away, but we were pretty concerned nonetheless. Emily made me promise that if the pain didn't go away, that I was to see a doctor and get it checked out. Well, the pain went away for a few days and I forgot about it, but then came back with a vengeance. I made an appointment the next morning to see a GP, which happened a few days later. He looked around, felt around, it was pretty embarrassing for both of us I think. He said &amp;quot;I'm pretty sure it's one of two things, neither of which is cancer, but let's get an ultrasound just to make sure.&amp;quot; The next day I was in the ultrasound lab, and then I was back at work and trying not to freak out.&lt;/p&gt;
&lt;p&gt;The next day I got a call from my doctor. He said that he put in a call to the urology department and they would put their best surgical oncologist on the job and that I would be taken care of. A few days later I finally got an appointment and went in. About two weeks after that I was in surgery. Mom flew out to help Emily take care of me while I recovered and stayed about a week. That's the longest she could stand to leave John, who was steadily getting worse.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;December 26th 2012&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;My first day of chemo was the day after Christmas. About a week prior my diagnosis came back: stage IIa metastatic testicular germ cell cancer. Merry Christmas, everybody! Emily and I went in to the clinic at 8am to get familiar with the setup and for me to get my first treatment. It was... uneventful? I guess I expected it to be kind of dramatic, but really it was no big deal. My chemo schedule was one week of 5 days straight followed by two weeks of one day, repeated three times. Nine weeks. The first we were ok but by week seven I was a slug, barely able to make it into the treatment chair before falling immediately asleep. I got nauseous just getting in the car to go to the clinic. I'm proud to say I didn't throw up once the entire time. I didn't actually go the whole nine weeks. Toward the end my platelet count was too low to get treatment but my medical oncologist was adamant that skipping the last two treatments was no big deal. In March my doctors pronounced me tentatively cured.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;April 19th 2013&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Mom called while I was out to lunch with coworkers. She said that John was rapidly getting worse. I already had bought a ticket for May and she told me not to change it. Yet. The next day at noon she called again and said that I had to get home as quickly as I could. I changed my ticket, threw clothes in my backpack, and Emily and I were on the road to the airport in 10 minutes so I could catch the last flight of the day to Wisconsin. I didn't speed from Milwaukee to Milton, since it was dark and rainy, but I didn't dawdle either. When I got there Mom and my sisters were all there in the living room with John, watching movies and taking turns holding his hand while he lay in his hospice bed, breathing slowly. At 8am the next morning he passed with all of us around him. He didn't give up, right to the end. Mom said he had been so happy that I was better.&lt;/p&gt;
&lt;p&gt;The funeral was that Friday. We spent the week gathering photos and figuring out the arrangements. I'm told it was a nice service, but I don't really remember much of it. Emily was able to be there for a few days which was helpful. John's ashes came back to the house that Wednesday. I won't tell you what him and Mom worked out for when she eventually passes, but I will say it's simultaneously the most romantic and the saddest thing I've ever heard.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;June 4th 2013&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I had my three-month follow up appointment. My blood markers were completely clear and the doctor said that my chest X-ray was so perfect she could put the scan in a medical textbook. I'm cured. Modern medicine works. Hallelujah.&lt;/p&gt;
&lt;p&gt;At this point we don't know what's going to happen with Emily's mom but we're hoping for the best. Emily and I are getting married next year and hopefully everyone will be there, but life might get in the way. I guess we'll find out.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;A very good friend of mine asked me on more than one occasion &amp;quot;Have you ever really thought about it?&amp;quot; &amp;quot;It&amp;quot; being death. At the time I didn't really have an answer for him. My mind was generally on other things back then. Girls, computers, cars, moving halfway across the country, etc.&lt;/p&gt;
&lt;p&gt;Now, though, I can confidently say that my own mortality has crossed my mind once or twice. If there's a lesson to be drawn from my story it's this: live life how you want for as long as you can, because it may be shorter than you're expecting.&lt;/p&gt;
&lt;hr /&gt;
&lt;h4 id="epilogue"&gt;Epilogue&lt;/h4&gt;
&lt;p&gt;Emily's mom passed away peacefully in her sleep on August 24th, exactly one week after Emily and I got married in an intimate family-only ceremony.&lt;/p&gt;</description><author>Pete Keen</author><pubDate>Thu, 06 Jun 2013 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.petekeen.net/cancer/</guid></item><item><title>Blog Relocation</title><link>https://www.petekeen.net/blog-relocation/</link><description>&lt;p&gt;After a lot of thought and deliberation I've decided to retire &lt;code&gt;bugsplat.info&lt;/code&gt; as my blog address. It's served me well for about six years, but the word &amp;quot;bugsplat&amp;quot; has recently gained some &lt;a href="https://support.leagueoflegends.com/entries/20739888-Bug-Splat-and-Game-Crashes"&gt;other&lt;/a&gt; &lt;a href="https://www.lush.co.uk/product/6328/Bugsplat"&gt;unrelated&lt;/a&gt; &lt;a href="http://www.cbc.ca/news/world/story/2013/02/07/f-drone-lexicon.html"&gt;connotations&lt;/a&gt;, the latter being the most unsavory. The other big reason is that at this point I would like people to associate my work with my actual name, not some other name that you would only know was related if you knew me already.&lt;/p&gt;
&lt;p&gt;That said, any &lt;code&gt;@bugsplat.info&lt;/code&gt; email addresses you have will continue to work. Any links to &lt;code&gt;bugsplat.info&lt;/code&gt; will also continue to work through the magic of HTTP &lt;code&gt;301 Moved Permanently&lt;/code&gt;. Read on to see how I set that up, because it's kind of interesting.&lt;/p&gt;
&lt;!--more--&gt;
&lt;p&gt;HTTP has a whole bunch of different status codes. Pretty much everyone knows about &lt;code&gt;404 Not Found&lt;/code&gt;, of course. Other important codes are &lt;code&gt;200 Ok&lt;/code&gt;, which is what servers respond to requests they can handle along with the content at that address. &lt;code&gt;401 Authorization Required&lt;/code&gt; is another interesting one. That's what triggers a browser login box that you might see from time to time.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;301 Moved Permanently&lt;/code&gt; (and its little brother &lt;code&gt;302 Moved Temporarily&lt;/code&gt;) are used to tell your browser &amp;quot;hey, the content that you're looking for is over at this other location&amp;quot;. Google also uses these redirections when generating search results, which is why they're so important to get right.&lt;/p&gt;
&lt;p&gt;Along with changing the domain of this I wanted to change the URL format of blog posts. I'm pretty tired of having the date in the URL itself. It's long and redundant and kind of ugly. Some &lt;a href="https://github.com/peterkeen/bugsplat.rb/commit/9867cffa7d996f360acd82b28a8f1e5cf620f5b0"&gt;small changes&lt;/a&gt; to the application code took care of generating those URLs, but what to do about the redirects? I installed a new app at &lt;code&gt;bugsplat.info&lt;/code&gt; running this code:&lt;/p&gt;
&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'rack/rewrite'&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Rack&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Rewrite&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;r301&lt;/span&gt; &lt;span class="sr"&gt;%r{^/payment-integration.html}&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"http://www.petekeen.net/mastering-modern-payments"&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;r301&lt;/span&gt; &lt;span class="sr"&gt;%r{^/\d{4}-\d{2}-\d{2}-(.*)$}&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"http://www.petekeen.net/$1"&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;r301&lt;/span&gt; &lt;span class="sr"&gt;%r{^(.*).html$}&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"http://www.petekeen.net$1"&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;r301&lt;/span&gt; &lt;span class="sr"&gt;%r{^(.*)$}&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"http://www.petekeen.net$1"&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="nb"&gt;lambda&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"Content-Type"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"text/plain"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Hello. The time is &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This app uses &lt;a href="https://github.com/jtrupiano/rack-rewrite"&gt;rack-rewrite&lt;/a&gt;, a small Rack middleware that emulates Apache's &lt;code&gt;mod_rewrite&lt;/code&gt; but using a Ruby DSL. There's four specific rewrite instructions here. The first one rewrites the URL for my &lt;a href="/mastering-modern-payments"&gt;guide to using Stripe with Rails&lt;/a&gt; to a URL matching its actual title, which should help for SEO purposes. The next one sends old blog posts with the date and with &lt;code&gt;.html&lt;/code&gt; at the end to the new site, sans date and suffix. The last two rules are generic catch-alls. Oh, and that little lambda thing is just there so the middleware has something to attach to, it's never actually called.&lt;/p&gt;
&lt;p&gt;I think I caught all of the links on the site itself, but if you notice any weirdness with this new setup, please &lt;a href="mailto:pete@petekeen.net"&gt;let me know&lt;/a&gt;.&lt;/p&gt;</description><author>Pete Keen</author><pubDate>Sun, 02 Jun 2013 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.petekeen.net/blog-relocation/</guid></item><item><title>Postgres Indexing - A collection of indexing tips</title><link>/2013/05/30/Postgres-Indexing-A-collection-of-indexing-tips/</link><description>&lt;p&gt;Even from intial reviews of my previous post on expression based indexes I received a lot of questions and feedback around many different parts of indexing in Postgres. Here&amp;rsquo;s a mixed collection of valuable tips and guides around much of that.&lt;/p&gt;
&lt;h3 id="unused-indexes"&gt;
&lt;div&gt;
Unused Indexes
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;In an earlier tweet I joked about some SQL that would generate the SQL to add an index to every column:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# SELECT 'CREATE INDEX idx_'
|| table_name || '_'
|| column_name || ' ON '
|| table_name || ' (&amp;quot;'
|| column_name || '&amp;quot;);'
FROM information_schema.columns;
?column?
---------------------------------------------------------------------
CREATE INDEX idx_pg_proc_proname ON pg_proc (&amp;quot;proname&amp;quot;);
CREATE INDEX idx_pg_proc_pronamespace ON pg_proc (&amp;quot;pronamespace&amp;quot;);
CREATE INDEX idx_pg_proc_proowner ON pg_proc (&amp;quot;proowner&amp;quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;!-- raw HTML omitted --&gt;
&lt;p&gt;The reasoning behind this is guessing whether an index will be helpful can be a bit hard within Postgres. So the easy solution is to add indexes to everything, then just observe if they&amp;rsquo;re being used. &lt;em&gt;Of course you want to add it to all tables/columns because you never know if core of Postgres may be missing some needed ones&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;As included with the &lt;a href="https://github.com/heroku/heroku-pg-extras"&gt;pg-extras plugin for Heroku&lt;/a&gt; you can run a query to show you all unused indexes. On Heroku simply install the plugin the run &lt;code&gt;heroku pg:unused_indexes&lt;/code&gt; to show the size and number of times an index scan has been used. On a non Heroku Postgres database you can run:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# SELECT
schemaname || '.' || relname AS table,
indexrelname AS index,
pg_size_pretty(pg_relation_size(i.indexrelid)) AS index_size,
idx_scan as index_scans
FROM pg_stat_user_indexes ui
JOIN pg_index i ON ui.indexrelid = i.indexrelid
WHERE NOT indisunique AND idx_scan &amp;lt; 50 AND pg_relation_size(relid) &amp;gt; 5 * 8192
ORDER BY pg_relation_size(i.indexrelid) / nullif(idx_scan, 0) DESC NULLS FIRST,
pg_relation_size(i.indexrelid) DESC;
table | index | index_size | index_scans
---------------------+--------------------------------------------+------------+-------------
public.grade_levels | index_placement_attempts_on_grade_level_id | 97 MB | 0
public.observations | observations_attrs_grade_resources | 33 MB | 0
public.messages | user_resource_id_idx | 12 MB | 0
(3 rows)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="costs-of-indexing"&gt;
&lt;div&gt;
Costs of Indexing
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;There are really a couple of primary costs when it comes to indexing your data. The first is the overall size of the index. Indexes take size on disk, fortunately in most cases disk is pretty cheap. If you&amp;rsquo;re limited on disk size and not on your current performance then its pretty clear the trade-off you want to take. If you do need to get the size of your index you can do that by running:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# SELECT pg_size_pretty(pg_total_relation_size('idx_name'));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The harder trade off to look at is the cost in terms of throughput. As your data comes in there&amp;rsquo;s a cost for maintaining that index as the data within it has to be computed. If you&amp;rsquo;re doing crazy regex&amp;rsquo;s in your index then you can expect this to have an impact on your throughput.&lt;/p&gt;
&lt;h3 id="composite-indexes-vs-multiple-indiviual-indexes"&gt;
&lt;div&gt;
Composite Indexes vs. Multiple Indiviual Indexes
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;A composite index is an index that includes multiple columns. Given an example table of purchases:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# \d purchases
Table &amp;quot;public.purchases&amp;quot;
Column | Type | Modifiers
-------------+-----------------------------+-----------
id | integer | not null
item | integer |
quantity | integer |
color | integer |
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You might want to add an index on item and quantity together. You can do this with:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CREATE INDEX idx_purchases_item_quantity_color ON purchases (item, quantity, color)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From now on if you included item and quantity in a query its likely it would use this index just as it would if you used item, quantity and color. If you have a large varied set of data within each of these such an index can prove very useful. The caveat is that if you&amp;rsquo;re querying against only quantity and color then this index is useless, it &lt;strong&gt;must&lt;/strong&gt; include the item column.&lt;/p&gt;
&lt;p&gt;In contrast if you have three individual indexes Postgres may combine these or simply use one that would be the most efficient out of the three.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CREATE INDEX idx_purchases_item ON purchases (item);
CREATE INDEX idx_purchases_quantity ON purchases (quantity);
CREATE INDEX idx_purchases_color ON purchases (color);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Of course in this case if you query any individual column it would use the index if appropriate.&lt;/p&gt;
&lt;h3 id="what-else"&gt;
&lt;div&gt;
What Else
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;What else do you want to know about Postgres Indexing? Drop me a line &lt;a href="mailto:craig.kerstiens@gmail.com"&gt;craig.kerstiens at gmail.com&lt;/a&gt; or hop over to &lt;a href="http://www.postgresguide.com"&gt;Postgres Guide&lt;/a&gt; and &lt;a href="http://postgresguide.com/performance/indexes.html"&gt;read a little there&lt;/a&gt; or even contribute some articles of your own.&lt;/p&gt;</description><author>CRAIG KERSTIENS</author><pubDate>Thu, 30 May 2013 23:55:56 GMT</pubDate><guid isPermaLink="true">/2013/05/30/Postgres-Indexing-A-collection-of-indexing-tips/</guid></item><item><title>Design for Failure: Processing Payments with a Background Worker</title><link>https://www.petekeen.net/design-for-failure-processing-payments-with-a-background-worker/</link><description>&lt;p&gt;Processing payments correctly is hard. This is one of the biggest lessons I've learned while writing my various &lt;a href="/projects.html"&gt;SaaS projects&lt;/a&gt;. Stripe does everything they can to make it easy, with &lt;a href="https://stripe.com/docs/tutorials/checkout"&gt;quick start guides&lt;/a&gt; and &lt;a href="https://stripe.com/docs/api"&gt;great documentation&lt;/a&gt;. One thing they really don't cover in the docs is what to do if your connection with their API fails for some reason. Processing payments inside a web request is asking for trouble, and the solution is to run them using a background job.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h3&gt;The Problem&lt;/h3&gt;
&lt;p&gt;Let's take Stripe's example code:&lt;/p&gt;
&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="no"&gt;Stripe&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'STRIPE_API_KEY'&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Get the credit card details submitted by the form&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:stripeToken&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Create the charge on Stripe's servers - this will charge the user's card&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;begin&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;charge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Stripe&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Charge&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="ss"&gt;:amount&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# amount in cents, again&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="ss"&gt;:currency&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"usd"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="ss"&gt;:card&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="ss"&gt;:description&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"payinguser@example.com"&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;Stripe&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;CardError&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="c1"&gt;# The card has been declined&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Pretty straight-forward. Using the &lt;code&gt;stripeToken&lt;/code&gt; that &lt;code&gt;stripe.js&lt;/code&gt; inserted into your form, create a charge object. If this fails due to a &lt;code&gt;CardError&lt;/code&gt;, you can safely assume that the customer's card got declined. Behind the scenes, &lt;code&gt;Stripe::Charge&lt;/code&gt; makes an &lt;code&gt;https&lt;/code&gt; call to Stripe's API. Typically, this completes almost immediately.&lt;/p&gt;
&lt;p&gt;But what if it doesn't? The internet between your server and Stripe's could be slow or down. DNS resolution could be failing. There's a million reasons why this code could take awhile. Browsers typically have around a one minute timeout and application servers like Unicorn usually will kill the request after 30 seconds. That's a long time to keep the user waiting just to end up at an error page.&lt;/p&gt;
&lt;h3&gt;The Solution&lt;/h3&gt;
&lt;p&gt;The solution is to put the call to &lt;code&gt;Stripe::Charge.create&lt;/code&gt; in a background job. This example is going to use a very simple background worker system named &lt;a href="https://github.com/brandonhilkert/sucker_punch"&gt;Sucker Punch&lt;/a&gt;. It runs in the same process as your web request but uses &lt;a href="https://github.com/celluloid/celluloid/"&gt;Celluloid&lt;/a&gt; to do things in a background thread.&lt;/p&gt;
&lt;p&gt;First, let's create a job class:&lt;/p&gt;
&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StripeCharger&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;SuckerPunch&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Worker&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&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;with_connection&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;  &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:token&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="n"&gt;txn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:transaction_id&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="k"&gt;begin&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="n"&gt;charge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Stripe&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Charge&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="ss"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;txn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="ss"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"usd"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="ss"&gt;card&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="ss"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;txn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="n"&gt;txn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'complete'&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="n"&gt;txn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stripe_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;charge&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="n"&gt;txn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save!&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;Stripe&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Error&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="n"&gt;txn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'failed'&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="n"&gt;txn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json_body&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="n"&gt;txn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save!&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Again, pretty straightforward. Sucker Punch will create an instance of your job class and call &lt;code&gt;#perform&lt;/code&gt; on it with a hash of values that you pass in to the queue, which we'll get to in a second. We look up a &lt;code&gt;Transaction&lt;/code&gt; record, initiate the charge, and capture any errors that happen along the way.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Transaction&lt;/code&gt; in this case is a simple &lt;code&gt;ActiveRecord&lt;/code&gt; object with just a few attributes, just enough to capture what Stripe gives us:&lt;/p&gt;
&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Transaction&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;attr_accessible&lt;/span&gt; &lt;span class="ss"&gt;:stripe_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Sucker Punch needs to know about our job class, so let's tell it in an initializer:&lt;/p&gt;
&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="no"&gt;SuckerPunch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;queue&lt;/span&gt; &lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="ss"&gt;:payments_queue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;StripeCharger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;workers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now for the controller that ties it all together:&lt;/p&gt;
&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TransactionsController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;txn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="ss"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="ss"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:email&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="ss"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'pending'&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;txn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="no"&gt;SuckerPunch&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Queue&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:payments_queue&lt;/span&gt;&lt;span class="o"&gt;].&lt;/span&gt;&lt;span class="n"&gt;async&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="ss"&gt;transaction_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;txn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="ss"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:stripeToken&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;json&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;txn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_json&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;json&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;txn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error_messages&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="ss"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;422&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;show&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;txn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="no"&gt;ActionController&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;RoutingError&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'not found'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;txn&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;json&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;txn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_json&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The &lt;code&gt;create&lt;/code&gt; method creates a new &lt;code&gt;Transaction&lt;/code&gt; record, setting its state to &lt;code&gt;pending&lt;/code&gt;. It then queues the transaction to be processed by &lt;code&gt;StripeCharger&lt;/code&gt;. The &lt;code&gt;show&lt;/code&gt; method simply looks up the transaction and spits back some JSON. On your customer-facing page you'd do something like this:&lt;/p&gt;
&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;doPoll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/transactions/'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s2"&gt;"complete"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'/thankyou'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;elsif&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s2"&gt;"failed"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="nx"&gt;handleFailure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          &lt;span class="nx"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(){&lt;/span&gt; &lt;span class="nx"&gt;doPoll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Your page will poll &lt;code&gt;/transactions/&amp;lt;id&amp;gt;&lt;/code&gt; until the transaction ends in either success or failure. You'd probably want to show a spinner or something to the user while this is happening.&lt;/p&gt;
&lt;p&gt;With this setup, you've insulated yourself from problems in your connection to Stripe, your connection to your customer, and everything in between.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;This is an excerpt from my guide &lt;a href="/mastering-modern-payments"&gt;Mastering Modern Payments: Using Stripe with Rails&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;</description><author>Pete Keen</author><pubDate>Thu, 30 May 2013 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.petekeen.net/design-for-failure-processing-payments-with-a-background-worker/</guid></item><item><title>Postgres Indexes – Expression/Functional Indexing</title><link>/2013/05/29/Postgres-Indexes-Expression/Functional-Indexing/</link><description>&lt;p&gt;Postgres is rich with options for indexing. First you&amp;rsquo;ve got a variety of types, and beyond that you can do a variety of things with each of these such as create unique indexes, use conditions to index only a portion of your data, or create indexes based on complex expressions or functions. In cases where you commonly use various PostgreSQL functions in your application or reporting you can get some great gains from this.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s take a look at a really simple case. Given a basic user table:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# \dt users
Table &amp;quot;public.users&amp;quot;
Column | Type | Modifiers
------------+-----------------------------+-----------
id | integer | not null
email | character varying(255) |
created_at | timestamp without time zone |
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You may commonly want to run a report against it showing your signups by date. Let&amp;rsquo;s say you do this by running the query:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT
count(*),
date_trunc('day', created_at)
FROM
users
GROUP BY
2;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you&amp;rsquo;re commonly using &lt;code&gt;date_trunc('day', created_at)&lt;/code&gt; for grouping, filtering, or projecting it out you can get some great gains by creating an index on this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# CREATE INDEX idx_user_created ON users(date_trunc('day', created_at));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Of course you can go beyond the built in functions of Postgres and use more complicated functions you create yourself. For example if you have JSON stored within PostgreSQL, have PLV8 enabled, and want to create a Javascript function to parse and return the text for a given key:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# CREATE OR REPLACE FUNCTION
get_text(key text, data json)
RETURNS text $$
return data[key];
$$ LANGUAGE plv8 IMMUTABLE STRICT;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Of note in the above function is &lt;code&gt;IMMUTABLE&lt;/code&gt; and &lt;code&gt;STRICT&lt;/code&gt;. Immutable specifies that the function given the same inputs will return the same result. Strict means that if you send in &lt;code&gt;NULL&lt;/code&gt; values you&amp;rsquo;ll get a null result.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Given some example data inside your JSON field:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
&amp;quot;name&amp;quot;: &amp;quot;Craig Kerstiens&amp;quot;,
&amp;quot;location&amp;quot;: &amp;quot;San Francisco&amp;quot;,
&amp;quot;numbers&amp;quot;: [
{
&amp;quot;type&amp;quot;: &amp;quot;work&amp;quot;,
&amp;quot;number&amp;quot;: &amp;quot;123.456.7890&amp;quot;
},
{
&amp;quot;type&amp;quot;: &amp;quot;home&amp;quot;,
&amp;quot;number&amp;quot;: &amp;quot;987.654.3210&amp;quot;
}
]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you wanted to return just the name you could index on:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# CREATE INDEX idx_name ON users(get_text('name', json_data));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or even combine with built ins for a case-insensitive version:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# CREATE INDEX idx_name ON users(lower(get_text('name', json_data)));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Indexes like all of the above can be useful when you&amp;rsquo;re filtering on something that postgres can take advantage of. In most cases any conditions with the exception of a &lt;code&gt;LIKE&lt;/code&gt; beginning with a &lt;code&gt;%&lt;/code&gt; work for this. With Postgres 9.2 even a count(*) in certain cases can take advantage of the index because of index only scans.&lt;/p&gt;
&lt;p&gt;Whether you&amp;rsquo;re looking to take advantage of all the power of Javascript with JSON or another procedural langauge – or simply speed up a basic report using built in functions expression indexes can give you some great benefits.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;If you&amp;rsquo;re looking for a deeper resource on Postgres I recommend the book &lt;a href="https://theartofpostgresql.com/?affiliate=cek"&gt;The Art of PostgreSQL&lt;/a&gt;. It is by a personal friend that has aimed to create the definitive guide to Postgres, from a developer perspective. If you use code CRAIG15 you&amp;rsquo;ll receive 15% off as well.&lt;/em&gt;&lt;/p&gt;</description><author>CRAIG KERSTIENS</author><pubDate>Wed, 29 May 2013 23:55:56 GMT</pubDate><guid isPermaLink="true">/2013/05/29/Postgres-Indexes-Expression/Functional-Indexing/</guid></item><item><title>My SQL Bad Habits</title><link>/2013/05/26/My-SQL-Bad-Habits/</link><description>&lt;p&gt;I&amp;rsquo;m reasonably proficient at SQL – &lt;em&gt;a coworker when pseudocoding some logic for him pointed out that my pseudocode is what he thought was executable SQL&lt;/em&gt;. I&amp;rsquo;m fully capable of writing clear and readable SQL – which most SQL is not. Despite that I still have several bad habits when it comes to SQL. Without further adieu heres some of my dirty laundry so hopefully others can not make the same mistakes.&lt;/p&gt;
&lt;h3 id="ordergroup-by-column-numbers"&gt;
&lt;div&gt;
Order/Group by Column Numbers
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;When quickly iterating on a query its a lot less typing to put the column number as the thing you want to order by. Here&amp;rsquo;s a quick lightweight example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT
email,
created_at
FROM
users
ORDER BY 2 DESC
LIMIT 5;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This gives me my last 5 users that have signed up for my site. Of course as soon as I have this I may want to add some data to it, like their first name so I can send them a welcome email. I quickly alter the query to:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT
email,
first_name,
created_at
FROM
users
ORDER BY 2 DESC
LIMIT 5;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And now I have 5 users that have signed up ordered by their first name. Sure its obvious when you have 1 column you&amp;rsquo;re ordering by, but when you have &lt;code&gt;GROUP BY 1, 2, 3, 4, 5, 6&lt;/code&gt; which is actually open in one of my tabs currently its a bit more confusing&amp;hellip;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Though if you really want to have some fun, share a query with someone that looks something like this:&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT
email as &amp;quot;3&amp;quot;,
first_name &amp;quot;2&amp;quot;,
created_at &amp;quot;1&amp;quot;
FROM
users
ORDER BY &amp;quot;1&amp;quot;, &amp;quot;3&amp;quot; DESC
LIMIT 5;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="implicit-joins"&gt;
&lt;div&gt;
Implicit Joins
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;I seldom use the syntax &lt;code&gt;INNER JOIN&lt;/code&gt;. Instead I simply put the two tables in my where clause and ensure I have a where condition. The problem with ensuring I have a where condition is sometimes I don&amp;rsquo;t, especially when you&amp;rsquo;re dealing with 3 tables.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT
email,
product.name,
product.price
FROM
users,
orders,
items
WHERE users.id = orders.user_id
AND orders.id = items.order_id
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Is less clear (especially when dealing with 5-6 tables) than the alternative:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT
email,
product.name,
product.price
FROM users
INNER JOIN orders on users.id = orders.user_id
INNER JOIN items on orders.id = items.order_id
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="lack-of-comments"&gt;
&lt;div&gt;
Lack of comments
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;I comment my SQL far less than I comment my code, yet it can be done just as easily. For example I have this in one of my queries:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT convert_from(CAST(E'\\x' || array_to_string(ARRAY(
SELECT
CASE
WHEN length(r.m[1]) = 1
THEN encode(convert_to(r.m[1], 'SQL_ASCII'), 'hex')
ELSE substring(r.m[1] from 2 for 2)
END
FROM regexp_matches(url_here, '%[0-9a-f][0-9a-f]|.', 'gi') AS r(m)
), '') AS bytea), 'UTF8');
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;While this has its own issues theres no documentation around what this actually does, in contrast:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;--- DECODES url ---
SELECT convert_from(CAST(E'\\x' || array_to_string(ARRAY(
SELECT
CASE
WHEN length(r.m[1]) = 1
THEN encode(convert_to(r.m[1], 'SQL_ASCII'), 'hex')
ELSE substring(r.m[1] from 2 for 2)
END
FROM regexp_matches(url_here, '%[0-9a-f][0-9a-f]|.', 'gi') AS r(m)
), '') AS bytea), 'UTF8');
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Comments also work well inline at the end of a line.&lt;/p&gt;
&lt;h3 id="large-manually-generated-lists"&gt;
&lt;div&gt;
Large Manually Generated Lists
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;A lot of times in working with some specific data set I&amp;rsquo;ll manually or automatically generate a list that I want to filter. A common example is filtering out staging/dev environments. I&amp;rsquo;ll often manually search and prune the list, then save that result for the queries I&amp;rsquo;m going to build going forward. This is a bit of effort but still feels reasonable the downside is it results in something like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT
foo
FROM
bar
WHERE
bar.id NOT IN (34723, 42735, 32321, 47205, 20375, 30261, 26194, 109371, 9313, 6351, 20184, 50273, 34735, 39854, 23954, 25323, 23405, 30528, 50182, 29340, 47659, ... and the list goes on)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;SQL is meant to be reasonable for containing some level of logic. Data changes, hard coding keys is going to bite you at some point, spend the extra effort and re-use something thats clear.&lt;/p&gt;
&lt;h3 id="what-else"&gt;
&lt;div&gt;
What else
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;I&amp;rsquo;m sure theres plenty more; I suspect within a few minutes of sitting down with someone they could point out some other bad habits. While I know mine at least some of mine I still often know the trade-off. What are yours? I&amp;rsquo;d love to hear to document them for others so hopefully they can prevent developing the same bad habits. Let me know; &lt;!-- raw HTML omitted --&gt;&lt;a href="mailto:craig.kerstiens@gmail.com"&gt;craig.kerstiens@gmail.com&lt;/a&gt;&lt;!-- raw HTML omitted --&gt;&lt;/p&gt;
&lt;!-- raw HTML omitted --&gt;
&lt;!-- raw HTML omitted --&gt;</description><author>CRAIG KERSTIENS</author><pubDate>Sun, 26 May 2013 23:55:56 GMT</pubDate><guid isPermaLink="true">/2013/05/26/My-SQL-Bad-Habits/</guid></item><item><title>Book Review: Tubes: a Journey to the Center of the Internet</title><link>https://www.petekeen.net/book-review-tubes-a-journey-to-the-center-of-the-internet/</link><description>&lt;p&gt;&lt;a class="thumbnail" href="http://www.amazon.com/gp/product/B006FOHWDI/" style="float: right;"&gt;&lt;img border="0" src="http://ecx.images-amazon.com/images/I/51AbQPCYSyL._SL160_.jpg" /&gt;&lt;/a&gt;&lt;a href="http://andrewblum.net"&gt;Andrew Blum&lt;/a&gt; took his Internet service completely for granted until one day a squirrel chewed through a cable in his neighborhood, cutting off his access and sending him around the world, attempting to answer the question: What exactly is the Internet? What is it made of? &lt;em&gt;Where&lt;/em&gt; is it, really? He documents his travels in the book &lt;em&gt;&lt;a href="http://www.amazon.com/gp/product/B006FOHWDI/ref=as_li_ss_il?ie=UTF8&amp;amp;camp=1789&amp;amp;creative=390957&amp;amp;creativeASIN=B006FOHWDI&amp;amp;linkCode=as2&amp;amp;tag=bugsplat04-20"&gt;Tubes: a Journey to the Center of the Internet&lt;/a&gt;&lt;/em&gt;.&lt;/p&gt;
&lt;!--more--&gt;
&lt;p&gt;&lt;a class="thumbnail" href="http://www.telegeography.com/telecom-maps/global-internet-map/"&gt;&lt;img alt="Map of the Internet 2012" src="https://d2s7foagexgnc2.cloudfront.net/files/cf1cd247ddaec4d656f3/wallpaper-1024.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Blum starts out describing a &lt;a href="http://www.telegeography.com/telecom-maps/global-internet-map/"&gt;map of the internet&lt;/a&gt; (note: not this other &lt;a href="http://xkcd.com/802/"&gt;map of the internet&lt;/a&gt;) made by a company named TeleGeography, which shows an incredibly intricate web of little lines connecting major cities around the globe. These lines are fiber optic cables, running over land and under oceans, hooking up with each other in giant data centers in places like Ashburn, VA and London, UK. Blum goes to these interconnect points and talks with the people who run them, the people who started them back in the early 1990s when the Internet was just getting going. The book concludes with a trip to Prineville, OR to see one of &lt;a href="https://www.facebook.com/prinevilleDataCenter"&gt;Facebook's newest data centers&lt;/a&gt;, an example of where all the data that traverses these fiber optic lines ends up.&lt;/p&gt;
&lt;p&gt;Prior to reading &lt;em&gt;Tubes&lt;/em&gt; I had a pretty good understanding of some of these topics. For example, &lt;a href="http://www.nealstephenson.com"&gt;Neil Stephenson&lt;/a&gt; (author of &lt;a href="http://www.amazon.com/gp/product/B0015DPXKI/"&gt;some&lt;/a&gt; &lt;a href="http://www.amazon.com/gp/product/B000FC11A6/"&gt;of my&lt;/a&gt; &lt;a href="http://www.amazon.com/gp/product/B000FBJCJE/"&gt;favorite&lt;/a&gt; &lt;a href="http://www.amazon.com/gp/product/B000FBJCKI/"&gt;books&lt;/a&gt;) wrote an &lt;a href="http://www.wired.com/wired/archive/4.12/ffglass_pr.html"&gt;amazing article&lt;/a&gt; for Wired Magazine in 1996 where he travels around the world following the building of &lt;a href="http://en.wikipedia.org/wiki/Fiber-Optic_Link_Around_the_Globe"&gt;FLAG&lt;/a&gt;, a state of the art (for the time) 26,000 kilometer undersea cable. &lt;em&gt;Tubes&lt;/em&gt; did have a lot to say about how the early days of the commercial internet came about, though, especially with regards to the early &lt;a href="http://en.wikipedia.org/wiki/Internet_exchange_point"&gt;Internet exchange points&lt;/a&gt; in Washington DC, San Jose, and London. Also, I had no idea that there was such a huge facility in Ashburn run by a company named Equinix. Blum goes into a lot of great detail about the history of these companies and the people that run them.&lt;/p&gt;
&lt;p&gt;Overall I think Blum does a good job communicating that the Internet really isn't a &lt;em&gt;thing&lt;/em&gt; so much as an &lt;em&gt;idea&lt;/em&gt;, very much made up of the people who design and build out the fiber optic links and the things they link together. The fiber is almost an afterthought, a means to an end. If you're at all interested in how the Internet came to penetrate our lives over the last two decades, you should check out &lt;em&gt;&lt;a href="http://www.amazon.com/gp/product/B006FOHWDI/"&gt;Tubes&lt;/a&gt;&lt;/em&gt;.&lt;/p&gt;</description><author>Pete Keen</author><pubDate>Sat, 25 May 2013 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.petekeen.net/book-review-tubes-a-journey-to-the-center-of-the-internet/</guid></item><item><title>New Blog Design</title><link>https://www.petekeen.net/new-blog-design/</link><description>&lt;p&gt;For the past three years this blog has has featured the same basic design. Header, sidebar, content, footer. Simple, classic, kind of ... homely. Today I decided to give a new design a whirl, and if you're reading this on the website you can see what's changed.&lt;/p&gt;
&lt;!--more--&gt;
&lt;img alt="Old Bugsplat Design" class="thumbnail" src="https://d2s7foagexgnc2.cloudfront.net/files/03847dfc1c2e1049b3c0/old_bugsplat.png" /&gt;
&lt;p&gt;Essentially I've removed the sidebar in favor of a clean header and footer. I've also condensed several pages onto a comprehensive &lt;a href="/about"&gt;About&lt;/a&gt; page. The colors are basically the same, although I've brightened up the background a bit. The biggest change by far has been switching from some random two-column css layout I found somewhere on the internet to a design based on &lt;a href="http://twitter.github.io/bootstrap"&gt;Twitter Bootstrap&lt;/a&gt;. I used a tool called &lt;a href="http://www.bootstrapthemeroller.com"&gt;Bootstrap ThemeRoller&lt;/a&gt; to customize the colors and fonts and then made a few remarkably small changes to the markup and here we are.&lt;/p&gt;
&lt;p&gt;See any weirdness or bugs, or want to offer some criticism, constructive or not? &lt;a href="mailto:pete@bugsplat.info"&gt;Email me&lt;/a&gt; or &lt;a href="https://twitter.com/zrail"&gt;Tweet at me&lt;/a&gt;.&lt;/p&gt;</description><author>Pete Keen</author><pubDate>Fri, 24 May 2013 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.petekeen.net/new-blog-design/</guid></item><item><title>Mounting a Magic Trackpad on a Kinesis Advantage Keyboard</title><link>https://www.petekeen.net/mounting-a-magic-trackpad-on-a-kinesis-advantage-keyboard/</link><description>&lt;p&gt;&lt;em&gt;&lt;strong&gt;Update:&lt;/strong&gt; The Magic Trackpad 2 fits in the same spot but you might have to use a different sticky material. See &lt;a href="https://twitter.com/floatingbones/status/662336996556296192"&gt;these&lt;/a&gt; &lt;a href="https://twitter.com/rfabbri/status/661537385939869696"&gt;tweets&lt;/a&gt; for details.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Way back in early 2009 my wrists started acting up. As a professional software developer only two years into my young career, this was super concerning. I asked around at the office and my coworkers told me I should get a &lt;a href="http://www.amazon.com/gp/product/B000LVJ9W8/"&gt;Kinesis Advantage Pro&lt;/a&gt; keyboard, which aside from being super goofy looking, seems to have cured a lot of people of their RSI symptoms. I looked into it and eventually broke down and bought one, and then another one because the first developed some sticky keys but I didn't want to part with it to send it back for service.&lt;/p&gt;
&lt;p&gt;Using a mouse with the Kinesis is a little bit tiring, though. It's a much larger keyboard than you might be used to, so the reach from keyboard to mouse is in kind of a weird direction and your mouse might be in a weird spot. I've been using an &lt;a href="http://www.amazon.com/gp/product/B003XIJ3MW"&gt;Apple Magic Trackpad&lt;/a&gt; for the past few years and after awhile I got sick of the reach and stuck it in the obvious place, the giant blank spot between the keywells of the Kinesis, like so:&lt;/p&gt;
&lt;img alt="Kinesis and Trackpad" class="thumbnail" src="https://d2s7foagexgnc2.cloudfront.net/files/49df782d46968ee719e7/kinesis_and_trackpad.jpg" /&gt;
&lt;!--more--&gt;
&lt;p&gt;It's not an exact fit, though, so I added a little shim made of two wrist rest pads stuck together:&lt;/p&gt;
&lt;img alt="Wrist rest pads" class="thumbnail" src="https://d2s7foagexgnc2.cloudfront.net/files/46b9eb719832f17db33d/old_solution.jpg" /&gt;
&lt;p&gt;This worked alright but as time went by the trackpad started slipping around and I had to adjust it every few seconds. I started dreaming about designing and 3D-printing a bracket to hold the trackpad in place so it wouldn't move around but wasn't permanently attached to the keyboard in case I need to replace it. That's expensive and tedious and probably not that fulfilling in the end, so I investigated a bunch of other options and finally hit upon the &lt;a href="http://www.amazon.com/gp/product/B003A8D6YG/"&gt;Heininger 1065 CommuteMate DashGrip&lt;/a&gt;. It's intended to be slapped onto a car dashboard so you can perch your sunglasses and keys (wait aren't they in your ignition?) and spare change and 40oz Slurpee on your dash, without worrying that they'll scatter everywhere when you take a corner too hard.&lt;/p&gt;
&lt;p&gt;The DashGrip is basically a rectangle of sticky gel-like material that's surprisingly difficult to photograph:&lt;/p&gt;
&lt;img alt="DashGrip" class="thumbnail" src="https://d2s7foagexgnc2.cloudfront.net/files/571361195ba01a31d761/dashgrip.jpg" /&gt;
&lt;p&gt;In this photo I've already cut it to size and attached it to the trackpad. The other half is still in its original shipping wrapper. It's easy to cut down to size with scissors, and with it the trackpad fits snuggly but very securely in its designated spot. By snug I mean very strong. Strong like ox:&lt;/p&gt;
&lt;img alt="Very strong" class="thumbnail" src="https://d2s7foagexgnc2.cloudfront.net/files/fad3b2c7d5f6ce062e0a/strong.jpg" /&gt;
&lt;p&gt;The coolest part is that it doesn't leave any adhesive residue on the keyboard or trackpad. I'm not really sure how this stuff works but it seems pretty magical. Here's a side view of what it looks like put together:&lt;/p&gt;
&lt;img alt="Side view" class="thumbnail" src="https://d2s7foagexgnc2.cloudfront.net/files/b9746e06066c388454e5/side_view.jpg" /&gt;
&lt;p&gt;There's two caveats to this otherwise great solution. First, it's not going to work for you if you hate tap-to-click. The switches for clicking are actually inside the two front feet of the trackpad, which in this setup are not really in a clicking kind of mood since they're embedded in soft sticky gel. Relatedly, if you want to use the trackpad &lt;em&gt;away&lt;/em&gt; from the keyboard it's going to be kind of a pain, because of how super strong the bond is (see photo above). I happen to love tap-to-click, though, and don't use the trackpad away from the keyboard often enough to care.&lt;/p&gt;
&lt;p&gt;If you can live with those two things, I suggest you try out this setup, especially if you already have a Magic Trackpad and a Kinesis. It's pretty awesome.&lt;/p&gt;</description><author>Pete Keen</author><pubDate>Fri, 17 May 2013 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.petekeen.net/mounting-a-magic-trackpad-on-a-kinesis-advantage-keyboard/</guid></item><item><title>Distributed Personal Wiki</title><link>https://www.petekeen.net/git-backed-personal-markdown-wiki/</link><description>&lt;p&gt;For as long as I can remember I've been trying to find a good way to keep personal text notes. Recipes, notes, ideas, that kind of thing. Things that aren't really suited to blogging. Along the way I've used (and stuck with) &lt;a href="http://www.pmwiki.org"&gt;PmWiki&lt;/a&gt;, &lt;a href="https://www.dokuwiki.org/dokuwiki"&gt;DocuWiki&lt;/a&gt;, &lt;a href="http://tiddlywiki.com"&gt;TiddlyWiki&lt;/a&gt;, and most recently I built my own sort-of-pseudo-wiki &lt;a href="https://www.marginalia.com"&gt;Marginalia&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Lately, though, it's been kind of a drag to use a web-based application just to write down some work notes. Having sort of an obsession with Markdown I decided to just start keeping notes in Markdown-formatted files in a directory. Of course, files that aren't backed up are likely to disappear at any moment, so I naturally stuck them in a git repository and pushed to my &lt;a href="/hosting-private-git-repositories-with-gitolite"&gt;personal git server&lt;/a&gt;. But then, how do I deal with synching my work and home machines? I guess I'll manually merge changes...&lt;/p&gt;
&lt;!--more--&gt;
&lt;p&gt;Yeah, that lasted about 10 minutes. I had a whole setup baked up that tied together a rake script and an OS X LaunchAgent that watched a directory and everything, but the merging is of course the hardest part.&lt;/p&gt;
&lt;p&gt;I went hunting for alternatives. I even briefly considered trying out Evernote, but that didn't really meet with my self-hosting ideals. Dropbox was also a non-starter because, again, not self-hosted. Then I came across &lt;a href="http://sparkleshare.org"&gt;SparkleShare&lt;/a&gt; and my eyes lit up. SparkleShare uses git as its transport mechanism, can sync with any git repository, and automatically watches a given directory for changes.&lt;/p&gt;
&lt;p&gt;SparkleShare is trivial to set up. Download the package, drag the app to &lt;code&gt;/Applications&lt;/code&gt;, and run it. It'll create a directory at &lt;code&gt;~/SparkleShare&lt;/code&gt; and then ask you to add a hosted project. Just create a git repo somewhere (or not, it can sync to any directory that it can talk to via &lt;code&gt;ssh&lt;/code&gt;), point SparkleShare at it, and you're done. Do this across every machine you want this share on and whenever you add, delete, or modify a file in that directory it'll get synced to all of the other machines automatically.&lt;/p&gt;
&lt;p&gt;So, that covers the sync and backup strategy. What about the wiki part? I've been using Emacs &lt;a href="http://jblevins.org/projects/markdown-mode/"&gt;markdown-mode&lt;/a&gt; for several years to get syntax highlighting when writing blog posts and such, and unbeknownst to me in a recent release Jason added Wiki links and keyboard shortcuts to follow them. I upgraded &lt;a href="/task-oriented-dotfiles"&gt;my dotfiles&lt;/a&gt; to the latest version of &lt;code&gt;markdown-mode&lt;/code&gt; and then bam, wiki in a git repo.&lt;/p&gt;
&lt;p&gt;There are two more things that I want to do that would make this system work really well. First, I want to set up &lt;a href="https://github.com/gollum/gollum"&gt;Gollum&lt;/a&gt; on one of my servers and point it at the git repo that SparkleShare is syncing, so that I can have a web interface and pretty formatting when I want it. The nice thing is that &lt;code&gt;markdown-mode&lt;/code&gt; and Gollum use the same syntax for wiki links.&lt;/p&gt;
&lt;p&gt;Second, I want to replicate the send-an-email-to-create-a-note functionality that Marginalia has. I think I can do this with a tiny CGI script hooked up to &lt;a href="http://mandrill.com"&gt;Mandrill&lt;/a&gt; or &lt;a href="http://www.mailgun.com"&gt;Mailgun&lt;/a&gt;'s incoming email processing system. All it has to do is drop the message text into the SparkleShare-synced directory with a filename based on the subject.&lt;/p&gt;</description><author>Pete Keen</author><pubDate>Fri, 10 May 2013 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.petekeen.net/git-backed-personal-markdown-wiki/</guid></item><item><title>Learning Ruby on Rails: Why? How? Pitfalls?</title><link>https://www.brightball.com/articles/learning-ruby-on-rails-why-how-pitfalls</link><description>I've always been a proponent of the "right tool for the job" approach to programming.  Different languages are well suited for different situations. Over the past 2 years I've spent a great deal of time with Ruby on Rails after coming from a background of PHP, Java and Perl.  Here's how I got started and some of the lessons I learned along the way.</description><author>Brightball Articles</author><pubDate>Thu, 09 May 2013 19:00:00 GMT</pubDate><guid isPermaLink="true">https://www.brightball.com/articles/learning-ruby-on-rails-why-how-pitfalls</guid></item><item><title>CX – Conference Experience (Facilitating Communication)</title><link>/2013/04/29/CX-Conference-Experience-Facilitating-Communication/</link><description>&lt;p&gt;Following up on my earlier post about CX or Conference Experience – I&amp;rsquo;m going to dig in a bit on how you get good conversation to happen. In the past two years I&amp;rsquo;ve been to nearly 20 conferences, I&amp;rsquo;ve been to conferences with great talks, with great parties, with great swag, and hands down my favorite conferences have always been a result of great conversation. With the number of talks that are recorded and immediately available online after, &lt;em&gt;what can I say I&amp;rsquo;m a hallway track guy&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve seen a number of conferences intentionally design around this concept, in some ways the unconference is purely a hallway track conference. I&amp;rsquo;ve also seen conferences that weren&amp;rsquo;t clearly planned for this and have pulled off some of the best situations where people turn their phones off and engage in real conversation.&lt;/p&gt;
&lt;h2 id="breaks"&gt;
&lt;div&gt;
Breaks
&lt;/div&gt;
&lt;/h2&gt;
&lt;p&gt;Breaks are always a hard part to balance. Every conference organizer I&amp;rsquo;ve talked to felt they had the perfect mix for breaks. If you&amp;rsquo;re designing for convesation then you need to allow time for conversation to actually happen. Whether a multi-track or single track conference 15 minutes for breaks doesn&amp;rsquo;t give you the time to actually engage in conversation. If multitrack its time enough to grab a drink then head to the next room. If single track its time to have the entire room leave and then come back.&lt;/p&gt;
&lt;p&gt;Even 30 minute breaks fall into this category. At a multitrack conference I&amp;rsquo;m usually saying hi to enough people during a 30 minute break that I don&amp;rsquo;t have the chance to really get deep into a conversation. If I do it usually results in skipping the next talk.&lt;/p&gt;
&lt;h2 id="staggered-talks"&gt;
&lt;div&gt;
Staggered Talks
&lt;/div&gt;
&lt;/h2&gt;
&lt;!-- raw HTML omitted --&gt;
&lt;!-- raw HTML omitted --&gt;
&lt;h2 id="locations-for-conversations"&gt;
&lt;div&gt;
Locations for Conversations
&lt;/div&gt;
&lt;/h2&gt;
&lt;p&gt;Having places for people to get away an talk is critical, in a venue that only has 1 main room and no convenient places for a quick coffee or beer it becomes very hard for conversations to happen. If its on the agenda to facilitate such things thats great, but going a step further and allowing for it to be facilitated naturally is even better. On more than one occasion I&amp;rsquo;ve been mid conversation wtih someone, we cut it off agreeing to pick it back up at the next break only to never locate them again for the rest of the conference. I&amp;rsquo;d have just as much preferred to continue the conversation there, but sadly there was in the middle of a talk room with no where else feasible to go.&lt;/p&gt;
&lt;h2 id="water"&gt;
&lt;div&gt;
Water
&lt;/div&gt;
&lt;/h2&gt;
&lt;!-- raw HTML omitted --&gt;
&lt;p&gt;This one caught me by surprise&amp;hellip; The event was &lt;a href="http://py.codeconf.com/"&gt;PyCodeConf in Miami&lt;/a&gt; and it was one of the evening activities – a pool party with food, drinks, and mariachi band. Early into the evening, inevitably someone was thrown into the pool (by friends in a general good nature), but thus ruining a lovely iPhone. As a result I&amp;rsquo;m pretty most people, just as I did, went to their rooms but their cell phone away, or set it near their stuff. Much of the rest of the evening was an entirely internet and twitter free evening. Causing lots of uninterrupted conversations to happen and resulted in a pool full of geeks.&lt;/p&gt;
&lt;h2 id="welcoming-noobs"&gt;
&lt;div&gt;
Welcoming noobs
&lt;/div&gt;
&lt;/h2&gt;
&lt;p&gt;The big difference between just assuming this will happen and actively working to facilitate it is who gets involved in the conversation. For a first time presenter there&amp;rsquo;s a wealth of nerves about talking. For a first time attendee there&amp;rsquo;s a wealth of knowledge to soak up. First time attendees may not feel as comfortable interjecting them into a conversation, approaching presenters, or talking to someone they&amp;rsquo;ve been following for years – &lt;em&gt;yet they have just as much to add as anyone else&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Creating places where more conversation is happening helps ease this and build a better community.&lt;/p&gt;</description><author>CRAIG KERSTIENS</author><pubDate>Mon, 29 Apr 2013 23:55:56 GMT</pubDate><guid isPermaLink="true">/2013/04/29/CX-Conference-Experience-Facilitating-Communication/</guid></item><item><title>CX – Conference Experience (People)</title><link>/2013/04/29/CX-Conference-Experience-People/</link><description>&lt;p&gt;Following up on my earlier post about CX or Conference Experience – I&amp;rsquo;m going to dig in a bit on getting the right people there. There&amp;rsquo;s a lot of different ways to approach this from having a good ratio of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Designers to Developers&lt;/li&gt;
&lt;li&gt;Females to Males&lt;/li&gt;
&lt;li&gt;Noobs to well known community members&lt;/li&gt;
&lt;li&gt;Overall lack of suits&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While this is by no means a how we can solve all of the above problems, theres some bas&lt;/p&gt;
&lt;h2 id="talks"&gt;
&lt;div&gt;
Talks
&lt;/div&gt;
&lt;/h2&gt;
&lt;p&gt;Quality talks are obviously important. A conference should at whatever cost ensure that the talks are good. This can happen a variety of ways, but regardless of the method you should ensure talks are worthwhile; since this is largely what most people are paying to attend for.&lt;/p&gt;
&lt;h3 id="invite-only"&gt;
&lt;div&gt;
Invite-only
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;Perhaps the easiest way to do this is via invite only to speakers you know will do a good job. The downside ot this of course is that you must already know enough people to fill out a good agenda, and also you limit the ability for others to contribute.&lt;/p&gt;
&lt;h3 id="coachingpracticing"&gt;
&lt;div&gt;
Coaching/Practicing
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;This is not an either/or option with any of the other pieces for getting a good agenda. Having presenters do a trial run can ensure a minimum level of quality, and working with them to coach them can help make the talk even more effective. Its likely that the organizers know the audience as well as anyone, so no one better than them to help with this. Of course this is a time sink for both parties, but can give good returns. Of course this could be done independent of a specific conference such as through &lt;a href="http://speakup.io/"&gt;speakup&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;At a very least getting a quick run through, outline, or something of that nature can ensure that a presenter doesn&amp;rsquo;t fill a 45 minute talk slot with only 5 minutes of content.&lt;/em&gt;&lt;/p&gt;
&lt;h3 id="open-cfp"&gt;
&lt;div&gt;
Open CFP
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;Likely the most common approach to getting speakers is having an entirely open CFP. Its typical then that either the organizers or a speaker selection commitee then discusses and makes selections. This can usually work to have a nice balance of experienced and known to be good speakers and newer less experienced speakers that can have great potential as well.&lt;/p&gt;
&lt;h3 id="open-open-cfp"&gt;
&lt;div&gt;
Open Open CFP
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;Going even further is an open CFP where all talks are published after the CFP then voted on. While this does a great deal to ensure transparency, it doesn&amp;rsquo;t necessarily improve ensuring theres a great line up of speakers. Being able to write an interesting talk proposal is an entirely separate process from delivering an interesting talk.&lt;/p&gt;
&lt;h3 id="blind"&gt;
&lt;div&gt;
Blind?
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;There&amp;rsquo;s a common question lately for both the open CFP and open open ones one whether to do blind review of the talks. The logic here is an attempt to be entirely fair, versus having some bias. This is an understandable goal, but can come at the expense of quality. Truth be told; I&amp;rsquo;m not entirely sure of a way to balance this.&lt;/p&gt;
&lt;h2 id="choosing"&gt;
&lt;div&gt;
Choosing
&lt;/div&gt;
&lt;/h2&gt;
&lt;p&gt;There&amp;rsquo;s definitely not a one size fits all. If you&amp;rsquo;re goal is to provide a good list of talks then you should keep that in mind in how you decide your talks. If you&amp;rsquo;re goal is to pull others in then it should be shaped differently. Zach Holman recently talked a bit about this and had some interesting ideas to &lt;a href="http://zachholman.com/posts/the-conference-circuit/"&gt;minimize risk for new speakers&lt;/a&gt;.&lt;/p&gt;</description><author>CRAIG KERSTIENS</author><pubDate>Mon, 29 Apr 2013 23:55:56 GMT</pubDate><guid isPermaLink="true">/2013/04/29/CX-Conference-Experience-People/</guid></item><item><title>The Drawback to Web Frameworks</title><link>https://www.brightball.com/articles/the-drawback-to-web-frameworks</link><description>Web frameworks are great, don't get me wrong here. They provide a structure and consistency across projects that will transcend developers over the life of a system while dramatically simplifying the code base amongst other wonderful side effects. But what's the downside?</description><author>Brightball Articles</author><pubDate>Mon, 29 Apr 2013 20:37:00 GMT</pubDate><guid isPermaLink="true">https://www.brightball.com/articles/the-drawback-to-web-frameworks</guid></item><item><title>"Make it faster."</title><link>https://www.brightball.com/articles/make-it-faster</link><description>I'm obsessed with performance tuning.  It's an itch that can never fully be scratched.  A sickness that can never be cured.  Here's the story of how I caught the bug.</description><author>Brightball Articles</author><pubDate>Sun, 28 Apr 2013 05:22:00 GMT</pubDate><guid isPermaLink="true">https://www.brightball.com/articles/make-it-faster</guid></item><item><title>CX – Conference Experience (Talks)</title><link>/2013/04/27/CX-Conference-Experience-Talks/</link><description>&lt;p&gt;A couple of weekends ago I had the great opportunity to attend &lt;a href="http://lessconf.com"&gt;lessconf&lt;/a&gt;. It was an all around great conference, and as a result of the greatness I ended up having a conversation with a few people around conference experience. I must give much of the credit to &lt;a href="http://twitter.com/swiftalphaone"&gt;Swift&lt;/a&gt;, as he mentioned he&amp;rsquo;d already been thinking alot about this since &lt;a href="http://waza.heroku.com"&gt;Waza&lt;/a&gt;. In general it feels like there&amp;rsquo;s a few key themes that any conference should focus on, then a lot of small things that can really push it over the top. Here&amp;rsquo;s a few:&lt;/p&gt;
&lt;p&gt;In general the key areas for any great conference are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Talks&lt;/li&gt;
&lt;li&gt;Great people&lt;/li&gt;
&lt;li&gt;Ensuring communication happens&lt;/li&gt;
&lt;li&gt;Bonus points&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Digging in deeper on the first area&amp;hellip;&lt;/p&gt;
&lt;h2 id="talks"&gt;
&lt;div&gt;
Talks
&lt;/div&gt;
&lt;/h2&gt;
&lt;p&gt;Quality talks are obviously important. A conference should at whatever cost ensure that the talks are good. This can happen a variety of ways, but regardless of the method you should ensure talks are worthwhile; since this is largely what most people are paying to attend for.&lt;/p&gt;
&lt;h3 id="invite-only"&gt;
&lt;div&gt;
Invite-only
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;Perhaps the easiest way to do this is via invite only to speakers you know will do a good job. The downside ot this of course is that you must already know enough people to fill out a good agenda, and also you limit the ability for others to contribute.&lt;/p&gt;
&lt;h3 id="coachingpracticing"&gt;
&lt;div&gt;
Coaching/Practicing
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;This is not an either/or option with any of the other pieces for getting a good agenda. Having presenters do a trial run can ensure a minimum level of quality, and working with them to coach them can help make the talk even more effective. Its likely that the organizers know the audience as well as anyone, so no one better than them to help with this. Of course this is a time sink for both parties, but can give good returns. Of course this could be done independent of a specific conference such as through &lt;a href="http://speakup.io/"&gt;speakup&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;At a very least getting a quick run through, outline, or something of that nature can ensure that a presenter doesn&amp;rsquo;t fill a 45 minute talk slot with only 5 minutes of content.&lt;/em&gt;&lt;/p&gt;
&lt;h3 id="open-cfp"&gt;
&lt;div&gt;
Open CFP
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;Likely the most common approach to getting speakers is having an entirely open CFP. Its typical then that either the organizers or a speaker selection commitee then discusses and makes selections. This can usually work to have a nice balance of experienced and known to be good speakers and newer less experienced speakers that can have great potential as well.&lt;/p&gt;
&lt;h3 id="open-open-cfp"&gt;
&lt;div&gt;
Open Open CFP
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;Going even further is an open CFP where all talks are published after the CFP then voted on. While this does a great deal to ensure transparency, it doesn&amp;rsquo;t necessarily improve ensuring theres a great line up of speakers. Being able to write an interesting talk proposal is an entirely separate process from delivering an interesting talk.&lt;/p&gt;
&lt;h3 id="blind"&gt;
&lt;div&gt;
Blind?
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;There&amp;rsquo;s a common question lately for both the open CFP and open open ones one whether to do blind review of the talks. The logic here is an attempt to be entirely fair, versus having some bias. This is an understandable goal, but can come at the expense of quality. Truth be told; I&amp;rsquo;m not entirely sure of a way to balance this.&lt;/p&gt;
&lt;h2 id="choosing"&gt;
&lt;div&gt;
Choosing
&lt;/div&gt;
&lt;/h2&gt;
&lt;p&gt;There&amp;rsquo;s definitely not a one size fits all. If you&amp;rsquo;re goal is to provide a good list of talks then you should keep that in mind in how you decide your talks. If you&amp;rsquo;re goal is to pull others in then it should be shaped differently. Zach Holman recently talked a bit about this and had some interesting ideas to &lt;a href="http://zachholman.com/posts/the-conference-circuit/"&gt;minimize risk for new speakers&lt;/a&gt;.&lt;/p&gt;</description><author>CRAIG KERSTIENS</author><pubDate>Sat, 27 Apr 2013 23:55:56 GMT</pubDate><guid isPermaLink="true">/2013/04/27/CX-Conference-Experience-Talks/</guid></item><item><title>The Impossible Assignment</title><link>https://www.brightball.com/articles/the-impossible-assignment</link><description>I had the opportunity to visit the class of one of my legendary former professors yesterday and got to share a classic story about him...the time he gave us an impossible assignment.</description><author>Brightball Articles</author><pubDate>Thu, 25 Apr 2013 20:12:00 GMT</pubDate><guid isPermaLink="true">https://www.brightball.com/articles/the-impossible-assignment</guid></item><item><title>Create Better Villains for your Adventure</title><link>https://benovermyer.com/blog/2013/04/create-better-villains-for-your-adventure/</link><description>&lt;p&gt;Villains are the mainstay of any good adventure. With very few exceptions, there is always some form of evil mind plotting dark things in the background. Whether this is a mindless mob of creatures that just happens to be devouring the locals, or a masterfully devious court magician manipulating a king to sinister ends, you can’t get away from the guys.&lt;/p&gt;
&lt;p&gt;This blog post aims to assist you in building memorable villains that are more than just numbers on a sheet of paper. If you’re a player and not a game master, the character building advice in here might help you too, but this is specifically geared towards the folks weaving the story behind the encounters.&lt;/p&gt;
&lt;h1 id="the-nature-of-a-villain"&gt;The Nature of a Villain&lt;/h1&gt;
&lt;p&gt;While it’s fairly obvious that a villain exists to ruin the lives of good people, a truly memorable villain has much more than that going on. He has his own motives and desires. He may be acting in accordance with his own morals, or he might not care at all.&lt;/p&gt;
&lt;p&gt;Most frequently, a villain’s goals shouldn’t be an end, but a way to reach some other starting point. The kobold king isn’t sending raiding parties out to nearby villages because he feels like it; he’s weakening the nearby human presence so his people can expand outward from their all-too-small cave system.&lt;/p&gt;
&lt;p&gt;The depth of a villain’s forward thinking depends on how intelligent you want him to be. Don’t restrict a villain’s planning just because he’s a minor NPC in your adventure. Players will inevitably do something you don’t expect, and if they just happen to latch onto your minor villain, you’ll then have a direction to take that in.&lt;/p&gt;
&lt;h1 id="journey-of-the-hero"&gt;Journey of the Hero&lt;/h1&gt;
&lt;p&gt;The player characters will only grow stronger as a result of having a characterful villain as their foil. You can consider your adventure a success when not only does it give your PCs the magical loot of awesomeness they’ve been waiting for, but it also encourages them to reconsider their characters’ actions and motives.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Joseph Campbell’s Hero with a Thousand Faces is a fantastic guide to setting up an epic story. His writings on the “road of trials” in particular are relevant to creating worthwhile villains for the players.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A player character that’s just starting out usually has weak stats, basic equipment, and almost zero personality. She might have a single personality quirk or cliché that her player jots down to guide her role-playing, but rarely will players take the time to build a full story for their characters. After all, that’s what they’re hoping you will do.&lt;/p&gt;
&lt;p&gt;As the PC encounters and interacts with your adventure’s many challenges, locations, and characters, she will grow in both power and personality. If her player is into role-playing, this will happen sooner rather than later, and with more depth. She will need a good villain, though, to balance that development against.&lt;/p&gt;
&lt;h1 id="brainstorming"&gt;Brainstorming&lt;/h1&gt;
&lt;p&gt;Creating a villain should start with looking at what you want to achieve with the adventure and with the campaign at large. An adventure isn’t a set narrative that you can just write and expect to stay the way you wrote it. The players will do things you don’t expect and interact with the environment in ways that force you to adapt the adventure to fit their actions. As such, your villain should support your goal with the adventure, rather than just play to the script.&lt;/p&gt;
&lt;p&gt;Jot down a few notes about your adventure’s premise. Is it a classic battle of good versus evil? Is it a day in the life of an adventurer? Is it an exploration of the unknown? Think about your favorite movies or books that fit the theme you’re going for. What twists in those stories really got your blood pumping?&lt;/p&gt;
&lt;h2 id="potential-for-loss"&gt;Potential for Loss&lt;/h2&gt;
&lt;p&gt;Think about what would really hurt the player characters. I don’t mean in terms of hit points lost or character sheets thrown away – I mean tragedies that specifically target each PC’s hopes, dreams, and beliefs. Whether the players are into role-playing or not only determines whether you have more or less fodder for this; it’s still possible to make a tactical gamer sit back in his chair with a thud when his character loses something dear to him. It usually just changes the context.&lt;/p&gt;
&lt;p&gt;Build something of that potential loss into each villain of the adventure. You don’t need to include every player character in every adventure, but you shouldn’t focus on only one player character either. Also, choose a handful of NPCs and target their potential for loss also. It helps build a living world when you include characters other than the players’ in your adventures.&lt;/p&gt;
&lt;h2 id="potential-for-gain"&gt;Potential for Gain&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Saruman was originally a hero, but gave in to temptation.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Now think about what the villain can offer the player characters. Temptation plays on base emotions; use it. For tactical gamers, this could be a particular piece of loot that they want desperately. For role-players, it might be a bit of information about a relative they’ve been searching for. For every player, write down at least one thing that the villain might have the ability to provide.&lt;/p&gt;
&lt;p&gt;As with the potential for loss, also make sure that some of your NPCs stand to gain something by working with the villain. Even if that incorruptible priest of all that is good can’t possibly be turned to the dark side, the players might not know that, and that creates tension. Tension is what every great story (and adventure) is built on.&lt;/p&gt;
&lt;h1 id="villains-are-story-generators"&gt;Villains are Story Generators&lt;/h1&gt;
&lt;p&gt;Other than trying to provoke emotional tension from players through the potential for loss or gain, a good villain should also act as a way to move the story along. The best stories, whether they’re explosion-ridden Hollywood blockbusters or 1500-page literary masterpieces, are about the heroes changing in some way over the course of the story. Villains are almost always the catalyst for that change.&lt;/p&gt;
&lt;p&gt;If your players are craving action, send a wave of minor baddies against them. Let them know that the villain made this happen, whether directly or indirectly. Weave a spider’s web with your villain at the center. Even if he is not of the evil genius persuasion, he’s still fully capable of planning ahead.&lt;/p&gt;
&lt;h1 id="summary-tl-dr-version"&gt;Summary / TL;DR Version&lt;/h1&gt;
&lt;p&gt;Keep your villains close to your players’ hearts. Tension is a great story device, so use it. Tempt the PCs. Hurt the PCs. Make a list of the villain’s goals and aspirations.&lt;/p&gt;
&lt;p&gt;And, above all else, remember: villains are not just stat blocks, they’re people too.&lt;/p&gt;</description><author>Ben Overmyer's Site</author><pubDate>Sat, 20 Apr 2013 03:00:00 GMT</pubDate><guid isPermaLink="true">https://benovermyer.com/blog/2013/04/create-better-villains-for-your-adventure/</guid></item><item><title>Heroku's Acquisition 2 Years Later</title><link>/2013/04/18/Herokus-Acquisition-2-Years-Later/</link><description>&lt;p&gt;Just over two years ago, Heroku was acquired. I was around and peripheral to this just before the acquisition and came on board only barely after. While there is a large group of people that have been there longer than I have (several for 4+ years) I&amp;rsquo;m still commonly asked how things have changed, how things work, and other questions of that nature. I wrote about some of these processes over a year ago in the months after joining Heroku around our &lt;a href="/2011/12/02/how-heroku-works-hiring/"&gt;hiring&lt;/a&gt;, our &lt;a href="/2011/11/02/how-heroku-works-teams-tools/"&gt;teams&lt;/a&gt;, and &lt;a href="/2011/11/07/how-heroku-works-maker-day/"&gt;how we work&lt;/a&gt;. Many of these things haven&amp;rsquo;t changed, and yet almost always at a conference I&amp;rsquo;m asked how are things different since being acquired.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s my personal take (and while I don&amp;rsquo;t typically include this – &lt;em&gt;to be safe, this is not an official Heroku view of what&amp;rsquo;s changed&lt;/em&gt;).&lt;/p&gt;
&lt;h3 id="are-things-different-since-the-acquisition"&gt;
&lt;div&gt;
Are things different since the acquisition?
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;Heroku in many ways operates like a wholly owned subsidiary or as an independent business within Salesforce. We still have our own office space, our own IT (if you can even call it that), and in general entirely own workflow. I have a salesforce email account that automatically forwards to my Heroku google apps account, and that&amp;rsquo;s about as much as I know about it. In fact, we&amp;rsquo;re currently preparing for our new office and its planned to be our home for somewhere between the next 5-10 years.&lt;/p&gt;
&lt;p&gt;At the same time some things do flow through Salesforce, some of those things are logistical some strategic. First the perhaps most unfortunate part – hiring. This has not really changed &lt;a href="/2011/12/02/how-heroku-works-hiring/"&gt;how we hire&lt;/a&gt;, but rather once you are coming on board theres more paperwork. In general this is the most painful part that instead of 1/2 pieces of paper theres a few to sign. At the same time some of these come with some greater gains such as benefit, etc. To be honest I dont fully recall what all of the paper work is, but either way there is a bit more of it. The other area we really see this effect is expense accounts. All expenses flow through concur, which in my opinion is a pretty good solution. I&amp;rsquo;ve used many worse expense solutions and seen few if any better.&lt;/p&gt;
&lt;h3 id="do-they-influence-the-product"&gt;
&lt;div&gt;
Do they influence the product?
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;Not really&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In general we at Heroku aim to have some broader alignment around what we&amp;rsquo;re trying to accomplish. Salesforce believes in helping their customers become a &lt;a href="http://www.youtube.com/watch?v=BwaZwm2dTCA"&gt;customer company&lt;/a&gt;. We believe that developers are worth of great experiences. Salesforce believes in ensuring its customers are successful. Heroku believes that developers should focus on adding value of their customers not just keeping the lights on. In all of these things its important to have alignment. Not for the company we&amp;rsquo;re building for the next six weeks or six months, but for the next six years. Given both Salesforce&amp;rsquo;s and Heroku&amp;rsquo;s worldview I believe what we&amp;rsquo;re trying to accomplish is in good alignment.&lt;/p&gt;
&lt;p&gt;But does Salesforce influence our product roadmap? No, we aim to listen to our customers problems and build to solve those problems and through that improve the way software is delivered. At the time Salesforce acquired us, the pieces of &lt;a href="https://blog.heroku.com/archives/2011/5/31/celadon_cedar"&gt;Cedar&lt;/a&gt; were already in motion; including Procfile support, logplex, and other pieces. Later was fully productized Cedar and which was a goal we&amp;rsquo;d long had – making other languages such as Node, &lt;a href="https://blog.heroku.com/archives/2011/8/25/java"&gt;Java&lt;/a&gt;, and &lt;a href="https://blog.heroku.com/archives/2011/9/28/python_and_django"&gt;Python&lt;/a&gt; available. This wasn&amp;rsquo;t driven because Salesforce wanted Java, but because we saw value in delivering the same value of the platform to other communities.&lt;/p&gt;
&lt;h3 id="have-we-changed"&gt;
&lt;div&gt;
Have we changed?
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;Sure, we&amp;rsquo;ve changed, but I&amp;rsquo;d surmise very little if at all due to Salesforce. We&amp;rsquo;ve grown from a 20 person company to now over 100. We were a primarily local team and now have people all over. Company offsites get a bit harder to coordinate with 100 people, and finding a date that absoluely everyone can make it is nearly impossible. Many of our changes are more strictly a change of growth than they are because of Salesforce.&lt;/p&gt;
&lt;h3 id="i-dont-get-it-why-acquire-you-then"&gt;
&lt;div&gt;
I dont get it, why acquire you then?
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;Because we&amp;rsquo;re aligned in a much bigger vision, we can both work together long term to accomplish our goal.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The internet is changing the world; software is everywhere.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I believe Salesforce truly understands this. Heroku does as well (though, at the time of the acquisition I fully suspect many Herokai held their breath to see how it would all go). Six weeks went by without trying to &amp;lsquo;change us&amp;rsquo;, then six months, and here we are over two years later. In many ways so many of us have started to better understand that Salesforce truly understands the above.&lt;/p&gt;
&lt;p&gt;If you understand the above, then you know that delivering software and improving that process gives any business a competitive advantage. This is at the core of the value we aim to provide. E.g. If you ranked all companies in terms of how strong they aligned with Heroku, Salesforce might be at the &lt;em&gt;very&lt;/em&gt; top.&lt;/p&gt;</description><author>CRAIG KERSTIENS</author><pubDate>Thu, 18 Apr 2013 23:55:56 GMT</pubDate><guid isPermaLink="true">/2013/04/18/Herokus-Acquisition-2-Years-Later/</guid></item><item><title>Profiling Django templates with Django-Debug-Toolbar</title><link>https://tomforb.es/blog/profiling-django-templates-with-django-debug-toolbar/</link><description>My last post about the speed of Django’s templating language caused a bit of a stir and it was clear that people didn’t really have a clue how long the templates were taking to render in their applications. Enter Template-timings Template-timings is a panel for Django-debug-toolbar (which everyone s...</description><author>Tom Forbes</author><pubDate>Thu, 18 Apr 2013 23:46:58 GMT</pubDate><guid isPermaLink="true">https://tomforb.es/blog/profiling-django-templates-with-django-debug-toolbar/</guid></item><item><title>Using array_agg in Postgres – powerful and flexible</title><link>/2013/04/17/Using-array_agg-in-Postgres-powerful-and-flexible/</link><description>&lt;p&gt;In almost any application it&amp;rsquo;s common to want to aggregate some set of values together, commonly in a comma separated form. Most developers do this by running a query to get much of the raw data, looping over the data and pushing it into a set, appending each new value to the appropriate key. Hopefully, it&amp;rsquo;s not a surprise that there&amp;rsquo;s a much better way to do this with PostgreSQL.&lt;/p&gt;
&lt;p&gt;Postgres has a flexible and robust &lt;a href="/2012/08/20/arrays-in-postgres/"&gt;array datatype&lt;/a&gt; that comes with a variety of functions. Even without taking advantage of the array datatype in &lt;a href="/2012/11/06/django-and-arrays/"&gt;your application&lt;/a&gt;, you can still take advantage of some of the functions to get the functionality you need. Lets take a look at an example schema and use case.&lt;/p&gt;
&lt;h3 id="an-example"&gt;
&lt;div&gt;
An example
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;Given a project management application, you may have &lt;code&gt;users&lt;/code&gt; who have &lt;code&gt;projects&lt;/code&gt; that have &lt;code&gt;tasks&lt;/code&gt;. An example piece of functionality might be to send an email with a list of all projects that have tasks that are past their due dates of completion. Your schema might look something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; # \d users
Table &amp;quot;public.users&amp;quot;
Column | Type | Modifiers
------------+-----------------------------+-----------
id | integer | not null
email | character varying(255) |
...
# \d projects
Table &amp;quot;public.projects&amp;quot;
Column | Type | Modifiers
------------+-----------------------------+-----------
id | integer | not null
user_id | integer | not null
name | character varying(255) | not null
...
# \d tasks
Table &amp;quot;public.tasks&amp;quot;
Column | Type | Modifiers
--------------+-----------------------------+-----------
id | integer | not null
project_id | integer | not null
completed_at | timestamp without time zone |
due_at | timestamp without time zone |
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To get a list of all projects that have tasks that haven&amp;rsquo;t been completed, you would start with something like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT
projects.name
FROM
projects,
tasks
WHERE projects.id = tasks.project_id
AND tasks.due_at &amp;gt; tasks.completed_at
AND tasks.due_at &amp;gt; now()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This would give you a list of projects which you could then easily join this with users:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT
users.email
projects.name
FROM
projects,
tasks,
users
WHERE projects.id = tasks.project_id
AND tasks.due_at &amp;gt; tasks.completed_at
AND tasks.due_at &amp;gt; now()
AND users.id = projects.user_id
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At this point you&amp;rsquo;ve got everything you need to pull this up into Ruby, Python, or other language of your choice and then build the full set. However if this is thousands or even hundreds of results you&amp;rsquo;ll be spending more time than necessary, grouping this data for a sensible email. With 3 other small changes you can have this already formatted for you to immediately send of in an email. The first is using a handy function called &lt;code&gt;array_agg&lt;/code&gt; which will aggregate items and then you can format them how you wish. The second is just ensuring you&amp;rsquo;re grouping correctly. Finally you&amp;rsquo;ll want to unnest the array so it formats the data in a clean way for you.&lt;/p&gt;
&lt;p&gt;Looking at it all put together:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT
users.email,
array_to_string(array_agg(projects.name), ',')) as projects
FROM
projects,
tasks,
users
WHERE projects.id = tasks.project_id
AND tasks.due_at &amp;gt; tasks.completed_at
AND tasks.due_at &amp;gt; now()
AND users.id = projects.user_id
GROUP BY
users.email
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This would give you a nice clean result of projects that have overdue tasks that you could then send to the user in an email:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; email | projects
---------------------------+-------------------
craig.kerstiens@gmail.com | blog, timetracker
craig@heroku.com | foo, bar, baz
&lt;/code&gt;&lt;/pre&gt;</description><author>CRAIG KERSTIENS</author><pubDate>Wed, 17 Apr 2013 23:55:56 GMT</pubDate><guid isPermaLink="true">/2013/04/17/Using-array_agg-in-Postgres-powerful-and-flexible/</guid></item><item><title>Building a Search Result Extract Generator in PHP</title><link>https://boyter.org/2013/04/building-a-search-result-extract-generator-in-php/</link><description>&lt;p&gt;During some contracting I was doing recently there was a requirement to implement some search logic using only PHP. There are no issues with that but it turns out I couldn&amp;rsquo;t find a decent extract generator handy as usually one would just plug into the search engines provided version to do this.&lt;/p&gt;
&lt;p&gt;Off the top of my head I could only think of one example I was aware of which lives in &lt;a href="http://www.sphider.eu/"&gt;Sphider&lt;/a&gt; (for the record it lives in searchfuncs.php from line 529 to 566). Sadly it has a few issues. Firstly the code is rather difficult to understand, and more importantly it usually has accuracy issues. A quick search turned up this link &lt;a href="http://stackoverflow.com/questions/1436582/how-to-generate-excerpt-with-most-searched-words-in-php"&gt;http://stackoverflow.com/questions/1436582/how-to-generate-excerpt-with-most-searched-words-in-php&lt;/a&gt; on StackOverflow. The second answer looked promising but its even more difficult to understand and a bit of profiling showed some performance issues will all of the regex going on in there.&lt;/p&gt;</description><author>Ben E. C. Boyter</author><pubDate>Wed, 17 Apr 2013 02:23:56 GMT</pubDate><guid isPermaLink="true">https://boyter.org/2013/04/building-a-search-result-extract-generator-in-php/</guid></item><item><title>Scaling Evangelism – Creating Advocates</title><link>/2013/04/16/Scaling-Evangelism-Creating-Advocates/</link><description>&lt;p&gt;The first area I tend to think about when it comes to developer marketing is around advocates. A simple definition of an advocate is &lt;em&gt;someone that speaks/writes in favor of some thing&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Creating advocates is a means of creating more of myself to go out and talk loudly and ideally effectively. However, they do take time to cultivate and it is a hard item to track. But by cultivating advocates I&amp;rsquo;m able to scale what would otherwise be a bottleneck of my own personal time.&lt;/p&gt;
&lt;h3 id="mentorship"&gt;
&lt;div&gt;
Mentorship
&lt;/div&gt;
&lt;/h3&gt;
&lt;!-- raw HTML omitted --&gt;
&lt;p&gt;The people in your group of 3 you&amp;rsquo;ll have nearly daily communications with, even if quick. These are the first people you think to work and collaborate with. It&amp;rsquo;s the case with all, but especially so here that there may be some strong mutual benefit. With both the groups of 3 and 12 you&amp;rsquo;ll be looking to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Co-author content with&lt;/li&gt;
&lt;li&gt;Collaborate on projects&lt;/li&gt;
&lt;li&gt;Help amplify their content&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="helping-them-succeed"&gt;
&lt;div&gt;
Helping them Succeed
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;For a few years I&amp;rsquo;ve followed a similar process to this. Essentially creating advocates is done through first &lt;strong&gt;making them successful&lt;/strong&gt;, then empowering to talk about their success. I&amp;rsquo;ve often done this by giving people a private channel to me – through IM, SMS, Phone, Skype, or even in person. Sure its easy enough to get my work email address, its a pretty easy one to guess, and even at that address I do aim to respond to every request. These channels in reality don&amp;rsquo;t make me more available; but they do allow a different type of communication to happen.&lt;/p&gt;
&lt;p&gt;My personal approach has found it more out of place to ask how someones weekend was over email – IM or Skype make this easy. Broader questions of how someones actually doing in life generally are much easier to do over a coffee or a beer. This allows for a deeper relationship; at the same time I can ensure everyone I interact with is successful. It&amp;rsquo;s easier to ensure someones successful if they&amp;rsquo;re willing to talk to you, actually connecting with people enables this.&lt;/p&gt;
&lt;h3 id="communicate"&gt;
&lt;div&gt;
Communicate
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;Once someones been successful theres usually some value in their story. This doesn&amp;rsquo;t need to come in a marketing-ese approach though. Instead contribute back the value of what you did to the broader community with &lt;a href="http://blog.mailgun.net/post/how-tealeaf-academy-increased-student-engagement-3x/"&gt;clear steps how they can get the same benefit&lt;/a&gt;. For many developers they feel talking about their own success is a bit of imposter syndrome. While this isn&amp;rsquo;t always the case; if it is you can help to work with someone to highlight the value in what they&amp;rsquo;ve done. This could be as simple as some encouragement to actually helping review drafts of blog posts.&lt;/p&gt;
&lt;p&gt;Of course once someones started talking about what they were able to accomplish help them celebrate the success. Yelling from the rooftops only helps to further their efforts and give them the confidence to do it all over again.&lt;/p&gt;
&lt;h3 id="conclusion"&gt;
&lt;div&gt;
Conclusion
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;To create advocates you don&amp;rsquo;t have to write a list of people you wish to shoot for, but realizing there are limiitations on your time can help you be more intentional. Knowing that where you spend your time and who you spend it with has a cost can allow you to be more explicit about ensuring you&amp;rsquo;re providing value to them, from there it should naturally run its course. Want to discuss this more? Email me at &lt;a href="mailto:craig.kerstiens@gmail.com"&gt;craig.kerstiens at gmail.com&lt;/a&gt;.&lt;/p&gt;</description><author>CRAIG KERSTIENS</author><pubDate>Tue, 16 Apr 2013 23:55:56 GMT</pubDate><guid isPermaLink="true">/2013/04/16/Scaling-Evangelism-Creating-Advocates/</guid></item><item><title>Doing Marketing (for developers) Differently</title><link>/2013/04/12/perspective-on-developer-marketing/</link><description>&lt;p&gt;As developers we can tend to be a fickle bunch; especially in certain open source communities. We like to see intelligence and systems applied to things, hope for a better world through making things open, and appreciate when others relate to our world as we often attempt to relate to theirs. Marketing is often a loaded word when it comes to developers – leaving mixed feelings about webinars, email campaigns, and the like.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;A quick clarification in terms of marketing I&amp;rsquo;m referring to product marketing, but of a technical product. This is based on the idea that &lt;a href="http://thenewkingmakers.com/"&gt;developers are the new kingmakers&lt;/a&gt; and when marketing a product to them it needs to be done differently.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;While many of the above steps can add immense value, they often miss when it comes to developers. By changing these processes only slightly there can be much more efficiency in connecting with and actually delivering the value you hope for to developers.&lt;/p&gt;
&lt;h3 id="email-marketing"&gt;
&lt;div&gt;
Email Marketing
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;This is an area that there are more and more companies already starting to solve the right problems. Companies like &lt;a href="http://intercom.io"&gt;intercom.io&lt;/a&gt; and &lt;a href="http://customer.io"&gt;customer.io&lt;/a&gt; are allowing for better tracking of what users are doing and notifying them at the &lt;em&gt;right&lt;/em&gt; time instead of via massive impersonal campaigns. Developers build systems that take into account similar factores every day, you should be doing the same.&lt;/p&gt;
&lt;p&gt;Emails that are sent at the wrong time or miss on what someone is trying to do can often do more damage towards building a rapport than good. Pushing content to solve a recent issue or problem at a relevant time should be table stakes for any email thats sent.&lt;/p&gt;
&lt;h3 id="community-engagement"&gt;
&lt;div&gt;
Community Engagement
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;Whether at conferences or in online collaborations developers are more social than so many give them credit for. We appreciate when things are done to improve the greater good particularly for developers, but often for the world as well. From supporting individual developers or projects financially such as through &lt;a href="http://www.gittip.com"&gt;gittip&lt;/a&gt; or &lt;a href="http://www.kickstarter.com/projects/andrewgodwin/schema-migrations-for-django"&gt;certain kickstarter projects&lt;/a&gt; all the way to employing developers to &lt;a href="http://www.twitter.com/tenderlove"&gt;work full time on open source&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="being-better-than-webinars"&gt;
&lt;div&gt;
Being Better Than Webinars
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;After doing a webinar for a particular audience I had a &lt;a href="http://www.heroku.com"&gt;Heroku&lt;/a&gt; co-worker come up to me. This individual has contributed many ideas that have become core functionality such as the &lt;a href="#"&gt;Cedar stack&lt;/a&gt; and is generally open minded and listens very well. He came up to me and genuinely asked, &amp;ldquo;so what is a webinar?&amp;rdquo; I explained its like a live webcast with an opportunity for questions at the end. He was quiet for minute and responded, &amp;ldquo;Why can&amp;rsquo;t we just say that?&amp;rdquo;&lt;/p&gt;
&lt;p&gt;There are some words that simply send off the wrong signals. Webinar rather clearly is one of those. As a community we&amp;rsquo;ve shown that we have a desire for this kind of content. The &lt;a href="http://www.lanyrd.com"&gt;conference community&lt;/a&gt; has continued to grow steadily, and while they are many parts to every conference a pretty common one is the talk track itself. A talk at a conference is essentially a live in person webinar, of course the experience is often a bit richer.&lt;/p&gt;
&lt;p&gt;Instead of the term webinar what is wrong with online office hours or webcasts with an opportunity for questions? Even doing the same thing with a different name is underdelivering on what we&amp;rsquo;re capable of.&lt;/p&gt;
&lt;p&gt;I welcome hearing from others directly at &lt;a href="mailto:craig.kerstiens@gmail.com"&gt;craig.kerstiens@gmail.com&lt;/a&gt; on what they&amp;rsquo;re doing to engage with developers thats working. Not only working in the sense of eyeballs or dollars, but in adding value, in improving communities, in make the world better for developers. If there&amp;rsquo;s enough interest would be happy to post more detail around things I&amp;rsquo;ve seen work and what I hear works for others.&lt;/p&gt;</description><author>CRAIG KERSTIENS</author><pubDate>Fri, 12 Apr 2013 23:55:56 GMT</pubDate><guid isPermaLink="true">/2013/04/12/perspective-on-developer-marketing/</guid></item><item><title>More holes than swiss cheese</title><link>https://tomforb.es/blog/more-holes-than-swiss-cheese/</link><description>tl;dr Never trust user input. Note: Before I published this I contacted the owner of the site and he has since replaced it with a new and more secure version When I browse the net I often see the hallmarks of a security issue with websites that I visit, and little alarm bells go off in my head. I wa...</description><author>Tom Forbes</author><pubDate>Thu, 11 Apr 2013 23:24:30 GMT</pubDate><guid isPermaLink="true">https://tomforb.es/blog/more-holes-than-swiss-cheese/</guid></item><item><title>Profession Talents for Ingenium</title><link>https://benovermyer.com/blog/2013/04/profession-talents-for-ingenium/</link><description>&lt;p&gt;This post presents a prototype profession mechanic we’re working on for Ingenium 2nd Edition. We’re looking at giving each profession its own Profession Talent to add a little more variety to the game.&lt;/p&gt;
&lt;p&gt;The Priest profession from first edition was pretty…. plain, and there was nothing priestly about her, other than role-playing ques. So with this new mechanic, she gets a Talent that reinforces her role as a messenger of the gods.&lt;/p&gt;
&lt;p&gt;The priest profession from first edition was presented like this:&lt;/p&gt;
&lt;h1 id="priest"&gt;Priest&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;Starting Talent Pool:&lt;/strong&gt; Mystic&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Attributes:&lt;/strong&gt; +1 Magical Aptitude&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Speed:&lt;/strong&gt; 3&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Hit Points:&lt;/strong&gt; 1d8&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Advancement:&lt;/strong&gt; Healing Arts&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Starting Gold:&lt;/strong&gt; 1d4 x 5&lt;/p&gt;
&lt;p&gt;Priests are those divine focal points who channel their gods’ will into physical manifestations. Their magic is not based on arcane rules and laws; rather, they are blessed as Chosen and receive gifts of power for as long as they serve their gods.&lt;/p&gt;
&lt;p&gt;The flavor text stands in Second Edition, but there are a number of changes. Here’s the new Priest block:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Starting Talent Pool:&lt;/strong&gt; White Magic&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Attribute Bonus:&lt;/strong&gt; +1 Magical Aptitude&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Hit Die:&lt;/strong&gt; d6&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Talent:&lt;/strong&gt; Exorcism&lt;/p&gt;
&lt;h2 id="exorcism"&gt;Exorcism&lt;/h2&gt;
&lt;p&gt;The Priest has the divine gift and knowledge to pull a demon out of a possession victim. She makes a Willpower roll against the demon’s Challenge Level, and if the total is higher, the demon vacates the possessed body as a cloud of roiling black smoke. The victim then returns to his original stats, and if this causes any damage he has sustained to exceed his normal maximum Hit Points, he dies instantly. If his Willpower was at least half of the demon’s, he remembers everything he did while possessed. A failed exorcism increases the Challenge Level of the target demon by 1.&lt;/p&gt;
&lt;h1 id="about-profession-talents"&gt;About Profession Talents&lt;/h1&gt;
&lt;p&gt;Every profession gets a unique Talent with this mechanic, and one that scales with the character’s power. She will never out-level her profession Talent.&lt;/p&gt;
&lt;p&gt;Also, by adding “Profession Talents,” we add another option for expansion down the road.&lt;/p&gt;</description><author>Ben Overmyer's Site</author><pubDate>Sat, 06 Apr 2013 03:00:00 GMT</pubDate><guid isPermaLink="true">https://benovermyer.com/blog/2013/04/profession-talents-for-ingenium/</guid></item><item><title>Unlocking Hillsborough</title><link>https://danpalmer.me/2013-04-06-unlocking-hillsborough/</link><description>&lt;p&gt;I&amp;rsquo;m writing this on the train home from &lt;a href="http://rewiredstate.org"&gt;Rewired State&lt;/a&gt;&amp;rsquo;s latest event: &lt;a href="http://rewiredstate.org/hacks/national-hack-the-government-2013"&gt;National Hack the Government Day 2013&lt;/a&gt; (&lt;a href="http://eventifier.co/event/nhtg13/"&gt;event summary page&lt;/a&gt;). It was another great event with the same friendly atmosphere that goes along with so many (especially Rewired State&amp;rsquo;s) developer events. My friend &lt;a href="http://twitter.com/elliotjh"&gt;Elliot&lt;/a&gt; and I won in one of the categories, and so this post is mostly about what we did, how we did it, and why we think it&amp;rsquo;s important.&lt;/p&gt;</description><author>Dan Palmer</author><pubDate>Sat, 06 Apr 2013 02:00:00 GMT</pubDate><guid isPermaLink="true">https://danpalmer.me/2013-04-06-unlocking-hillsborough/</guid></item><item><title>Why I Blog</title><link>/2013/03/31/Why-I-Blog/</link><description>&lt;p&gt;I blog because I&amp;rsquo;m lazy. There&amp;rsquo;s more too it though: In any given day I may explain something to someone, the first time I do this I make a bit of a mental note. The second time I do this, especially within a short time frame I make a physical note of this in the form of the title of a blog post. Once I&amp;rsquo;m already to a second time of doing this its almost inevitable I&amp;rsquo;ll continue repeating myself – and its valuable to others.&lt;/p&gt;
&lt;h3 id="becoming-replaceable"&gt;
&lt;div&gt;
Becoming replaceable
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;My goal in doing this is actually to become heavily replaceable. Keeping information locked away means I&amp;rsquo;m the only one capable of doing it. Making myself highly replaceable means I can continue to work on new and interesting things.&lt;/p&gt;
&lt;h3 id="lots-can-be-shared"&gt;
&lt;div&gt;
Lots can be shared
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;I don&amp;rsquo;t recall exactly where I read it, I believe it may have been &lt;a href="http://www.twitter.com/monkchips"&gt;James Governor&lt;/a&gt;, but any email that doesn&amp;rsquo;t contain proprietary info or trade secrets should be blogged. I fully expect the sentiment around this is something to the effect that anything that contains good ideas/learning/processes can also be helpful to others.&lt;/p&gt;
&lt;h3 id="distilling-information"&gt;
&lt;div&gt;
Distilling information
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;For every time I explain something I often linger about one piece too long, hit something that doesn&amp;rsquo;t need to be discussed, or miss something entirely. Putting an idea or guide together in written form you re-read it, which is hard to do when simply verbally explaining. If you&amp;rsquo;ve video taped yourself before for public speaking practice you know this is an awkward but valuable experience. Its much easier to practice as an exercise in writing.&lt;/p&gt;
&lt;h3 id="economies-of-scale"&gt;
&lt;div&gt;
Economies of scale
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;While in some ways I am absolutely lazy when I blog I get economies of scale I couldn&amp;rsquo;t otherwise have. On a given day on a high day I may have 15 one on one conversations. Every post has the opportunity to become a one on one conversation.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;One on one conversation is my preferred method instead of in a large group. In a related area I&amp;rsquo;ve been having an ongoing conversation wth &lt;a href="http://www.twitter.com/rwdaigle"&gt;Ryan Daigle&lt;/a&gt; on whether blog posts should have comments. I opt for having you reach out to me directly via &lt;a href="mailto:craig.kerstiens@gmail.com"&gt;email&lt;/a&gt;, he believes comments should be available. Putting this in words crystalizes that to me its just relative to how you prefer to interact.&lt;/em&gt;&lt;/p&gt;
&lt;h3 id="summary"&gt;
&lt;div&gt;
Summary
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;As with any other post, this is one conversation I&amp;rsquo;ve now had a few times over. Hopefully this gives some basis to why I feel others should contribute content as well. Whether you want make your self dispensible, refine your thoughts, or reach new economies of scale I believe its a worthwhile exercise for many. If you have other reasons you feel its valuable as always please &lt;a href="mailto:craig.kerstiens@gmail.com"&gt;reach out&lt;/a&gt;.&lt;/p&gt;</description><author>CRAIG KERSTIENS</author><pubDate>Sun, 31 Mar 2013 23:55:56 GMT</pubDate><guid isPermaLink="true">/2013/03/31/Why-I-Blog/</guid></item><item><title>Being Defiant: Finishing Out Rift's Free Tier</title><link>https://benovermyer.com/blog/2013/03/being-defiant-finishing-out-rifts-free-tier/</link><description>&lt;p&gt;Today is the last day of March and the last day I’ll be writing about the MMORPG Rift as it applies to tabletop RPGs. If you’ve missed any of these posts, you might want to check them out. Go ahead, I’ll wait.&lt;/p&gt;
&lt;p&gt;This post is a retrospective and has little to do with tabletop RPGs. If you’re just looking for new ways to experience your favorite RPG, come back next week – I’ve got something very interesting planned!&lt;/p&gt;
&lt;img class="photo" src="https://benovermyer.com/processed_images/rift-sidious.0a3618da0dec8507.jpg" /&gt;
&lt;p&gt;Generally, it takes me about one month to get bored of an MMORPG. I’ll steep myself in the lore, powerlevel up to about 75-90% of endgame, get involved in crafting, and then… burn out. The novelty factor wears off, and I stop playing.&lt;/p&gt;
&lt;p&gt;Most of the time, it’s not a conscious decision to stop playing. I’ll just go a day without playing the game, since I find something else I want to do more that one day. Then two days go by. Then three. Before I realize it, it’s been over a week since I last logged in, and I have no intention of going back.&lt;/p&gt;
&lt;p&gt;A rare handful of MMORPGs keep my interest for longer than that. The list so far has been Star Wars: Galaxies and World of Warcraft. Now, I’m adding Rift to the list.&lt;/p&gt;
&lt;img class="photo" src="https://benovermyer.com/processed_images/rift-amun.bb92924a55ecf1be.jpg" /&gt;
&lt;p&gt;I originally reinstalled the game because it was going to be free and I’d heard good things about Dimensions. So, I did, and started this blog series to tie the game to tabletop gaming. It just clicked. The novelty factor has worn off, and I’m still playing. That bodes well for its longevity, and I’m going to resubscribe.&lt;/p&gt;
&lt;p&gt;The Dimensions are awesome. The fishing is weirdly addictive. And playing an MMO with a great and friendly community is refreshing.&lt;/p&gt;
&lt;p&gt;If you’re interested in trying it out, jump on the Faeblight server and look me up. I’ll be playing either my Eth cleric Amun or my Kelari mage Sidious.&lt;/p&gt;</description><author>Ben Overmyer's Site</author><pubDate>Sun, 31 Mar 2013 02:00:00 GMT</pubDate><guid isPermaLink="true">https://benovermyer.com/blog/2013/03/being-defiant-finishing-out-rifts-free-tier/</guid></item><item><title>Being Defiant: Currencies Beyond Gold and Silver</title><link>https://benovermyer.com/blog/2013/03/being-defiant-currencies-beyond-gold-and-silver/</link><description>&lt;p&gt;In Rift, there are five ways to acquire new armor and weapons. You can take it from the corpses of monsters you kill. You can run through dungeons, collecting new pieces from the tougher monsters and bosses. You can run from rift to rift, defeating the planar invaders and getting new gear from the rift rewards. You can craft it or purchase it from other players who craft gear. You can also use special currencies earned through various means to purchase new gear from special NPC merchants.&lt;/p&gt;
&lt;p&gt;In existing tabletop RPGs, there are really only three ways to get gear – buying it from merchants, looting bodies, and finding it lying around in dungeons.&lt;/p&gt;
&lt;p&gt;Many MMORPG loot mechanics won’t work in a tabletop RPG because the gear just appears in the player’s inventory when certain conditions are met. In a tabletop game, gear rarely just appears. I’ve touched on crafting in tabletop RPGs before, so this time around I’ll focus on special currencies.&lt;/p&gt;
&lt;h1 id="rift-s-currency-for-planar-invasions"&gt;Rift’s Currency for Planar Invasions&lt;/h1&gt;
&lt;p&gt;In games like Rift and World of Warcraft, you receive units of these currencies for achieving feats of excellence. For example, in Rift, you earn “planarite” and “sourcestone” for defeating planar invaders. Sourcestone is much rarer than planarite, each unit of its most common version being worth 50 planarite. It’s only awarded for helping defeat special rift events like full invasions.&lt;/p&gt;
&lt;img alt="A screenshot of the currencies tab in Rift" class="photo" src="https://benovermyer.com/blog/2013/03/being-defiant-currencies-beyond-gold-and-silver/currencies-tab.jpg" /&gt;
&lt;p&gt;While there’s only one version of planarite, there are multiple types of sourcestone, each being dropped by progressively more difficult planar invaders. In order to get the higher sourcestones, you must defeat higher-level invaders.&lt;/p&gt;
&lt;p&gt;All of these can be traded in for impressive, rare gear. The best items cost a combination of sourcestone and planarite.&lt;/p&gt;
&lt;h1 id="how-to-deal-with-special-currencies-in-tabletop-games"&gt;How to Deal with Special Currencies in Tabletop Games&lt;/h1&gt;
&lt;p&gt;In tabletop RPGs, by comparison, you’re looking at a single currency – gold, credits, silver, whatever. In some games there are multiple types of currency but only one “track.” For example, in Dungeons &amp;amp; Dragons, you have copper, silver, electrum, gold, and platinum, but they’re all just higher denominations of each other. This currency is accepted everywhere from all merchants.&lt;/p&gt;
&lt;p&gt;To make sense, a unique currency needs to be widespread in use and creation, and to make sense in a setting, the powers-that-be need to allow that currency to exist alongside the official currency of the realm. That last part is historically difficult to come by, but in fictional worlds, we can gloss over that fact.&lt;/p&gt;
&lt;p&gt;The best reason to include a currency like this is to gate powerful gear behind difficult tasks. It also provides a way to uniquely theme gear and work in factional alliances and reputation.&lt;/p&gt;
&lt;p&gt;You could make up a large order dedicated to service to a particular god, for example. Instead of just being all priests or all paladins, this order could include a variety of skilled craftsmen who produce special gear. The craftsmen may only part with the gear in exchange for a special currency that only the order uses. Higher-ranking members of this order may be giving the PCs quests, and in gratitude for their help, rewards them with some of the order’s currency.&lt;/p&gt;
&lt;p&gt;This also offers role-playing opportunities, oddly enough. Possession of particular currencies could trigger interesting reactions from NPCs. Maybe they’re associated with the producer of the currency. Maybe they’re afraid of the producer. Maybe it’s more complicated than that.&lt;/p&gt;
&lt;p&gt;If it makes sense for your setting, try adding a special currency and unique rewards for that currency to your next adventure. It just might take your campaign in a very interesting direction.&lt;/p&gt;</description><author>Ben Overmyer's Site</author><pubDate>Sun, 24 Mar 2013 02:00:00 GMT</pubDate><guid isPermaLink="true">https://benovermyer.com/blog/2013/03/being-defiant-currencies-beyond-gold-and-silver/</guid></item><item><title>Bird Watching</title><link>https://blog.nawaz.org/posts/2013/Mar/bird-watching/</link><description>&lt;p&gt;A friend of mine recently took his bird watching hobby to another
level. Armed with a 400mm lens, a scope and a sturdy tripod, he and his
wife scour the local area for birds. The absence of leaves makes it
easier to find them. &lt;a class="footnote-reference" href="#footnote-1" id="footnote-reference-1"&gt;[1]&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I met up with them …&lt;/p&gt;</description><author>Beetle Space</author><pubDate>Fri, 15 Mar 2013 09:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.nawaz.org/posts/2013/Mar/bird-watching/</guid></item><item><title>Prioritizing and Planning within Heroku Postgres</title><link>/2013/03/13/planning-and-prioritizing/</link><description>&lt;p&gt;Over a year ago I blogged about Heroku&amp;rsquo;s approach to &lt;a href="http://www.craigkerstiens.com/2011/11/02/how-heroku-works-teams-tools/"&gt;Teams and Tools&lt;/a&gt;. Since that time Heroku has grown from around 25 people to over 100, we&amp;rsquo;ve continued to iterate and find new tools that work for how we do things. For many of the &lt;a href="http://www.amazon.com/Inspired-Create-Products-Customers-Love/dp/0981690408?ref=as_li_tf_tl?ie=UTF8&amp;amp;tag=mypred-20&amp;amp;linkCode=as2&amp;amp;camp=1789&amp;amp;creative=9325&amp;amp;creativeASIN=0932633439"&gt;product management&lt;/a&gt; and software engineering books I&amp;rsquo;ve read I&amp;rsquo;ve yet to find something that helps a team priorize in a fashion I that feels right.&lt;/p&gt;
&lt;p&gt;One process emerged nearly a year ago from within the &lt;a href="https://postgres.heroku.com"&gt;Heroku Postgres&lt;/a&gt; team and is now followed by many others. Within a team this process is now commonly conducted each 6 months. Lets take a look at how this process looks&lt;/p&gt;
&lt;h3 id="it-starts-with-ideas"&gt;
&lt;div&gt;
It Starts with Ideas
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;Hopefully having ideas of things to work on isn&amp;rsquo;t a problem, if it is just go spend some time with customers – listen to their problems, see how they use the product, then come back and write down the ideas. For most teams this is simply an excercise of thinking back and writing it down. Some teams at Heroku have resorted to keeping running backlogs of things they&amp;rsquo;d like to do this. We do this by keeping a Trello board which columns for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;New ideas&lt;/li&gt;
&lt;li&gt;Ponies&lt;/li&gt;
&lt;li&gt;Stallions&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;Ponies and Stallions are things that would be great to do, however a sizeable amount of work must be done on them and we&amp;rsquo;re not currently tackling them. Ponies are less sizeable and likely to get done not in coming weeks but perhaps in coming months up to a year. Stallions are great but large effort and may or may not get done but in the category of things we would like to be able to do.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Once you&amp;rsquo;ve got your ideas whether in your head on a backlog we begin by writing them out typically on sticky notes or index cards.&lt;/p&gt;
&lt;h3 id="laying-it-all-out"&gt;
&lt;div&gt;
Laying it all out
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;From here we create a simple grid:&lt;/p&gt;
&lt;p&gt;&lt;img alt="grid" src="https://f.v1.n0.cdn.getcloudapp.com/items/1x1J2u0g390C1u1A1R1a/Screenshot_3_8_13_10_37_AM.png" /&gt;&lt;/p&gt;
&lt;p&gt;The grid has two axis. One is for impact the other for difficulty. At this point we aim to lay out every idea that we&amp;rsquo;ve already written down into a quadrant. Commonly this is done at team offsites where the team is free of distractions and able to devote appropriate time to it. Being able to accomplish this in one sitting with the team is important to having cohesion around the result.&lt;/p&gt;
&lt;h3 id="a-plan"&gt;
&lt;div&gt;
A plan
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;At this point hopefully its quite obvious what you want to tackle. If there&amp;rsquo;s anything in the top right it should be an easy win for something for you to focus on. From we often transcribe this into a powerpoint/keynote document and highlight things that we will definitely aim to accomplish in the next 6 months as well as things we&amp;rsquo;re intentionally not working on. This leaves us with an artifact of both things we will work on and explicit things we wont work on.&lt;/p&gt;
&lt;p&gt;&lt;img alt="grid" src="https://f.v1.n0.cdn.getcloudapp.com/items/2J3K2E0q2z2P0y0C0M3B/Screenshot_3_9_13_8_45_AM.png" /&gt;&lt;/p&gt;
&lt;h3 id="what-works-for-us"&gt;
&lt;div&gt;
What works for us
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;In general we try to have more work than we can tackle to ensure we&amp;rsquo;re constrained in a good form and not wasting idle time of people. Ensuring we&amp;rsquo;re selective about the things we&amp;rsquo;re working on and that we&amp;rsquo;re working on the right things works for us. We&amp;rsquo;ve found this simple exercise valuable for many teams to plan and ensure we&amp;rsquo;re working on those right things. Of course this may not work for everyone but for our goals and culture aligns well for us.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;If you&amp;rsquo;ve got simple but unique techniques that work for your team as always would love to hear about them – &lt;a href="mailto:craig.kerstiens@gmail.com"&gt;craig.kerstiens@gmail.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</description><author>CRAIG KERSTIENS</author><pubDate>Wed, 13 Mar 2013 22:55:56 GMT</pubDate><guid isPermaLink="true">/2013/03/13/planning-and-prioritizing/</guid></item><item><title>Just how slow are Django templates?</title><link>https://tomforb.es/blog/just-how-slow-are-django-templates/</link><description>Edit 2: I made a Django debug toolbar panel that profiles your Django templates and all their components. You can find it here: https://pypi.python.org/pypi/django-debug-toolbar-template-timings Edit: It appears that Django does have some form of template caching. The graphs have been updated to inc...</description><author>Tom Forbes</author><pubDate>Wed, 13 Mar 2013 20:17:38 GMT</pubDate><guid isPermaLink="true">https://tomforb.es/blog/just-how-slow-are-django-templates/</guid></item><item><title>Fixing Database Connections in Django</title><link>/2013/03/07/Fixing-django-db-connections/</link><description>&lt;p&gt;If you&amp;rsquo;re looking to get better performance from your Django apps you can check out &lt;a href="http://www.amazon.com/Pro-Django-Experts-Voice-Development/dp/1430210478?ref=as_li_tf_tl?ie=UTF8&amp;amp;tag=mypred-20&amp;amp;linkCode=as2&amp;amp;camp=1789&amp;amp;creative=9325&amp;amp;creativeASIN=0932633439"&gt;Pro Django&lt;/a&gt;, &lt;a href="http://www.amazon.com/PostgreSQL-High-Performance-Gregory-Smith/dp/184951030X?ref=as_li_tf_tl?ie=UTF8&amp;amp;tag=mypred-20&amp;amp;linkCode=as2&amp;amp;camp=1789&amp;amp;creative=9325&amp;amp;creativeASIN=0932633439"&gt;PostgreSQL High Performance&lt;/a&gt;, or read some my earlier posts on &lt;a href="http://www.craigkerstiens.com/2013/01/10/more-on-postgres-performance/"&gt;Postgres Performance&lt;/a&gt;. All of these are of course good things to do – you can also start by correcting an incredibly common but also painful performance issue, that until 1.6 is unaddressed in Django.&lt;/p&gt;
&lt;p&gt;Django&amp;rsquo;s current default behavior is to establish a connection for each request within a Django application. In many cases any particularly in distributed cloud environments this is a large time sink of your response time. An example application running on &lt;a href="http://www.heroku.com"&gt;Heroku&lt;/a&gt; shows a typical connection time of 70ms. A large part of this time is the SSL negotiation that occurs in connecting to your database, &lt;em&gt;which is a good practice to ensure security of your data&lt;/em&gt;. Regardless, this is a long time in simply establishing a connection. As a point of comparisson its commonly encourage that most queries to your database are under 10ms.&lt;/p&gt;
&lt;p&gt;An example that highlights this in a small lightweight application shows the bulk of a request time being within a connection displayed by &lt;a href="http://www.newrelic.com"&gt;New Relic&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img alt="connection time" src="https://f.cl.ly/items/0X3u0e3Q3G0L19263k2Z/Screenshot_3_6_13_1_12_PM.png" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="connection time" src="https://f.cl.ly/items/1h2w450F3n0X1m1c0S38/Screenshot_2_22_13_3_18_PM-2.png" /&gt;&lt;/p&gt;
&lt;p&gt;One option to remedy this is by running a connection pooler on your Database side such as &lt;a href="http://www.pgpool.net/mediawiki/index.php/Main_Page"&gt;Pgpool&lt;/a&gt; or &lt;a href="http://pgfoundry.org/projects/pgbouncer"&gt;PgBouncer&lt;/a&gt;. In fact &lt;a href="http://www.askthepony.com/blog/2011/07/getting-django-on-heroku-prancing-8-times-faster/"&gt;Ask the Pony&lt;/a&gt; already highlighted these potential gains. While running an external DB they&amp;rsquo;re essentially testing the benefits of conncetion pooling. This is an obvious gain and can be in a much more lightweight format.&lt;/p&gt;
&lt;h3 id="connection-pooling-in-django"&gt;
&lt;div&gt;
Connection Pooling in Django
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;As Django establishes a connection on each request it has an opportunity to both pool connections and persist connections. There are two major options for pooling, each works quite well with Django and provides some dramatic improvements. While the first request may take the 70ms of connection time, subsequent requests show absolutely no connection time since the connection already exists. This is highlighed by these two comparissons of before and after in actually the times it grabs a connection:&lt;/p&gt;
&lt;p&gt;&lt;img alt="before" src="https://f.cl.ly/items/2H0J2x3P2L2K2n0M3i38/Screen_Shot_2013-02-22_at_8.28.21_PM.png" /&gt;
&lt;img alt="after" src="https://f.cl.ly/items/0T1l1I03433u0c0t3e2o/Screen_Shot_2013-02-22_at_8.28.36_PM.png" /&gt;&lt;/p&gt;
&lt;p&gt;Clearly theres plenty of value to having a persistent connection or a pool within Django itself. As of today theres a few options for that:&lt;/p&gt;
&lt;h3 id="django-postgrespool"&gt;
&lt;div&gt;
Django-PostgresPool
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;The first &lt;a href="https://github.com/kennethreitz/django-postgrespool"&gt;Django-PostgresPool&lt;/a&gt; is created by &lt;a href="http://twitter.com/kennethreitz"&gt;kennethreitz&lt;/a&gt;. As in general I&amp;rsquo;d encourage the use of &lt;a href=""&gt;dj_database_url&lt;/a&gt; you can easily begin using his package (once installed) with:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import dj_database_url
DATABASE = { 'default': dj_database_url.config() }
DATABASES['default']['ENGINE'] = 'django_postgrespool'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;An important thing to note is if you&amp;rsquo;re using &lt;a href="http://south.aeracode.org/"&gt;South&lt;/a&gt; you&amp;rsquo;ll also want to setup the adapter for it:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SOUTH_DATABASE_ADAPTERS = {
'default': 'south.db.postgresql_psycopg2'
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="djorm-ext-pool"&gt;
&lt;div&gt;
djorm-ext-pool
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;The second option &lt;a href="https://github.com/niwibe/djorm-ext-pool"&gt;djorm-ext-pool&lt;/a&gt; is created by &lt;a href="http://twitter.com/niwibe"&gt;niwibe&lt;/a&gt;. Once you&amp;rsquo;ve installed &lt;code&gt;djorm-ext-pool&lt;/code&gt; you then add it to your &lt;code&gt;INSTALLED_APPS&lt;/code&gt; within your &lt;code&gt;settings.py&lt;/code&gt;. From here then you can setup your pool:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;DJORM_POOL_OPTIONS = {
&amp;quot;pool_size&amp;quot;: 20,
&amp;quot;max_overflow&amp;quot;: 0
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="django-db-pool"&gt;
&lt;div&gt;
django-db-pool
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;The third and final option is &lt;a href="https://github.com/gmcguire/django-db-pool"&gt;django-db-pool&lt;/a&gt;. You can set it up with:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;DATABASES = {'default': dj_database_url.config()}
DATABASES['default']['ENGINE'] = 'dbpool.db.backends.postgresql_psycopg2'
DATABASES['default']['OPTIONS'] = {
'MAX_CONNS': 10
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="gotchas"&gt;
&lt;div&gt;
Gotchas
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;Each of these does work with recent versions of Django, though in some cases there are gotchas. If using a prodution worthy python web server such as Gunicorn or uwsgi and running with gevent or eventlet some edge cases can present themselves. Regardless of potential gotchas it is worth attempting this and of course providing feedback to maintainers and the community as you find those.&lt;/p&gt;
&lt;h3 id="the-future"&gt;
&lt;div&gt;
The future
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;Django more recently has directly started to address these issues of large costs of establishing a connection. The first major step here is &lt;a href="https://github.com/django/django/commit/2ee21d"&gt;this patch&lt;/a&gt; from &lt;a href="http://twitter.com/aymericaugustin"&gt;Aymeric&lt;/a&gt;. You can find more dicussion around this particular patch &lt;a href="https://groups.google.com/forum/#!topic/django-developers/NwY9CHM4xpU/discussion"&gt;here&lt;/a&gt;. Essentially with this patch which will hit in Django 1.6 developers then get a persistent connection which will help reduce the time. If you&amp;rsquo;re interested in trying the 1.6 master you can do this by adding it to your requirements.txt as:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;https://github.com/django/django/archive/master.zip
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At this point it does not introduce pooling which could allow even more gains, though I&amp;rsquo;m sure if there&amp;rsquo;s enough need it&amp;rsquo;ll be on a roadmap at some point. Though, as it stands today before 1.6 your best bet is one of the above options.&lt;/p&gt;</description><author>CRAIG KERSTIENS</author><pubDate>Thu, 07 Mar 2013 22:55:56 GMT</pubDate><guid isPermaLink="true">/2013/03/07/Fixing-django-db-connections/</guid></item><item><title>Simple database read scaling without sharding in rails</title><link>/2013/03/06/Simple-database-read-scaling-without-sharding-in-rails/</link><description>&lt;p&gt;In an earlier post I provided a high level &lt;a href="http://www.craigkerstiens.com/2012/11/30/sharding-your-database/"&gt;overview of sharding&lt;/a&gt;. Sharding while a very solid approach to scaling capacity versus simply only relying on vertical scaling can also be a time intensive one. Additionally in some cases certain sites may only need extra capacity for a short lived period of time. Fortunately theres a nice middle ground alternative for scaling capacity that works well in quite a few cases. It even has a benefit that can potentially in place of sharding.&lt;/p&gt;
&lt;p&gt;This method results in scaling your reads to replica databases, you can do this on Heroku by taking advantage of followers. A follower is a read only database on Heroku Postgres that receives asynchronous updates of your data usually only lagging a very few commits behind. This means you can write all of your data to the leader (main) database, and then read from another.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;While you can arbitrarily do this there&amp;rsquo;s some major benefits to doing it based on the models. This is because Postgres maintains a cache on each instance its running on. Though you may have the same dataset, Postgres maintains frequently accessed or queried data in the cache giving you better performance. For more on this you can read earlier posts on &lt;a href="http://www.craigkerstiens.com/2012/11/30/sharding-your-database/"&gt;PostgreSQL Performance&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;h3 id="setting-it-up-with-rails"&gt;
&lt;div&gt;
Setting it up with Rails
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;With a follower database created you can begin adding support for this to your application. The first thing is to add the gem to your Gemfile:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gem 'ar-octopus', :require =&amp;gt; &amp;quot;octopus&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then of course to install it with &lt;code&gt;bundle install&lt;/code&gt;. Now we can actually begin to add the code needed to have specific models access the follower.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;octopus:
shards:
shard_sqlite:
adapter: sqlite3
database: db/db_one.sqlite3
pool: 5
timeout: 5000
shard_pgsql:
adapter: postgresql
username: postgres
password:
database: db_two
encoding: unicode
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;a&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Project &amp;lt; ActiveRecord::Base
octopus_establish_connection(:adapter =&amp;gt; &amp;quot;sqlite3&amp;quot;, :database =&amp;gt; &amp;quot;db_one&amp;quot;)
end&lt;/code&gt;&lt;/pre&gt;</description><author>CRAIG KERSTIENS</author><pubDate>Wed, 06 Mar 2013 22:55:56 GMT</pubDate><guid isPermaLink="true">/2013/03/06/Simple-database-read-scaling-without-sharding-in-rails/</guid></item><item><title>Finding fried chicken with C-Sharp, Mono and ServiceStack</title><link>https://tomforb.es/blog/finding-fried-chicken-with-c-mono-and-servicestack/</link><description>I think I just solved a very first world problem - Where to find the nearest takeaway that sells fried chicken. When you are stumbling out of Spiders at 1am in the morning and craving some SFC goodness the last thing you want to do is wander the backstreets of hull in the cold looking for an open ta...</description><author>Tom Forbes</author><pubDate>Sat, 02 Mar 2013 23:57:06 GMT</pubDate><guid isPermaLink="true">https://tomforb.es/blog/finding-fried-chicken-with-c-mono-and-servicestack/</guid></item><item><title>Why Code Search is Difficult</title><link>https://boyter.org/2013/02/why-code-search-is-difficult/</link><description>&lt;p&gt;I was chatting with a colleague the other day and he was asking me why code search is a difficult problem. After all its not quite as dynamic as the web so it should be easier to index.&lt;/p&gt;
&lt;p&gt;In truth its a mixed bag. Some things like like crawling are easy, others such as indexing are much harder then you would think. I thought I would write down why this is.&lt;/p&gt;</description><author>Ben E. C. Boyter</author><pubDate>Fri, 01 Mar 2013 01:26:36 GMT</pubDate><guid isPermaLink="true">https://boyter.org/2013/02/why-code-search-is-difficult/</guid></item><item><title>Getting more out of psql (The PostgreSQL CLI)</title><link>/2013/02/21/more-out-of-psql/</link><description>&lt;p&gt;&lt;em&gt;After my last post I had a variety of readers reach out about many different tweaks they&amp;rsquo;d made to their workflows using with psql. One people &lt;a href="https://github.com/chanmix51/"&gt;Grégoire Hubert&lt;/a&gt; had a wondeful extensive list of items. Grégoire has been a freelance in web development and he has worked with Postgresql for some time now in addition to being the author of Pomm. Without further ado heres what he has to say on how he uses psql:&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="get-the-most-of-psql"&gt;
&lt;div&gt;
Get the most of psql
&lt;/div&gt;
&lt;/h2&gt;
&lt;p&gt;Psql, the CLI postgreSQL client, is a powerful tool. Sadly, lot of developers are not aware of the features and instead look for a GUI to provide what they need. Let&amp;rsquo;s fly over what can psql do for you.&lt;/p&gt;
&lt;h2 id="feel-yourself-at-home"&gt;
&lt;div&gt;
Feel yourself at home
&lt;/div&gt;
&lt;/h2&gt;
&lt;p&gt;One of the most common misconception people have about CLI is «They are a poor user interface». C&amp;rsquo;mon, the CLI is &lt;strong&gt;the most efficient user interface ever&lt;/strong&gt;. There is nothing to disturb you from what you are doing and you are by far fastest without switching to your mouse all the time. Let&amp;rsquo;s see how we can configure psql at our convenience.&lt;/p&gt;
&lt;p&gt;First, you&amp;rsquo;ll have managed to choose a nice and fancy &lt;a href="http://hivelogic.com/articles/top-10-programming-fonts"&gt;terminal font&lt;/a&gt; like monofur or inconsolata. Do not underestimate the power of the font&lt;/p&gt;
&lt;p&gt;&lt;img alt="monofur font in action" src="https://public.coolkeums.org/github/power_font.png" /&gt;&lt;/p&gt;
&lt;p&gt;The nice line style shown above can be set with &lt;code&gt;\pset linestyle unicode&lt;/code&gt; and &lt;code&gt;\pset border 2&lt;/code&gt;. This is just an example of the many environment variables you can play with to get your preferred style of working out of psql.&lt;/p&gt;
&lt;p&gt;For example, I found the character ¤ the most accurate to express nullity (instead of default &lt;code&gt;NULL&lt;/code&gt;). Let&amp;rsquo;s just &lt;code&gt;\pset null ¤&lt;/code&gt; and here it is:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT * FROM very_interesting_stat;
┌──────┬──────┬──────┬──────┬──────┐
│ a │ b │ c │ d │ e │
├──────┼──────┼──────┼──────┼──────┤
│ 9.06 │ ¤ │ ¤ │ ¤ │ ¤ │
│ 7.30 │ 3.55 │ 7.57 │ 3.31 │ ¤ │
│ 7.20 │ 5.08 │ ¤ │ 6.58 │ 5.90 │
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Another hugely value to get environment variables is colors in the prompt. Colors in the prompt are important because it makes easier to spot where output starts and ends between two interactions at the console. The &lt;a href="http://www.postgresql.org/docs/9.2/static/app-psql.html#APP-PSQL-PROMPTING"&gt;PROMPT1&lt;/a&gt; environment variable will even let you set an indicator to notify you are inside a transaction or not, give this a try for a sweet surprise&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;\set PROMPT1 '%[%033[33;1m%]%x%[%033[0m%]%[%033[1m%]%/%[%033[0m%]%R%# '
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I also like to disable the pager by default &lt;code&gt;\pset pager off&lt;/code&gt; and display the time every issued query takes &lt;code&gt;\timing&lt;/code&gt;. If you are used to psql, you may notice in the picture above, some content is wrapped. This is &lt;code&gt;\pset format wrapped&lt;/code&gt; option.&lt;/p&gt;
&lt;p&gt;Of course, writing all that on every connection would be a pain, so just write them in a &lt;code&gt;~/.psqlrc&lt;/code&gt; file, it will be sourced every time psql is launched.&lt;/p&gt;
&lt;p&gt;If you are familiar with &lt;code&gt;bash&lt;/code&gt; or other recent unix shells, you might also declare aliases in your configuration file. You can do the same with psql. For example if you want to have a query for slow queries such as from this &lt;a href="/2013/01/10/more-on-postgres-performance/"&gt;earlier post&lt;/a&gt; but not have to remember the query every time you can set it up as:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;\set show_slow_queries
'SELECT
(total_time / 1000 / 60) as total_minutes,
(total_time/calls) as average_time, query
FROM pg_stat_statements
ORDER BY 1 DESC
LIMIT 100;'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, just entering &lt;code&gt;:show_slow_queries&lt;/code&gt; in your psql client will launch this query and give you the results:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; total_time | avg_time | query
------------------+------------------+------------------------------------------------------------
295.761165833319 | 10.1374053278061 | SELECT id FROM users WHERE email LIKE ?
219.138564283326 | 80.24530822355305 | SELECT * FROM address WHERE user_id = ? AND current = True
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="psql-at-your-fingertips"&gt;
&lt;div&gt;
Psql at your fingertips
&lt;/div&gt;
&lt;/h2&gt;
&lt;p&gt;Now you have got a fancy prompt, here is the real question you ask, what can psql do for me ? and &lt;code&gt;\?&lt;/code&gt; has all of the answers. It has built-in queries to describe almost all database objects from tables to operators, indexes, triggers etc&amp;hellip; with clever auto-completion. Not only completion on tables and columns &amp;ndash; but also on aliases (sweet), &lt;strong&gt;SQL commands&lt;/strong&gt; (w00t) and database objects.&lt;/p&gt;
&lt;p&gt;Now we can enter some SQL commands. As usual, you need to check in the documentation how the heck to write this damn &lt;code&gt;ALTER TABLE&lt;/code&gt;. Relax, psql proposes inline documentation. Just enter &lt;code&gt;\h alter table&lt;/code&gt; (auto complete w00t) and you ll be ok.&lt;/p&gt;
&lt;h3 id="interacting-with-your-editor"&gt;
&lt;div&gt;
Interacting with your editor
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;psql provides two very handy commands: \e and \i. This last command sources a sql file in the client&amp;rsquo;s current session. \e edits the last command using the editor defined in the &lt;code&gt;EDITOR&lt;/code&gt; shell environment variables (aka vim). This grant you with real editor feature when it comes to writing long queries. What psql does, it saves the buffer in a temporary file and fires up the editor with that file. Once the editor is terminated, psql sources the file. Of course, you can use your editor to save queries in other places where they would be under version control, but the \e has a serious limitation: it spawns only the last query. Even if you sent several queries on the same line. (Note that \r clears psql&amp;rsquo;s last query buffer).&lt;/p&gt;
&lt;p&gt;Note: &lt;code&gt;\ef my_function&lt;/code&gt; opens stored function source code (With auto completion, I know, it&amp;rsquo;s awesome).&lt;/p&gt;
&lt;p&gt;Vim users can here benefit from Vim&amp;rsquo;s server mode. If you launch a vim specifying a server name (let&amp;rsquo; say &amp;ldquo;PSQL&amp;rdquo;) somewhere, and set the EDITOR variable as is &lt;code&gt;export EDITOR=&amp;quot;vim --servername PSQL --remote-tab-wait&lt;/code&gt; then psql will open a new tab on the running vim with the last query and run it as soon as you close this tab. Tmux or gnu/screen users will split their screen to have Vim and psql running on the same terminal window.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Vim, psql and tmux" src="https://public.coolkeums.org/github/vim_tmux.png" /&gt;&lt;/p&gt;
&lt;h3 id="call-a-friend"&gt;
&lt;div&gt;
Call a friend
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;Vim power users know it is possible to pipe a buffer (or selection) directly in a program that can be &amp;hellip; psql (Using the &lt;code&gt;:w !psql&lt;/code&gt; syntax). Even from the shell, you might want to take advantage of the fantastic &lt;code&gt;\copy&lt;/code&gt; feature that loads formated file in the database (I use it to load apache logs). But always having to specify connection parameters are a hassle. Let&amp;rsquo;s use shell environment instead. Psql is sensitive to the following variables:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PGDATABASE&lt;/li&gt;
&lt;li&gt;PGHOST&lt;/li&gt;
&lt;li&gt;PGPORT&lt;/li&gt;
&lt;li&gt;PGUSER&lt;/li&gt;
&lt;li&gt;PGCLUSTER (debian wrapper).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Set them once for all in you shell environment and call &lt;code&gt;psql&lt;/code&gt; to connect to the database. In case you want to skip password prompt, you can store your pass in a 600 mode access file named &lt;code&gt;.pgpass&lt;/code&gt; in your home (do not do that on shared or exposed computers). Although this is nice for development database servers, I do NOT recommend this for production servers since it should not be easy to mess with them.&lt;/p&gt;
&lt;p&gt;Resource for additional information is &amp;hellip; the man page and &lt;a href="http://www.postgresql.org/docs/9.2/static/index.html"&gt;Postgres Docs&lt;/a&gt;. All &lt;a href="http://www.postgresql.org/docs/9.2/static/index.html"&gt;PostgreSQL documentation&lt;/a&gt; is an example of what software reference documentation should be. Enjoy!&lt;/p&gt;</description><author>CRAIG KERSTIENS</author><pubDate>Thu, 21 Feb 2013 22:55:56 GMT</pubDate><guid isPermaLink="true">/2013/02/21/more-out-of-psql/</guid></item><item><title>HtmlToWord</title><link>https://tomforb.es/blog/htmltoword/</link><description>You can find the code here on github and the package here on PyPi I have written and continue to maintain a reporting system for a group of pentesters. During/after the tests the results and details are inputted into a web application using a WYSIWYG editor called Redactor (which is pretty awesome!)...</description><author>Tom Forbes</author><pubDate>Mon, 18 Feb 2013 07:38:08 GMT</pubDate><guid isPermaLink="true">https://tomforb.es/blog/htmltoword/</guid></item><item><title>How I work with Postgres – psql, My PostgreSQL Admin</title><link>/2013/02/13/How-I-Work-With-Postgres/</link><description>&lt;p&gt;On at least a weekly basis and not uncommonly multiple times in a single week I get this question:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://twitter.com/neilmiddleton"&gt;@neilmiddleton&lt;/a&gt;
I&amp;rsquo;ve been hunting for a nice PG interface that works within other things. PGAdmin kinda works, except the SQL editor is a piece of shit&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Sometimes it leans more to, what is the Sequel Pro equivilant for Postgres. My default answer is I just use psql, though I do have to then go on to explain how I use it. For those just interested you can read more below or just get the highlights here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Set your default &lt;code&gt;EDITOR&lt;/code&gt; then use \e&lt;/li&gt;
&lt;li&gt;On postgres 9.2 and up &lt;code&gt;\x auto&lt;/code&gt; is your friend&lt;/li&gt;
&lt;li&gt;Set history to unlimited&lt;/li&gt;
&lt;li&gt;&lt;code&gt;\d&lt;/code&gt; all the things&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Before going into detail on why psql works perfectly fine as an interface I want to rant for a minute about what the problems with current editors are and where I expect them to go in the future. First this is not a knock on the work thats been done on previous ones, for their time PgAdmin, phpPgAdmin, and others were valuable tools, but we&amp;rsquo;re coming to a point where theres a broader set of users of databases than ever before and empowering them is becoming ever more important.&lt;/p&gt;
&lt;p&gt;Empowering developers, DBA&amp;rsquo;s, product people, marketers and others to be comfortable with their database will lead to more people taking advantage of whats in their data. &lt;a href="/2013/01/10/more-on-postgres-performance/"&gt;pg_stat_statements&lt;/a&gt; was a great start to this laying a great foundation for valuable information being captured. Even with all of the powerful stats being captured in the statistics of PostgreSQL so many are still terrified when they see something like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; QUERY PLAN
----------------------------------------------------------------------------------------------------------------
Hash Join (cost=4.25..8.62 rows=100 width=107) (actual time=0.126..0.230 rows=100 loops=1)
Hash Cond: (purchases.user_id = users.id)
-&amp;gt; Seq Scan on purchases (cost=0.00..3.00 rows=100 width=84) (actual time=0.012..0.035 rows=100 loops=1)
-&amp;gt; Hash (cost=3.00..3.00 rows=100 width=27) (actual time=0.097..0.097 rows=100 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 6kB
-&amp;gt; Seq Scan on users (cost=0.00..3.00 rows=100 width=27) (actual time=0.007..0.042 rows=100 loops=1)
Total runtime: 0.799 ms
(7 rows)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Empowering more developers by surfacing this information in a digestable form, such as building on top of &lt;code&gt;pg_stat_statements&lt;/code&gt; tools such as &lt;a href="http://datascope.heroku.com"&gt;datascope&lt;/a&gt; by &lt;a href="http://www.twitter.com/leinweber"&gt;@leinweber&lt;/a&gt; and getting this to be part of the default admin we will truly begin empowering a new set of user.&lt;/p&gt;
&lt;p&gt;But enough of a detour, those tools aren&amp;rsquo;t available today. If you&amp;rsquo;re interested in helping build those to make the community better please reach out. For now I live in a work where I&amp;rsquo;m quite content with simple ole &lt;code&gt;psql&lt;/code&gt; here&amp;rsquo;s how:&lt;/p&gt;
&lt;h3 id="editor"&gt;
&lt;div&gt;
Editor
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;Ensuring you&amp;rsquo;ve exported your preferred editor to the environment variable &lt;code&gt;EDITOR&lt;/code&gt; when you run \e it will allow you to view and edit your last run query in your editor of choice. This works for vim, emacs, or even sublime text.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export EDITOR=subl
psql
\e
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Gives me:&lt;/p&gt;
&lt;p&gt;&lt;img alt="sublime text" src="https://f.cl.ly/items/2I0f3M0B1T3k0d290v3k/Screenshot_2_12_13_9_58_AM.png" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note you need to make sure you connect with psql and have your editor set, once you do that saving and exiting the file will then execute the query&lt;/em&gt;&lt;/p&gt;
&lt;h3 id="x-auto"&gt;
&lt;div&gt;
\x auto
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;psql has long had a method of formatting output. You can toggle this on and off easily by just running the &lt;code&gt;\x&lt;/code&gt; command. Running a basic query you get the output:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT *
FROM users
LIMIt 1;
id | first_name | last_name | email | data | created_at | updated_at | last_login
----+------------+-----------+----------------------------+------------+---------------------+---------------------+---------------------
1 | Rosemary | Wassink | Rosemary.Wassink@yahoo.com | &amp;quot;sex&amp;quot;=&amp;gt;&amp;quot;F&amp;quot; | 2010-07-01 18:16:00 | 2011-05-14 11:47:00 | 2011-06-07 23:04:00
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With toggling the output and re-running the same query we can see how its now formatted:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;\x
Expanded display is on.
craig=# SELECT * from users limit 1;
-[ RECORD 1 ]--------------------------
id | 1
first_name | Rosemary
last_name | Wassink
email | Rosemary.Wassink@yahoo.com
data | &amp;quot;sex&amp;quot;=&amp;gt;&amp;quot;F&amp;quot;
created_at | 2010-07-01 18:16:00
updated_at | 2011-05-14 11:47:00
last_login | 2011-06-07 23:04:00
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Using &lt;code&gt;\x auto&lt;/code&gt; will automatically put this in what Postgres believes is the most intelligible format to read it in.&lt;/p&gt;
&lt;h3 id="psql-history"&gt;
&lt;div&gt;
psql history
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;Hopefully this needs no justification&amp;hellip; having an unlimited history of all your queries is incredibly handy. Ensuring you set the following environment variables will ensure you never lose that query you ran several months ago again:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export HISTFILESIZE=
export HISTSIZE=
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="d"&gt;
&lt;div&gt;
\d
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;And while the last on the list one of the first things I do when connecting to any database is check out whats in it. I don&amp;rsquo;t do this by running a bunch of queries but rather checking out the schema and then poking at definitions of specific tables. &lt;code&gt;\d&lt;/code&gt; and variations on it are incredibly handy for this. Here&amp;rsquo;s a few highlights below:&lt;/p&gt;
&lt;p&gt;Listing all relations with simply &lt;code&gt;\d&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;\d
List of relations
Schema | Name | Type | Owner
--------+------------------+---------------+-------
public | products | table | craig
public | products_id_seq | sequence | craig
public | purchases | table | craig
public | purchases_id_seq | sequence | craig
public | redis_db0 | foreign table | craig
public | users | table | craig
public | users_id_seq | sequence | craig
(7 rows)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;List only all tables with &lt;code&gt;dt&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;\dt
List of relations
Schema | Name | Type | Owner
--------+-----------+-------+-------
public | products | table | craig
public | purchases | table | craig
public | users | table | craig
(3 rows)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Describe a specific relation with &lt;code&gt;\d RELATIONNAMEHERE&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;\d users
Table &amp;quot;public.users&amp;quot;
Column | Type | Modifiers
------------+-----------------------------+----------------------------------------------------
id | integer | not null default nextval('users_id_seq'::regclass)
first_name | character varying(50) |
last_name | character varying(50) |
email | character varying(255) |
data | hstore |
created_at | timestamp without time zone |
updated_at | timestamp without time zone |
last_login | timestamp without time zone |
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One more pro-tip if you&amp;rsquo;re running a transaction with many tables and forget which are involved in it you can run &amp;lsquo;\d *transaction*&amp;rsquo; and it&amp;rsquo;ll display tables curently affected.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Have a tool you prefer, have something you use daily in psql that I missed, or interested in helping create a new admin experience please reach out and lets talk craig.kerstiens at gmail.com&lt;/em&gt;&lt;/p&gt;</description><author>CRAIG KERSTIENS</author><pubDate>Wed, 13 Feb 2013 22:55:56 GMT</pubDate><guid isPermaLink="true">/2013/02/13/How-I-Work-With-Postgres/</guid></item><item><title>World of Warcraft, Modern RPGs, and the Gear Grind</title><link>https://benovermyer.com/blog/2013/02/world-of-warcraft-modern-rpgs-and-the-gear-grind/</link><description>&lt;p&gt;After a full year of not playing, yesterday I jumped back into World of Warcraft. I’d heard enough good things about the latest changes that I decided to give it another shot. It remains to be seen whether that’ll last or not. Something that did make me pause, though, is the obvious reminder that WoW is based on a gear grind. As soon as my character, a &lt;a href="http://us.battle.net/wow/en/character/kelthuzad/Exsecratus/simple" rel="external"&gt;worgen warlock named Exsecratus&lt;/a&gt;, made it to the new continent, I almost immediately replaced my raid epic gear from the Cataclysm days with greens. Today’s junk is better than yesterday’s hard-won treasure. That theme carries across many modern MMORPGs, but in all honesty, it’s also present in tabletop RPGs these days. Dungeons &amp;amp; Dragons is one of the worst culprits, but there are plenty of others. That got me to thinking. Is the gear grind necessarily bad? The perpetual need to have better equipment may seem like a thin excuse to keep people playing a game, but taken as a component of a larger and more complex system, it’s a reliable foundation to build on. In a game like &lt;a href="http://silvergryphongames.com/games/Ingenium/" rel="external"&gt;my own Ingenium&lt;/a&gt;, where if you have too many static modifiers it makes the randomization provided by the dice useless, a gear grind based on incremental bonus increases wouldn’t make much sense. However, if there were a game out there that had more complex calculation involved with point bonuses, then having a Sword +513 wouldn’t necessarily be any more unbalanced than a Sword +3. The trick is that to get to that level, you need to involve multiplication and division in the core mechanic, and that’s the real holdup for a tabletop RPG. Let’s face it - most tabletop gamers don’t want to sit and recalculate their stats every time they pick up a new item. In video games it’s possible because the game itself handles it automatically in the background without the player even being aware of the math. To introduce infinitely-scaling item stats into a tabletop RPG, you would have to do one of two things - either create a phone/tablet app to handle the math for you, or make the math so simplified that it would be possible to calculate quickly. Being a web developer by profession, I’m partial to the first one. These days it’s not unreasonable to expect gamers to have a smartphone. How would you handle bringing the gear grind into a tabletop game?&lt;/p&gt;</description><author>Ben Overmyer's Site</author><pubDate>Sun, 10 Feb 2013 02:00:00 GMT</pubDate><guid isPermaLink="true">https://benovermyer.com/blog/2013/02/world-of-warcraft-modern-rpgs-and-the-gear-grind/</guid></item><item><title>Introducing django-db-tools</title><link>/2013/02/08/Introducing-django-db-tools/</link><description>&lt;p&gt;For any successful web application there is likely to come a time when you need to conduct some large migration on the backend. I dont mean simple add a &lt;a href="http://www.craigkerstiens.com/2012/05/07/why-postgres-part-2/"&gt;column here or add an index there&lt;/a&gt;, but rather truly sizeable migrations&amp;hellip; Going from &lt;a href="http://lanyrd.com/blog/2012/lanyrds-big-move/"&gt;MySQL to Postgres&lt;/a&gt; or migrating from an older version of Postgres such as a &lt;a href="http://blog.sendhub.com/post/30041247598/how-to-upgrade-a-legacy-heroku-database"&gt;32 bit instance&lt;/a&gt; to a newer 64 bit instance. In these cases the default approach is to just schedule downtime often throwing up a splash screen saying so.&lt;/p&gt;
&lt;p&gt;For many sites this approach is simply wrong and lazy, with little effort you can improve the experience and there by ease the burden in conducting these types of migrations. By having the ability to turn your site into a read only mode which &lt;a href="http://twitter.com/simonw"&gt;Simon Wilson&lt;/a&gt; talked about in his post on Lanyrd you can still continue to operate just in a limited capacity. &lt;a href="http://www.aeracode.org/2012/11/13/one-change-not-enough/"&gt;Andrew Godwin&lt;/a&gt; further talks about some of this as well in regards to the Lanyrd move and even includes the script they used to &lt;a href="https://github.com/lanyrd/mysql-postgresql-converter/"&gt;migrate data from MySQL to Postgres&lt;/a&gt;. Though just in talking with Simon about this a week ago it occurred to me they had not released the code for their read-only mode.&lt;/p&gt;
&lt;p&gt;Finally onto the announcing, today I&amp;rsquo;m releasing &lt;a href="https://github.com/craigkerstiens/django-db-tools"&gt;django-db-tools&lt;/a&gt;. This is currently a very lightweight utility that allows you to flip your site into two modes.&lt;/p&gt;
&lt;h3 id="anonymous-mode"&gt;
&lt;div&gt;
Anonymous Mode
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;For sites that offer a bulk of their data to unauthenticated users anonymous mode will be what you want. This ensures all users appear logged out and thus cannot interact with data. To enable anonymous mode you&amp;rsquo;d simple set the environment variable or config var on heroku as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;READ_ONLY_MODE = True
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="restricting-posts"&gt;
&lt;div&gt;
Restricting POSTs
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;The other bucket of sites is one that allows users to stay logged in but not insert data. Django did not appear to have a convenient means to know whether data was actually being inserted into the DB or not. As a good practice when inserting data it should be receiving a HTTP POST. The &lt;code&gt;GET_ONLY_MODE&lt;/code&gt; mimmicks all POSTs as if they were sent via GETs thus hopefully eliminating inserting data into your application. To turn it on simply set the environment variabel or config var on heroku to:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;GET_ONLY_MODE = True
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="installing"&gt;
&lt;div&gt;
Installing
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;The tool itself is largely middleware, to install:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Run &lt;code&gt;pip install django-db-tools&lt;/code&gt; or add it to your &lt;code&gt;requirements.txt&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;db_tools&lt;/code&gt; to your &lt;code&gt;INSTALLED_APPS&lt;/code&gt; in your &lt;code&gt;settings.py&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;'dbtools.middleware.ReadOnlyMiddleware',&lt;/code&gt; to your &lt;code&gt;MIDDLEWARE_CLASSES&lt;/code&gt; in &lt;code&gt;settings.py&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="contributing"&gt;
&lt;div&gt;
Contributing
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;As with all code this is largely a work in progress. There&amp;rsquo;s many items still to do such as providing default copy and error pages and potentially handling other edge cases. I&amp;rsquo;d welcome others to contribute and give feedback if they find it helpful or how it can be improved on Github.&lt;/p&gt;</description><author>CRAIG KERSTIENS</author><pubDate>Fri, 08 Feb 2013 22:55:56 GMT</pubDate><guid isPermaLink="true">/2013/02/08/Introducing-django-db-tools/</guid></item><item><title>Want to write a search engine? Have some links</title><link>https://boyter.org/2013/01/want-to-write-a-search-engine-have-some-links/</link><description>&lt;p&gt;A recent comment I left on Hacker News managed to get quite a lot of up-votes which surprised me since it was effectively just a collection of links about search engines. You can read the full thread at &lt;a href="http://news.ycombinator.com/item?id=5129530"&gt;http://news.ycombinator.com/item?id=5129530&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Anyway since it did do so well I thought I would flesh it out with some more information. Here are a collection of posts/blogs/discussions which go into the details of how to write a search engine.&lt;/p&gt;</description><author>Ben E. C. Boyter</author><pubDate>Thu, 31 Jan 2013 01:29:36 GMT</pubDate><guid isPermaLink="true">https://boyter.org/2013/01/want-to-write-a-search-engine-have-some-links/</guid></item><item><title>Increasing the Encryption Noise Floor</title><link>https://www.petekeen.net/increasing-the-encryption-noise-floor/</link><description>&lt;p&gt;Inspired by Tim Bray's &lt;a href="https://www.tbray.org/ongoing/When/201x/2012/12/02/HTTPS"&gt;recent post about encrypting his website&lt;/a&gt;, I decided to enable and force HTTPS for bugsplat.info. The process was straightforward and, turns out, completely free. Read on to find out how and why.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h3&gt;Why?&lt;/h3&gt;
&lt;p&gt;Because I think the whole web should be encrypted and I figured I should practice what I preach. Bugsplat is primarily static html and is completely public but that doesn't mean I can't increase the encryption noise floor, so to speak. If only the websites with secret data are encrypted then just using them is suspicious. If every site is encrypted then there's nothing to be suspicious about.&lt;/p&gt;
&lt;h3&gt;How?&lt;/h3&gt;
&lt;p&gt;Bugsplat is deployed to my &lt;a href="https://clientarea.ramnode.com/aff.php?aff=142"&gt;RamNode&lt;/a&gt; VPS (notice: affiliate link) with &lt;a href="https://github.com/peterkeen/capistrano-buildpack"&gt;Capistrano::Buildpack&lt;/a&gt;, a Capistrano add-on that allows you to deploy applications using &lt;a href="https://devcenter.heroku.com/articles/buildpacks"&gt;Heroku-style buildpacks&lt;/a&gt;. Recently I added support for simply configuring HTTPS with a few options in your Capfile. Here's the relevant config from bugsplat.info's Capfile:&lt;/p&gt;
&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="ss"&gt;:use_ssl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="ss"&gt;:force_ssl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="ss"&gt;:ssl_cert_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'/etc/nginx/certs/bugsplat.info.crt'&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="ss"&gt;:ssl_key_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'/etc/nginx/certs/bugsplat.info.key'&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;:use_ssl&lt;/code&gt; enables listening on port 443 with SSL and the two &lt;code&gt;path&lt;/code&gt; options just tell nginx where to find the keys on the server, which are deployed separately with Puppet. &lt;code&gt;:force_ssl&lt;/code&gt; adds this small snippet to the exported nginx config file which redirects plain requests to SSL:&lt;/p&gt;
&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="s"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ssl_protocol&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"")&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;   &lt;span class="kn"&gt;rewrite&lt;/span&gt; &lt;span class="s"&gt;^https://&lt;/span&gt;&lt;span class="nv"&gt;$server_name$request_uri?&lt;/span&gt; &lt;span class="s"&gt;permanent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;As for the certificate, I ended up going with a free certificate from &lt;a href="https://www.startssl.com"&gt;StartSSL&lt;/a&gt;. This certificate doesn't necessarily guarantee that I am who I say I am because I just had to validate an email address, but it does guarantee that the connection is encrypted which is really all I care about. At some point I plan on going through the verification steps needed to get Class 2 certificates from StartSSL, but that's for another day.&lt;/p&gt;</description><author>Pete Keen</author><pubDate>Sun, 27 Jan 2013 20:06:32 GMT</pubDate><guid isPermaLink="true">https://www.petekeen.net/increasing-the-encryption-noise-floor/</guid></item><item><title>Crafting in Tabletop RPGs</title><link>https://benovermyer.com/blog/2013/01/crafting-in-tabletop-rpgs/</link><description>&lt;p&gt;One of the systems near and dear to my heart in MMORPGs is crafting. I love being able to make things from materials that I’ve collected myself, and in so doing improve my own capabilities without having to rely on an external source (e.g., loot, or vendors). Very few tabletop RPGs have ever attempted to include a crafting system, and those that have (Dungeons &amp;amp; Dragons, I’m looking at you) focus primarily on high-cost, high-powered magical items. Here, I explain how a crafting system for a tabletop RPG might work, taking several cues from MMORPGs. Crafting in MMORPGs has three primary component systems - gathering, refining, and building finished items. Gathering is the collection of raw resources from the world. This usually takes the form of withdrawing units of resources from a resource node that randomly spawns in the world. Refining takes those raw materials and works them into usable forms. For example, smelting iron ore into iron bars is one such process. This step is sometimes optional. Building finished items takes resources and puts them together into a final usable form. In MMORPGs, the process of actually crafting the item is abstracted away such that the player doesn’t need to worry about things like quenching steel in the proper temperature of liquid. Tabletop RPGs generally can’t follow MMORPG mechanics, since a lot of the latter is based on intensive calculation behind the scenes. With crafting, my perception is that it’s not popular because it’s not a story component - in a tabletop gaming session, anything that smacks of busywork is not going to go over well. To address that concern, there are two possibilities:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Make the whole crafting process fun and exciting, or&lt;/li&gt;
&lt;li&gt;Make the crafting process something that happens between adventures.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Personally, I think a mix of the two is ideal. The gathering process should be divided into two parts - acquiring rare raw materials through normal adventuring, and acquiring mundane raw materials in a quick process that occurs between gaming sessions. The refining process should be purely between sessions, unless it’s something story-bound like imbuing a raw material with a specific magical aura that can only be done by a powerful hermit-mage that lives in the…. you get the picture. The crafting of the final item could go either way, though I’d tend to put it at the very beginning of a game session before the actual adventure starts to “prime” players’ minds. A big component of crafting, and one of its greatest variables, is time. In real life, it takes a long time to smith a sword. In World of Warcraft, it takes seconds. Crafting in tabletop RPGs, as a character activity, should take little time. Crafting is a secondary system, and it’s not epic to say that your character spent a month of in-game down time smithing a new longsword in a borrowed forge while the other members of the party twiddled their thumbs. This problem can be explained away in two ways:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Give the character NPC assistants to do the crafting for him, or&lt;/li&gt;
&lt;li&gt;Make the crafting process itself something magical that takes little time.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I prefer the first option, since it involves more story hooks and feels less like a deus ex machina. If a player discovers he has the materials to make an upgraded suit of armor just as a session is about to begin, you can allow him to do that and retcon that he contracted the work out while he was adventuring in the last session. This can tend to make retconning a standard, accepted practice for crafting, but I think that’s of minor concern in this case. The other consideration when designing a crafting system for use in a tabletop RPG is the effect this can have on player equipment and expectations of loot. As such, it can’t be designed in a vacuum - it needs to be part of the system from the beginning, or you risk turning a game into a Monty Haul campaign inadvertently. The secret here is in the timing - you can gate the frequency of crafting more powerful items by introducing a crafting skill advancement system and making it as restrictive as you like, thereby keeping crafted items in line with looted items (or not, if that’s what you prefer). The Dungeons &amp;amp; Dragons system of spending experience to craft powerful items is one way to gate the acquisition of crafted items, but I think it’s fundamentally flawed in that it makes little sense from a story point of view. Sauron didn’t lose part of his essence to craft the One Ring - he just lost time and resources. To make something, a player has three resources available to him - Time, Money, and Effort. The ratio can vary between those three, but if you abstract out those concepts, you can arrive at a more sensible requirement ladder than Dungeons &amp;amp; Dragons’. In the end, crafting is a perfectly viable addition to a gaming system, as long as it’s well-integrated with the rest of the game. I’m working on one such system for inclusion in a game of mine, and I like to think the players will love it.&lt;/p&gt;</description><author>Ben Overmyer's Site</author><pubDate>Sun, 13 Jan 2013 02:00:00 GMT</pubDate><guid isPermaLink="true">https://benovermyer.com/blog/2013/01/crafting-in-tabletop-rpgs/</guid></item><item><title>More on Postgres Performance</title><link>/2013/01/10/More-on-Postgres-Performance/</link><description>&lt;p&gt;If you missed my previous post on &lt;a href="/2012/10/01/understanding-postgres-performance/"&gt;Understanding Postgres Performance&lt;/a&gt; its a great starting point. On this particular post I&amp;rsquo;m going to dig in to some real life examples of optimizing queries and indexes.&lt;/p&gt;
&lt;h3 id="it-all-starts-with-stats"&gt;
&lt;div&gt;
It all starts with stats
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;I wrote about some of the &lt;a href="https://postgres.heroku.com/blog/past/2012/12/6/postgres_92_now_available/"&gt;great new features in Postgres 9.2&lt;/a&gt; in the recent announcement on support of Postgres 9.2 on &lt;a href="https://www.heroku.com"&gt;Heroku&lt;/a&gt;. One of those awesome features, is &lt;a href="http://www.postgresql.org/docs/9.2/static/pgstatstatements.html"&gt;pg_stat_statements&lt;/a&gt;. Its not commonly known how much information Postgres keeps about your database (beyond the data of course), but in reality it keeps a great deal. Ranging from basic stuff like table size to cardinality of joins to distribution of indexes, and with pg_stat_statments it keeps a normalized record of when queries are run.&lt;/p&gt;
&lt;!-- raw HTML omitted --&gt;
&lt;p&gt;First you&amp;rsquo;ll want to turn on pg_stat_statments:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CREATE extension pg_stat_statements;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What this means it would record both:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT id
FROM users
WHERE email LIKE 'craig@heroku.com';
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT id
FROM users
WHERE email LIKE 'craig.kerstiens@gmail.com';
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To a normalized form which looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT id
FROM users
WHERE email LIKE ?;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="understanding-them-from-afar"&gt;
&lt;div&gt;
Understanding them from afar
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;While Postgres collects a great deal of this information dissecting it to something useful is sometimes more mystery than it should be. This simple query will show a few very key pieces of information that allow you to begin optimizing:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT
(total_time / 1000 / 60) as total_minutes,
(total_time/calls) as average_time,
query
FROM pg_stat_statements
ORDER BY 1 DESC
LIMIT 100;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The above query shows three key things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The total time a query has occupied against your system in minutes&lt;/li&gt;
&lt;li&gt;The average time it takes to run in milliseconds&lt;/li&gt;
&lt;li&gt;The query itself&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Giving an output something like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; total_time | avg_time | query
------------------+------------------+------------------------------------------------------------
295.761165833319 | 10.1374053278061 | SELECT id FROM users WHERE email LIKE ?
219.138564283326 | 80.24530822355305 | SELECT * FROM address WHERE user_id = ? AND current = True
(2 rows)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="what-to-optimize"&gt;
&lt;div&gt;
What to optimize
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;A general rule of thumb is that most of your very common queries that return 1 or a small set of records should return in ~ 1 ms. In some cases there may be queries that regularly run in 4-5 ms, but in most cases ~ 1 ms or less is an ideal.&lt;/p&gt;
&lt;p&gt;To pick where to begin I usually attempt to strike some balance between total time and long average time. In this case I&amp;rsquo;d start with the second probably, as on the first one I could likely shave an order of magnitude off, on the second I&amp;rsquo;m hopeful to shave two order of magnitudes off thus reducing the time spent on that query from a cumulative 220 minutes down to 2 minutes.&lt;/p&gt;
&lt;h3 id="optimizing"&gt;
&lt;div&gt;
Optimizing
&lt;/div&gt;
&lt;/h3&gt;
&lt;p&gt;From here you probably want to first example my other detail on understanding the explain plan. I want to highlight some of this with a more specific case based on the second query above. The above second query on an example data set does contain an index on user_id and yet there&amp;rsquo;s still high query times. To start to get an idea of why I would run:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;EXPLAIN ANALYZE
SELECT *
FROM address
WHERE user_id = 245
AND current = True
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This would yield results:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Aggregate (cost=4690.88..4690.88 rows=1 width=0) (actual time=519.288..519.289 rows=1 loops=1)
-&amp;gt; Nested Loop (cost=0.00..4690.66 rows=433 width=0) (actual time=15.302..519.076 rows=213 loops=1)
-&amp;gt; Index Scan using idx_address_userid on address (cost=0.00..232.52 rows=23 width=4) (actual time=10.143..62.822 rows=1 loops=8)
Index Cond: (user_id = 245)
Filter: current
Rows Removed by Filter: 14
Total runtime: 219.428 ms
(1 rows)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Hopefully not being too overwhelmed by this due to having read the other detail on &lt;a href="/2012/10/01/understanding-postgres-performance/"&gt;query plans&lt;/a&gt; we can see that it is using an index as expected. The difference is its having to fetch 15 different rows from the index then discard the bulk of them. The number of rows discarded is showcased by the line:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Rows Removed by Filter: 14
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;This is just one more of the many improvements in Postgres 9.2 alongside pg_stat_statements.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;To further optimize this we would great a conditional OR composite index. A conditional would be where only current = true, where as the composite would index both values. A conditional is commonly more valuable when you have a smaller set of what the values may be, meanwhile the composite is when you have a high variability of values. Creating the conditional index:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CREATE INDEX CONCURRENTLY idx_address_userid_current ON address(user_id) WHERE current = True;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can then see the query plan is now even further improved as we&amp;rsquo;d hope:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;EXPLAIN ANALYZE
SELECT *
FROM address
WHERE user_id = 245
AND current = True
QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Aggregate (cost=4690.88..4690.88 rows=1 width=0) (actual time=519.288..519.289 rows=1 loops=1)
-&amp;gt; Index Scan using idx_address_userid_current on address (cost=0.00..232.52 rows=23 width=4) (actual time=10.143..62.822 rows=1 loops=8)
Index Cond: ((user_id = 245) AND (current = True))
Total runtime: .728 ms
(1 rows)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;If you&amp;rsquo;re looking for a deeper resource on Postgres I recommend the book &lt;a href="https://theartofpostgresql.com/?affiliate=cek"&gt;The Art of PostgreSQL&lt;/a&gt;. It is by a personal friend that has aimed to create the definitive guide to Postgres, from a developer perspective. If you use code CRAIG15 you&amp;rsquo;ll receive 15% off as well.&lt;/em&gt;&lt;/p&gt;</description><author>CRAIG KERSTIENS</author><pubDate>Thu, 10 Jan 2013 22:55:56 GMT</pubDate><guid isPermaLink="true">/2013/01/10/More-on-Postgres-Performance/</guid></item><item><title>Code a Search Engine in PHP Part 5</title><link>https://boyter.org/2013/01/code-for-a-search-engine-in-php-part-5/</link><description>&lt;p&gt;[Are you really interested in learning how to Write a Search Engine from Scratch? Click this link and register your interest for my book about how to do so.][1]&lt;/p&gt;
&lt;p&gt;This is part 5 of a 5 part series.&lt;/p&gt;
&lt;p&gt;[Part 1][2] – [Part 2][3] – [Part 3][4] – [Part 4][5] – [Part 5][6] – [Downloads/Code][7]&lt;/p&gt;
&lt;p&gt;So we need to convert the indexer to a method that wont consume as much memory. Looking at how it works now we can determine a few areas that could be improved before we implement out new method.```
public function index(array $documents) {
$documenthash = array(); // so we can process multiple documents faster&lt;/p&gt;</description><author>Ben E. C. Boyter</author><pubDate>Thu, 10 Jan 2013 07:34:18 GMT</pubDate><guid isPermaLink="true">https://boyter.org/2013/01/code-for-a-search-engine-in-php-part-5/</guid></item><item><title>Code a Search Engine in PHP Part 4</title><link>https://boyter.org/2013/01/code-for-a-search-engine-in-php-part-4/</link><description>&lt;p&gt;&lt;a href="https://leanpub.com/creatingasearchenginefromscratch"&gt;Are you really interested in learning how to Write a Search Engine from Scratch? Click this link and register your interest for my book about how to do so.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This is part 4 of a 5 part series.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://www.boyter.org/2013/01/code-for-a-search-engine-in-php-part-1/%22"&gt;Part 1&lt;/a&gt; – &lt;a href="http://www.boyter.org/2013/01/code-for-a-search-engine-in-php-part-2/"&gt;Part 2&lt;/a&gt; – &lt;a href="http://www.boyter.org/2013/01/code-for-a-search-engine-in-php-part-3/"&gt;Part 3&lt;/a&gt; – &lt;a href="http://www.boyter.org/2013/01/code-for-a-search-engine-in-php-part-4/"&gt;Part 4&lt;/a&gt; – &lt;a href="http://www.boyter.org/2013/01/code-for-a-search-engine-in-php-part-5/"&gt;Part 5&lt;/a&gt; – &lt;a href="http://www.boyter.org/2013/01/code-for-a-search-engine-in-php-part-5/#downloads"&gt;Downloads/Code&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;So previously we had 4 main issues identified. Im going to go with low hanging fruit and add a simple porn filter.&lt;/p&gt;</description><author>Ben E. C. Boyter</author><pubDate>Thu, 10 Jan 2013 07:31:34 GMT</pubDate><guid isPermaLink="true">https://boyter.org/2013/01/code-for-a-search-engine-in-php-part-4/</guid></item><item><title>Code a Search Engine in PHP Part 3</title><link>https://boyter.org/2013/01/code-for-a-search-engine-in-php-part-3/</link><description>&lt;p&gt;[Are you really interested in learning how to Write a Search Engine from Scratch? Click this link and register your interest for my book about how to do so.][1]&lt;/p&gt;
&lt;p&gt;This is part 3 of a 5 part series.&lt;/p&gt;
&lt;p&gt;[Part 1][2] – [Part 2][3] – [Part 3][4] – [Part 4][5] – [Part 5][6] – [Downloads/Code][7]&lt;/p&gt;
&lt;p&gt;At this point I had a large chunk of the web sitting on my disk waiting to be indexed. The first thing we need to think about is what are we going to index. Since we are dealing with HTML files we have marked up content can can pick to a certain extent what we want to index on. For our search im going to index on the title and the meta description tags. Titles are still a part of search engine ranking algorithms as many people search for things like &amp;ldquo;Yahoo&amp;rdquo; and &amp;ldquo;Facebook&amp;rdquo; and the title/URL usually contains this information. The meta description had fallen out of favor with being something search engines use these days due to being gamed, but since its unlikely the top 1 million are going to be doing this I am going to use it as well. These two terms should work fairly well at our scale and ensure our index isn&amp;rsquo;t too massive. We can always add the rest of page content at a later point.&lt;/p&gt;</description><author>Ben E. C. Boyter</author><pubDate>Thu, 10 Jan 2013 07:27:57 GMT</pubDate><guid isPermaLink="true">https://boyter.org/2013/01/code-for-a-search-engine-in-php-part-3/</guid></item><item><title>Code a Search Engine in PHP Part 2</title><link>https://boyter.org/2013/01/code-for-a-search-engine-in-php-part-2/</link><description>&lt;p&gt;&lt;a href="https://leanpub.com/creatingasearchenginefromscratch"&gt;Are you really interested in learning how to Write a Search Engine from Scratch? Click this link and register your interest for my book about how to do so.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This is part 2 of a 5 part series.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://www.boyter.org/2013/01/code-for-a-search-engine-in-php-part-1/%22"&gt;Part 1&lt;/a&gt; – &lt;a href="http://www.boyter.org/2013/01/code-for-a-search-engine-in-php-part-2/"&gt;Part 2&lt;/a&gt; – &lt;a href="http://www.boyter.org/2013/01/code-for-a-search-engine-in-php-part-3/"&gt;Part 3&lt;/a&gt; – &lt;a href="http://www.boyter.org/2013/01/code-for-a-search-engine-in-php-part-4/"&gt;Part 4&lt;/a&gt; – &lt;a href="http://www.boyter.org/2013/01/code-for-a-search-engine-in-php-part-5/"&gt;Part 5&lt;/a&gt; – &lt;a href="http://www.boyter.org/2013/01/code-for-a-search-engine-in-php-part-5/#downloads"&gt;Downloads/Code&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The first implementation has a few issues that are pretty apparent. Chiefly is performance. Secondly it stores the index in a single folder which is loosely related to the first. Finally it shows duplicates in the search results. Lets fix the above and test things again to see how far we can go.&lt;/p&gt;</description><author>Ben E. C. Boyter</author><pubDate>Thu, 10 Jan 2013 05:03:54 GMT</pubDate><guid isPermaLink="true">https://boyter.org/2013/01/code-for-a-search-engine-in-php-part-2/</guid></item><item><title>Code a Search Engine in PHP Part 1</title><link>https://boyter.org/2013/01/code-for-a-search-engine-in-php-part-1/</link><description>&lt;p&gt;[Are you really interested in learning how to Write a Search Engine from Scratch? Click this link and register your interest for my book about how to do so.][1]&lt;/p&gt;
&lt;p&gt;This is part 1 of a 5 part series.&lt;/p&gt;
&lt;p&gt;[Part 1][2] – [Part 2][3] – [Part 3][4] – [Part 4][5] – [Part 5][6] – [Downloads/Code][7]&lt;/p&gt;
&lt;p&gt;I imagine that if you have landed on this page you probably have an interest in search and search engines. Not only that, you have probably read the following [Why Writing Your Own Search Engine is Hard by Anna Patterson (of Cuil fame) http://queue.acm.org/detail.cfm?id=988407][8] If not, go read it and come back here. I have always had an interest in search and indexing I thought I would take the time to write a simple indexer as a way of testing if I had actually learnt anything.&lt;/p&gt;</description><author>Ben E. C. Boyter</author><pubDate>Thu, 10 Jan 2013 04:55:54 GMT</pubDate><guid isPermaLink="true">https://boyter.org/2013/01/code-for-a-search-engine-in-php-part-1/</guid></item><item><title>Full Text Search with Whistlepig</title><link>https://www.petekeen.net/full-text-search-with-whistlepig/</link><description>&lt;p&gt;Yesterday I suddenly developed the intense need to add search to this site. Among the problems with this is that the site is kind of a weird hybrid between static and dynamic, and it has no database backend. If posts were stored in Postgres this would be a trivial matter, but they're just markdown files on disk. After flailing around for awhile I came across a library named &lt;a href="http://masanjin.net/whistlepig/"&gt;Whistlepig&lt;/a&gt; which purported to do in-memory full text indexing with a full query language.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;November 5, 2013: I've removed search because nobody used it and this way the site can be 100% static.&lt;/em&gt;&lt;/p&gt;
&lt;!--more--&gt;
&lt;h3&gt;First pass: Regular expressions&lt;/h3&gt;
&lt;p&gt;To rewind a bit, my first horrible stab at search was to find all of the posts that matched a user-provided regular expression:&lt;/p&gt;
&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="vi"&gt;@results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@posts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;find_all&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;match&lt;/span&gt; &lt;span class="sr"&gt;/\b&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;user_query&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sr"&gt;\b/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is of course complete madness. Not only am I allowing the user to put whatever they want in a regex but it only matches whole words. Not to mention that, while I only have around sixty pages right now, that number's never going to go down.&lt;/p&gt;
&lt;h3&gt;Other Solutions&lt;/h3&gt;
&lt;p&gt;There's a bunch of different hosted and external options that I briefly considered, none of which were very satisfactory.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sqlite3&lt;/code&gt; has built-in FTS but I would have to build a bunch of stuff around it.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://xapian.org/"&gt;xapian&lt;/a&gt; is a FTS engine but like with &lt;code&gt;sqlite3&lt;/code&gt; I'd have to build stuff.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;elasticsearch&lt;/code&gt; would work but it's an external process that I'd have to run and it's an awful lot of overhead&lt;/li&gt;
&lt;li&gt;Some kind of hosted &lt;code&gt;elasticsearch&lt;/code&gt; or &lt;code&gt;solr&lt;/code&gt; provider would work, but again lots of overhead and not free and I'm then dependent on their uptime.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Whistlepig to the Rescue&lt;/h3&gt;
&lt;p&gt;&lt;a href="http://masanjin.net/whistlepig/"&gt;Whistlepig&lt;/a&gt; is a small text search index. Small as in not very many features and not much code, but the features that are there are perfect for my needs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Full query language&lt;/li&gt;
&lt;li&gt;In-memory, in-process&lt;/li&gt;
&lt;li&gt;Arbitrary number of indexes for the same document&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here's a full example of how to index and query a document:&lt;/p&gt;
&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'rubygems'&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'whistlepig'&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;document&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Hi there"&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Whistlepig&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Index&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt; &lt;span class="s2"&gt;"index"&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Whistlepig&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Entry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_string&lt;/span&gt; &lt;span class="s2"&gt;"body"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;document&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;docid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_entry&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Query&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"body"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"hi"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;assert_equal&lt;/span&gt; &lt;span class="n"&gt;docid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The &lt;a href="https://github.com/peterkeen/bugsplat.rb/blob/master/page.rb#L33"&gt;indexing code&lt;/a&gt; in bugsplat's app is not much more complicated. Here's the interesting bit:&lt;/p&gt;
&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="vi"&gt;@pages_by_docid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="vi"&gt;@pages&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Whistlepig&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Entry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_string&lt;/span&gt; &lt;span class="s2"&gt;"body"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@strip_renderer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_string&lt;/span&gt; &lt;span class="s2"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_string&lt;/span&gt; &lt;span class="s2"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;downcase&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_string&lt;/span&gt; &lt;span class="s2"&gt;"tags"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tags&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="s2"&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;downcase&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_string&lt;/span&gt; &lt;span class="s2"&gt;"page_id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;page_id&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_string&lt;/span&gt; &lt;span class="s2"&gt;"blog_post"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_blog_post?&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;"yes"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"no"&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="n"&gt;docid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@index&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_entry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="vi"&gt;@pages_by_docid&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;docid&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In bugsplat a &lt;code&gt;Page&lt;/code&gt; encapsulates everything about an entry written in Markdown. I maintain six indexes on the pages, including &lt;code&gt;body&lt;/code&gt; rendered with a &lt;a href="https://github.com/peterkeen/bugsplat.rb/blob/master/strip_renderer.rb"&gt;Markdown-stripping, downcasing renderer&lt;/a&gt;, &lt;code&gt;name&lt;/code&gt; which is the canonical name of the post, &lt;code&gt;title&lt;/code&gt;, &lt;code&gt;tags&lt;/code&gt;, &lt;code&gt;page_id&lt;/code&gt; which is a short-code type of thing, and &lt;code&gt;blog_post&lt;/code&gt; which is a simple boolean as to whether the post has a date or not.&lt;/p&gt;
&lt;p&gt;&amp;quot;Why so many indexes?&amp;quot; you may find yourself asking. Because instead of just implementing search and being done with it, I went and refactored the guts of the blog to use it throughout. See, I had these terrible little things everywhere, all over the place:&lt;/p&gt;
&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="vi"&gt;@page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@pages&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;find_all&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;has_tag?&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:tag&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Doing linear searches across the list of in-memory pages isn't &lt;em&gt;too&lt;/em&gt; terrible but man it bugged me to have to repeat that everywhere. Instead of that, I can do nice things like this:&lt;/p&gt;
&lt;pre class="chroma"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="vi"&gt;@tagged_pages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@pages&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:tag&lt;/span&gt;&lt;span class="o"&gt;].&lt;/span&gt;&lt;span class="n"&gt;downcase&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"tags"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Each time I found myself iterating over all of the pages to get a subset I replaced it with a search query. The code is much nicer to read and faster, although almost all of it is cached as static HTML in production.&lt;/p&gt;
&lt;h3&gt;Try it out!&lt;/h3&gt;
&lt;p&gt;Go ahead and search for some stuff and let me know what you think! And next time you find yourself with a full text search problem, see if Whistlepig would help you out. It's not for everybody, but it's very good at what it does.&lt;/p&gt;</description><author>Pete Keen</author><pubDate>Wed, 09 Jan 2013 09:51:11 GMT</pubDate><guid isPermaLink="true">https://www.petekeen.net/full-text-search-with-whistlepig/</guid></item><item><title>Conquering 2013: Goals for the Year</title><link>https://benovermyer.com/blog/2013/01/conquering-2013-goals-for-the-year/</link><description>&lt;p&gt;I’m joining the vast numbers of bloggers who are publicly declaring their goals for the new year right now. Scott Dinsmore of Live Your Legend fame has challenged people to, well, Live Your Legend in 2013, and offers a &lt;a href="https://web.archive.org/web/20221002031539/http://liveyourlegend.net/free-2013-goal-setting-guide/" rel="external"&gt;Goal Setting &amp;amp; Action Workbook&lt;/a&gt; to help achieve that. I took four hours this morning to fill it out and plan my goals for the year, and this blog post summarizes the results of that. Two of my six big goals for the year have to do with gaming, but the rest are personal goals. I’ll share all of them with you after the jump.&lt;/p&gt;
&lt;h1 id="goal-1-release-a-computer-game"&gt;Goal #1) Release a Computer Game&lt;/h1&gt;
&lt;p&gt;I’ve worked, briefly, in the computer game industry. I’ve started numerous game projects. But I’ve never actually released a finished game. 2013 is the year to change that. While I won’t be releasing my dream game this year (the MMORPG tentatively called &lt;a href="https://starbornuniverse.wordpress.com/" rel="external"&gt;Starborn: Universe&lt;/a&gt;), I’m going to fire off a single-player action sandbox game called Starborn: Genesis. My release goal is by December 1, 2013. Future blog posts will detail this, my most difficult goal of 2013, as I work on it.&lt;/p&gt;
&lt;h1 id="goal-2-join-a-local-developer-group"&gt;Goal #2) Join a Local Developer Group&lt;/h1&gt;
&lt;p&gt;This goal is much smaller than #1. The purpose, though, is no less important; it has been said that you are an average of the five people you spend the most time with, and all of the five people I hang out with most are at best only tangentially related to computer game development. I want to connect with people more, and I especially want to connect with others who are passionate about computer game development. Joining a local developer group is an important part of that.&lt;/p&gt;
&lt;h1 id="goal-3-take-a-vacation-with-treasure-to-hawaii"&gt;Goal #3) Take a Vacation with Treasure to Hawaii&lt;/h1&gt;
&lt;p&gt;My wife Treasure and I have not yet taken a vacation outside of the Midwest. As both of us have traveled the world and love new places, this is a problem. This year, I’m committing to getting us both to Hawaii for a week in June, near or on our 4th anniversary. It will be an event to remember, I’m sure! Now I just need to figure out how to pay for it…&lt;/p&gt;
&lt;h1 id="goal-4-lead-a-yoga-class"&gt;Goal #4) Lead a Yoga Class&lt;/h1&gt;
&lt;p&gt;I don’t know a thing about yoga. I’ve never taken a class and know only a few movements from Wii Fit. So why would I make a goal to lead a class? Because I’m interested in it, and no one ever achieved anything meaningful by aiming low. I’ll need to join a class and become proficient enough over the coming year to teach a community class by the end of December. I’ve already picked out the location and instructor to help me achieve that. It’s just a matter of getting it done.&lt;/p&gt;
&lt;h1 id="goal-5-reach-1-000-visitors-per-month-on-dicejockey"&gt;Goal #5) Reach 1,000 Visitors Per Month On Dicejockey&lt;/h1&gt;
&lt;p&gt;Yep, my blog wasn’t going to slide by without a major goal this year. Dicejockey’s not about Top Ten lists or Five Steps to Success, but I like to think I say things worth talking about, and that’s worth pushing harder on. My life is my passion, and Dicejockey is a window to that passion. With a little luck and an extra dose of hard work, I hope to make Dicejockey a popular choice for gaming geeks.&lt;/p&gt;
&lt;h1 id="goal-6-collaborate-on-something-with-tasha"&gt;Goal #6) Collaborate on Something With Tasha&lt;/h1&gt;
&lt;p&gt;My sister Tasha and I don’t talk very frequently. This isn’t because we’re not on speaking terms; it’s just because we don’t have much in common. This year I want to change that. We’re both very passionate about a wide variety of topics, and I’m sure we can find something to truly connect on. I’ve got a tentative plan on how to achieve this goal, and I really want to get closer to my family, so I know I’ll meet this goal by year’s end. So those are my goals for 2013. How about you? What are you looking to achieve this year?&lt;/p&gt;</description><author>Ben Overmyer's Site</author><pubDate>Sat, 05 Jan 2013 02:00:00 GMT</pubDate><guid isPermaLink="true">https://benovermyer.com/blog/2013/01/conquering-2013-goals-for-the-year/</guid></item></channel></rss>