<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>devroom.io</title><description>Ariejan de Vroom's homepage.</description><link>https://www.devroom.io/</link><language>en</language><copyright>Copyright 2025, Calvin Tran</copyright><lastBuildDate>Wed, 23 Oct 2024 00:00:00 +0000</lastBuildDate><generator>Hugo - gohugo.io</generator><docs>http://cyber.harvard.edu/rss/rss.html</docs><atom:link href="https://www.devroom.io//atom.xml" rel="self" type="application/atom+xml"/><item><title>Why Asahi Linux is not for me</title><link>https://www.devroom.io/2024/10/23/why-asahi-linux-is-not-for-me/</link><description>&lt;p>In &lt;a href="https://www.devroom.io/2024/10/15/macbookpro-m1-with-asahi-linux-fractional-display-scaling-in-gnome/">previous&lt;/a> &lt;a href="https://www.devroom.io/2024/10/14/macbookpro-m1-with-asahi-linux-fixing-the-touchpad/">posts&lt;/a> I discussed some &lt;a href="https://asahilinux.org/">Asahi Linux&lt;/a> specific setup to make
Asahi Linux play nice with my MacbookPro M1 Max.&lt;/p>
&lt;p>After running Asahi for about a week on my &lt;em>production laptop&lt;/em>, I&amp;rsquo;m ready
to do some evaluation.&lt;/p>
&lt;p>Before I start of this rant, I want to take a moment to appreciate the enormous
amounts of work the Asahi team put into their release. Reverse engineering all
this hardware is not a small feat and I&amp;rsquo;m grateful for their work. The &lt;a href="https://github.com/AsahiLinux/docs/wiki/M1-Series-Feature-Support">M1 Series Feature Support&lt;/a> page gives you an indication of what to expect.&lt;/p>
&lt;h2 id="the-good">The good&lt;/h2>
&lt;p>Let&amp;rsquo;s start with the good.&lt;/p>
&lt;ul>
&lt;li>I can run Linux on my Mac. I love open source software, so this is a big deal to me. The
installer script is, by far, the easiest linux install I ever did.&lt;/li>
&lt;li>I was able to re-use my (Arch focused) Ansible machine setup playbook for about 80%, so Gnome was
set up the way I like it in no time.&lt;/li>
&lt;li>The touchpad is working, albeit not as smooth as on macOS.&lt;/li>
&lt;li>Brightness, volume and keyboard backlight keys work out of the box.&lt;/li>
&lt;li>The webcam works out of the box.&lt;/li>
&lt;li>Battery and sensors work out of the box.&lt;/li>
&lt;/ul>
&lt;h2 id="the-bad">The bad&lt;/h2>
&lt;p>Things that are not working as expected, but have viable workarounds.&lt;/p>
&lt;ul>
&lt;li>Bluetooth works, most of the time. Sometimes it would just not work and my AirPods Pro
would not connect. I think it&amp;rsquo;s related to sleep/suspend, but I&amp;rsquo;m not sure.&lt;/li>
&lt;li>Wifi works out of the box, but it&amp;rsquo;s slow. On macos I get about 150-200mbps to the
internet, on Asahi I get about 30-40mbps. It&amp;rsquo;s not a deal breaker, but I like my bandwidth.&lt;/li>
&lt;li>TouchID is not supported. I can live without it, but I&amp;rsquo;m quite used to it to unlock my machine.&lt;/li>
&lt;li>Some proprietary apps, like Spotify and Slack, have no &lt;code>aarch64&lt;/code> packages. Which means
I have to use them in the browser.&lt;/li>
&lt;/ul>
&lt;h2 id="the-ugly">The ugly&lt;/h2>
&lt;p>Things that are not working as expected, but are deal breakers for me.&lt;/p>
&lt;ul>
&lt;li>Audio output is horrible. It&amp;rsquo;s working, but the subwoofer is clearly not working and
the audio quality is sub par. For someone who like to listen to some music, this is
disappointing.&lt;/li>
&lt;li>The built-in microphone is not working. This means that any video call you &lt;em>will&lt;/em> need
a headset. If my Airpods Pods would just connect when I need them, that would not be
a problem, but they don&amp;rsquo;t. A wired 3.5mm headset would work, but I just moved to going
wireless.&lt;/li>
&lt;li>Power management is not great. Where on macos I can go a full day without charging, on
Asahi I need to charge after a few hours. Even when in sleep/suspend mode, the battery
keeps draining. When I close the lid at 80% battery, an hour later, I&amp;rsquo;m at 70%. I feel
that the battery is draining faster than it should and it makes it hard to depend on
any charge remaining when I need it.&lt;/li>
&lt;/ul>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>Should you run Asahi Linux on your Mac? If you are a Linux die-hard and can live with
the bad and the ugly, sure.&lt;/p>
&lt;p>But alas, this is a mobile work station and I really need those audio features and power
management is important to me. If I was running this on a machine that was connected to
a dock most of the day for power and an external audio interface, I would say: yes.&lt;/p>
&lt;p>All in all, Asahi Linux is a monumental effort to make Linux run on Apple hardware.&lt;/p>
&lt;p>If I really wanted Linux on my daily driver I would not go for Apple hardware.&lt;/p>
&lt;p>Because I like AMD I&amp;rsquo;d probably go for one a &lt;a href="https://frame.work">Framework 16&lt;/a> or &lt;a href="https://www.tuxedocomputers.com">Tuxedo Computers&lt;/a>
&lt;a href="https://www.tuxedocomputers.com/en/TUXEDO-Stellaris-Slim-15-Gen6-AMD.tuxedo">Stellaris 15&lt;/a>. The Dell XPS is a nice option too, but it only comes with Intel CPUs.&lt;/p>
&lt;p>For now, I&amp;rsquo;m back to macos on the MacbookPro, but still running Linux on my desktop.&lt;/p>
&lt;p>&lt;em>I run Arch, BTW.&lt;/em>&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2024/10/23/why-asahi-linux-is-not-for-me/</guid><pubDate>Wed, 23 Oct 2024 00:00:00 +0000</pubDate></item><item><title>MacbookPro M1 with Asahi Linux: Fractional Display Scaling in GNOME</title><link>https://www.devroom.io/2024/10/15/macbookpro-m1-with-asahi-linux-fractional-display-scaling-in-gnome/</link><description>&lt;p>The MacbookPro has a high pixels-per-inch screen, or as Apple likes to call it,
a &lt;em>Liquid Retina XDR display&lt;/em>. Including the notch area, it measures in at
3456x2234 pixels at 254 PPI. It&amp;rsquo;s a beautiful screen&amp;hellip;&lt;/p>
&lt;p>&amp;hellip;until you run Asahi Linux and learn that you either get 100% or 200% scaling for your
display.&lt;/p>
&lt;h2 id="what-is-scaling-anyway">What is scaling, anyway?&lt;/h2>
&lt;p>When using the 3456x2234 screent at a 100% scale - or no scaling. This means that things
will be come incredibly small and hard to read. Normally, Apple runs at a scale of 200%,
doubling the size of everything on screen. This scaling ensures that visual elements
maintain clarity and are more appropriately sized for people on high-resolution screens.&lt;/p>
&lt;p>In GNOME the options are 100% and 200% and nothing in between.&lt;/p>
&lt;p>On Apple devices, you get a few choices ranging from &lt;em>Large Text&lt;/em> to &lt;em>More Space&lt;/em>.&lt;/p>
&lt;p>&lt;img
src="apple-retina-display.png"
alt="Apple Display Settings"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;p>Wouldn&amp;rsquo;t it be nice if we had the same options in GNOME?&lt;/p>
&lt;h2 id="enabling-fractional-scaling-in-gnome">Enabling fractional scaling in GNOME&lt;/h2>
&lt;p>Fractional scaling is not an officially supported feature in GNOME yet. Luckily,
it&amp;rsquo;s available as an experimental feature. You just need to enable it:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-plaintext" data-lang="plaintext">&lt;span class="line">&lt;span class="cl">gsettings set org.gnome.mutter experimental-features &amp;#34;[&amp;#39;scale-monitor-framebuffer&amp;#39;]&amp;#34;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>When you now go into GNOME Settings -&amp;gt; Displays you&amp;rsquo;ll see that the 100% and 200% buttons
have been replaced with a dropdown, offering you 100% up to 400% in steps of 25%. I&amp;rsquo;m currently running at 175%, just like on macos, as I find it a good balance between readability and screen real
estate.&lt;/p>
&lt;p>&lt;img
src="gnome-settings.png"
alt="GNOME Display Settings with fractional scaling"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;div class="alert info">
The resolution is stated as 2160 high, instead of 2234. This is because I do not have
the notch enabled (yet), so Linux is using a fractionally smaller screen to avoid the
notch part of the screen altogether.
&lt;/div>
&lt;h2 id="fix-for-chromium">Fix for Chromium&lt;/h2>
&lt;p>Now, all apps I&amp;rsquo;ve used so far work brilliantly with the fractional scaling at 175%. The
one exception was &lt;em>Chromium&lt;/em>. It looked blurry and annoyed me enormously.&lt;/p>
&lt;p>&lt;img
src="chrome-flags.png"
alt="Chrome Ozone Flags"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;p>Chrome by default will not use Wayland as it&amp;rsquo;s platform backend so scaling will be
a bit iffy. Go to &lt;a href="chrome://flags">chrome://flags&lt;/a> and search for &lt;code>Ozone&lt;/code> and select
either &lt;code>Wayland&lt;/code> or &lt;code>Auto&lt;/code> so the correct backend is used. Restart Chromium and you&amp;rsquo;re
done.&lt;/p>
&lt;h2 id="saving-for-automation">Saving for automation&lt;/h2>
&lt;p>Because I&amp;rsquo;m a lazy programmer I spend a disproportionate amount of time automating
things that probably don&amp;rsquo;t need automating. Having an automated setup with Ansible
that configures and sets up a new machine for me is &lt;em>very&lt;/em> helpful indeed.&lt;/p>
&lt;p>I&amp;rsquo;ll share more on this later, maybe, but my Ansible setup will configure anything
from my user account, hostname and installed packages to configuring GPG, cloning
dotfiles and tweaking GNOME settings.&lt;/p>
&lt;p>Basically, when my Ansible playbook has finished, I have a working setup, across
machines, to start working. My Fedora Asahi Linux machine is no different, since
it need just a few changes from my Arch setup (mainly, a different package manager).&lt;/p>
&lt;p>Anyway. Where you&amp;rsquo;d normally be able to use &lt;code>dconf&lt;/code> to save settings for GNOME, that
was not possible with &lt;em>Displays&lt;/em>. Of course, the dislay settings are more fundamental
than GNOME itself.&lt;/p>
&lt;p>Luckily, I found out that a file named &lt;code>~/.config/monitors.xml&lt;/code> was generated. You
can clearly see the &lt;code>scale&lt;/code> entry here. (I guess using that arcane floating point number
instead of &lt;code>1.75&lt;/code> gives better results.)&lt;/p>
&lt;p>So, this file was not quickly put into my Ansible playbook and will now be automtically
restored with my setup.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="c">&amp;lt;!-- ~/.config/monitors.xml --&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;monitors&lt;/span> &lt;span class="na">version=&lt;/span>&lt;span class="s">&amp;#34;2&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;configuration&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;logicalmonitor&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;x&amp;gt;&lt;/span>0&lt;span class="nt">&amp;lt;/x&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;y&amp;gt;&lt;/span>0&lt;span class="nt">&amp;lt;/y&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;scale&amp;gt;&lt;/span>1.7489879131317139&lt;span class="nt">&amp;lt;/scale&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;primary&amp;gt;&lt;/span>yes&lt;span class="nt">&amp;lt;/primary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;monitor&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;monitorspec&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;connector&amp;gt;&lt;/span>eDP-1&lt;span class="nt">&amp;lt;/connector&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;vendor&amp;gt;&lt;/span>unknown&lt;span class="nt">&amp;lt;/vendor&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;product&amp;gt;&lt;/span>unknown&lt;span class="nt">&amp;lt;/product&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;serial&amp;gt;&lt;/span>unknown&lt;span class="nt">&amp;lt;/serial&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/monitorspec&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;mode&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;width&amp;gt;&lt;/span>3456&lt;span class="nt">&amp;lt;/width&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;height&amp;gt;&lt;/span>2160&lt;span class="nt">&amp;lt;/height&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;rate&amp;gt;&lt;/span>60.000&lt;span class="nt">&amp;lt;/rate&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/mode&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/monitor&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/logicalmonitor&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/configuration&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/monitors&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2024/10/15/macbookpro-m1-with-asahi-linux-fractional-display-scaling-in-gnome/</guid><pubDate>Tue, 15 Oct 2024 00:00:00 +0000</pubDate></item><item><title>MacbookPro M1 with Asahi Linux: Fixing the touchpad</title><link>https://www.devroom.io/2024/10/14/macbookpro-m1-with-asahi-linux-fixing-the-touchpad/</link><description>&lt;p>I&amp;rsquo;m a &lt;em>big&lt;/em> Linux fan. On my Ryzen 3700X desktop rig I run &lt;a href="https://archlinux.org">Arch Linux&lt;/a>
and I like it. The only times I boot into Windows are to play
&lt;em>Rocksmith 2014&lt;/em>, because I still haven&amp;rsquo;t got that game to work
properly on Steam/Proton.&lt;/p>
&lt;p>My job provides me with nice &lt;em>&amp;ldquo;developer laptop&amp;rdquo;&lt;/em>, which has been
a high-end &lt;em>MacbookPro&lt;/em> ever since I started there in 2007. The
current iteration is a well spec&amp;rsquo;ed 16&amp;quot; M1 Max.&lt;/p>
&lt;div class="alert info">
&lt;strong>I run Arch, BTW!&lt;/strong>&lt;br>
&amp;hellip;and now also Fedora on &lt;code>aarch64&lt;/code>, apparently.
&lt;/div>
&lt;p>Now, I&amp;rsquo;ve never had any qualm with macos. It was a breath of
fresh air when Windows was the only viable desktop OS. It&amp;rsquo;s really
polished but still has that BSD underpinning that makes it a breeze
using it as a development machine.&lt;/p>
&lt;p>On previous Intel Macs I tried installing Arch and Ubuntu a few times,
but always ran into unsupported hardware. Each time I gave up pretty
quickly and accepted the fact that Apple hardware and macos are
married.&lt;/p>
&lt;p>Then a co-worker told me about his success in running &lt;a href="https://asahilinux.org">Asahi Linux&lt;/a> on
his M1 machine. I &lt;em>had&lt;/em> to try.&lt;/p>
&lt;p>Although this is not a review of Asahi Linux: it&amp;rsquo;s amazing! There are a
few quirks, as is expected, but nothing that is a real deal breaker.&lt;/p>
&lt;p>In this post I want to address my issues with the trackpad and how I solved them.&lt;/p>
&lt;h2 id="accidental-tapping">Accidental tapping&lt;/h2>
&lt;p>Touchpads are great, just not when they register taps when you&amp;rsquo;re typing and
shift focus to the most ungodly places. When you palms touch the touchpad, they
can register ghost taps. With the MacbookPro&amp;rsquo;s touchpad being so huge, that&amp;rsquo;s
quite a high risk.&lt;/p>
&lt;p>The solution to this problem is quite easily fixed with some settings in GNOME.&lt;/p>
&lt;p>First, &lt;code>Disable Touchpad While Typing&lt;/code>. It will not completely fix the tapping
issue, but it&amp;rsquo;s a great help. You find this in GNOME Settings -&amp;gt; Mouse &amp;amp; Touchpad
and then the Touchpad tab.&lt;/p>
&lt;p>&lt;img
src="settings-disable-while-typing.png"
alt="Disable trackpad while typing"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;p>Next, scroll down a bit and disable &lt;code>Tap to Click&lt;/code>. You know with touchpads, where
you can either click on it (it makes that clicking sound), or just tap it, like
you would your phone. Well, this option turns of that tapping behaviour.&lt;/p>
&lt;p>&lt;img
src="disable-tap-to-click.png"
alt="Disable Tap to Click"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;h2 id="fixing-trackpad-scroll-speed">Fixing trackpad scroll speed&lt;/h2>
&lt;p>This is &lt;em>by far&lt;/em> the most annoying problem. The scroll speed. It feels like the
trackpad is on cocaine or something.&lt;/p>
&lt;p>Unfortunately, GNOME cannot help us out here, because it simply does not have
a setting for it. Long story short, GNOME is pointing to Wayland&amp;rsquo;s &lt;code>libinput&lt;/code> to
fix this problem, and &lt;code>libinput&lt;/code> is pointing to windows managers (like GNOME) to
handle it.&lt;/p>
&lt;p>Luckily, there&amp;rsquo;s some great people out there who created kind-of-a-hack for
&lt;code>libinput&lt;/code> to allow scaling the scroll-speed.&lt;/p>
&lt;p>You can find that code here: &lt;a href="https://gitlab.com/warningnonpotablewater/libinput-config/">warningnonpotablewater/libinput-config&lt;/a>.&lt;/p>
&lt;p>Here&amp;rsquo;s a quick rundown of how to install it:&lt;/p>
&lt;ol>
&lt;li>Install dependencies first:
&lt;code>sudo dnf install meson ninja-build cmake libinput-devel&lt;/code>&lt;/li>
&lt;li>Clone that repo:
&lt;code>git clone https://gitlab.com/warningnonpotablewater/libinput-config.git&lt;/code>&lt;/li>
&lt;li>&lt;code>meson build&lt;/code> then, go into the &lt;code>build/&lt;/code> directory.&lt;/li>
&lt;li>&lt;code>ninja &amp;amp;&amp;amp; sudo ninja install&lt;/code> to build and install the library.&lt;/li>
&lt;/ol>
&lt;p>Then, all you need to is create a config file in &lt;code>/etc/libinput.conf&lt;/code> and reboot.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-toml" data-lang="toml">&lt;span class="line">&lt;span class="cl">&lt;span class="c"># /etc/libinput.conf&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">scroll-factor&lt;/span>&lt;span class="p">=&lt;/span>&lt;span class="mf">0.5&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>In this case, the scrolling was &lt;em>too fast&lt;/em>, so I multiply it with a factor of &lt;code>0.5&lt;/code> to bring it down. You
may want to try some other values if you&amp;rsquo;re not satisfied with the result.&lt;/p>
&lt;p>If you want &lt;em>faster&lt;/em> scrolling, use a factor &lt;code>&amp;gt;1&lt;/code>.&lt;/p>
&lt;p>Simply reboot your machine for the changes to take effect.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2024/10/14/macbookpro-m1-with-asahi-linux-fixing-the-touchpad/</guid><pubDate>Mon, 14 Oct 2024 00:00:00 +0000</pubDate></item><item><title>Peel before use: mistakes were made</title><link>https://www.devroom.io/2024/04/07/peel-before-use-mistakes-were-made/</link><description>&lt;p>The stock AMD Wraith cooler on my linux box was getting loud. Since I recently migrated the complete system from a (too) tight mini-ATX case to a larger &lt;a href="https://www.corsair.com/us/en/p/pc-cases/cc-9011200-ww/4000d-airflow-tempered-glass-mid-tower-atx-case-black-cc-9011200-ww">Corsair 4000D Airflow&lt;/a>, I decided to splurge on some new cooling.&lt;/p>
&lt;p>&lt;a href="https://www.thermalright.com/">Thermalright&lt;/a> has a nice and affordable offering of CPU coolers and case fans.&lt;/p>
&lt;p>So when my &lt;a href="https://www.thermalright.com/product/peerless-assassin-120/">Peerless Assassin 120&lt;/a> arrived I immediately set out to install it. You know where this is going&amp;hellip;&lt;/p>
&lt;p>Upon booting up I ran &lt;code>stress-ng&lt;/code> to put some load on my CPU and see what amazingly cool my Ryzen 3700X would stay. Well, it hit 94C within 10 seconds. Not good. Idling at about 50-60C.&lt;/p>
&lt;p>&lt;img
src="https://www.devroom.io/images/2024/04/remove-before-use.jpg"
alt="Please peel before use"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;p>So yeah. It turns out plastic &amp;ldquo;Peel before use&amp;rdquo; are not great thermal conductors.&lt;/p>
&lt;p>With the plastic now removed and &lt;em>the perfect amount of thermal paste&lt;/em> the CPU runs at 35-40C at idle and &amp;lt; 70C when at full load for 5 minutes. Noice!&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2024/04/07/peel-before-use-mistakes-were-made/</guid><pubDate>Sun, 07 Apr 2024 00:00:00 +0000</pubDate></item><item><title>Import Google Photos Takeout into Immich</title><link>https://www.devroom.io/2024/03/21/import-google-photos-takeout-into-immich/</link><description>&lt;p>Okay, importing into Immich from a Google Photos Takeout is &lt;em>super easy&lt;/em>. Well, it is if you&amp;rsquo;re familiar with a CLI.&lt;/p>
&lt;h2 id="prepare-your-takeout">Prepare your takeout&lt;/h2>
&lt;p>There are two options for getting your Google Photos Takeout ready for importing:&lt;/p>
&lt;ul>
&lt;li>Export as &lt;code>.zip&lt;/code> (any size, bigger is better)&lt;/li>
&lt;li>Export as &lt;code>.tgz&lt;/code> (any size, bigger is better) and &lt;a href="https://www.devroom.io/2024/03/20/how-to-extract-multiple-.tgz-google-takeout-archives/">extract as per my previous post&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="getting-immich-go">Getting &lt;code>immich-go&lt;/code>&lt;/h2>
&lt;p>&lt;a href="https://github.com/simulot/immich-go">&lt;code>immich-go&lt;/code>&lt;/a> is a single binary command line tool for importing photos and videos into Immich. It has awesome support for Google Photos takeout archives.&lt;/p>
&lt;p>You should get the latest &lt;code>immich-go&lt;/code> release for your platform from &lt;a href="https://github.com/simulot/immich-go/releases">Immich github releases&lt;/a>. I dropped the executable into where I extracted my takeout archives.&lt;/p>
&lt;pre tabindex="0">&lt;code>-rwxrwxr-x 1 ariejan ariejan 8.5M Mar 20 18:45 immich-go
drwxrwxr-x 3 ariejan ariejan 4 Mar 20 14:57 Takeout
&lt;/code>&lt;/pre>&lt;h2 id="getting-your-immich-api-key">Getting your Immich API key&lt;/h2>
&lt;p>Go to your Immich account settings (click your avatar at the top right). Then select API Keys and create a new key.&lt;/p>
&lt;p>You probably already know your Immich url, which might optionally require a port number, depending on your setup.&lt;/p>
&lt;h2 id="dry-run">Dry-run&lt;/h2>
&lt;p>First, let&amp;rsquo;s do a dry run. This will read your Takeout archives and test the connection to Immich. It will spit out a lot of info about what&amp;rsquo;s going to be imported, skipped, etc.&lt;/p>
&lt;pre tabindex="0">&lt;code>./immich-go \
-server=https://immich.devroom.io \
-key=Ir3za64rggrbK3zSIhDfskw8R9b97GTljPPK5NlIEY \
upload \
-dry-run \
-create-albums \
-google-photos \
Takeout/
&lt;/code>&lt;/pre>&lt;p>&lt;code>-create-albums&lt;/code> will re-create your Google Photos albums, which is probably what you want.&lt;/p>
&lt;p>In this case I&amp;rsquo;m providing an extracted Takeout archive, so I need to specify the &lt;code>-google-photos&lt;/code> option. If you have &lt;code>.zip&lt;/code> files, you can supply &lt;code>takeout-*.zip&lt;/code> instead, and the &lt;code>-google-photos&lt;/code> option is not strictly necessary.&lt;/p>
&lt;h2 id="importing-for-realz">Importing for realz&lt;/h2>
&lt;p>If you&amp;rsquo;re happy what &lt;code>immich-go&lt;/code> reported, go ahead and start your import. It can take a few hours, depending on how many photos and videos you want to import.&lt;/p>
&lt;pre tabindex="0">&lt;code>./immich-go \
-server=https://immich.devroom.io \
-key=Ir3za64rggrbK3zSIhDfskw8R9b97GTljPPK5NlIEY \
upload \
-create-albums \
-google-photos \
Takeout/
&lt;/code>&lt;/pre>&lt;h2 id="result">Result?&lt;/h2>
&lt;p>All my photos and videos are now safely in Immich (backed up to a separate 3TB mirror; offline backup to a Hetzner Storagebox).&lt;/p>
&lt;p>&lt;img
src="https://www.devroom.io/images/2024/03/immich-usage.png"
alt=""
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2024/03/21/import-google-photos-takeout-into-immich/</guid><pubDate>Thu, 21 Mar 2024 00:00:00 +0000</pubDate></item><item><title>How to extract multiple .tgz Google takeout archives</title><link>https://www.devroom.io/2024/03/20/how-to-extract-multiple-.tgz-google-takeout-archives/</link><description>&lt;p>I love Google Photos for its easy of use and features. But, it&amp;rsquo;s &lt;em>Google&lt;/em>. As you may knwo I like to selfhost all the things, but for the longest time I was not able to find a good selfhosted alternative for Google Photos.&lt;/p>
&lt;h2 id="immich">Immich&lt;/h2>
&lt;p>&lt;a href="https://immich.app/">Immich&lt;/a> is a selfhosted photo and video management system. Sounds fancy, and it is. Aside from having a good mobile app for iOS that will do background backups, it sports facial recognition, hardware transcoding of videos, reverse geocoding and lots more. There&amp;rsquo;s a big disclaimer though, Immich is still under active development. I see that more as an encouragment to use it than a warning :-)&lt;/p>
&lt;p>So, naturally, I want to move all my photos and videos (going back to 2006) from Google Photos to Immich.&lt;/p>
&lt;h2 id="google-takeout">Google Takeout&lt;/h2>
&lt;p>Due to great EU legislation Google Takeout exists. It allows you to easily create an archive of your data for export. Now, how useful that data export is going to be is another matter.&lt;/p>
&lt;p>Because I have &lt;em>a lot&lt;/em> of data, Google is offering me to create multiple &lt;code>.tgz&lt;/code> files for export of 50GB each. I now have six Google takeout archives:&lt;/p>
&lt;pre tabindex="0">&lt;code>-rwxrwxr-x 1 ariejan ariejan 50G Mar 5 11:16 takeout-001.tgz
-rwxrwxr-x 1 ariejan ariejan 50G Mar 5 11:16 takeout-002.tgz
-rwxrwxr-x 1 ariejan ariejan 50G Mar 5 11:16 takeout-003.tgz
-rwxrwxr-x 1 ariejan ariejan 50G Mar 5 11:16 takeout-004.tgz
-rwxrwxr-x 1 ariejan ariejan 50G Mar 5 11:16 takeout-005.tgz
-rwxrwxr-x 1 ariejan ariejan 39G Mar 5 11:16 takeout-006.tgz
&lt;/code>&lt;/pre>&lt;p>So, I was wondering, how do I unpack these files? Well, there are several ways I want to document.&lt;/p>
&lt;h2 id="cat--tar">cat | tar&lt;/h2>
&lt;p>With some linux-fu I could easily pipe all these files into tar to extract:&lt;/p>
&lt;pre tabindex="0">&lt;code>cat takeout-{001..006}.tgz | tar xzivf -
&lt;/code>&lt;/pre>&lt;p>Or, I could just glob all the files, they will be ordered automatically. How nice is that!&lt;/p>
&lt;pre tabindex="0">&lt;code>cat takeout-*.tgz | tar xzivf -
&lt;/code>&lt;/pre>&lt;p>Both these methods will pipe the data from the archives, in order, into tar, which will extract the provided data.&lt;/p>
&lt;h2 id="pv--tar">pv | tar&lt;/h2>
&lt;p>If you want to go fancy and have &lt;code>pv&lt;/code> installed, you can use that as well. &lt;code>pv&lt;/code> is a utility that monitors progress of data through a pipe.&lt;/p>
&lt;pre tabindex="0">&lt;code>pv takeout-*.tgz | tar xzif -
88.9GiB 0:19:19 [89.2MiB/s] [==========&amp;gt; ] 30% ETA 0:43:04
&lt;/code>&lt;/pre>&lt;h2 id="importing-into-immich">Importing into Immich&lt;/h2>
&lt;p>Now, with the data extracted, how am I going to import it into Immich? There are tools for that and I&amp;rsquo;ll post again on how that process went.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2024/03/20/how-to-extract-multiple-.tgz-google-takeout-archives/</guid><pubDate>Wed, 20 Mar 2024 00:00:00 +0000</pubDate></item><item><title>ZFS: Upgrade single disk to mirror</title><link>https://www.devroom.io/2024/03/07/zfs-upgrade-single-disk-to-mirror/</link><description>&lt;p>For a while now I&amp;rsquo;ve had a single 3TB WD Green in my NAS to store local &lt;a href="https://www.borgbackup.org/">Borg backups&lt;/a> before syncing them to &lt;del>Google Drive&lt;/del> a &lt;a href="https://www.hetzner.com/storage/storage-box/">Hetzner Storage Box&lt;/a>.&lt;/p>
&lt;p>Because I recently replaced my 3TB Reds for 10TB HGST&amp;rsquo;s, I have some spare drives left.&lt;/p>
&lt;p>My goals are:&lt;/p>
&lt;ul>
&lt;li>Turn the single disk zpool for backups into a mirrored pair.&lt;/li>
&lt;li>Replace the WD Green with a WD Red, eventually.&lt;/li>
&lt;/ul>
&lt;p>So, today is about upgrading a single disk ZFS pool to a mirrored pair.&lt;/p>
&lt;h3 id="prepare-the-new-disk">Prepare the new disk&lt;/h3>
&lt;p>The 3TB Red came out of my &lt;code>tank&lt;/code> pool. This means it has partitions and metedata suggesting it&amp;rsquo;s a disk in use by a ZFS pool. To prepare this disk, I&amp;rsquo;ll need to clean it fully. Note that I&amp;rsquo;m not going to zero out this disk, since it&amp;rsquo;ll be me who&amp;rsquo;s going to use it.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">wipefs -af /dev/sdq
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This will &lt;em>destructively&lt;/em> clean any and all filesystem / ZFS information on the disk.&lt;/p>
&lt;h3 id="upgrading-the-zfs-pool">Upgrading the ZFS pool&lt;/h3>
&lt;p>The next step is to upgrade the pool by adding the new device into a mirrored pair.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">zfs attach &amp;lt;pool&amp;gt; &amp;lt;existing hdd guid&amp;gt; &amp;lt;new hdd guid&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>By using the &lt;code>zfs attach&lt;/code> command like this, you&amp;rsquo;re creating a mirrored pair out of the existing and new HDDs. After this you&amp;rsquo;ll need to wait for the new HDD to resilver, but after that you&amp;rsquo;re all set.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2024/03/07/zfs-upgrade-single-disk-to-mirror/</guid><pubDate>Thu, 07 Mar 2024 00:00:00 +0000</pubDate></item><item><title>Arch Linux: Improve boot time performance</title><link>https://www.devroom.io/2024/02/08/arch-linux-improve-boot-time-performance/</link><description>&lt;figure class="side-image">&lt;img src="https://www.devroom.io/images/2024/02/arch-linux-logo.png">
&lt;/figure>
&lt;p>I run Debian on all my servers. It&amp;rsquo;s a great stable OS and I love it. Proxmox, &lt;a href="https://www.devroom.io/2020/11/12/the-big-diy-nas-update/#proxmox">which I run on my homelab server&lt;/a>, is also based on Debian.&lt;/p>
&lt;p>However, on my desktop I run &lt;a href="https://archlinux.org/">Arch Linux&lt;/a>. It&amp;rsquo;s a great distro to tinker with. It comes with a lot of &lt;em>up to date&lt;/em> packages, but it also has the AUR - Arch User Repository. So for any app you can find, there probably is an easy way to install it.&lt;/p>
&lt;h3 id="slllooooowwww">Slllooooowwww&amp;hellip;&lt;/h3>
&lt;p>As of late, I noticed that boot times on my system were getting longer. Which is strange, because I run some pretty okay hardware.&lt;/p>
&lt;p>As it turns out, cold booting this box takes 1min 7.538s, according to my logs.&lt;/p>
&lt;p>Luckily, the &lt;a href="https://wiki.archlinux.org/">Arch Wiki&lt;/a> offers a &lt;a href="https://wiki.archlinux.org/title/Improving_performance/Boot_process">nice guide on how to trouble shoot boot performance&lt;/a>.&lt;/p>
&lt;p>There&amp;rsquo;s &lt;code>systemd-analyze blame&lt;/code> which will show the time it takes each service to start up. I&amp;rsquo;ve copied the top 10 here, which incidentally are also all &amp;gt;1 second start-up times.&lt;/p>
&lt;pre>&lt;font color="#E64747">❯&lt;/font> &lt;font color="#42E66C">systemd-analyze&lt;/font> blame
20.771s docker.service
3.514s dev-sdb3.device
2.459s systemd-journal-flush.service
1.880s upower.service
1.806s ldconfig.service
1.687s systemd-tmpfiles-setup.service
1.587s containerd.service
1.287s systemd-modules-load.service
1.032s systemd-fsck@dev-disk-by\x2duuid-96EB\x2d4C82.service
1.028s cups.service
&lt;/pre>
&lt;p>Docker is a clear offender here. &lt;code>dev-sdb3&lt;/code> is also quite slow it seems.&lt;/p>
&lt;p>Another command recommended in the wiki is &lt;code>systemd-analyze critical-chain&lt;/code>. This will show you the critical chain to boot your system. Again, docker is here clearly a big offender.&lt;/p>
&lt;pre>&lt;font color="#E356A7">❯&lt;/font> &lt;font color="#42E66C">systemd-analyze&lt;/font> critical-chain
The time when unit became active or started is printed after the &amp;quot;@&amp;quot; character.
The time the unit took to start is printed after the &amp;quot;+&amp;quot; character.
graphical.target @33.660s
└─multi-user.target @33.660s
└─&lt;font color="#E356A7">&lt;b>docker.service @12.888s +20.771s&lt;/b>&lt;/font>
└─&lt;font color="#E356A7">&lt;b>containerd.service @11.264s +1.587s&lt;/b>&lt;/font>
└─network.target @11.236s
└─&lt;font color="#E356A7">&lt;b>wpa_supplicant.service @27.465s +268ms&lt;/b>&lt;/font>
└─basic.target @10.366s
└─&lt;font color="#E356A7">&lt;b>dbus-broker.service @9.822s +541ms&lt;/b>&lt;/font>
└─dbus.socket @9.793s
└─sysinit.target @9.759s
└─&lt;font color="#E356A7">&lt;b>systemd-update-done.service @9.722s +36ms&lt;/b>&lt;/font>
└─&lt;font color="#E356A7">&lt;b>systemd-journal-catalog-update.service @9.375s +326ms&lt;/b>&lt;/font>
└─&lt;font color="#E356A7">&lt;b>systemd-tmpfiles-setup.service @7.657s +1.687s&lt;/b>&lt;/font>
└─local-fs.target @7.587s
└─&lt;font color="#E356A7">&lt;b>boot.mount @7.458s +128ms&lt;/b>&lt;/font>
└─&lt;font color="#E356A7">&lt;b>systemd-fsck@dev-disk-by\x2duuid-96EB\x2d4C82.service @6.398s +1.032s&lt;/b>&lt;/font>
└─dev-disk-by\x2duuid-96EB\x2d4C82.device @6.397s
&lt;/pre>
&lt;p>But wait, there&amp;rsquo;s more. &lt;code>systemd-analyze plot &amp;gt; plot.svg&lt;/code> will generate an SVG image showing you the entire boot process in time. It&amp;rsquo;s big, but there are some clear red markers that indicate issues.&lt;/p>
&lt;p>At the bottom right you&amp;rsquo;ll find &lt;code>graphical.target&lt;/code>, where we want to end up as quickly as possible. And it&amp;rsquo;s clear &lt;code>docker&lt;/code> is in the way.&lt;/p>
&lt;p>&lt;img
src="https://www.devroom.io/images/2024/02/pre-plot.svg"
alt=""
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;em>Open the SVG in a new window to see more detail.&lt;/em>&lt;/p>
&lt;h2 id="fixed-it">Fixed it!&lt;/h2>
&lt;p>So, with &lt;code>docker&lt;/code> as a clear offender in slowing down the boot process, let&amp;rsquo;s fix that.&lt;/p>
&lt;p>There are two systemd units: &lt;code>docker.service&lt;/code> and &lt;code>docker.socket&lt;/code>.&lt;/p>
&lt;ul>
&lt;li>&lt;code>docker.service&lt;/code> is there to start docker and make sure it is up and running.&lt;/li>
&lt;li>&lt;code>docker.socket&lt;/code> listens on &lt;code>/run/docker.sock&lt;/code> (or &lt;code>/var/run/docker.sock&lt;/code> through a symlink) and will start &lt;code>docker.service&lt;/code> when needed.&lt;/li>
&lt;/ul>
&lt;p>I think you know where this is going. &lt;code>docker.socket&lt;/code> is disabled by default and &lt;code>docker.service&lt;/code> is enabled. Which makes sense, because when you boot your machine you want docker up and running as well. Especially for servers this makes sense.&lt;/p>
&lt;p>For my desktop, not so much. I use docker, but not always and I prefer to login and check my email while docker is booting in the background anyway.&lt;/p>
&lt;p>The trick thus is to disable &lt;code>docker.service&lt;/code> from starting automatically and make sure &lt;code>docker.socket&lt;/code> is enabled. That will take docker out of the criticial chain when booting and start docker when I&amp;rsquo;m logged in and ready to use it.&lt;/p>
&lt;pre tabindex="0">&lt;code>$ sudo systemctl disable docker.service
$ sudo systemctl enable docker.socket
&lt;/code>&lt;/pre>&lt;p>So, what does that look like in &lt;code>systemd-analyze&lt;/code>?&lt;/p>
&lt;pre>&lt;font color="#E356A7">❯&lt;/font> &lt;font color="#42E66C">systemd-analyze&lt;/font> critical-chain
The time when unit became active or started is printed after the &amp;quot;@&amp;quot; character.
The time the unit took to start is printed after the &amp;quot;+&amp;quot; character.
graphical.target @3.893s
└─multi-user.target @3.893s
└─&lt;font color="#E356A7">&lt;b>cups.service @3.672s +220ms&lt;/b>&lt;/font>
└─nss-user-lookup.target @3.763s
&lt;/pre>
&lt;pre>&lt;font color="#E356A7">❯&lt;/font> &lt;font color="#42E66C">systemd-analyze&lt;/font> blame
2.152s systemd-modules-load.service
1.295s dev-sdb3.device
622ms boot.mount
385ms NetworkManager.service
310ms systemd-udev-trigger.service
280ms udisks2.service
258ms systemd-remount-fs.service
220ms cups.service
203ms user@1000.service
189ms systemd-tmpfiles-setup.service
&lt;/pre>
&lt;p>&lt;img
src="https://www.devroom.io/images/2024/02/post_plot.svg"
alt=""
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;em>Open the SVG in a new window to see more detail.&lt;/em>&lt;/p>
&lt;pre>&lt;font color="#E64747">❯&lt;/font> &lt;font color="#42E66C">systemctl&lt;/font> status docker.socket
&lt;font color="#42E66C">&lt;b>●&lt;/b>&lt;/font> docker.socket - Docker Socket for the API
Loaded: loaded (/usr/lib/systemd/system/docker.socket; &lt;font color="#42E66C">&lt;b>enabled&lt;/b>&lt;/font>; preset: &lt;font color="#D7D75F">&lt;b>disabled&lt;/b>&lt;/font>)
Active: &lt;font color="#42E66C">&lt;b>active (running)&lt;/b>&lt;/font> since Thu 2024-02-08 10:38:47 CET; 5min ago
Triggers: &lt;font color="#42E66C">&lt;b>●&lt;/b>&lt;/font> docker.service
Listen: /run/docker.sock (Stream)
Tasks: 0 (limit: 38400)
Memory: 0B (peak: 516.0K)
CPU: 1ms
CGroup: /system.slice/docker.socket
&lt;/pre>
&lt;p>and&lt;/p>
&lt;pre>&lt;font color="#E64747">❯&lt;/font> &lt;font color="#42E66C">systemctl&lt;/font> status docker.service
&lt;font color="#42E66C">&lt;b>●&lt;/b>&lt;/font> docker.service - Docker Application Container Engine
Loaded: loaded (/usr/lib/systemd/system/docker.service; &lt;font color="#D7D75F">&lt;b>disabled&lt;/b>&lt;/font>; preset: &lt;font color="#D7D75F">&lt;b>disabled&lt;/b>&lt;/font>)
Active: &lt;font color="#42E66C">&lt;b>active (running)&lt;/b>&lt;/font> since Thu 2024-02-08 10:39:33 CET; 5min ago
TriggeredBy: &lt;font color="#42E66C">&lt;b>●&lt;/b>&lt;/font> docker.socket
Docs: https://docs.docker.com
Main PID: 2522 (dockerd)
Tasks: 42
Memory: 222.1M (peak: 235.7M)
CPU: 797ms
CGroup: /system.slice/docker.service
└─&lt;font color="#8A8A8A">2522 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock&lt;/font>
&lt;/pre>
&lt;h2 id="was-it-worth-it">Was it worth it?&lt;/h2>
&lt;p>Before:&lt;/p>
&lt;blockquote>
&lt;p>Startup finished in 14.729s (firmware) + 6.386s (loader) + 12.761s (kernel) + 33.661s (userspace) = 1min 7.538s graphical.target reached after 33.660s in userspace.&lt;/p>
&lt;/blockquote>
&lt;p>After:&lt;/p>
&lt;blockquote>
&lt;p>Startup finished in 13.735s (firmware) + 4.074s (loader) + 6.744s (kernel) + 3.893s (userspace) = 28.448s graphical.target reached after 3.893s in userspace.&lt;/p>
&lt;/blockquote>
&lt;p>Total boot time went down from 1m8s to 28s. I cannot explain the difference in kernel boot time, but the userspace savings are significant.&lt;/p>
&lt;p>From here I could probably optimize more by compiling a customized kernel or using a different bootloader. Suspend to RAM would be even faster, but that feels like cheating against a hard boot.&lt;/p>
&lt;p>Hopefully this will give you some pointers in how to troubleshoot slow boot times on your machine.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2024/02/08/arch-linux-improve-boot-time-performance/</guid><pubDate>Thu, 08 Feb 2024 00:00:00 +0000</pubDate></item><item><title>Troubleshooting zfs online not working</title><link>https://www.devroom.io/2024/01/27/troubleshooting-zfs-online-not-working/</link><description>&lt;p>Yesterday the &lt;a href="https://www.devroom.io/2024/01/24/prepare-new-harddisk-for-zfs/nas/">first 10TB recertified drive tested all okay&lt;/a>. I&amp;rsquo;ve decided to upgrade the four 3TB drives in my pool to 10TB to expand capacity.&lt;/p>
&lt;p>I have a 16 bay hot-swap chassis with 13 slots filled.&lt;/p>
&lt;ul>
&lt;li>4x 3TB (raidz1, tank)&lt;/li>
&lt;li>4x 8TB (raidz1, tank)&lt;/li>
&lt;li>4x 14TB (raidz1, tank)&lt;/li>
&lt;li>A single 3TB drive as &amp;lsquo;scratch&amp;rsquo; disk for temporary backup data&lt;/li>
&lt;/ul>
&lt;p>I really want to have the same vdev drives stacked in the same column physically. I&amp;rsquo;m like that. So my first order of business was to move one of the 3TB drives to a free slot to make room for the 10TB disks.&lt;/p>
&lt;p>So I performed a &lt;code>zfs offline tank &amp;lt;id&amp;gt;&lt;/code> on the disk, took it out and stuck it in another drive bay. &lt;code>dmesg&lt;/code> clearly showed me the new disk was ready an available:&lt;/p>
&lt;pre tabindex="0">&lt;code>sd 0:0:18:0: [sdi] Attached SCSI disk
&lt;/code>&lt;/pre>&lt;p>Since it&amp;rsquo;s the same drive, with the same GUID, I expected ZFS to recognise this disk, online it and then resilver it as necessary. I&amp;rsquo;ve seen this work before with one of the 14TB drives when I had a bad SATA cable. But instead, sadness:&lt;/p>
&lt;pre tabindex="0">&lt;code>tank DEGRADED
raidz1-0 DEGRADED
wwn-0x50014ee2bb55b28f OFFLINE
wwn-0x50014ee265ff666a ONLINE
wwn-0x50014ee210aaa174 ONLINE
wwn-0x50014ee265ff7131 ONLINE
&lt;/code>&lt;/pre>&lt;p>So, let&amp;rsquo;s online the disk manually, and get the vdev back in working order!&lt;/p>
&lt;pre tabindex="0">&lt;code>$ zfs online tank wwn-0x50014ee2bb55b28f
(no output)
&lt;/code>&lt;/pre>&lt;p>That didn&amp;rsquo;t work.&lt;/p>
&lt;pre tabindex="0">&lt;code>$ zfs online tank /dev/disk/by-id/wwn-0x50014ee2bb55b28f
(no output)
$ zfs online tank sdi
(no output)
$ zfs online tank /dev/sdi
(no output)
$ zfs online tank &amp;lt;GUID&amp;gt;
(no output)
&lt;/code>&lt;/pre>&lt;p>No dice.&lt;/p>
&lt;p>The obvious solution would be to wipe the 3TB disk and replace it with itself, doing a full 3TB resilver on it. But, that would be too easy. Also, onlining a disk should work - so why isn&amp;rsquo;t it.&lt;/p>
&lt;p>At this point, I had exhausted my Google-fu. I even asked ChatGPT, but it didn&amp;rsquo;t come up with anything I hadn&amp;rsquo;t tried yet.&lt;/p>
&lt;p>Next step: &lt;strong>IRC&lt;/strong>&lt;/p>
&lt;p>I hopped on &lt;code>#zfs&lt;/code> on &lt;a href="https://libera.chat/">Libera Chat&lt;/a> and dropped my question. A few people were online and started giving the obvious answers. Together we decided that &lt;em>it&amp;rsquo;s not working&lt;/em>. With one of the people (thanks, Rich) in &lt;code>#zfs&lt;/code> I dove into partition tables, labels, cache files, etc. Turns out Rich does quite some work on ZFS and he was of great help investigating the issue.&lt;/p>
&lt;p>Then, I thought to check the exit code for the &lt;code>zfs online&lt;/code> command.&lt;/p>
&lt;pre tabindex="0">&lt;code>$ zfs online tank wwn-0x50014ee2bb55b28f
(no output)
$ echo $?
1
&lt;/code>&lt;/pre>&lt;p>Aah! So, &lt;code>zfs online&lt;/code> exists with an error. But why?&lt;/p>
&lt;p>&lt;img
src="https://www.devroom.io/images/2024-01-27/zpool-patch.jpg"
alt="A quick patch for zpool"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;p>Well, it was helpful in the sense that &lt;code>zpool online&lt;/code> now says it cannot online the drive, but the exact reason was still unknown. I then tried to, instead of the whole disk, online the partition. Let me explain.&lt;/p>
&lt;pre tabindex="0">&lt;code>Device Start End Sectors Size Type
/dev/sdb1 128 4194431 4194304 2G FreeBSD swap
/dev/sdb2 4194432 5860533127 5856338696 2.7T FreeBSD ZFS
&lt;/code>&lt;/pre>&lt;p>So this is how FreeNAS (based on FreeBSD) partitioned the disk when it was added to the original pool. So now, instead of adding &lt;code>/dev/sdb&lt;/code> I was going to try to online just the ZFS partition &lt;code>/dev/sdb2&lt;/code>. Not that &lt;code>/dev/disk/by-id/wwn-0x50014ee2bb55b28f-part2&lt;/code> symlinks to &lt;code>/dev/sda2&lt;/code>.&lt;/p>
&lt;pre tabindex="0">&lt;code>$ zpool online tank sdb2
cannot online sdb2: cannot relabel &amp;#39;sdb2&amp;#39;: unable to read disk capacity
&lt;/code>&lt;/pre>&lt;p>In some way, this error makes sense, since I&amp;rsquo;m trying to online a partition and not a disk. Still, I threw the message into Google, which led me to an &lt;a href="https://github.com/openzfs/zfs/issues/8449">open issue on OpenZFS&lt;/a> that says drives cannot be online&amp;rsquo;d when &lt;code>autoexpand=on&lt;/code> for the pool.&lt;/p>
&lt;p>Well, that would be easy to test:&lt;/p>
&lt;pre tabindex="0">&lt;code>$ zpool set autoexpand=off tank
$ zpool online tank &amp;lt;guid&amp;gt;
$ zpool set autoexpand=on tank
&lt;/code>&lt;/pre>&lt;p>It took the better part of 15 seconds for the drive to sync up and resilver. Done! I think @behlendorf explained the root cause of this problem better that I can:&lt;/p>
&lt;blockquote>
&lt;p>The core issue here is that since the pool was created on FreeBSD the partition layout differs slightly from what&amp;rsquo;s expected on Linux. This difference prevents ZFS from being able to auto-expand the device since it can&amp;rsquo;t safely re-partition it to use the additional space. Unfortunately, that results in the device being online at all.&lt;/p>
&lt;/blockquote>
&lt;p>There is nothing I can do to remedy this problem. The only way to &lt;em>&amp;ldquo;fix&amp;rdquo;&lt;/em> it is to offline a disk with FreeBSD partitioning, wipe it and then replace it with itself so a new Linux compatible partition table is created.&lt;/p>
&lt;p>For now, I&amp;rsquo;m going to replace the 3TB drives anyway. The new 10TB ones are getting the Linux compatible partition table anyway. That leaves my 8TB drives still in limbo, but since I now know the root cause of the problem, I can easily work around it when necessary.&lt;/p>
&lt;p>Big shout out to &lt;a href="https://github.com/rincebrain">Rich&lt;/a> for assisting me with this issue! I probably wouldn&amp;rsquo;t have been able to figure it out with you.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2024/01/27/troubleshooting-zfs-online-not-working/</guid><pubDate>Sat, 27 Jan 2024 00:00:00 +0000</pubDate></item><item><title>Prepare new harddisk for ZFS/NAS</title><link>https://www.devroom.io/2024/01/24/prepare-new-harddisk-for-zfs/nas/</link><description>&lt;p>You can read more on my homelab and datahoarding problem &lt;a href="https://www.devroom.io/2020/02/28/building-a-diy-home-server-with-freenas/">here&lt;/a> and &lt;a href="https://www.devroom.io/2020/11/12/the-big-diy-nas-update/">here&lt;/a>.&lt;/p>
&lt;p>Today I scored two &lt;em>recertified&lt;/em> 10TB HGST drives for very little. Normally I&amp;rsquo;d go for the brand new stuff, but this deal was too good to be true.&lt;/p>
&lt;p>My main goal is to check if these recertified disks are worth the money/effort. Next up I want to experiment with a new ZFS pool setups. (You still cannot remove a raidz vdev from your pool in 2024&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup>)&lt;/p>
&lt;h2 id="current-state-of-affairs">Current state of affairs&lt;/h2>
&lt;p>Right now my main ZFS storage pool looks like this:&lt;/p>
&lt;ul>
&lt;li>&lt;code>tank&lt;/code>
&lt;ul>
&lt;li>&lt;code>raidz1&lt;/code> 4x 3TB WD Red&lt;/li>
&lt;li>&lt;code>raidz1&lt;/code> 4x 8TB WD White&lt;/li>
&lt;li>&lt;code>raidz1&lt;/code> 4x 14TB WD White&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>All in all good for 100TB raw storage space and roughly 75TB of usable storage. And yet, it&amp;rsquo;s getting full. Linux ISO&amp;rsquo;s take up a lot of space. I also have a 2TB (2x 1TB SSD mirrors) pool for VM storage and a single 3TB drive as a backup intermediate disk (i.e. backups are copied there, then uploaded elsewhere).&lt;/p>
&lt;p>As stated, I cannot &lt;em>remove&lt;/em> any of those &lt;code>raidz1&lt;/code> vdevs. My only options are to build a new pool with new disks or replace disks in this pool.&lt;/p>
&lt;h2 id="its-all-about-trust">It&amp;rsquo;s all about trust&lt;/h2>
&lt;p>So, new drives I normally trust. If they don&amp;rsquo;t work, they don&amp;rsquo;t work. But if they spin-up, I&amp;rsquo;ve alwasy assumed they&amp;rsquo;d be okay. Yeah, I know.&lt;/p>
&lt;p>But, since I now have a pair of 10TB &lt;em>recertified&lt;/em> drives, I&amp;rsquo;d like to be sure they&amp;rsquo;re good to go. Recertified in this context probably means they were retired from a data center somewhere. Their power-on time is a little over 5 years with a production data of December 2017.&lt;/p>
&lt;p>To make sure these disks are good to go, I&amp;rsquo;m going to run a bunch of tests against them to see if they hold up.&lt;/p>
&lt;ol>
&lt;li>SMART conveyance test&lt;/li>
&lt;li>SMART extended test&lt;/li>
&lt;li>&lt;code>badblocks&lt;/code>&lt;/li>
&lt;/ol>
&lt;h2 id="smart">S.M.A.R.T.&lt;/h2>
&lt;p>If you don&amp;rsquo;t know about different SMART tests, here&amp;rsquo;s a refresher. I&amp;rsquo;m skipping the short test, because I&amp;rsquo;m running a long test.&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>Short&lt;/strong> Checks the electrical and mechanical performance as well as the read performance of the disk. Electrical tests might include a test of buffer RAM, a read/write circuitry test, or a test of the read/write head elements. Mechanical test includes seeking and servo on data tracks. Scans small parts of the drive&amp;rsquo;s surface (area is vendor-specific and there is a time limit on the test). Checks the list of pending sectors that may have read errors, and it usually takes under two minutes.&lt;/p>
&lt;/blockquote>
&lt;blockquote>
&lt;p>&lt;strong>Long/extended&lt;/strong> A longer and more thorough version of the short self-test, scanning the entire disk surface with no time limit. This test usually takes several hours, depending on the read/write speed of the drive and its size.&lt;/p>
&lt;/blockquote>
&lt;blockquote>
&lt;p>&lt;strong>Conveyance&lt;/strong> Intended as a quick test to identify damage incurred during transporting of the device from the drive manufacturer to the computer manufacturer. Only available on ATA drives, and it usually takes several minutes.&lt;/p>
&lt;/blockquote>
&lt;p>Running these is as easy as:&lt;/p>
&lt;pre tabindex="0">&lt;code>$ sudo smartctl -t &amp;lt;short|long|conveyance&amp;gt; /dev/sda
&lt;/code>&lt;/pre>&lt;p>If you want to know how long theses tests are going to take:&lt;/p>
&lt;pre tabindex="0">&lt;code>$ sudo smartctl -c /dev/sda
...
Short self-test routine
recommended polling time: ( 2) minutes.
Extended self-test routine
recommended polling time: (1144) minutes.
...
&lt;/code>&lt;/pre>&lt;p>So it&amp;rsquo;ll take a little of 19 hours to do a full extended SMART test.&lt;/p>
&lt;h2 id="badblocks">Badblocks&lt;/h2>
&lt;p>Badblocks is a utility, well, searches a disk for bad blocks. It will write data to the entire disk an verify each block to good.&lt;/p>
&lt;p>Naively I ran the following to do a full write test with progress indicator and verbose output:&lt;/p>
&lt;pre tabindex="0">&lt;code>$ sudo badblocks -wsv /dev/sda
badblocks: Value too large for defined data type invalid end block (9766436864): must be 32-bit value
&lt;/code>&lt;/pre>&lt;p>As it turns out, &lt;code>badblocks&lt;/code> uses a default block size of 1024, meaning it cannot - out of the box - scan disks 8TB or larger. Let&amp;rsquo;s figure out what blocksize our disk uses, and plug that information into &lt;code>badblocks&lt;/code>.&lt;/p>
&lt;pre tabindex="0">&lt;code>$ sudo blockdev --getbsz /dev/sda
4096
$ sudo badblocks -t random -w -v -s -b 4096 /dev/sda
Checking for bad blocks in read-write mode
From block 0 to 2441609215
Testing with random pattern: 5.97% done, 39:45 elapsed. (0/0/0 errors)
&lt;/code>&lt;/pre>&lt;p>And we&amp;rsquo;re in business. Well, now we wait a few hours / days for &lt;code>badblocks&lt;/code> to complete. I might even do a second pass just for the fun of it.&lt;/p>
&lt;h2 id="whats-next">What&amp;rsquo;s next?&lt;/h2>
&lt;p>After I&amp;rsquo;ve run at least two badblocks passes and both they conveyance and extended SMART tests on this disk, I&amp;rsquo;m going to do it on the other one as well. If that all goes well I&amp;rsquo;ll probably put them as a ZFS mirror pair in a new pool and do some testing there.&lt;/p>
&lt;div class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1">
&lt;p>There are good technical reasons for that, I know. Wish I&amp;rsquo;d known about it before I built my pool, though.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2024/01/24/prepare-new-harddisk-for-zfs/nas/</guid><pubDate>Wed, 24 Jan 2024 00:00:00 +0000</pubDate></item><item><title>Volkswagen Golf Cabrio - Update</title><link>https://www.devroom.io/2024/01/24/volkswagen-golf-cabrio-update/</link><description>&lt;p>It&amp;rsquo;s been almost 18 months since I purchased a, let&amp;rsquo;s be honest here, beat-up Volkswagen Golf Cabrio from 1994. At the time my knowledge about car mechanics was pretty much zero. Did I tell you I like a good challenge?&lt;/p>
&lt;p>I needed to start with the basics, like new brakes, struts and an obviously leaking exhaust to pass the annual safety and roadworthiness inspection. A big shout-out to &lt;a href="https://www.youtube.com/@chrisfix">ChrisFix&lt;/a> for all his detailed videos.&lt;/p>
&lt;p>&lt;img
src="https://www.devroom.io/images/2024-01-24-golf3/status-update-jan-2024.jpg"
alt=""
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;h2 id="big-jobs">Big jobs&lt;/h2>
&lt;p>With the basics out of the way I had to move on to the mechanical part of things. I really wanted to keep this thing on the road. Because I had a roadtrip planned with my co-workes from &lt;a href="https://www.kabisa.nl">Kabisa&lt;/a> I &lt;em>really&lt;/em> needed to sort out the clutch, because it was gone. It was not completely dead yet, but almost.&lt;/p>
&lt;p>This was my first big job. One that &lt;em>real&lt;/em> mechanics and dealerships charge you hundreds of Euro for. It was daunting, to be honest. However, with good preparation, tips from friends (who actually know what they&amp;rsquo;re talking about), and a lot of curse words, I managed it.&lt;/p>
&lt;p>Another big job, which I managed to do last year after retiring the car to storage for winter, was the timing belt. Another &lt;em>big job&lt;/em> that feels impossible and is normally hugely expensive. Again, with some good preparation it was, actually, a breeze. Okay, the 1.8 liter, single cam, petrol engine is rather &lt;em>simple&lt;/em> compared to modern engines.
Since I needed to drain the coolant for the new water pump, I replaced some more coolant flanges at the same time. Those had sprung small leaks. Note for next time: replace them all in one go on a car of this age.&lt;/p>
&lt;p>&lt;img
src="https://www.devroom.io/images/2024-01-24-golf3/golf-timing-belt.jpg"
alt=""
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;p>I also ran into a problem with the O2 sensor. In a 2001 Polo I used to drive, this was like a problem every six months. Turns out that you just need a new sensor (which are not that expensive), the right socket for an O2 sensor and the strength of a giant.&lt;/p>
&lt;h2 id="looks">Looks&lt;/h2>
&lt;p>I also spent some time on making the car look better and just that bit more luxurious. I found an full leather interior set, which was easily swapped out and makes the
whole car look &lt;em>soooo&lt;/em> much better.&lt;/p>
&lt;p>I also found a nice car stereo (Blaupunkt Milano 200 BT). It&amp;rsquo;s didn&amp;rsquo;t cost a fortune and works way better than the cheap unit it came with. I would have liked to get a
vintage Beta or Gamma set from VW, but it was hard to find an affordable one.&lt;/p>
&lt;p>&lt;img
src="https://www.devroom.io/images/2024-01-24-golf3/golf-3-car-stereo.jpg"
alt=""
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;h2 id="whats-next">What&amp;rsquo;s next?&lt;/h2>
&lt;p>Right now I have three main jobs left to do on the car:&lt;/p>
&lt;ol>
&lt;li>Re-paint (part of) the front bumper where it&amp;rsquo;s scratched. This will probably need some sanding, priming and painting. I hope to do this early spring when the weather is a bit warmer.&lt;/li>
&lt;li>Re-seal the ventilation blend doors. The foam is gone, so now it only blows warm air. Check &lt;a href="https://humblemechanic.com/fix-blend-doors-install-heater-core-reseal-hvac-box/">HumbleMechanic&lt;/a>, he can explain it better than I can.&lt;/li>
&lt;li>Clean and polish the entire car and touch up the plastic trim. I tried &lt;em>Meguiars Ultimate Black Plastic Restorer&lt;/em> and it seems to work wonderfully.&lt;/li>
&lt;/ol>
&lt;p>When all that&amp;rsquo;s done. Well, I think I&amp;rsquo;m going to drive it for a bit and put it up for sale. I&amp;rsquo;m going to miss this little car, but I also really want to move on to a new project. Maybe a Skoda Octaiva Vrs or some other nice VAG car to sink my teeth in.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2024/01/24/volkswagen-golf-cabrio-update/</guid><pubDate>Wed, 24 Jan 2024 00:00:00 +0000</pubDate></item><item><title>Base2Tone Cave Dark for Slack</title><link>https://www.devroom.io/2023/05/17/base2tone-cave-dark-for-slack/</link><description>&lt;p>I like my colorschemes. The most recent one I discoved is &lt;a href="https://base2t.one/">Base2Tone&lt;/a> &lt;a href="https://base2t.one/demo/cave/">Cave Dark&lt;/a>.&lt;/p>
&lt;p>So, naturally I wanted to have it in Slack too, so I created a custom theme. Here it is.&lt;/p>
&lt;h3 id="preview">Preview&lt;/h3>
&lt;div class="auto-width">
&lt;img src="https://www.devroom.io/img/base2tone-cave-dark-slack.png" alt="Base2Tone Cave Dark Slack preview" />
&lt;/div>
&lt;h3 id="installation">Installation&lt;/h3>
&lt;pre tabindex="0">&lt;code>#222021,#1A2B23,#EBBC47,#1A1D21,#936C7A,#EBBC47,#EBBC47,#AD1F51,#222021,#EBBC47
&lt;/code>&lt;/pre>&lt;p>Got to &lt;em>Preferences&lt;/em> → &lt;em>Themes&lt;/em> and paste the above in the appropriate box.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2023/05/17/base2tone-cave-dark-for-slack/</guid><pubDate>Wed, 17 May 2023 00:00:00 +0000</pubDate></item><item><title>10GB Ethernet with Proxmox and Ryzentosh 3700X with macos Catalina</title><link>https://www.devroom.io/2020/11/18/10gb-ethernet-with-proxmox-and-ryzentosh-3700x-with-macos-catalina/</link><description>&lt;p>&lt;em>Please read my previous post on my DIY NAS here: &lt;a href="https://www.devroom.io/2020/02/28/building-a-diy-home-server-with-freenas/">1&lt;/a> and &lt;a href="https://www.devroom.io/2020/11/12/the-big-diy-nas-update/">2&lt;/a>.&lt;/em>&lt;/p>
&lt;p>&lt;em>10 gigabit&lt;/em>. That is &lt;em>10,000 megabit&lt;/em>.&lt;/p>
&lt;p>I come from a time when fast internet meant you had ISDN. For reference, that&amp;rsquo;s 192kbps, or roughly 24KB/s.&lt;/p>
&lt;p>For the past eight years I&amp;rsquo;ve had CAT-5E / CAT 6 installed through-out my house and have enjoyed LAN speeds of 1000mbps or 1gbps. At full throttle that ways in at about 125 Megabyte/s.&lt;/p>
&lt;p>Now, gigabit ethernet is fine for almost everything. Unless you either transfer large files or if you use that NAS as a direct access disk on a workstation.&lt;/p>
&lt;p>The latter is pretty much my use-case. I make music and the occasional video, and storing &lt;em>and&lt;/em> manipulating all this over a 1gbps line is not so fun.&lt;/p>
&lt;p>Yes, I &lt;em>do&lt;/em> have an SSD, yes I do have spinning rust in my workstation. The problem is that they lack redundancy and extra work to backup. Everything that&amp;rsquo;s on my NAS profits
from the redudancy of my ZFS pool, periodical ZFS snapshots, decent drive health monitoring (thanks, Proxmox!) and automated backups to another set of disks and the cloud.&lt;/p>
&lt;p>When I found out that there&amp;rsquo;s a &lt;em>really&lt;/em> cheap option to add 10 gigabit networking between my Ryzentosh and Proxmox NAS - I went for it.&lt;/p>
&lt;p>Total cost of this project:&lt;/p>
&lt;ul>
&lt;li>$40 - 2x Solarflare SFN5122F shipped. yes, that is &lt;em>two&lt;/em> network cards, with &lt;em>two&lt;/em> 10 gbit ports each. For $40. Including shipping.&lt;/li>
&lt;li>$20 - A SFP+ 10gbe copper cable (1 meter).&lt;/li>
&lt;/ul>
&lt;p>That. Is. It.&lt;/p>
&lt;p>&lt;img
src="https://www.devroom.io/img/nas-10gbe-solarflare.jpg"
alt=""
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;h2 id="proxmox-setup">Proxmox setup&lt;/h2>
&lt;p>Okay, let&amp;rsquo;s start with the easy part. Proxmox. Or Debian Linux, more specifically. It&amp;rsquo;s just plug and play. ;-) Plug the card in any PCIe x8 slot and you&amp;rsquo;re
good to go.&lt;/p>
&lt;p>&lt;img
src="https://www.devroom.io/img/nas-10gbe-proxmox.png"
alt=""
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;p>Next, you only need to configure your NIC. It&amp;rsquo;s a good idea to create a &lt;code>Linux Bridge&lt;/code> in Proxmox. This will allow you to assign this interface to containers as well,
as I&amp;rsquo;ll come back to in a minute.&lt;/p>
&lt;p>You can pick any IP address you like in a private range. You don&amp;rsquo;t need a gateway - well I didn&amp;rsquo;t - because I have a point-to-point connection. Litteraly a cable
between the two systems.&lt;/p>
&lt;p>&lt;img
src="https://www.devroom.io/img/nas-10gbe-proxmox-mtu.png"
alt=""
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;p>Be sure to enable &lt;em>advanced&lt;/em> options and set &lt;code>MTU&lt;/code> to &lt;code>9000&lt;/code> - jumbo frames. This will enable you to reach those juicy 10gbps speeds later on.&lt;/p>
&lt;h2 id="hackintosh--open-core-setup">Hackintosh / Open Core setup&lt;/h2>
&lt;p>&lt;a href="https://www.devroom.io/files/macos/solarflare-10gbe-mac-drivers.zip">Download Solarflare 10GbE Mac Drivers.zip&lt;/a>&lt;/p>
&lt;p>&lt;em>Solarflare is no longer supporting macos and has since removed these drivers from their website. The drivers they made still work in Catalina.&lt;/em>&lt;/p>
&lt;p>This is where things get a bit more tricky. I can&amp;rsquo;t vouch for this working on a &lt;em>real&lt;/em> Mac, but then again, adding (non-Apple) hardware is not what you&amp;rsquo;re
supposed to do with these anyway, right? ;-)&lt;/p>
&lt;p>Any modern hackintosh should run &lt;a href="https://github.com/acidanthera/OpenCorePkg">Open Core&lt;/a> these days. &lt;a href="https://github.com/acidanthera/OpenCorePkg">Open Core&lt;/a> is awesome. It takes a bit of work to setup, but your mac will continue working after that,
updates and everything. So, the next bit assumes you&amp;rsquo;re familiar with Open Core.&lt;/p>
&lt;ul>
&lt;li>Download the archive above. It contains a &lt;em>signed&lt;/em> installer for macos 10.9. Mount that disk image. Don&amp;rsquo;t bother using the installer.&lt;/li>
&lt;li>Use &amp;ldquo;View contents&amp;rdquo; on the installer and find &lt;code>Archive.pax&lt;/code>. Unpack that and find your kext and some &lt;code>Application Support&lt;/code> stuff.&lt;/li>
&lt;li>Drag folder under &lt;code>Application Support&lt;/code> to &lt;code>/Library/Application Support&lt;/code> if you need it.&lt;/li>
&lt;li>Mount your &lt;code>EFI&lt;/code> partition and drop the kext in the folder with your other kexts.&lt;/li>
&lt;li>Then update your &lt;code>config.plist&lt;/code> to load the kext - you can copy paste any other entry and change the filename. But again, if you&amp;rsquo;re already running &lt;a href="https://github.com/acidanthera/OpenCorePkg">Open Core&lt;/a>, you probably know the drill.&lt;/li>
&lt;li>Reboot&lt;/li>
&lt;/ul>
&lt;p>Next up, all you need to do is configure your NIC under Network Preferences. Again, pick an IP address in the same subnet as your server.&lt;/p>
&lt;p>&lt;img
src="https://www.devroom.io/img/nas-10gbe-network-1.png"
alt=""
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;p>Make sure to select &amp;ldquo;Jumbo Frames (9000)&amp;rdquo; under &amp;ldquo;Hardware&amp;rdquo; here as well. Both sides need to have them enabled (or disabled).&lt;/p>
&lt;p>&lt;img
src="https://www.devroom.io/img/nas-10gbe-network-2.png"
alt=""
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;h2 id="testing-speed">Testing speed&lt;/h2>
&lt;p>Okay, time to do some benchmarks. Let&amp;rsquo;s break out &lt;code>iperf3&lt;/code>.&lt;/p>
&lt;ul>
&lt;li>On Proxmox: &lt;code>iperf3 -s&lt;/code> to start the server&lt;/li>
&lt;li>On the Ryzentosh: &lt;code>iperf3 -c 172.16.0.20 -f g&lt;/code>&lt;/li>
&lt;li>Wait 10 seconds (for 11.2 GIGABYTES to be transferred)&lt;/li>
&lt;/ul>
&lt;pre tabindex="0">&lt;code># iperf3 -c 172.16.0.20 -f g
Connecting to host 172.16.0.20, port 5201
[ 5] local 172.16.0.10 port 50113 connected to 172.16.0.20 port 5201
[ ID] Interval Transfer Bitrate
[ 5] 0.00-1.00 sec 1.12 GBytes 9.63 Gbits/sec
[ 5] 1.00-2.00 sec 1.12 GBytes 9.64 Gbits/sec
[ 5] 2.00-3.00 sec 1.12 GBytes 9.63 Gbits/sec
[ 5] 3.00-4.00 sec 1.12 GBytes 9.63 Gbits/sec
[ 5] 4.00-5.00 sec 1.12 GBytes 9.64 Gbits/sec
[ 5] 5.00-6.00 sec 1.12 GBytes 9.63 Gbits/sec
[ 5] 6.00-7.00 sec 1.12 GBytes 9.63 Gbits/sec
[ 5] 7.00-8.00 sec 1.12 GBytes 9.63 Gbits/sec
[ 5] 8.00-9.00 sec 1.12 GBytes 9.64 Gbits/sec
[ 5] 9.00-10.00 sec 1.12 GBytes 9.64 Gbits/sec
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval Transfer Bitrate
[ 5] 0.00-10.00 sec 11.2 GBytes 9.64 Gbits/sec sender
[ 5] 0.00-10.00 sec 11.2 GBytes 9.63 Gbits/sec receiver
iperf Done.
&lt;/code>&lt;/pre>&lt;p>You can also adjust the output formatting to give you bitrates in GBytes/sec instead:&lt;/p>
&lt;pre tabindex="0">&lt;code># iperf3 -c 172.16.0.20 -f Gbytes  1|2 ↵  1005  18:50:53
Connecting to host 172.16.0.20, port 5201
[ 5] local 172.16.0.10 port 50155 connected to 172.16.0.20 port 5201
[ ID] Interval Transfer Bitrate
[ 5] 0.00-1.00 sec 1.12 GBytes 1.12 GBytes/sec
[ 5] 1.00-2.00 sec 1.12 GBytes 1.12 GBytes/sec
[ 5] 2.00-3.00 sec 1.12 GBytes 1.12 GBytes/sec
[ 5] 3.00-4.00 sec 1.12 GBytes 1.12 GBytes/sec
[ 5] 4.00-5.00 sec 1.12 GBytes 1.12 GBytes/sec
[ 5] 5.00-6.00 sec 1.12 GBytes 1.12 GBytes/sec
[ 5] 6.00-7.00 sec 1.12 GBytes 1.12 GBytes/sec
[ 5] 7.00-8.00 sec 1.12 GBytes 1.12 GBytes/sec
[ 5] 8.00-9.00 sec 1.12 GBytes 1.12 GBytes/sec
[ 5] 9.00-10.00 sec 1.12 GBytes 1.12 GBytes/sec
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval Transfer Bitrate
[ 5] 0.00-10.00 sec 11.2 GBytes 1.12 GBytes/sec sender
[ 5] 0.00-10.00 sec 11.2 GBytes 1.12 GBytes/sec receiver
&lt;/code>&lt;/pre>&lt;p>That is 1.12GB or 1120MB per &lt;em>&lt;strong>SECOND&lt;/strong>&lt;/em>. Imaging that. That&amp;rsquo;s a bit more than a full CD of data. Heck, that&amp;rsquo;s about 1000 floppy disks per second. Okay, I&amp;rsquo;m claiming that. 1000fdps. :-)&lt;/p>
&lt;h2 id="samba">Samba&lt;/h2>
&lt;p>I run Samba in a LXC container on Proxmox. There&amp;rsquo;s now only a few things to do for Samba to use that fat pipe of networking goodness.&lt;/p>
&lt;p>First up, add another Network device. Pick another IP address in the private subnet and use the Linux Bridge you created earlier. You can add this &lt;em>in addition&lt;/em> to the network device you
probably already have for your old and slow gigabit LAN.&lt;/p>
&lt;p>&lt;img
src="https://www.devroom.io/img/nas-10gbe-proxmox-lxc.png"
alt=""
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;p>You may need to check &lt;code>/etc/network/interfaces&lt;/code>. Proxmox probably updated it for you already, but it&amp;rsquo;s missing the &lt;code>MTU&lt;/code> setting. So, just add it and restart networking (or you&amp;rsquo;re container, it&amp;rsquo;s fast anyway) ;-)&lt;/p>
&lt;pre tabindex="0">&lt;code>iface eth1 inet static
address 172.16.0.22/16
mtu 9000
&lt;/code>&lt;/pre>&lt;p>If you did it all correctly you should see:&lt;/p>
&lt;pre tabindex="0">&lt;code># ip link list
1: lo: &amp;lt;LOOPBACK,UP,LOWER_UP&amp;gt; mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0@if82: &amp;lt;BROADCAST,MULTICAST,UP,LOWER_UP&amp;gt; mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
link/ether 8a:b3:db:74:b9:60 brd ff:ff:ff:ff:ff:ff link-netnsid 0
3: eth1@if86: &amp;lt;BROADCAST,MULTICAST,UP,LOWER_UP&amp;gt; mtu 9000 qdisc noqueue state UP mode DEFAULT group default qlen 1000
link/ether ea:49:ef:01:82:80 brd ff:ff:ff:ff:ff:ff link-netnsid 0
&lt;/code>&lt;/pre>&lt;p>Then you need to configure Samba to listen on both your gigabit and 10 gigabit interfaces. Check &lt;code>/etc/samba/smb.conf&lt;/code>, there&amp;rsquo;s probably a comment in there
already about the &lt;code>interfaces&lt;/code> flag. I simply added the following to enable both my interfaces.&lt;/p>
&lt;p>&lt;code>interfaces = 172.16.0.22/16 10.0.2.245/16&lt;/code>&lt;/p>
&lt;p>Restart samba or reboot. And you&amp;rsquo;re good to go.&lt;/p>
&lt;h2 id="accessing-10000mbps-samba-shares">Accessing 10,000mbps Samba shares&lt;/h2>
&lt;p>You have to routes to your NAS / Samba shares now. One over your old gigabit network and one over your fast 10gbps point-to-point connection. Samba can handle both, so
it&amp;rsquo;s only a matter of accessing it correctly from macos.&lt;/p>
&lt;p>Open Finder and press ⌘-K (or select Go -&amp;gt; Connect to Server&amp;hellip;). Then you&amp;rsquo;ll need to enter the proper URL for your Samba share like so. Remember to use the
IP for your container - not the IP for your Proxmox host.&lt;/p>
&lt;p>&lt;img
src="https://www.devroom.io/img/nas-10gbe-samba-mount.png"
alt=""
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;h2 id="thats-all">That&amp;rsquo;s all&lt;/h2>
&lt;p>That&amp;rsquo;s all there is to it. Mount those shares and enjoy blistering fast transfer speeds. Your HDD&amp;rsquo;s are once again the bottleneck now.&lt;/p>
&lt;p>No, I&amp;rsquo;m not upgrading to a full SSD ZFS pool&amp;hellip;&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2020/11/18/10gb-ethernet-with-proxmox-and-ryzentosh-3700x-with-macos-catalina/</guid><pubDate>Wed, 18 Nov 2020 00:00:00 +0000</pubDate></item><item><title>The Big DIY NAS Update</title><link>https://www.devroom.io/2020/11/12/the-big-diy-nas-update/</link><description>&lt;p>&lt;em>Please read my previous post on my DIY NAS here: &lt;a href="https://www.devroom.io/2020/02/28/building-a-diy-home-server-with-freenas/">https://www.devroom.io/2020/02/28/building-a-diy-home-server-with-freenas&lt;/a>&lt;/em>&lt;/p>
&lt;h2 id="a-brief-history">A brief history&lt;/h2>
&lt;p>In May 2019 I decided it was time to jump on the NAS bandwagon. I started out with the following:&lt;/p>
&lt;ul>
&lt;li>Fractal Design Node 804&lt;/li>
&lt;li>4x 3TB WD Red (retail)&lt;/li>
&lt;li>Gigabyte Z370M D3H&lt;/li>
&lt;li>Intel i3 i8350K 4c/4t CPU&lt;/li>
&lt;li>16GB Non-ECC Memory&lt;/li>
&lt;li>128 GB Gigabyte SSD Boot drive&lt;/li>
&lt;li>FreeNAS with a single RaidZ1 (4x 3TB) pool&lt;/li>
&lt;/ul>
&lt;p>That&amp;rsquo;s when it all started. Then things got wild.&lt;/p>
&lt;h2 id="update-november-2020">Update November 2020&lt;/h2>
&lt;p>So, how are things today.&lt;/p>
&lt;ul>
&lt;li>Norco 4U, 16x 3.5&amp;quot; LFF Hot swap case&lt;/li>
&lt;li>4x 3TB WD Red (retail)&lt;/li>
&lt;li>4x 8TB WD White (shucked)&lt;/li>
&lt;li>4x 14TB WD White (shucked)&lt;/li>
&lt;li>1x 3TB WD Green (retail)&lt;/li>
&lt;li>2x 120GB Crucial BX500 SSD&amp;rsquo;s (because why not?)&lt;/li>
&lt;li>&lt;a href="https://www.supermicro.com/en/products/motherboard/X10SRi-F">SuperMicro X10SRi-F motherboard&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://ark.intel.com/content/www/us/en/ark/products/83352/intel-xeon-processor-e5-2620-v3-15m-cache-2-40-ghz.html">Intel Xeon E5 2620v3 6c/12t CPU&lt;/a>&lt;/li>
&lt;li>64GB (2x32) ECC Memory&lt;/li>
&lt;li>2x Dell PERC H200 HBA&lt;/li>
&lt;li>250 GB Samsung SSD Boot drive&lt;/li>
&lt;li>Proxmox 6&lt;/li>
&lt;/ul>
&lt;p>So, aside from the original 4x 3TB WD Reds, &lt;em>everything&lt;/em> has changed. From enclosure to operating system.&lt;/p>
&lt;p>&lt;img
src="https://www.devroom.io/img/treebeard-2020-11-12.jpg"
alt=""
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;h3 id="are-you-done-now-what-more-could-you-want">Are you done now?! What more could you want?!&lt;/h3>
&lt;p>Nope. Never. I have some parts on the way to setup 10Gb ethernet between my NAS and Ryzentosh work station. This is one of the reasons I upgrade the motherboard and CPU - to accommodate 2x PCIe x8 HBA&amp;rsquo;s and 1x PCIe x8 10GB Ethernet.&lt;/p>
&lt;h3 id="norco-4u-rack-with-hot-swap-bays">Norco 4U Rack with hot swap bays&lt;/h3>
&lt;p>Great choice. It&amp;rsquo;s much easier to swap drives if need be. But I also have 4 empty bays right now. This is great to test a new disk or copy stuff over from old cold-storage disks. It also helped a lot migrating to the new SuperMicro motherboard.&lt;/p>
&lt;p>Because the case is bulky, it also offers lots of room for 120mm fans. And 120mm fans (especially Noctua ones) are &lt;em>quiet&lt;/em>.&lt;/p>
&lt;h3 id="motherboard--cpu--memory">Motherboard + CPU + Memory&lt;/h3>
&lt;p>My 4 core / 4 threads i3 8350K has served me well. It&amp;rsquo;s quite powerful too. But when running about 20 VM&amp;rsquo;s / Containers I feel it&amp;rsquo;s getting spread too thin. That, and the lack of PCIe slots on the micro-ATX board were a reason to look for something new.&lt;/p>
&lt;p>There are two options if you want lots of PCIe slots: high-end gaming motherboards that support 3 or 4 GPUs or (used) server hardware. Since this is turning into a proper home-lab server, I decided to go with used server gear. It&amp;rsquo;s a bit cheaper and more suited to the task.&lt;/p>
&lt;p>After scouring eBay and local classified sites I found a &lt;em>really&lt;/em> good deal on a &lt;a href="https://www.supermicro.com/en/products/motherboard/X10SRi-F">SuperMicro X10SRi-F&lt;/a>. It has quite a lot to offer that makes me very happy:&lt;/p>
&lt;ul>
&lt;li>IPMI remote management&lt;/li>
&lt;li>10x SATA3 on board&lt;/li>
&lt;li>Lots of PCIe slots:
&lt;ul>
&lt;li>1x PCIe 3.0 x16&lt;/li>
&lt;li>2x PCIe 3.0 x8&lt;/li>
&lt;li>1x PCIe 3.0 x4&lt;/li>
&lt;li>1x PCIe 2.0 x4&lt;/li>
&lt;li>1x PCIe 2.0 x2&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Dual gigabit ethernet&lt;/li>
&lt;/ul>
&lt;p>The accompanying &lt;a href="https://ark.intel.com/content/www/us/en/ark/products/83352/intel-xeon-processor-e5-2620-v3-15m-cache-2-40-ghz.html">Intel Xeon E5 2620v3 6c/12t CPU&lt;/a> has a lower clock speed of 2.4GHz (boost to 3.2GHz) as opposed to the 4.0GHz of my previous i3 8350K, but it does offer 6 cores instead of 4 &lt;em>and&lt;/em> supports HyperThreading. So that offers a lot more flexibility with running a multitude of VMs and containers.&lt;/p>
&lt;p>I also added 2 stick of 32GB ECC Registered memory for a total of 64GB. It&amp;rsquo;s enough for now, but with LRDIMMs, this motherboard has 8 DIMM slots available for a maximum of 512GB of memory. Quite enough for now and room to expand. Great!&lt;/p>
&lt;h3 id="proxmox">Proxmox&lt;/h3>
&lt;p>Proxmox is great if you want to have a ZFS pool and run lots of services on top. As discussed in my previoust post, FreeNAS is great, but it&amp;rsquo;s not made for running additional services. Jails restrict you to FreeBSD and the bhyve hypervisor is not quite mature yet to easily run VMs.&lt;/p>
&lt;p>Proxmox is &lt;em>just&lt;/em> Debian linux with a lot of goodies for running a homelab server. ZFS on Linux being one of them. First class support for containers and virtual machines is another.&lt;/p>
&lt;p>The only thing &amp;rsquo;lacking&amp;rsquo; from Proxmox, coming from FreeNAS, is easily setting up file sharing. But in the end all I needed was to spin-up a container, install Samba and configure the shares I wanted to create. Voila. Nice shares &lt;em>and&lt;/em> TimeMachine backups.&lt;/p>
&lt;h3 id="10gb-ethernet">10GB Ethernet&lt;/h3>
&lt;p>Yeah. That&amp;rsquo;s happening. I find myself storing more and more data on a network share on the NAS. But with lots of audio and video files, 1000mbit can be limiting (e.g. 120-ish MB/s).&lt;/p>
&lt;p>I found myself a pair of Solarflare SFN5122F 10G Ethernet Dual Port (SFP+) cards on eBay. These should even work under macos, which is what I&amp;rsquo;d need. They&amp;rsquo;re still in the mail right now - but it should be pretty straightforward setting these up for a direct 10G link between my Ryzentosh and NAS.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2020/11/12/the-big-diy-nas-update/</guid><pubDate>Thu, 12 Nov 2020 00:00:00 +0000</pubDate></item><item><title>Repairing a Xiaomi Roborock S5 Max</title><link>https://www.devroom.io/2020/10/03/repairing-a-xiaomi-roborock-s5-max/</link><description>&lt;p>Many less-tech savvy people I know have a robot vacuum cleaner. So why didn&amp;rsquo;t I? I don&amp;rsquo;t know,
so I bought one. I was able to get a good deal through a Chinese reseller that could ship
directly from Spain. As it turned out, they shipped with Amazon.&lt;/p>
&lt;p>The S5 Max has some nifty features, including mopping and LDS laser
navigation. The reviews were good. The price was good. So, here we are.&lt;/p>
&lt;p>&lt;img
src="https://www.devroom.io/img/s5-max.jpg"
alt="Roborock S5 Max"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;p>The first few weeks went by without problem. But then &lt;em>disaster struck&lt;/em>. The companion
app reported a short circuit in the left wheel. I was surprised at this level of detailed
error reporting, but that didn&amp;rsquo;t make the problem go away.&lt;/p>
&lt;p>I contact support. Honestly, I was a bit wary about contacting support from this Chinese
reseller as they don&amp;rsquo;t have a good reputation in general. But I got a friendly email back
a day later, stating that the left wheel unit should be replaced. This is under warranty.&lt;/p>
&lt;p>I was offered two options:&lt;/p>
&lt;ol>
&lt;li>Ship the unit to Poland for repair. All shipping costs would be reimbursed. Nice!&lt;/li>
&lt;li>I could buy a replacment left wheel unit from them, and they would refund that amount
to me. I would have to replace the wheel unit myself.&lt;/li>
&lt;/ol>
&lt;p>As I&amp;rsquo;m not averse to opening up electronic devices, I chose option 2. The refund was
processed before the left wheel arrived, which took only a week or so.&lt;/p>
&lt;p>But how do I replace a left wheel unit on a &lt;strong>Xiaomi Roborock S5 Max&lt;/strong>?&lt;/p>
&lt;p>Youtube to the rescue! I found this 12 minute video (I have a copy archived if it ever goes missing) that shows you how to disassemble and re-assemble this model of Roborock.&lt;/p>
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/0vLa4-iikzM?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"
>&lt;/iframe>
&lt;/div>
&lt;p>Armed with this video it took about 30 minutes to disassemble the necessary parts, replace the wheel unit and put is all back together.&lt;/p>
&lt;p>What surprised me most was how amazingly repairable this unit is. Okay, it takes you a good 15 minutes to take it apart - but it&amp;rsquo;s all philips screws and easy to remove modules. No glue. No difficult to remove parts, no weird screws. The fact that they&amp;rsquo;re modules makes them easily replaceable. Wheels, fan, LDS unit, 360° wheel, etc are all separate modules.&lt;/p>
&lt;p>&lt;img
src="https://www.devroom.io/img/roborock-repair.jpg"
alt=""
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;p>Looking around on AliExpress you can find many replacement parts for the Roborock - and it&amp;rsquo;s actually feasible for anyone who knows how to hold a screwdriver to replace the part.&lt;/p>
&lt;p>This is such a breath of fresh air after looking to replace a battery and screen on my OnePlus 6. I didn&amp;rsquo;t even bother to buy replacement parts for that. Heck, even modern AV-receivers are
harder to repair than this.&lt;/p>
&lt;p>This goes to show that Chinese products nowadays sometimes &lt;em>do&lt;/em> come with fast and helpful support and that repairing one of these units if rather easy to do.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2020/10/03/repairing-a-xiaomi-roborock-s5-max/</guid><pubDate>Sat, 03 Oct 2020 00:00:00 +0000</pubDate></item><item><title>Why I will not repair your amplifier</title><link>https://www.devroom.io/2020/03/29/why-i-will-not-repair-your-amplifier/</link><description>&lt;p>I like tinkering with electronics and one awesome way of doing that is
by &lt;a href="https://www.devroom.io/tags/audiorepair/">repairing things that are broken&lt;/a>. I get about ten or more requests
every month from random people from the internet to help them repair there device. My
reaction is always the same: no, I will not. This posts tries to explain why
I say no and what alternative I can offer them.&lt;/p>
&lt;p>When I repair something (or anybody else for that matter), it will cost
something. There&amp;rsquo;s a lot of things that go into a successful repair,
especially of hardware you&amp;rsquo;re not intimately familar with. These are
the steps I normally take:&lt;/p>
&lt;ol>
&lt;li>Disassemble the device&lt;/li>
&lt;li>Isolate and/or reproduce the problem&lt;/li>
&lt;li>Find a service manual, datasheet, and other documentation&lt;/li>
&lt;li>Theorize a solution to the problem&lt;/li>
&lt;li>Find and order replacement parts&lt;/li>
&lt;li>Perform the actual repair&lt;/li>
&lt;li>Test the repair&lt;/li>
&lt;li>Reassemble the device&lt;/li>
&lt;/ol>
&lt;p>Sometimes a problem is obvious. Like the &lt;a href="https://www.devroom.io/projects/repair-hp-34401a-bench-multimeter/">repair of the HP 34401A&lt;/a>,
which turned out to be blown fuse. Sometimes the problem is more subtle and difficult to find, like with the
&lt;a href="https://www.devroom.io/projects/repair-philips-42pfl6057h-12/">repair of my Philips 3D LED TV&lt;/a>.&lt;/p>
&lt;p>When I&amp;rsquo;m looking for a repair project, I always do some research first - I&amp;rsquo;m not blindly buying things
marked &amp;lsquo;with defect&amp;rsquo;. TV&amp;rsquo;s often have a broken screen - which I can&amp;rsquo;t fix. Some amplifiers have issues
with HDMI ports, which I know will require me to purchase a rather expensive replacement circuit board. The
repair will be expensive and not very interesting. If I&amp;rsquo;m looking to resell a device, those costs add up and
may not make it worth the effort.&lt;/p>
&lt;p>Now imagine some guy, let&amp;rsquo;s call him John, emails you out of the blue:&lt;/p>
&lt;blockquote>
&lt;p>Hello,&lt;/p>
&lt;p>I have a problem with my Denon some model or other. There is no sound at all.
Can you help me repair this. I have no experience with electronics, but I
do have a screwdriver.&lt;/p>
&lt;p>Thanks!&lt;/p>
&lt;/blockquote>
&lt;p>There are quite a few problems with answering these kinds of messages.&lt;/p>
&lt;p>Let&amp;rsquo;s take a look at some legitimate reasons why your amplifier might not
produce and sound:&lt;/p>
&lt;ul>
&lt;li>Did you turn the amplifier on?&lt;/li>
&lt;li>Did you connect speakers?
&lt;ul>
&lt;li>To the right outputs?&lt;/li>
&lt;li>That are not blown or shorted?&lt;/li>
&lt;li>With enough power?&lt;/li>
&lt;li>With the correct impedance for your amplifier?&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Is your amp muted or is the volume set to its lowest setting?&lt;/li>
&lt;li>Does the source contain audio?&lt;/li>
&lt;li>Did you select the correct source?&lt;/li>
&lt;li>Did you connect the source correctly?&lt;/li>
&lt;li>Is the source using a digitial format your amp can decode?&lt;/li>
&lt;li>Did you mess with assignable inputs and does the input use the correct audio connector?&lt;/li>
&lt;li>If your source is phono, do you need a pre-amplifier?&lt;/li>
&lt;li>Did you leave your headphones connected?&lt;/li>
&lt;li>If using HDMI or optical, maybe you try a different cable first?&lt;/li>
&lt;/ul>
&lt;p>These are all legit reasons why an amplifier could not produce sound. And this is
just the part to make sure there&amp;rsquo;s actually something broken in the first place.&lt;/p>
&lt;p>Next, the person has little to no experience with electronics. At the very least you
need to understand the dangers of opening an amplifier and what precautions to take
to work with it safely.&lt;/p>
&lt;p>Next, you will need at least a multimeter and maybe even an oscilloscope to find the
exact problem. You will need to look for shorts, high resistances, check diodes and
transistors.&lt;/p>
&lt;p>After that you will need a soldering iron or SMD rework station. You will need solder,
flux, solder wick or solder sucker. Do you know how to use these tools?&lt;/p>
&lt;p>When finished, you need to verify your work - did you put everything back together
correctly? Did you not make any shorts by accident?&lt;/p>
&lt;p>It&amp;rsquo;s not feasible for me to personally take someone by the hand and guide them
through this process.&lt;/p>
&lt;p>Some people will then reply to me:&lt;/p>
&lt;blockquote>
&lt;p>Can I just ship it to you, then?&lt;/p>
&lt;/blockquote>
&lt;p>Well, no.&lt;/p>
&lt;p>I don&amp;rsquo;t have time for it. I&amp;rsquo;m not a repair shop. Also, I don&amp;rsquo;t want to risk damaging your stuff
beyond repair - or it to be damaged in transit. And most of all, I&amp;rsquo;m not interested. This is not
my day job.&lt;/p>
&lt;p>So, what &lt;em>can&lt;/em> I offer people who ask for my help.&lt;/p>
&lt;p>First of all, if you device is under warranty, take that route. Just go back
to the place you bought it or to the manufacturer and get them to repair it under
warranty. You paid for that service, use it.&lt;/p>
&lt;p>If your device is out of warranty, there are two options. Which you choose depends
on your confidence level for repairing electronics.&lt;/p>
&lt;p>If you don&amp;rsquo;t have any tools available and this is your first time, you&amp;rsquo;re probably
better off finding a local technician to perform the repair. This will cost you
money, and there&amp;rsquo;s a chance the repair cannot be carried out because parts are
no longer available or the cost of the repair is higher than purchasing a new
comparable product.&lt;/p>
&lt;p>If you do have some tools, basic electronics knowledge, and a willingness to
learn more, you should hop over to &lt;a href="https://www.reddit.com/r/AskElectronics/">/r/AskElectronics&lt;/a>
and &lt;a href="https://www.reddit.com/r/audiorepair/">/r/audiorepair&lt;/a>. There are over 80,000 people
there who can help you out. Most of them will have more experience than me. Some
might even know your particular device or the problem you&amp;rsquo;re having.&lt;/p>
&lt;p>An added benefit is that your quest will be documented online for others to read.&lt;/p>
&lt;p>Repairing can be great fun. Most people can do it, given you have the right
tools and an eagerness to learn. There&amp;rsquo;s a great bunch of people out there who&amp;rsquo;re
ready to help you out - for your benefit and that of the entire community.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2020/03/29/why-i-will-not-repair-your-amplifier/</guid><pubDate>Sun, 29 Mar 2020 00:00:00 +0000</pubDate></item><item><title>Test Flutter on Drone CI</title><link>https://www.devroom.io/2020/03/17/test-flutter-on-drone-ci/</link><description>&lt;p>&lt;em>This post is specific to &lt;a href="https://drone.io/">Drone CI&lt;/a>, but is probably easily adoptable to other CI systems, like Gitlab.&lt;/em>&lt;/p>
&lt;p>&lt;a href="https://flutter.dev/">Flutter&lt;/a> is UI library based on &lt;a href="https://dart.dev/">Dart&lt;/a> to create beautiful, natively compiled applications for mobile. Web and desktop targets are also in the works.&lt;/p>
&lt;p>Recently I&amp;rsquo;ve been playing around with Flutter and Dart. As a backend engineer I have to say I quite like it. Dart is a breeze to work with, the static typing helps a lot. Flutter itself offers a react-like way of structuring your app, but it runs natively on Android and iOS. At &lt;a href="https://www.kabisa.nl">Kabisa&lt;/a> we&amp;rsquo;re in the process of adopting Flutter as our go-to solution for cross platform mobile apps.&lt;/p>
&lt;p>Anyway, one of the first things I do is hook a new project up with my CI, currently &lt;a href="https://drone.io/">Drone CI&lt;/a>. I started looking for
an already published docker image that contains the latest flutter-stable installation. I soon found &lt;a href="https://github.com/cirruslabs/docker-images-flutter">cirruslabs/docker-images-flutter&lt;/a>,
but ran into an permission error. Turns out [I&amp;rsquo;m not the only one][cl-issues], but with a few simple steps you can test your flutter app with this image on Drone CI without issue.&lt;/p>
&lt;p>&lt;code>.drone.yml&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">pipeline&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">default&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">steps&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">test&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">cirrusci/flutter:stable&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">commands&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">sudo chown -R cirrus:cirrus .&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">flutter doctor&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">flutter test&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The magic is the &lt;code>sudo chown -R cirrus:cirrus .&lt;/code>. The flutter docker images are based on Cirrus Labs&amp;rsquo; android SDK images, which runs
commands as the &lt;code>cirrus&lt;/code> user. This will change permissions and the flutter commands can now run without issue.&lt;/p>
&lt;p>Any next steps in your pipeline will encounter files owned by &lt;code>cirrus&lt;/code>. Since most drone plugins and images run as &lt;code>root&lt;/code> anyway, this is not much of and issue. You could add another command to the snippet above to reset permissions back to &lt;code>root&lt;/code>.&lt;/p>
&lt;p>Note that I&amp;rsquo;m using &lt;code>cirrusci/flutter:stable&lt;/code>. They&amp;rsquo;ve also got images available for &lt;code>beta&lt;/code> and &lt;code>dev&lt;/code>, just use the appropriate tag.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2020/03/17/test-flutter-on-drone-ci/</guid><pubDate>Tue, 17 Mar 2020 00:00:00 +0000</pubDate></item><item><title>A Drone Hugo plugin that works</title><link>https://www.devroom.io/2020/03/11/a-drone-hugo-plugin-that-works/</link><description>&lt;p>&lt;em>This article contains some background to why I wrote this plugin. If you just want to use it,
see &lt;a href="https://github.com/ariejan/drone-hugo">github.com/ariejan/drone-hugo&lt;/a> for details.&lt;/em>&lt;/p>
&lt;hr>
&lt;p>I recently moved away from &lt;a href="https://about.gitlab.com/">Gitlab + Gitlab CI&lt;/a> to a &lt;a href="https://gitea.com/">Gitea&lt;/a> + &lt;a href="https://drone.io/">Drone&lt;/a> setup.
Both Gitea and Drone are lightweight and fast, and let&amp;rsquo;s be honest, more than enough for
an engineering enthusiast like myself. They now also run on my &lt;del>NAS&lt;/del> &lt;a href="https://www.devroom.io/2020/02/28/building-a-diy-home-server-with-freenas/">Homelab Server&lt;/a> as docker
containers, which helps to save a few bucks every month in hosting fees.&lt;/p>
&lt;h2 id="hugo">Hugo&lt;/h2>
&lt;p>This website (and a few others I manage) are built using the static site generator named &lt;a href="https://gohugo.io">Hugo&lt;/a>. It&amp;rsquo;s
written in Go andis super fast:&lt;/p>
&lt;pre tabindex="0">&lt;code> ~/src/devroom.io $ hugo
| EN
-------------------+-------
Pages | 1209
Paginator pages | 0
Non-page files | 13
Static files | 249
Processed images | 0
Aliases | 450
Sitemaps | 1
Cleaned | 0
Total in 3073 ms
&lt;/code>&lt;/pre>&lt;p>Because I like using &lt;code>.scss&lt;/code> and some other goodies, I need to use &lt;code>hugo_extended&lt;/code>.&lt;/p>
&lt;h2 id="drone--hugo">Drone + Hugo&lt;/h2>
&lt;p>There&amp;rsquo;s an official Hugo plugin for drone, but it has two problems:&lt;/p>
&lt;ul>
&lt;li>The official documentation is lacking on the use of the &lt;em>extended&lt;/em> version of hugo&lt;/li>
&lt;li>When you manage to get the extended version installed, it doesn&amp;rsquo;t work&lt;/li>
&lt;/ul>
&lt;p>As a good open source citizen, I opened up &lt;a href="https://github.com/drone-plugins/drone-hugo">the code&lt;/a> and took a look around.
At this point I noticed a few more &amp;ldquo;problems&amp;rdquo; with this plugin.&lt;/p>
&lt;p>The plugin needs to run on multiple architectures, that&amp;rsquo;s cool, but I&amp;rsquo;m happy with ye good &amp;lsquo;ole amd64.\&lt;/p>
&lt;p>It contains a custom Go program to kick-off hugo builds. I can understand that this is useful for more
complex plugins or plugins that are easier written in go because of available libraries. However,
all this plugin needs to do is install Hugo and run it.&lt;/p>
&lt;p>It supports a metric ton of command line options for Hugo. I understand why that&amp;rsquo;s important, but I
have no use for any of them.&lt;/p>
&lt;h2 id="can-i-do-better">Can I do better?&lt;/h2>
&lt;p>I could have taken up the official plugin and try to make it work. The problem here is that it&amp;rsquo;s a
rather complicated piece of software for a rather simple task:&lt;/p>
&lt;ol>
&lt;li>Download the specified versio of Hugo&lt;/li>
&lt;li>Run &lt;code>hugo&lt;/code> to generate static HTML files&lt;/li>
&lt;/ol>
&lt;p>What I found is that a Drone plugin can be written in whatever. Settings from &lt;code>.drone.yml&lt;/code> are passed
on as environment variables with a &lt;code>PLUGIN_&lt;/code> prefix.&lt;/p>
&lt;p>So, I opened up Vim and started to write a simple shell script that would download the proper,
extended version of hugo and run it. This is all there is to it:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#!/bin/sh
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&lt;/span>&lt;span class="nv">HUGO_VERSION&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nv">PLUGIN_HUGO_VERSION&lt;/span>&lt;span class="k">:-&lt;/span>&lt;span class="s2">&amp;#34;0.67.0&amp;#34;&lt;/span>&lt;span class="si">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">HUGO_ARCH&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;64bit&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">HUGO_URL&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;https://github.com/gohugoio/hugo/releases/download/v&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nv">HUGO_VERSION&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">/hugo_extended_&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nv">HUGO_VERSION&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">_Linux-&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nv">HUGO_ARCH&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">.tar.gz&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">echo&lt;/span> &lt;span class="s2">&amp;#34;Fetching Hugo &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nv">HUGO_VERSION&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> from &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nv">HUGO_URL&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">wget -q -O- &lt;span class="si">${&lt;/span>&lt;span class="nv">HUGO_URL&lt;/span>&lt;span class="si">}&lt;/span> &lt;span class="p">|&lt;/span> tar xz -C /usr/local/bin
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">hugo
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This will take the specified Hugo version (or default 0.67.0), download and untar it and run it. That&amp;rsquo;s all I need.&lt;/p>
&lt;p>It does need to be packaged up in a Docker image to be useful with Drone, so I needed a &lt;code>Dockerfile&lt;/code> as well.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-Dockerfile" data-lang="Dockerfile">&lt;span class="line">&lt;span class="cl">&lt;span class="k">FROM&lt;/span>&lt;span class="s"> alpine:3.11&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">RUN&lt;/span> apk add --no-cache &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> ca-certificates &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> mailcap &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> git &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> wget &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> libc6-compat &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> libstdc++&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">ADD&lt;/span> drone-hugo.sh /bin/&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">RUN&lt;/span> chmod +x /bin/drone-hugo.sh&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">ENTRYPOINT&lt;/span> /bin/drone-hugo.sh&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Again, this is pretty straighforward. Use alpine for a small image size, add some dependencies and copy over the shell script.&lt;/p>
&lt;h2 id="using-ariejandrone-hugo">Using ariejan/drone-hugo&lt;/h2>
&lt;p>In your &lt;code>.drone.yml&lt;/code> you can add a &lt;code>build&lt;/code> step. You&amp;rsquo;ll want to follow that up with something that uploads your HTML files.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">steps&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">build&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ariejan/drone-hugo&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">settings&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">hugo_version&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">0.65.3&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>At this time there&amp;rsquo;s no option to pass along any arguments to Hugo - as I don&amp;rsquo;t need that. Instead of exposing each option
manually, I&amp;rsquo;m considering to add a single &lt;code>hugo_args&lt;/code> for that purpose.&lt;/p>
&lt;p>You can find the full source and install instructions on &lt;a href="https://github.com/ariejan/drone-hugo">github.com/ariejan/drone-hugo&lt;/a>.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2020/03/11/a-drone-hugo-plugin-that-works/</guid><pubDate>Wed, 11 Mar 2020 00:00:00 +0000</pubDate></item><item><title>The git submodule cheat sheet</title><link>https://www.devroom.io/2020/03/09/the-git-submodule-cheat-sheet/</link><description>&lt;p>A git submodule, in its essence, is a reference to another git repository. It&amp;rsquo;s a great
way to include vendor code (like plugins or themes) into your own code base. This post
contains some examples on how to use git submodules effectively.&lt;/p>
&lt;h2 id="add-a-submodule">Add a submodule&lt;/h2>
&lt;p>You need to know the remote git repository url and where you want to place that it in your repository.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">git submodule add https://example.com/submodule-repo.git path/to/submodule
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">git add .
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">git commit -m &lt;span class="s2">&amp;#34;adds submodule path/to/submodule&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="cloning-a-project-with-submodules">Cloning a project with submodules&lt;/h2>
&lt;p>When you clone a repository that contains submodules there are a few extra steps to be taken.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">git clone http://example.com/repo.git repo
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> repo
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">git submodule init
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">git submodule update
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>If you&amp;rsquo;re sure you want to fetch all submodules (and their submodules), you can also use this
fancy one-liner:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">git clone --recurse-submodules http://example.com/repo.git
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="update-your-submodule">Update your submodule&lt;/h2>
&lt;p>If you&amp;rsquo;re simply tracking the &lt;code>master&lt;/code> branch for the submodule, you can suffice with
a simple &lt;code>fetch&lt;/code> and &lt;code>merge&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> path/to/submodule
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">git fetch
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">git merge origin/master
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>If you&amp;rsquo;re in a hurry, you can streamline this for all submodules in your repo with:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">git submodule update --remote --recursive
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Don&amp;rsquo;t forget to commit this change to your own repo, so others are locked to this new version of the
submodule as well.&lt;/p>
&lt;h2 id="track-a-specific-branch-of-version">Track a specific branch of version&lt;/h2>
&lt;p>The repo for your submodule may have a specific branch (e.g. &lt;code>stable&lt;/code>) or tag you want to track, instead
of &lt;code>master&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">git config -f .gitmodules submodule.path/to/submodule.branch stable
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">git submodule update --remote
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Again, don&amp;rsquo;t forget to commit your changes to &lt;code>.gitmodules&lt;/code> to send this change to other contributors
to you repository.&lt;/p>
&lt;h2 id="remove-a-submodule">Remove a submodule&lt;/h2>
&lt;p>Removing a git submodule consists of two steps: removing the reference and removing the locally cached
version.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">git submodule deinit path/to/submodule
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">git rm path/to/submodule
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">git commit -m &lt;span class="s2">&amp;#34;removes submodule path/to/submodule&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">rm -rf .git/modules/path/to/submodule
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="bonus-see-submodule-status-in-git-status">Bonus: see submodule status in &lt;code>git status&lt;/code>&lt;/h2>
&lt;p>You can configure git to show a submodule summary when you do a &lt;code>git status&lt;/code>. There is a small
performance trade-off here, but it might be useful to you.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">git config status.submodulesummary &lt;span class="m">1&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2020/03/09/the-git-submodule-cheat-sheet/</guid><pubDate>Mon, 09 Mar 2020 00:00:00 +0000</pubDate></item><item><title>Building a DIY Home Server with FreeNAS</title><link>https://www.devroom.io/2020/02/28/building-a-diy-home-server-with-freenas/</link><description>&lt;p>&lt;span class='important'>Keep reading! I&amp;rsquo;ve documented several upgrades to my DIY
NAS at the bottom of this post.
&lt;br/>
After you&amp;rsquo;re doing reading this, hop over to my &lt;a href="https://www.devroom.io/2020/11/12/the-big-diy-nas-update/">latest update&lt;/a> on my homelab server / NAS.&lt;/span>&lt;/p>
&lt;p>This post is almost a year over due. I think it might still be relevant for people
looking to start a NAS project or upgrade from a consumer-grade NAS like QNAP or
Synology.&lt;/p>
&lt;p>This is by no means a definitive guide on how to build a reliable network attached
storage server. I made choices relevant to my use case, yours might be different.&lt;/p>
&lt;p>For you eager people, there&amp;rsquo;s a full part list at the bottom of this post.&lt;/p>
&lt;p>&lt;img
src="https://www.devroom.io/images/diy-nas/freenas-screenshot.png"
alt="FreeNAS Screenshot"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;h2 id="some-history">Some history&lt;/h2>
&lt;p>Up until May 2019 I stored my photos, old documents and other things on a single 3TB
Western Digital Green drive in my &lt;a href="https://www.devroom.io/2012/11/22/customac/">Hackintosh&lt;/a>.&lt;/p>
&lt;p>Backups were already part of my setup by using Backblaze. This was great because it allowed
me to use unlimited backup data for a fixed price.&lt;/p>
&lt;p>But I was running into some limitations of this setup.&lt;/p>
&lt;h3 id="filesize-and-volume-of-digital-media">Filesize and volume of digital media&lt;/h3>
&lt;p>Every new phone has a better camera, and I like to shoot in the best quality available. With my
current Oneplus 6 that means 3456×4608px per photo (roughly 4-8MB per photo). Not to mention
4k video at 60fps&amp;hellip;&lt;/p>
&lt;p>Having kids also means taking &lt;em>a lot&lt;/em> more photos and videos.&lt;/p>
&lt;p>Before I would automatically backup media on my phone to Dropbox, because that was
convenient. But with a 5GB limit I would often be reminded to either upgrade or clean
up some files, which means manually moving older photos and videso from Dropbox to
my trusty 3TB hard drive.&lt;/p>
&lt;h3 id="restoring-backups-sucks">Restoring backups sucks&lt;/h3>
&lt;p>I have not kept track on how many disks failed on me, but is has happened on a few occassions.
Backblaze to the rescue! However, restoring 1TB+ of data is not a fun activity.&lt;/p>
&lt;p>The reality is, disks fail. Especially if you buy the cheapest ones available and don&amp;rsquo;t
take care of them.&lt;/p>
&lt;h3 id="streaming-video">Streaming video&lt;/h3>
&lt;p>I&amp;rsquo;ve always been a fan of Plex. With a collection of over 200 DVDs, there&amp;rsquo;s plenty to watch,
but physical drives are disappearing all around. Plex is a great solution, but it works best
if you media are available when you need them. Having to be at home and turn on your computer
to watch something is a nuisance.&lt;/p>
&lt;h2 id="the-plan">The plan&lt;/h2>
&lt;p>So, after much consideration, I decided I needed a NAS. Many of my friends have one and they
seem to be happy with them. Of course, I could not run to a local electronics store and buy
just &lt;em>any&lt;/em> storage server. First I had to write down what I wanted out of this system.&lt;/p>
&lt;ul>
&lt;li>Store 1TB+ of data safely
&lt;ul>
&lt;li>Redundancy to cope with disk failure&lt;/li>
&lt;li>Extendable in the future&lt;/li>
&lt;li>Write/Read performance should be okay, but not a priority (I&amp;rsquo;m not editing 8k video on this thing)&lt;/li>
&lt;li>Problaby needs a UPS for safe shutdown in the case of power loss&lt;/li>
&lt;li>Share data/disks over Samba/AFS/NFS, Time Machine support would be nice&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>It needs to be always-on
&lt;ul>
&lt;li>Energy efficiency is important&lt;/li>
&lt;li>It needs to be silent, will probably sit in the study&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Needs to run Nextcloud and Plex
&lt;ul>
&lt;li>Needs a companion mobile app to auto-backup photos and videos&lt;/li>
&lt;li>Needs sufficient memory&lt;/li>
&lt;li>Needs sufficient CPU power for transcoding 1, maybe 2 simultanious streams&lt;/li>
&lt;li>Room to grow to run more apps or VMs&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Needs to be future proof
&lt;ul>
&lt;li>More storage&lt;/li>
&lt;li>More memory&lt;/li>
&lt;li>Be able to use this a desktop computer if this NAS thing really sucks&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h2 id="what-nas-solution-fits-best">What NAS solution fits best&lt;/h2>
&lt;p>What I had to figure out is what NAS solutions are available to me, and which one is most
suitable for my usecase.&lt;/p>
&lt;p>Consumer grade NAS systems like those from QNAP or Synology are quite popular. They do come with
a few major drawbacks for me. They are not (or minimally) upgradeable. A simple Celeron CPU and 8GB
of memory will probably not suffice. I&amp;rsquo;m also stuck with a custom operating system, something which
I try to avoid like the plague.&lt;/p>
&lt;p>At this point, my &lt;em>best&lt;/em> option in this category was the &lt;em>&lt;a href="https://www.synology.com/en-uk/products/DS918+">Synology DS918+&lt;/a>&lt;/em>, retailing locally for €550. With room for four drives, it already felt limited. I
want it to be expandable. The Intel Celeron J3455 (quad core @ 1.5Ghz) and 4GB RAM seemed especially
limiting. Also the vendor lock-in is not very appealing.&lt;/p>
&lt;p>Next I visited &lt;a href="https://www.reddit.com/r/DataHoarder/">/r/DataHoarder&lt;/a>, a nice corner of the internet
where people come together to discuss hoarding data - and all the hardware needed to do that. Many here
opt for used enterprise hardware from SuperMicro or Dell in 19&amp;quot; rack cases. The hardware can be very
powerfull, but comes at a cost too. Space and noise are two considerations. This enterprise hardware
is made for use in datacenters where noise is not much of an issue. Having to mod a case to be silent
would be a major effort. Secondly, a 4u 19&amp;quot; server is quite bulky and not something you easily fit under
your desk.&lt;/p>
&lt;p>This left me to find the middle ground: consumer grade hardware, but with special, preferably open
source, software. /r/DataHoarder quickly pointed me towards &lt;a href="https://www.freenas.org/">FreeNAS&lt;/a>.
_&amp;ldquo;Enterprise-Grade features, open source, BSD licensed&amp;rdquo; grabbed my attention. Based on FreeBSD,
FreeNAS leans heavily on ZFS - which sounded really good to me.&lt;/p>
&lt;p>At this point I setup a VirtualBox VM on my Mac with 4 &amp;ldquo;8GB Disks&amp;rdquo; to give FreeNAS a spin. The setup
was rather easy (not how I remember installing FreeBSD 5.x years ago). I toyed around with ZFS, setting
up a pool, removing / replacing / resilvering disks. It was awesome.&lt;/p>
&lt;p>My mind was made up: I wanted a x86/amd64, FreeNAS compatible system with 8+ 3.5&amp;quot; drive bays in a
small-ish form factor.&lt;/p>
&lt;h2 id="freenas-fud">FreeNAS FUD&lt;/h2>
&lt;p>Here&amp;rsquo;s the deal with FreeNAS. It&amp;rsquo;s maintained by a company named iXsystems, who sell hardare and also
offer a more professional version of FreeNAS called TrueNAS. The forums are filled with people who
run NAS&amp;rsquo;es in professional settings.&lt;/p>
&lt;p>If I were to setup a NAS for a company, yes, I would opt for the Pro/Enterprise SAS drives, Intel
Xeon or AMD ThreadRipper CPUs and loads of ECC RAM.&lt;/p>
&lt;p>However. I&amp;rsquo;m building a home NAS system with four users (this includes two pre-schoolers who can&amp;rsquo;t even
read yet).&lt;/p>
&lt;p>So, although many of the recommendations of helpful people on the internet are true, it does not necessarily
mean you&amp;rsquo;re wrong if you do it differently.&lt;/p>
&lt;h2 id="shopping-time">Shopping time!&lt;/h2>
&lt;p>Time to go shopping! I always try to find the best value for money, so these choices were not always
directly obvious.&lt;/p>
&lt;h3 id="motherboard--cpu--ram">Motherboard / CPU / RAM&lt;/h3>
&lt;p>When I built my NAS, Intel was still king. If I had to do a rebuild, I&amp;rsquo;d happily opt for some AMD gear, but
alas, here I am. I was happy with the performance of my Intel i5-3570K and I looked for something comparable
in a newer generation. The Intel i3 8-series is now quad core, and the i3-8350K goes up to 4Ghz and a
maximum of 64GB DDR4 RAM.&lt;/p>
&lt;p>To go with this, I opted for the &lt;a href="https://www.gigabyte.com/Motherboard/Z370M-D3H-rev-10#kf">Gigabyte Z370M D3H&lt;/a>
motherboard. It&amp;rsquo;s a decent board in µATX format with 6 SATA ports and 2 NVMe slots. It can accomodate
a few expansion cards and has 4 slots for a maximum of 4x 16GB RAM. It also on the osx86 compatibility
list, so if I need to, I could run macos on this board.&lt;/p>
&lt;p>I wanted decent memory (but without all the RGB madness). I started out with 16GB (single module)
Corsair Vengeance (DDR4 2400Mhz).&lt;/p>
&lt;p>Many will swear on their mother that you need to have ECC memory when
running ZFS. There is a point to be made for the benefits of ECC memory. But this is a pro-sumer system. As
Matt Ahren (somewhat head of the OpenZFS project) once wrote:&lt;/p>
&lt;blockquote>
&lt;p>There&amp;rsquo;s nothing special about ZFS that requires/encourages the use of ECC RAM more so than any other filesystem. If you use UFS, EXT, NTFS, btrfs, etc without ECC RAM, you are just as much at risk as if you used ZFS without ECC RAM. Actually, ZFS can mitigate this risk to some degree if you enable the unsupported ZFS_DEBUG_MODIFY flag (zfs_flags=0x10). This will checksum the data while at rest in memory, and verify it before writing to disk, thus reducing the window of vulnerability from a memory error.&lt;/p>
&lt;/blockquote>
&lt;blockquote>
&lt;p>I would simply say: if you love your data, use ECC RAM. Additionally, use a filesystem that checksums your data, such as ZFS.&lt;/p>
&lt;/blockquote>
&lt;p>I did not get a graphics card for this system.&lt;/p>
&lt;h3 id="enclosure">Enclosure&lt;/h3>
&lt;p>I didn&amp;rsquo;t want a large 19&amp;quot; rack server. I also had a hard time imagening where I&amp;rsquo;d put a tall tower system.
This led me to investigate some of the more &amp;ldquo;exotic&amp;rdquo; enclosures on the market. I was looking for something
that could fit 8+ 3.5&amp;quot; drives and a µATX motherboard.&lt;/p>
&lt;p>When I found the &lt;a href="https://www.fractal-design.com/products/cases/node/node-804/black/">Fractal Design Node 804&lt;/a>
I was sold on it immediately. It&amp;rsquo;s a beautiful case, fits ten (10!) 3.5&amp;quot; drives, has good cooling options,
and it fits perfectly on the bottom shelve of my IKEA Ivar shelving unit.&lt;/p>
&lt;p>&lt;img
src="https://www.devroom.io/images/diy-nas/fd804.png"
alt="FreeNAS Screenshot"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;h3 id="disks">Disks&lt;/h3>
&lt;p>I wanted as much storage as possible. But at a reasonable price. I had about 1TB of space in use already,
so I would need at least four times that. I found a good deal on four 3TB Western Digital Red drives,
which in RaidZ (the ZFS &amp;ldquo;equivalent&amp;rdquo; of RAID5) leaves about 9TB of usable space. That seemed plenty.&lt;/p>
&lt;h2 id="updates-1---moar-memories">Updates 1 - Moar Memories!&lt;/h2>
&lt;p>I quickly realised that 16GB was fine, but since I was running more and more services on this system, and I
was looking into running at least a Linux VM to play with Docker, It&amp;rsquo;d need more memory. An easy upgrade,
since I was using only one of the four memory slots.&lt;/p>
&lt;p>&lt;img
src="https://www.devroom.io/images/diy-nas/moar-memories.jpg"
alt="FreeNAS Screenshot"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;h2 id="update-2---moar-spinners">Update 2 - Moar Spinners!&lt;/h2>
&lt;p>More spinners, rust, disks, drives, whatever your want to call it. As it turns out, once you have 9TB of
storage at your disposal, it quickly fills up. This is also around the time I discovered shucking.&lt;/p>
&lt;p>It turns out that Red drives are kind of special in how the can deal with vibrations. Their firmware is
also optimized to run drives 24/7. The thing is, they are rather expensive.&lt;/p>
&lt;p>&lt;img
src="https://www.devroom.io/images/diy-nas/moar-spinners.jpg"
alt="FreeNAS Screenshot"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;p>Now that&amp;rsquo;s a whopping 44TB Raw storage in a single pool. The pool consists for 2 vdevs, each with 4 drives
in RaidZ1. I can lose one drive in each vdev without issue.&lt;/p>
&lt;p>&lt;img
src="https://www.devroom.io/images/diy-nas/cozy-drives.jpg"
alt="FreeNAS Screenshot"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;p>&lt;small>(Sorry, I have to remove identifiable drive data so other can&amp;rsquo;t abuse them)&lt;/small>&lt;/p>
&lt;p>The external USB drives (My Book and Essential) are much cheaper, and they often contain &amp;ldquo;white&amp;rdquo; drives,
which are identical in specs to reds, but for &amp;ldquo;internal use&amp;rdquo; by Western Digital. Once the were on sale, it
was easy to pick up four of those external drives, remove the disk and put them in the NAS.&lt;/p>
&lt;p>For those looking to go the &lt;em>shucking&lt;/em> route, keep in mind the following:&lt;/p>
&lt;ul>
&lt;li>You void your warranty by removing the disks from their enclosure. WD might be lenient
when you RMA them, but know what you&amp;rsquo;re doing.&lt;/li>
&lt;li>Not all models have white drives and some models are known to have many issues (like the 6TB drives
from WD). Check out &lt;a href="https://www.reddit.com/r/DataHoarder/">/r/DataHoarder&lt;/a>
for up to date info.&lt;/li>
&lt;li>Be sure to check the entire disk in its enclosure. You can easily RMA it if you find defects, and
you know you&amp;rsquo;re putting something working into your machine.&lt;/li>
&lt;li>White drives use a newer SATA spec, which re-uses a pin to disable power to the drive. If your
system or power supply cannot handle that, the disk will not start-up. This issue can be easily
fixed &lt;a href="https://www.instructables.com/id/How-to-Fix-the-33V-Pin-Issue-in-White-Label-Disks-/">by using a piece of tape&lt;/a>.&lt;/li>
&lt;/ul>
&lt;h2 id="a-real-hba-card">A real HBA Card&lt;/h2>
&lt;p>With only six SATA connectors on board, how did I manage to hook up 8 SATA drives? Well, I bought
the cheapest SATA controller card I could find on AliExpress. It &lt;em>said&lt;/em> it supports four drives,
but I could only get it to work with two.&lt;/p>
&lt;p>After some research I found a good deal on a use Dell H200 PERC card, which sports the LSI 9211-8i
chipset. It has 2 SAS connectors, and when flashed into IT mode, supports 8 SATA drives - and it
works flawlessly with FreeNAS.&lt;/p>
&lt;p>Only problem is that it needs a bit of external coolingk as the card is used to sitting in a well
cooled 19&amp;quot; server.&lt;/p>
&lt;p>&lt;img
src="https://www.devroom.io/images/diy-nas/dell-h200.jpg"
alt="FreeNAS Screenshot"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;p>Yes, I cleaned out the dust, but after running for about 9 months I found the inside rather clean.&lt;/p>
&lt;h2 id="parts-list">Parts list&lt;/h2>
&lt;p>This is an up-to-date parts list of my current Home Lab NAS / Server&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align: left">Type&lt;/th>
&lt;th style="text-align: left">Item&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>CPU&lt;/strong>&lt;/td>
&lt;td style="text-align: left">&lt;a href="https://nl.pcpartpicker.com/product/fCs8TW/intel-core-i3-8350k-40ghz-quad-core-processor-bx80684i38350k">Intel Core i3-8350K 4 GHz Quad-Core Processor&lt;/a>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>CPU Cooler&lt;/strong>&lt;/td>
&lt;td style="text-align: left">&lt;a href="https://nl.pcpartpicker.com/product/PNm323/arctic-alpine-12-cpu-cooler-acalp00027a">ARCTIC Alpine 12 CPU Cooler&lt;/a>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>Motherboard&lt;/strong>&lt;/td>
&lt;td style="text-align: left">&lt;a href="https://nl.pcpartpicker.com/product/wZgPxr/gigabyte-z370m-d3h-rev-10-atx-lga1151-motherboard-z370m-d3h-rev-10">Gigabyte Z370M D3H (rev. 1.0) Micro ATX LGA1151 Motherboard&lt;/a>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>Memory&lt;/strong>&lt;/td>
&lt;td style="text-align: left">&lt;a href="https://nl.pcpartpicker.com/product/dDTrxr/corsair-vengeance-lpx-16gb-1-x-16gb-ddr4-2400-memory-cmk16gx4m1a2400c14">Corsair Vengeance LPX 16 GB (1 x 16 GB) DDR4-2400 Memory&lt;/a>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>Memory&lt;/strong>&lt;/td>
&lt;td style="text-align: left">&lt;a href="https://nl.pcpartpicker.com/product/dDTrxr/corsair-vengeance-lpx-16gb-1-x-16gb-ddr4-2400-memory-cmk16gx4m1a2400c14">Corsair Vengeance LPX 16 GB (1 x 16 GB) DDR4-2400 Memory&lt;/a>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>Power Supply&lt;/strong>&lt;/td>
&lt;td style="text-align: left">&lt;a href="https://nl.pcpartpicker.com/product/gt22FT/be-quiet-pure-power-11-cm-400-w-80-gold-certified-semi-modular-atx-power-supply-bn296">be quiet! Pure Power 11 CM 400 W 80+ Gold&lt;/a>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>Case&lt;/strong>&lt;/td>
&lt;td style="text-align: left">&lt;a href="https://nl.pcpartpicker.com/product/yTdqqs/fractal-design-case-fdcanode804blw">Fractal Design Node 804 MicroATX Mid Tower Case&lt;/a>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>Storage (Boot)&lt;/strong>&lt;/td>
&lt;td style="text-align: left">&lt;a href="https://nl.pcpartpicker.com/product/XwJtt6/gigabyte-128gb-m2-2280-solid-state-drive-gp-gsm2ne8128gntd">Gigabyte 128 GB M.2-2280 NVME Solid State Drive&lt;/a>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>Storage&lt;/strong>&lt;/td>
&lt;td style="text-align: left">&lt;a href="https://nl.pcpartpicker.com/product/7sTmP6/western-digital-internal-hard-drive-wd30efrx">Western Digital Red 3 TB 3.5&amp;quot; 5400RPM Internal Hard Drive&lt;/a>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>Storage&lt;/strong>&lt;/td>
&lt;td style="text-align: left">&lt;a href="https://nl.pcpartpicker.com/product/7sTmP6/western-digital-internal-hard-drive-wd30efrx">Western Digital Red 3 TB 3.5&amp;quot; 5400RPM Internal Hard Drive&lt;/a>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>Storage&lt;/strong>&lt;/td>
&lt;td style="text-align: left">&lt;a href="https://nl.pcpartpicker.com/product/7sTmP6/western-digital-internal-hard-drive-wd30efrx">Western Digital Red 3 TB 3.5&amp;quot; 5400RPM Internal Hard Drive&lt;/a>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>Storage&lt;/strong>&lt;/td>
&lt;td style="text-align: left">&lt;a href="https://nl.pcpartpicker.com/product/7sTmP6/western-digital-internal-hard-drive-wd30efrx">Western Digital Red 3 TB 3.5&amp;quot; 5400RPM Internal Hard Drive&lt;/a>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>Shucked Storage&lt;/strong>&lt;/td>
&lt;td style="text-align: left">&lt;a href="https://nl.pcpartpicker.com/product/YpJtt6/western-digital-elements-8tb-external-hard-drive-wdbwlg0080hbk-nesn">Western Digital ELEMENTS 8 TB External Hard Drive&lt;/a>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>Shucked Storage&lt;/strong>&lt;/td>
&lt;td style="text-align: left">&lt;a href="https://nl.pcpartpicker.com/product/YpJtt6/western-digital-elements-8tb-external-hard-drive-wdbwlg0080hbk-nesn">Western Digital ELEMENTS 8 TB External Hard Drive&lt;/a>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>Shucked Storage&lt;/strong>&lt;/td>
&lt;td style="text-align: left">&lt;a href="https://nl.pcpartpicker.com/product/YpJtt6/western-digital-elements-8tb-external-hard-drive-wdbwlg0080hbk-nesn">Western Digital ELEMENTS 8 TB External Hard Drive&lt;/a>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>Shucked Storage&lt;/strong>&lt;/td>
&lt;td style="text-align: left">&lt;a href="https://nl.pcpartpicker.com/product/YpJtt6/western-digital-elements-8tb-external-hard-drive-wdbwlg0080hbk-nesn">Western Digital ELEMENTS 8 TB External Hard Drive&lt;/a>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>UPS&lt;/strong>&lt;/td>
&lt;td style="text-align: left">&lt;a href="https://nl.pcpartpicker.com/product/QGNypg/apc-back-ups-bx-unterbrechungsfreie-stromversorgung-700va-bx700u-gr-avr-4-schuko-ausgange-usb-shutdown-software">APC Back-UPS BX700U-GR 700VA/390W&lt;/a>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>Cables&lt;/strong>&lt;/td>
&lt;td style="text-align: left">&lt;a href="https://nl.pcpartpicker.com/product/NLKhP6/cabledeconn-05m-red-mini-sas-36p-sff-8087-to-7p-sata-hdd-fanout-wihout-latch-cable">CableDeconn 0.5M Red Mini SAS 36P SFF-8087 To 7P SATA&lt;/a>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align: left">&lt;strong>HBA&lt;/strong>&lt;/td>
&lt;td style="text-align: left">&lt;a href="https://nl.pcpartpicker.com/product/RkBrxr/dell-perc-h200-integrated-sas-pci-e-20-raid-controller-for-dell-servers-h215j">Dell Perc H200 Integrated Sas Pci-e 2.0 Raid Controller&lt;/a>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>&lt;a href="https://nl.pcpartpicker.com/list/fV2N27">PCPartPicker Part List&lt;/a>&lt;/p>
&lt;h2 id="future-upgrades">Future upgrades&lt;/h2>
&lt;p>There are some future upgrades I&amp;rsquo;m look at.&lt;/p>
&lt;ul>
&lt;li>&lt;del>Add four 120mm Nocuta fans at the front to optimize air intake for cooling both the SAS Controller
as well as the eight spinners. Still have figure out how to PWM control four fans.&lt;/del>
I&amp;rsquo;ve added two front fans on the motherboard side to provide enough airflow for the passively cooled Dell Perc H200. I&amp;rsquo;ve
also added another front fan on the harddisk side (at the top) to increase airflow across all eight HDD&amp;rsquo;s. Temperatures
are much more stable now.&lt;/li>
&lt;li>Add an additional mirror of two spinners for a seperate pool, maybe local Borg backups of the most
important data.&lt;/li>
&lt;li>&lt;del>Add an additional NVMe SSD.&lt;/del> My motherboard has two m.2 slots, so I added another (relatively cheap) Gigabyte 128 GB SSD and put that into a ZFS mirror configuration.&lt;/li>
&lt;li>Add a mirror of two 2.5&amp;quot; SSDs to run my VM off.&lt;/li>
&lt;li>Upgrade the 3TBs to something better (16TB, anyone?). Heck, upgrade &lt;em>all&lt;/em> drives to 16TB for 128TB RAW storage capacity (96TB in the current 2x RaidZ1 setup)
Additionally I might opt for a DAS enclosure with 4-8 more HDDs and hook these up through another SAS expander card.&lt;/li>
&lt;li>&lt;del>Fill up the RAM slots for a total of 64GB&lt;/del>&lt;/li>
&lt;/ul>
&lt;p>Want to build a NAS yourself or have any questions? Feel free to &lt;a href="https://www.devroom.io/contact/">drop me a line&lt;/a>.&lt;/p>
&lt;h2 id="big-august-2020-update">Big August 2020 update&lt;/h2>
&lt;p>Right, so it&amp;rsquo;s time to write an update to my NAS project. Quite a few things have changed, so gear up!&lt;/p>
&lt;p>&lt;img
src="https://www.devroom.io/images/diy-nas/IMG_20200702_004507.jpg"
alt="New DIY NAS enclosure"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;p>The image above shows it already; I changed enclosures. But why? Let me start by saying that the Fractal Design Node 804 is an
amazing case, but it has three drawbacks.&lt;/p>
&lt;ol>
&lt;li>The airflow, even with 3 fans blowing across the hard disks, was not enough to keep the disks cool. Especially the 8TB Reds
would easily reach over 50°C (~122°F) on hot days. That&amp;rsquo;s &lt;em>probably&lt;/em> not something to worry about, but better safe than sorry.&lt;/li>
&lt;li>The HDDs are not really easily accessible. Now I didn&amp;rsquo;t have any issues with disks yet, but with the amount of cable clutter
and caddies with four drives each, it&amp;rsquo;s not trivial to replace a disk. This becomes more important now as failures are more likely
to start happening and I&amp;rsquo;m still looking to upgrade the 3TB Reds sometime.&lt;/li>
&lt;li>It can only hold 8 disks. Well, that&amp;rsquo;s not really a fault of this case.&lt;/li>
&lt;/ol>
&lt;p>So, what&amp;rsquo;s a reasonable upgrade for a Node 804 that makes disks easily accessible, accommodates more drives and can be fit
under my desk. I &lt;em>could&lt;/em> have gone with a Fractal Design Define 7 XL, which can house up to 18 HDDs, but it&amp;rsquo;s quite expensive at over €220. It also did not really make the drives any more accessible.&lt;/p>
&lt;p>I soon ended up with a 19&amp;quot; rack mount form factor. I saw a few deals on cheap Dell machines with 8 or 12 bays and decent
Xeon-based hardware. I passed on those, mainly because these things are power hogs and really &lt;em>LOUD&lt;/em>. Besides, I already have
all the hardware I need, I just want another case.&lt;/p>
&lt;p>There are a few manufacturers out there that sell 19&amp;quot; rack mount storage chassis. The 2U versions house up to 8 HDD&amp;rsquo;s, which would
be fine. But, if I&amp;rsquo;m going to invest in a new case, I want that little bit of extra room to expand in the future. This soon leads
to 4U cases, which have 10, 16, 24 or even more HDD bays. The problem is, at this time, they&amp;rsquo;re &lt;em>really&lt;/em> hard to find, especially
if you want to stick consumer ATX hardware into them. Also, they are almost all sold out. And if you manage to find one, they&amp;rsquo;re
not cheap either at over €500. Such is life in Europe, I guess.&lt;/p>
&lt;p>Luckily, while browsing the classified ads section of &lt;a href="https://tweakers.net">Tweakers.net&lt;/a> I came across a nice fellow who
was selling his old Norcotek 4U 16 bay chassis. I informed about the price and we ma deal for €75. The only problem was, he
lived at the other side of the country. And you simply don&amp;rsquo;t ship a 4U server easily or cheaply.&lt;/p>
&lt;p>So, I took a day off work and drove over 3 hours to pick up my new NAS enclosure.&lt;/p>
&lt;p>&lt;img
src="https://www.devroom.io/images/diy-nas/son-breugel-groningen.png"
alt="Route Son en Breugel to Groningen, 3 hours and 10 minutes without traffic"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;small>Map by &lt;a href="https://www.openstreetmap.org">OpenStreetMap.org&lt;/a>&lt;/small>&lt;/p>
&lt;p>When I went to pick up the case, it turned out it came with a 520W power supply (already neatly cable managed), four
Mini-SAS 8087 to 4x SATA cables and all the original screws in their originally labele bags. Bonus are nice scythe case fanse. Wow!&lt;/p>
&lt;p>Installation was pretty straight forward, simply move everything to the new case. My single Dell PERC H200 card could accommodate
8 of the 16 hot swappable caddies. So I would need another one of those to handle the other 8. That would free up my motherboard
SATA connectors for additional, internal SSDs and what not. Luckily, I could buy another one pre-flashed from a local ebay seller.&lt;/p>
&lt;p>&lt;img
src="https://www.devroom.io/images/diy-nas/IMG_20200708_111038.jpg"
alt="Transplant complete"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;h2 id="enter-proxmox">Enter Proxmox&lt;/h2>
&lt;p>Another item on my list was to move to Proxmox and virtualize FreeNAS. Let me explain why I want to do this.&lt;/p>
&lt;p>First, FreeNAS / TrueNAS is a great system. FreeBSD is rock solid and ZFS offers an amazing &amp;ldquo;storage experience&amp;rdquo;. The only
thing lacking is virtualization support. FreeBSD jails work fine, but I had some recent trouble with &lt;code>iocage&lt;/code> commands being
terribly slow, and I often struggle to convert linux install instructions to their FreeBSD equivalents. Updating jails
has also proven to be a painful process for me. The solution was to run a virtual machine (with a fixed amount of RAM) and
run docker on it. It all worked, but it didn&amp;rsquo;t feel solid. Also, I didn&amp;rsquo;t hear many great things about bhyve, although I
haven&amp;rsquo;t gotten into any real trouble.&lt;/p>
&lt;p>Well, enter Proxmox. Proxmox is a custom debian linux os that offers qemu virtual machines and LXC / Linux Containers. That
sounds really awesome. With Proxmox it&amp;rsquo;s also possible to pass through PCI devices to a virtual machine. This means that I can
map both my HBA cards to my FreeNAS virtual machine and run FreeNAS just as before.&lt;/p>
&lt;p>The process was quite painless: backup my FreeNAS configuration, install Proxmox, create a VM for FreeNAS, and install it. Then
restore your configuration (maybe make a few tweaks for changed device names, like your NIC, or scheduled SMART tasks), and
you continue where you left off. It works really great.&lt;/p>
&lt;p>I&amp;rsquo;ve migrated most of my jailed / dockerized services over to linux containers based on Debian. Some more complex ones,
like Gitea and Plex are still where they were (a docker VM and FreeNAS jail, respectively).&lt;/p>
&lt;p>For fun an giggles I added three old 500GB spinners to create a new ZFS pool with to try things out, like replacing disks, etc.&lt;/p>
&lt;h2 id="more-ram">More RAM&lt;/h2>
&lt;p>All the while, the original &lt;a href="https://nl.pcpartpicker.com/product/dDTrxr/corsair-vengeance-lpx-16gb-1-x-16gb-ddr4-2400-memory-cmk16gx4m1a2400c14">Corsair Vengeance LPX 16 GB (1 x 16 GB) DDR4-2400 Memory&lt;/a> were on sale at Amazon.de, so I decided to scoop up
a few and extend my NAS&amp;rsquo; memory to 64GB total. 24GB of this RAM has now been allocated to FreeNAS, which uses about half of that
for services, the rest is used for ZFS cache.&lt;/p>
&lt;h2 id="so-what-about-those-disk-temperatures">So what about those disk temperatures?&lt;/h2>
&lt;p>My eight spinners are all located on the left side of the chassis right now, so they&amp;rsquo;re packed tight. But with the recent
heat wave in the Netherlands with ambient temperatures rising to 32°C, no disk went above 44°C, which is a big win if you ask me.&lt;/p>
&lt;h2 id="treebeard-and-gandalf">Treebeard and Gandalf&lt;/h2>
&lt;p>&lt;img
src="https://www.devroom.io/images/diy-nas/IMG_20200708_113303.jpg"
alt="Treebeard and Gandalf"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;small>&lt;code>treebeard&lt;/code> and &lt;code>gandalf&lt;/code> in my Lack Rack.&lt;/small>&lt;/p>
&lt;p>My NAS has the official hostname of &lt;code>treebeard&lt;/code>. I also setup &lt;code>gandalf&lt;/code>, a 1U 19&amp;quot; rack mount server with a simple ASRock mini-ITX
board an i3 4130T processor. It&amp;rsquo;s fitted with 2x 128GB SSDs locally. &lt;code>gandalf&lt;/code> also runs Proxmox. In fact, they form a cluster of two. This makes it super easy to migrate
services between the two nodes. &lt;code>gandalf&lt;/code> runs some essential home network services (all in Linux Containers), like pihole, wireguard,
and home automation.&lt;/p>
&lt;p>&lt;em>Update: &lt;code>gandalf&lt;/code> was using over 150W of power while idle. It was also running quite hot, even though I upgrade the three 40x40mm
fans with Noctua ones. For now, &lt;code>gandalf&lt;/code> has been retired and removed from the cluster until I can figure out what&amp;rsquo;s causing this
insane power consumption. For comparison, &lt;code>treebeard&lt;/code> with its 11 spinners, is running at about 100W idle.&lt;/em>&lt;/p>
&lt;h2 id="apc-ups-woos">APC UPS woos&lt;/h2>
&lt;p>A year ago I purchased an APC Back-UPS 700. It&amp;rsquo;s been working great so far. However, after disconnecting it from the old server,
and hooking it up again to the new one, I noticed that the USB connection would no longer work. Just nothing. I found out I needed
to reset the UPS by powering it off, disconnecting the battery (with the large yellow plug on the back) and turn it back on again.&lt;/p>
&lt;p>As that worked to get USB working again, it did yield some strange values in &lt;code>apcaccess&lt;/code>, namely that the status was not &lt;code>ONLINE&lt;/code>,
but &lt;code>BOOST&lt;/code> and that it was not reporting some critical values, like &lt;code>TIMELEFT&lt;/code>. After a quick call to tech support I had to
perform a battery callibration. Basically: you disconnect your load and instead hook up something that burns power, like an old
light bulb or an electric heater. You then disconnect mains power and run the battery dry (this is safe, as the UPS will shutdown
before you can damange your battery). Then, without any load, reconnect mains and let the unit charge fully.&lt;/p>
&lt;p>After this the UPS was working great again.&lt;/p>
&lt;p>Recently I read about how this budget line of UPS devices does not work well with no battery connection. E.g. if you disconnect
the battery, your load will be disconnected from power, even if you have mains power connected. Read more at &lt;a href="https://fitzcarraldoblog.wordpress.com/2020/08/09/that-ups-you-bought-for-your-home-server-may-not-be-as-useful-as-you-think/">Fitzcarraldo&amp;rsquo;s blog post about his UPS experiences&lt;/a>.&lt;/p>
&lt;h2 id="september-2020-update">September 2020 update&lt;/h2>
&lt;p>This is starting to turn in a blog-in-a-blog kind of thing. But here&amp;rsquo;s another update for you.&lt;/p>
&lt;h3 id="more-apc-ups-woos">More APC UPS woos&lt;/h3>
&lt;p>The reset I performed worked for a few days. Then, in the middle of the night:&lt;/p>
&lt;pre>&lt;code>STATUS : ONLINE REPLACEBATT
&lt;/code>&lt;/pre>
&lt;p>Of course, this happened in the middle of the night, accompanied by a loud beeping tone from the UPS. I purchased a replacement
battery online (same model / ratings, just not the expensive, APC branded one). This fixed the problem and the UPS has been
working as it should. Still very strange that the battery gave out after just one year of usage.&lt;/p>
&lt;h3 id="no-more-freenas">No more FreeNAS!&lt;/h3>
&lt;p>This may come as a shocker ;-) I dropped FreeNAS. As you may remember I started running FreeNAS in a VM on Proxmox with the HBA
card passed through to FreeNAS. In order to share my pool/datasets with other VMs / containers in Proxmox, I needed to share them
over NFS to the host system, which would then require my containers to run in priviledged mode to mount the NFS shares. The real
issue was performance, many apps had issues with locking to the NFS mounts. Downloading large files would sometimes throw errors.
It was not fun.&lt;/p>
&lt;p>But then I thought, what am I really still using FreeNAS for? I don&amp;rsquo;t like the jails - they&amp;rsquo;re FreeBSD and I prefer linux. I have
an NFS share - but only because I need to share with the host. Maybe a Samba share for time machine, but that&amp;rsquo;s really all. Well,
and for ZFS of course.&lt;/p>
&lt;p>But Proxmox support ZFS as well! A small container can run Samba for Time Machine backups. So, I took the plunge:&lt;/p>
&lt;ol>
&lt;li>Shut down the FreeNAS Vm&lt;/li>
&lt;li>Disable auto-start :-)&lt;/li>
&lt;li>&lt;code>zfs import core-storage tank&lt;/code> - importing and renaming the pool in one go&lt;/li>
&lt;/ol>
&lt;p>That. Was. It.&lt;/p>
&lt;h3 id="moar-drives">Moar drives!&lt;/h3>
&lt;p>My chassis can house 16 3.5&amp;quot; HDDs in hot-swap caddies. I ordered a second Dell PERC H200 HBA card from eBay. I now have the
following drives in my pool:&lt;/p>
&lt;ul>
&lt;li>4x Western Digital 3TB Red&lt;/li>
&lt;li>4x Western Digital 8TB White (shucked from WD Elements)&lt;/li>
&lt;li>4x Western Digital 14TB White (shucked from WD Elements)&lt;/li>
&lt;/ul>
&lt;p>Yes, you read that right. Amazon.de had the 14TB WD Elements on sale, so I grabbed four of them. My pool now
consists of three RAIDZ1 vdevs:&lt;/p>
&lt;pre tabindex="0">&lt;code># zpool status -v tank
pool: tank
state: ONLINE
scan: scrub repaired 0B in 0 days 20:08:21 with 0 errors on Thu Sep 17 05:25:13 2020
config:
NAME STATE READ WRITE CKSUM
tank ONLINE 0 0 0
raidz1-0 ONLINE 0 0 0
sdc ONLINE 0 0 0
sdb ONLINE 0 0 0
sdg ONLINE 0 0 0
sde ONLINE 0 0 0
raidz1-1 ONLINE 0 0 0
sdd ONLINE 0 0 0
sdf ONLINE 0 0 0
sdh ONLINE 0 0 0
sda ONLINE 0 0 0
raidz1-2 ONLINE 0 0 0
sdi ONLINE 0 0 0
sdj ONLINE 0 0 0
sdl ONLINE 0 0 0
sdm ONLINE 0 0 0
&lt;/code>&lt;/pre>&lt;p>And some specifics:&lt;/p>
&lt;pre tabindex="0">&lt;code># zpool list tank
NAME SIZE ALLOC FREE CKPOINT EXPANDSZ FRAG CAP DEDUP HEALTH ALTROOT
tank 90.8T 35.2T 55.6T - - 9% 38% 1.00x ONLINE -
&lt;/code>&lt;/pre>&lt;p>That should be sufficient for a while, but don&amp;rsquo;t quote me on that. :-)&lt;/p>
&lt;h3 id="smart-udma-crc-errors">Smart UDMA CRC Errors&lt;/h3>
&lt;p>For a bit &lt;code>/dev/sdi&lt;/code> had some UDMA CRC errors. These are, especially with new drives, often caused by cable issues.
I offlined the drive, stuck in one of the four remaining free slots, and the problem was solved. ZFS of course had
picked up on this as well and Proxmox immediately nofitified me of the SMART and ZFS issues by email. Yay for Proxmox!&lt;/p>
&lt;pre>&lt;code>ID# ATTRIBUTE_NAME FLAG VALUE WORST THRESH TYPE UPDATED WHEN_FAILED RAW_VALUE
199 UDMA_CRC_Error_Count 0x000a 100 100 000 Old_age Always - 16
&lt;/code>&lt;/pre>
&lt;p>Resetting the ZFS pool status is easy with &lt;code>zfs clear tank&lt;/code>. For good measure I ordered a scrub, which finished without
any further issue.&lt;/p>
&lt;h2 id="next-steps">Next steps?&lt;/h2>
&lt;p>Nothing right now. I have several TB&amp;rsquo;s of storage left to hoard. CPU-wise I&amp;rsquo;m good. Memory is all max&amp;rsquo;ed out. I love
Proxmox for being Linux &lt;em>and&lt;/em> supporting ZFS.&lt;/p>
&lt;h2 id="one-more-thing">One more thing&amp;hellip;&lt;/h2>
&lt;p>Yeah, I know what I said last time I updated this post. I &lt;em>upgraded&lt;/em> my motherboard/CPU/Memory even further.&lt;/p>
&lt;p>Read more in my latest post on my NAS: &lt;a href="https://www.devroom.io/2020/11/12/the-big-diy-nas-update/">https://www.devroom.io/2020/11/12/the-big-diy-nas-update&lt;/a>&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2020/02/28/building-a-diy-home-server-with-freenas/</guid><pubDate>Fri, 28 Feb 2020 00:00:00 +0000</pubDate></item><item><title>How to background a running process over SSH</title><link>https://www.devroom.io/2019/08/28/how-to-background-a-running-process-over-ssh/</link><description>&lt;p>Today I was logged in on my FreeNAS server to setup Duplicity backups. After initial success on a small dataset of about 100MB, I cloned my configuration to backup my long term storage dataset. It contains about 50GB of data - or so I thought. It turns out there&amp;rsquo;s over 500GB there.&lt;/p>
&lt;p>I let the job run throughout the day, not minding it much. But then time comes to close my SSH connection, and I know what that means: the process will be killed. And I really &lt;strong>do not&lt;/strong> want to kill this running backup.&lt;/p>
&lt;p>I know about &lt;code>screen&lt;/code>. And &lt;code>tmux&lt;/code>. And &lt;code>nohup&lt;/code>. Even about &lt;code>&amp;lt;command&amp;gt; &amp;amp;&lt;/code>. But alas it&amp;rsquo;s too late for that.&lt;/p>
&lt;p>Luckily, there&amp;rsquo;s solution to this (if you&amp;rsquo;re running Bash or Zsh at least, I didn&amp;rsquo;t do a compatibility chec).&lt;/p>
&lt;h2 id="1-background-your-running-process">1. Background your running process&lt;/h2>
&lt;p>Your process is probably running in the foreground, so you&amp;rsquo;ll need to background it. There are two steps to this.&lt;/p>
&lt;ol>
&lt;li>Stop your process with &lt;code>Ctrl-Z&lt;/code>. This basically pauzes the process, it does not kill it.&lt;/li>
&lt;li>Background the process by issueing the &lt;code>bg&lt;/code> command. This will continue the process where it left off in the background. No harm done.&lt;/li>
&lt;/ol>
&lt;h2 id="2-disown-the-process">2. Disown the process&lt;/h2>
&lt;p>The final step is to use &lt;code>disown&lt;/code> to remove the job from the current shell. This means than when the shell is killed when you sign off, the process is not killed as well.&lt;/p>
&lt;p>You can use &lt;code>jobs&lt;/code> (or &lt;code>jobs -l&lt;/code> on FreeBSD) to see a list of current jobs and their process ID. To disown a process, use &lt;code>disown -h &amp;lt;process id&amp;gt;&lt;/code>.&lt;/p>
&lt;p>That&amp;rsquo;s all. Your process has now been removed from your shell session and is running happily in the background.&lt;/p>
&lt;h2 id="bonus-action-background-your-process-from-_another_-ssh-session">Bonus action: Background your process from &lt;em>another&lt;/em> SSH session&lt;/h2>
&lt;p>In case you can&amp;rsquo;t use &lt;code>Ctrl-Z&lt;/code>, you can log in with another SSH session and use the &lt;code>kill&lt;/code> command to stop and continue the process that way.&lt;/p>
&lt;ol>
&lt;li>Sign in with another session and use &lt;code>ps&lt;/code> to find the process ID.&lt;/li>
&lt;li>&lt;code>kill -SIGSTOP &amp;lt;process id&amp;gt;&lt;/code> to stop the process&lt;/li>
&lt;li>&lt;code>kill -SIGCONT &amp;lt;process id&amp;gt;&lt;/code> to continue the process in the background.&lt;/li>
&lt;li>Use &lt;code>disown&lt;/code> as described above to disconnect the process from it&amp;rsquo;s parent shell process.&lt;/li>
&lt;/ol>
&lt;h2 id="disclaimer">Disclaimer&lt;/h2>
&lt;p>This approach does not with &lt;em>all&lt;/em> processes, namely those that require access to the TTY to function. &lt;code>vim&lt;/code> is a good example of this. If you background Vim, it will be stopped and you cannot disown it.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2019/08/28/how-to-background-a-running-process-over-ssh/</guid><pubDate>Wed, 28 Aug 2019 00:00:00 +0000</pubDate></item><item><title>Bug severity explained</title><link>https://www.devroom.io/2017/12/08/bug-severity-explained/</link><description>&lt;p>Recently I got an email asking me about how I classify bugs and issues and how the different
categories for a bug&amp;rsquo;s severity, like &lt;em>Critical&lt;/em> and &lt;em>Major&lt;/em> can be explained clearly.&lt;/p>
&lt;p>I&amp;rsquo;m a software engineer and for the longest time have I approached everything in my
work as a &lt;em>software engineering problem&lt;/em>. Bug reports are one of them. Any bug report
is a report on how software does not behave like it should.&lt;/p>
&lt;blockquote>
&lt;p>The overview page does not load &amp;lt; 2 seconds&lt;/p>
&lt;/blockquote>
&lt;p>or&lt;/p>
&lt;blockquote>
&lt;p>Fix the calculation of the thing to reflect real world exchange rates&lt;/p>
&lt;/blockquote>
&lt;p>Bug reports are not about your software. Bug reports are about users and stakeholders
&lt;em>using&lt;/em> your software to perform a task. And a bug report tells you that a user
cannot perform that task in an acceptable manner.&lt;/p>
&lt;h2 id="how-do-_real_-engineers-do-it">How do &lt;em>real&lt;/em> engineers do it?&lt;/h2>
&lt;p>Computer programmers have a tendency to reinvent the wheel every few months. It&amp;rsquo;s surprising to
see new ecosystems (looking at you, JavaScript) reinvent a lot of unix principles that have been
around for over forty years.&lt;/p>
&lt;p>So, let&amp;rsquo;s take a look at how the non-computer engineers tackle the categorization of bugs and issues.
I found that &lt;a href="http://blog.proqc.com/classifying-defects-is-it-major-minor-or-critical/">ProQC&lt;/a> has a
nice write up on the topic. Note that their guidelines are aimed at &lt;em>physical&lt;/em> products, but there&amp;rsquo;s
a sensible parallel to software products.&lt;/p>
&lt;h3 id="critical">Critical&lt;/h3>
&lt;blockquote>
&lt;p>Any condition found which poses the possibility of causing injury or harm to,
or otherwise endangering the life or safety of, the end user of the product or others in the
immediate vicinity of its use.&lt;/p>
&lt;/blockquote>
&lt;p>Luckily for most software engineers, nobody dies or gets injured from your software not working
correctly (although many stakeholders want you to think otherwise). This is probably true for
99% of all web applications. That does not mean that bugs cannot cause &lt;em>harm&lt;/em> in other ways.&lt;/p>
&lt;p>Critical issues result in (most likely) financial damages for you or your customer.&lt;/p>
&lt;ul>
&lt;li>Missed business opportunities&lt;/li>
&lt;li>Waste due to product inavailablity (imagine an office of 100 people thumb twiddling because the website is down)&lt;/li>
&lt;li>Fines because of failure to comply with rules and regulations&lt;/li>
&lt;li>Customers leaving or seeking compensation for their damages&lt;/li>
&lt;/ul>
&lt;p>Basically any issue that results in real word damages should be classified as critical. Of course, this
is related to your terms of service or service level agreement.&lt;/p>
&lt;h3 id="major">Major&lt;/h3>
&lt;blockquote>
&lt;p>Any condition found adversely affecting the product’s marketability and sale-ability or adversely
affecting its required form, fit or function and which is likely to result in the end user returning
it to the source from which is was purchased for replacement or refund.&lt;/p>
&lt;/blockquote>
&lt;p>Imagine you buy a new iPhone and when you unpack it, it has a scratch across the screen. You don&amp;rsquo;t want
that, although the phone probably works just fine.&lt;/p>
&lt;p>In software I would categorize this as any issue that prevents a user from correctly or efficiently performing
a task. It&amp;rsquo;s closely related to critical issues, but the financial impact is limited.&lt;/p>
&lt;h3 id="minor">Minor&lt;/h3>
&lt;blockquote>
&lt;p>Any condition found which while possibly less than desirable to the end user of the product, does not
adversely affect its required marketability, sale-ability, form, fit or function and is unlikely
to result in its return to the source from which it was purchased.&lt;/p>
&lt;/blockquote>
&lt;p>This will include most software issues that you would call &lt;em>annoyances&lt;/em> or improvements.&lt;/p>
&lt;h3 id="trivial">Trivial&lt;/h3>
&lt;p>Trivial is not mentioned in the ProQC list, which kind of makes sense. A physical product is impossible
to update without substantial cost. Software, however, can be updated frequently and strategies like &lt;em>continuous
deployment&lt;/em> make rolling out these changes almost painless.&lt;/p>
&lt;p>I&amp;rsquo;m not a fan of the &lt;em>trivial&lt;/em> category. It&amp;rsquo;s just too confusing for everyone involved. Trivial has too many
different meanings to different people.&lt;/p>
&lt;ul>
&lt;li>this is a huge issue and should be fixed asap&lt;/li>
&lt;li>this fix is so easy&lt;/li>
&lt;li>this issue is not a big deal, but I had to report it anyway&lt;/li>
&lt;/ul>
&lt;p>Any bug you&amp;rsquo;d classify as &lt;em>trivial&lt;/em> has either a critical, major or minor impact on the user, so classify it as such.&lt;/p>
&lt;h2 id="quantitative-measurements">Quantitative measurements&lt;/h2>
&lt;p>It&amp;rsquo;s tempting to add metrics to each category&amp;rsquo;s description. Of course you need to specify what you expect
your application to do, but a description like, &amp;ldquo;It&amp;rsquo;s a major issue if the overview page takes longer than 10 seconds
to load,&amp;rdquo; will not hold up. Aside from how and who measures this, what if the load time is a consistent 9.5 seconds?&lt;/p>
&lt;p>The question should always be: in what way if the user impacted with this defect? Does it cost them money, will they
stop paying for your SaaS subscription or is it an annoyance that can be optimized later?&lt;/p>
&lt;p>I hope this post was helpful to you to get a grip on how to categorize bugs and issues. This is by no means
a definitive list and I believe that it&amp;rsquo;s a good practice to regularly evaluate which categories you use and
how you use them.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2017/12/08/bug-severity-explained/</guid><pubDate>Fri, 08 Dec 2017 00:00:00 +0000</pubDate></item><item><title>Tmux and Vim: Copy and Paste on macOS Sierra</title><link>https://www.devroom.io/2017/03/22/tmux-and-vim-copy-and-paste-on-macos-sierra/</link><description>&lt;p>As a developer, more than anything, you copy and paste things. Making copy and paste work in properly with Vim and Tmux is, unfortunately, not trivial.&lt;/p>
&lt;p>There&amp;rsquo;s a lot of information to be found on the internet about setting up copy and paste with Tmux. There&amp;rsquo;s also
lots of information on how to setup Vim. But then you run Vim 8. On macOS Sierra. And things break down
quickly.&lt;/p>
&lt;p>This short guide helps you setup Tmux and Vim on macOS Sierra for proper copy pasting glory!&lt;/p>
&lt;h2 id="dependencies">Dependencies&lt;/h2>
&lt;p>First, you&amp;rsquo;ll have to install &lt;em>one&lt;/em> dependency. I also recommend you install the latest and greatest
Vim instead of using the (old) version bundled with macOS, and of course, tmux.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">brew install reattach-to-user-namespace
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">brew install vim tmux
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="configure-tmux">Configure Tmux&lt;/h2>
&lt;p>Tmux is a weird beast. Copy and pasting can be done in different ways, but I prefer the Vim style navigation.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl"># Configure your default shell, Zsh in my case.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">set -g default-shell $SHELL
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># Override the default command to use `reattach-to-user-namespace` for everything.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">set -g default-command &amp;#34;reattach-to-user-namespace -l ${SHELL}&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># Remap prefix to ctrl-a (or caps-a for my mac)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">set -g prefix C-a
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># Vim style navigation in copy mode
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">setw -g mode-keys vi
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># Setup &amp;#39;v&amp;#39; to begin selection, just like Vim
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">bind-key -t vi-copy v begin-selection
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># Setup &amp;#39;y&amp;#39; to yank (copy), just like Vim
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">bind-key -t vi-copy y copy-pipe &amp;#34;reattach-to-user-namespace pbcopy&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># Update default binding of `Enter` to also use copy-pipe
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">unbind -t vi-copy Enter
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">bind-key -t vi-copy Enter copy-pipe &amp;#34;reattach-to-user-namespace pbcopy&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># Bind &amp;#39;]&amp;#39; to use pbbaste
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">bind ] run &amp;#34;reattach-to-user-namespace pbpaste | tmux load-buffer - &amp;amp;&amp;amp; tmux paste-buffer&amp;#34;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This is how copy pasting will working after you&amp;rsquo;ve made the proper changes to your &lt;code>.tmux.conf&lt;/code>. Note that
I have remapped &lt;kbd>ctrl&lt;/kbd> + &lt;kbd>b&lt;/kbd> to &lt;kbd>ctrl&lt;/kbd> + &lt;kbd>a&lt;/kbd>, because that works pretty
fast when you&amp;rsquo;ve already remapped Caps-Lock to Control.&lt;/p>
&lt;ul>
&lt;li>&lt;kbd>ctrl&lt;/kbd> + &lt;kbd>a&lt;/kbd>, &lt;kbd>[&lt;/kbd> to enter copy mode. This allows you to use normal Vim
motion keys to move around.&lt;/li>
&lt;li>&lt;kbd>v&lt;/kbd> to start selection, again use vim motion keys to move around.&lt;/li>
&lt;/ul>
&lt;ul>
&lt;li>&lt;kbd>y&lt;/kbd> to yank the selection. &lt;kbd>enter&lt;/kbd> also works.&lt;/li>
&lt;/ul>
&lt;p>Pasting works just as before:&lt;/p>
&lt;ul>
&lt;li>&lt;kbd>ctrl&lt;/kbd> + &lt;kbd>a&lt;/kbd>, &lt;kbd>]&lt;/kbd>&lt;/li>
&lt;/ul>
&lt;p>Congratulation, you can now copy and paste with Tmux.&lt;/p>
&lt;h2 id="configure-vim">Configure Vim&lt;/h2>
&lt;p>The changes needed for Vim are minimal. All you need to do is &lt;em>unset&lt;/em> the name of the default clipboard,
so it will pass through to Tmux. In &lt;code>~/.vimrc&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-vim" data-lang="vim">&lt;span class="line">&lt;span class="cl">&lt;span class="c">&amp;#34; Clipboard &lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">set&lt;/span> &lt;span class="nx">clipboard&lt;/span>&lt;span class="p">=&lt;/span>&lt;span class="nx">unnamed&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="bonus-features">Bonus features&lt;/h2>
&lt;p>So, copy and paste is now working from Vim, Tmux and Vim-in-Tmux. Pasting is also working as expected.&lt;/p>
&lt;p>As a bonus, when you have two tmux panes or windows with different instances of Vim running, you can now
easily copy and paste between them by using normal Vim commands!&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>Copy and paste with Vim and Tmux does not work out of the box, but once setup works like a charm.&lt;/p>
&lt;p>Here are links to my &lt;a href="https://git.devroom.io/ariejan/dotfiles/blob/e54317082b2a72363dad9aa6bb38310bfe8a6cc8/vim/.vimrc">&lt;code>~/.vimrc&lt;/code>&lt;/a> and &lt;a href="https://git.devroom.io/ariejan/dotfiles/blob/e54317082b2a72363dad9aa6bb38310bfe8a6cc8/tmux/.tmux.conf">&lt;code>~/.tmux.conf&lt;/code>&lt;/a> for your perusal.&lt;/p>
&lt;p>I highly recommend &lt;a href="http://amzn.to/2n8I3Ax">tmux 2: Productive Mouse-Free Development&lt;/a>, but feel free to check all
my &lt;a href="https://www.devroom.io/recommendations">book recommendations&lt;/a>.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2017/03/22/tmux-and-vim-copy-and-paste-on-macos-sierra/</guid><pubDate>Wed, 22 Mar 2017 00:00:00 +0000</pubDate></item><item><title>Image Zoom with plain JavaScript and CSS</title><link>https://www.devroom.io/2017/03/20/image-zoom-with-plain-javascript-and-css/</link><description>&lt;p>&lt;em>Note: I&amp;rsquo;ve moved away from the solution outlined in this post. Mainly because I did not want
to write an entire gallery from scratch. I&amp;rsquo;ve since opted to use &lt;a href="https://sachinchoolur.github.io/lightgallery.js/">lightgallery.js&lt;/a>
which is a pure JS image gallery solution with some nice goodies.&lt;/em>&lt;/p>
&lt;p>The premise is simple. A post may contain images. These images are restricted in rendered size to
keep the flow of the page in tact. Clicking an image allows you to zoom in. Here&amp;rsquo;s an example:&lt;/p>
&lt;p>&lt;em>Go ahead, click that bunny!&lt;/em>&lt;/p>
&lt;p>&lt;img
src="https://www.devroom.io/img/bunny.jpg"
alt="Sample zoomable image"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;h2 id="the-css">The CSS&lt;/h2>
&lt;p>Let&amp;rsquo;s get the CSS out of the way first. The selector used is &lt;code>article img&lt;/code>, which means
any image in the post. By default I limit it to a maximum width of its parent container.
Also, I change the cursor to a pointer, to indicate you can click on the image, like a link.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-css" data-lang="css">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">article&lt;/span> &lt;span class="nt">img&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">max-width&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">100&lt;/span>&lt;span class="kt">%&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">cursor&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">pointer&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Then there are images with the &lt;code>zoomed&lt;/code> class. This is still the same image element,
but with an additional class:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-css" data-lang="css">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">article&lt;/span> &lt;span class="nt">img&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nc">zoomed&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">position&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">fixed&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">top&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">5&lt;/span>&lt;span class="kt">vh&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">bottom&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">5&lt;/span>&lt;span class="kt">vh&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">left&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">5&lt;/span>&lt;span class="kt">vw&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">right&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">5&lt;/span>&lt;span class="kt">vw&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">max-width&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">90&lt;/span>&lt;span class="kt">vw&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">max-height&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">90&lt;/span>&lt;span class="kt">vh&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">margin&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">auto&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">border&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">4&lt;/span>&lt;span class="kt">px&lt;/span> &lt;span class="kc">solid&lt;/span> &lt;span class="mh">#000&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Okay, that&amp;rsquo;s a bit more CSS, but this basically overlays the image on to the page and adds some
whitespace around it.&lt;/p>
&lt;p>The trick to zooming is adding the &lt;code>zoomed&lt;/code> class to the &lt;code>img&lt;/code> element. Zooming out means
removing that &lt;code>zoomed&lt;/code> class again.&lt;/p>
&lt;p>Now, on to the JavaScript&amp;hellip;&lt;/p>
&lt;h2 id="the-jquery-solution">The jQuery solution&lt;/h2>
&lt;p>For years now jQuery has been my go-to tool for anything JavaScript, mainly because it
comes bundled with Rails. (Yes, I used prototype as well in the old days.)&lt;/p>
&lt;p>The jQuery solution is as you rather straight forward. Wait for the DOM to be loaded,
and handle &lt;code>click&lt;/code> events on all &lt;code>article img&lt;/code> elements. When clicked, toggle the &lt;code>zoomed&lt;/code>
class.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="line">&lt;span class="cl">&lt;span class="nx">$&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kd">function&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">$&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">document&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">on&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;click&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;article img&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kd">function&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">$&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">toggleClass&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;zoomed&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Because I&amp;rsquo;m a keyboard user (Vim, not Emacs, thank you), I prefer to map &lt;kbd>ESC&lt;/kbd> to
also close any zoomed imaged.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="line">&lt;span class="cl">&lt;span class="nx">$&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kd">function&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">$&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">document&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">keyup&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kd">function&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">e&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">e&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">keyCode&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">27&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">$&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;img.zoomed&amp;#39;&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">each&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kd">function&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">idx&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">$&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">toggleClass&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;zoomed&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Again, hook into the &lt;code>keyup&lt;/code> event, check if &lt;kbd>ESC&lt;/kbd> was pressed and toggle
the &lt;code>zoomed&lt;/code> class for all zoomed in images.&lt;/p>
&lt;p>But, using jQuery means adding an extra dependency: 1 extra HTTP request and 85kB download for you.
Also, the few friends I have who practice JavaScript tell me that &lt;em>pure&lt;/em> JavaScript is the way to
go these days. So, let&amp;rsquo;s try!&lt;/p>
&lt;h2 id="the-javascript-solution">The JavaScript solution&lt;/h2>
&lt;p>With some help from the &lt;a href="http://youmightnotneedjquery.com/">You Might Not Need jQuery&lt;/a> website,
I managed to drop the 85kB big jQuery dependency and rewrite the above functionality in plain old
JavaScript.&lt;/p>
&lt;p>First, let&amp;rsquo;s write a function that waits for the DOM to load.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">function&lt;/span> &lt;span class="nx">ready&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">fn&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">document&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">readyState&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="s1">&amp;#39;loading&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">fn&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">document&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">addEventListener&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;DOMContentLoaded&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">fn&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This was taken straight from You Might Not Need jQuery to wrap any functions you want to run
when the document has loaded fully.&lt;/p>
&lt;p>Next I wrote a function to handle toggling the &lt;code>zoomed&lt;/code> CSS class on the images:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">function&lt;/span> &lt;span class="nx">imageClick&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">e&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">e&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">preventDefault&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">classList&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">toggle&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;zoomed&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>It turns out that JavaScript is more than capable of toggling classes on its own.&lt;/p>
&lt;p>While we&amp;rsquo;re at it, let&amp;rsquo;s also write the function that handles the &lt;kbd>ESC&lt;/kbd> presses.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">function&lt;/span> &lt;span class="nx">handleEsc&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">e&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">e&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">keyCode&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">27&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">var&lt;/span> &lt;span class="nx">zoomedImages&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">document&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">querySelectorAll&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;img.zoomed&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">Array&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">prototype&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">forEach&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">call&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">zoomedImages&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kd">function&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">el&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">i&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">el&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">classList&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">toggle&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;zoomed&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This is a bit more involed. I still check for the proper &lt;code>keyCode&lt;/code>, and then proceed to
find all zoomed images using &lt;code>document.querySelectorAll&lt;/code>. It&amp;rsquo;s really that easy.&lt;/p>
&lt;p>Next I use the &lt;code>Array&lt;/code> prototype to map a function to each zoomed image. That function simply
toggles the &lt;code>zoomed&lt;/code> class, just like &lt;code>imageClick&lt;/code> does.&lt;/p>
&lt;p>What remains is nothing more than some glue to put the above fuctions together.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="line">&lt;span class="cl">&lt;span class="nx">ready&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kd">function&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">var&lt;/span> &lt;span class="nx">images&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">document&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">querySelectorAll&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;article img&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">Array&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">prototype&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">forEach&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">call&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">images&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kd">function&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">el&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">i&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">el&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">addEventListener&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;click&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">imageClick&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">document&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">addEventListener&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;keyup&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">handleEsc&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Here I use the &lt;code>ready&lt;/code> function I wrote. Just like &lt;code>handleEsc&lt;/code>, I find all
&lt;code>article img&lt;/code> elements and add the event listener for clicks. Then I also
add an event listener for the &lt;kbd>ESC&lt;/kbd> key.&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>Rewriting a trivial piece of jQuery code to plain JavaScript appears to be more
than worth the while. Besides the warm fuzzy feeling of dumping jQuery, it saves
quite a few kilobytes from each page on devroom.io. Especially for mobile users
this matters.&lt;/p>
&lt;p>&lt;code>git&lt;/code> says 11 deletions (bye, jQuery) and 27 additions (hello, JavaScript). This does
not tell the full story, as one of these deleted lines is this one:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-html" data-lang="html">&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">script&lt;/span> &lt;span class="na">src&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&amp;lt;/&lt;/span>&lt;span class="nt">script&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>I can highly recommend you take a closer look at what your jQuery code is &lt;em>actually doing&lt;/em>
and consider moving away from unnecessary dependencies. Yay for lean and mean web pages!&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2017/03/20/image-zoom-with-plain-javascript-and-css/</guid><pubDate>Mon, 20 Mar 2017 00:00:00 +0000</pubDate></item><item><title>Bitcoin Mining Anno 2017</title><link>https://www.devroom.io/2017/03/08/bitcoin-mining-anno-2017/</link><description>&lt;p>Bitcoin was introduced in 2008 and has gained much popularity since then. At
the time of writing the exchange rate for 1 BTC is about €1100 (or $1166), quite
an increase from when you could get 1 BTC for just a few euro.&lt;/p>
&lt;p>&lt;em>It is 29 November 2017, just eight months after writing this post, and the exchange
rate has skyrocketed. The $10,000 barrier has already been broken and the €10,000
barrier is in sight. If you&amp;rsquo;re interest in mining bitcoin, this post is still
relevant. Fees will be lower (as they are charged in $/€) and your net yield will
be higher. Good times!&lt;/em>&lt;/p>
&lt;h2 id="the-problem-with-mining-bitcoin">The problem with mining Bitcoin&lt;/h2>
&lt;p>Mining bitcoin can be quite lucrative, but it&amp;rsquo;s hugely dependent on the exchange
rate and the difficulty of mining. The difficulty is something that is only going
to increase. In the early days a fast CPU was enough to mine new Bitcoin blocks,
followed by GPU mining and eventually specialized &lt;abbr title="Application Specific Integrated Circuit">ASIC&lt;/abbr>
chips.&lt;/p>
&lt;p>Besides investing in powerful hardware to mine blocks, there&amp;rsquo;s also the utilities
bill. Calculationg millions of SHA-256 hashes per seconds is not very power
efficient.&lt;/p>
&lt;p>If you&amp;rsquo;re like me, you don&amp;rsquo;t want to have a machine in your study or living room
generating noise and heat to mine Bitcoin. There are several options, like renting
a powerful GPU machine at Amazon AWS, but there&amp;rsquo;s a huge upfront cost. For the
past few years did not find mining bitcoin interesting or cost effective enough.
Last week that changed.&lt;/p>
&lt;h2 id="welcome-to-2017">Welcome to 2017&lt;/h2>
&lt;p>Last week I found &lt;a href="https://hashflare.io/r/B91F1C3F">Hashflare.io&lt;/a>.
The great thing about &lt;a href="https://hashflare.io/r/B91F1C3F">Hashflare.io&lt;/a> is that you choose how much you want to invest
in hardware by purchasing mining capacity in units of 10 GH/s, currently at a
price of $1.20 per unit.&lt;/p>
&lt;p>Your mining capacity is put towards mining Bitcoin in different pools (you can
change these), resulting in a daily payout of mined Bitcoin.&lt;/p>
&lt;p>The good part? This is a life-time purchase. You keep the mining capacity - forever.&lt;/p>
&lt;p>The only &lt;em>cost&lt;/em> is a fee for power and management of your hardware, which is
substracted automatically from your daily payout.&lt;/p>
&lt;p>Purchasing capacity is easy and straightforward. Payment options include credit card
and Bitcoin. It&amp;rsquo;s also possible to &lt;em>reinvest&lt;/em> your earned bitcoin into purchasing more
mining capacity.&lt;/p>
&lt;h2 id="return-on-investment">Return on Investment&lt;/h2>
&lt;p>Right now I&amp;rsquo;m seeing about 40% of my Bitcoin yield go to power and management costs.
This means I&amp;rsquo;ll earn back my initial investment in ± 5 months. After that it&amp;rsquo;s pure
profit.&lt;/p>
&lt;p>The current exchange rate is also of importance here, because power and management
fees are calculated in USD. Any increase (or decrease) in exchange rate will influence
the amount of BTC that goes towards fees.&lt;/p>
&lt;p>Since the value of Bitcoin has been steadily rising since 2008 (there have been some
extreme fluctuations), it&amp;rsquo;s very well possible that I&amp;rsquo;ll break even before 5 months.&lt;/p>
&lt;h2 id="lets-get-mining">Let&amp;rsquo;s get mining!&lt;/h2>
&lt;p>There&amp;rsquo;s no reason not to start mining Bitcoin today. &lt;a href="https://hashflare.io/r/B91F1C3F">Hashflare.io&lt;/a> makes it
rediculously easy to purchase mining capacity and start minting Bitcoin.&lt;/p>
&lt;p>Will you make your money back? Given time, probably. Is it fun to be part of
this Bitcoin thing? Oh yeah!&lt;/p>
&lt;p>Note: Besides SHA-256 hardware, for Bitcoin, you can also purchase capacity
for mining with Scrypt, ETHASH and X11.&lt;/p>
&lt;p>&lt;em>Disclaimer: The links to Hashflare.io in the article are referral links,
I will get 10% of any purchases you make on Hashflare.io. I understand
if you object to using a referral link, here&amp;rsquo;s a &lt;a href="https://hashflare.io">non-referral link&lt;/a> instead.&lt;/em>&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2017/03/08/bitcoin-mining-anno-2017/</guid><pubDate>Wed, 08 Mar 2017 00:00:00 +0000</pubDate></item><item><title>Squash git commits when merging</title><link>https://www.devroom.io/2016/08/17/squash-git-commits-when-merging/</link><description>&lt;p>Today I&amp;rsquo;ve been fighting to get our test suite to run against a newly delivered Oracle
12 database. Of course, that didn&amp;rsquo;t work out of the box, so there was some debugging,
trial-and-error, and cursing involved. Finally, I managed to get the build back up and running. Yay!&lt;/p>
&lt;p>The commit history for this pull request was horrible and some would call it &lt;em>unprofessional&lt;/em>,
looking at the various commit messages written in anger.&lt;/p>
&lt;p>Normally I&amp;rsquo;d &lt;a href="https://ariejan.net/2011/07/05/git-squash-your-latests-commits-into-one/">squash all commits together using rebase&lt;/a> in the feature
branch and create a nice pull request. In this case I just wanted to merge this to
&lt;code>develop&lt;/code> so the build was working. Turns out this is rather easy:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">git checkout develop
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">git merge --squash fix_for_oracle_12
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This applies all changes from the &lt;code>fix_for_oracle_12&lt;/code> branch to your working copy, ready
for you to commit in a single commit.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">git commit -m &lt;span class="s2">&amp;#34;Apply workaround for Oracle DB 12&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Done.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2016/08/17/squash-git-commits-when-merging/</guid><pubDate>Wed, 17 Aug 2016 00:00:00 +0000</pubDate></item><item><title>Music Streaming Setup</title><link>https://www.devroom.io/2016/04/24/music-streaming-setup/</link><description>&lt;p>I love music.&lt;/p>
&lt;p>Recently I got into vinyl and I love it. Although it&amp;rsquo;s not digital 24/96 studio quality, there is something relaxing about picking a record, putting it on my turn table and listening to the small diamond needle scanning the grooves of the record pressed 30 or 40 years ago. This post is not about vinyl, though.&lt;/p>
&lt;p>Today I listen mostly to music on &lt;a href="https://www.spotify.com">Spotify&lt;/a>. It&amp;rsquo;s cheap, offers good quality and is available both at home and at work.&lt;/p>
&lt;p>The thing is I have lots of great music lying around on CD from when I was a kid. It&amp;rsquo;s all sitting in a box in the attic, wasting away.&lt;/p>
&lt;p>So, I thought to myself, what does it take to:&lt;/p>
&lt;ul>
&lt;li>Archive my music collection in FLAC&lt;/li>
&lt;li>Catalog my music properly&lt;/li>
&lt;li>Stream high quality music over the web (to my laptop)&lt;/li>
&lt;li>Possibly play directly (analog/digital) to my integrated amplifier&lt;/li>
&lt;/ul>
&lt;h2 id="catalog-your-music">Catalog your music&lt;/h2>
&lt;p>I&amp;rsquo;ve only ever used iTunes before to manage my music collection (in low quality mp3, back in the day). These days I use a command line utility named &lt;a href="http://beets.io/">beets&lt;/a>. Beets is simple yet effective. Let me just copy the best features from their website:&lt;/p>
&lt;ul>
&lt;li>Fetch or calculate all the metadata you could possibly need: album art, lyrics, genres, tempos, ReplayGain levels, or acoustic fingerprints.&lt;/li>
&lt;li>Get metadata from MusicBrainz, Discogs, or Beatport. Or guess metadata using songs’ filenames or their acoustic fingerprints.&lt;/li>
&lt;li>Transcode audio to any format you like.&lt;/li>
&lt;li>Check your library for duplicate tracks and albums or for albums that are missing tracks.&lt;/li>
&lt;/ul>
&lt;p>It takes a bit of getting used to, but it&amp;rsquo;s really easy to use and powerful. I&amp;rsquo;m not moving to anything else anytime soon.&lt;/p>
&lt;h2 id="enter-raspberry-pi">Enter Raspberry Pi&lt;/h2>
&lt;p>The first and most obvious choice was to purchase a Raspberry Pi (v3 in this case). It&amp;rsquo;s a cheap device with ethernet and audio playback capabilities. I also know there are expansions boards that offer digital out or analog out using high quality DACs. I had an old 32GB USB Thumbdrive to use for storage (until I need more).&lt;/p>
&lt;p>My first install was using &lt;a href="https://www.raspbian.org/">Raspbian&lt;/a> and &lt;a href="http://koel.phanan.net/">Koel&lt;/a>. Koel looks a lot like Spotify and I had high hopes for it.&lt;/p>
&lt;p>&lt;img
src="https://www.devroom.io/img/flac-logo.png"
alt=""
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;h2 id="flac">FLAC&lt;/h2>
&lt;p>I love FLAC for two reasons. It&amp;rsquo;s open source and it stores lossless compressed audio. The problem, however, is that FLAC is not as widely supported as I&amp;rsquo;d like. Mac OS X has no native FLAC support (because Apple obviously favours their own ALAC lossless format). Browsers also have limited support (if any) for FLAC.&lt;/p>
&lt;p>This means that my web player, in this case Koel, should transcode FLAC files on the fly to a format that &lt;em>is&lt;/em> supported, like mp3.&lt;/p>
&lt;p>For this purpose I installed &lt;code>ffmpeg&lt;/code> on the Pi and it worked. Sort of. There were two problems with my setup of Koel + Transcoding.&lt;/p>
&lt;p>The transcoding could keep up with streaming, were it not that Koel first transcodes the entire file and &lt;em>then&lt;/em> starts streaming.&lt;/p>
&lt;p>Also, Koel, seemed not to deal with album artists vs. performer particularly well. For instance, Fun, by &amp;ldquo;Coldplay feat. Tove Lo&amp;rdquo; would show up as a separate album.&lt;/p>
&lt;h2 id="mp3fs">mp3fs&lt;/h2>
&lt;p>I got tipped off by a coworker that I should look into &lt;a href="https://khenriks.github.io/mp3fs/">mp3fs&lt;/a>. mp3fs is pretty awesome. It allows you to mount a directory/disk with FLAC files and &lt;em>use&lt;/em> those files as if they were mp3s. Yes. Awesome.&lt;/p>
&lt;p>The only thing is that everytime you access a file it will use ffmpeg to transcode the FLAC to mp3. Not a problem, until you start importing your collection into Koel. Because the Pi has limited memory, this would crash mp3fs with an out-of-memory error.&lt;/p>
&lt;p>Meh. This is not the awesome stable solution I&amp;rsquo;m looking for.&lt;/p>
&lt;h2 id="file-size">File size&lt;/h2>
&lt;p>Well, FLAC is a lossless, compressed format. Despite it begin compressed, it&amp;rsquo;s still fairly large. Especially if you want to keep your files on 32GB USB drive.&lt;/p>
&lt;p>At this point I decided to take another route: keep my high quality FLAC collection on my computer (where it&amp;rsquo;ll get backed up automatically as well), transcode it to high quality mp3 and use &lt;code>rsync&lt;/code> to get it the Pi for streaming. Luckily I&amp;rsquo;m using beets, which has a &lt;code>convert&lt;/code> plugin. This plugin makes life easy to keep a copy of my library available as mp3. For now I&amp;rsquo;m using mp3 V0 (highest quality variable bitrate), which suits me fine.&lt;/p>
&lt;h2 id="sonerezh">Sonerezh&lt;/h2>
&lt;p>I feel Koel is still too early in its development and I didn&amp;rsquo;t want some bloated music player like Ampache or Subsonic. Then I stumbled up &lt;a href="https://www.sonerezh.bzh/">Sonerezh&lt;/a>. It has a clean iTunes-esque interface and just works.&lt;/p>
&lt;p>&lt;img
src="https://www.devroom.io/img/sonerezh.png"
alt="Sonerezh playing"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>I love my current setup. Keep and manage my FLAC collection on my Mac. Use beets to get proper metadata for each song and convert new or changed files to high quality mp3. These in turn are rsync&amp;rsquo;ed to my Raspberry Pi, where they become available in Sonerezh for a great streaming experience.&lt;/p>
&lt;p>Am I cancelling Spotify? Not just yet, there&amp;rsquo;s too much music out there to be discovered and Spotify is still great for that. Am I going to expand my collection? You bet!&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2016/04/24/music-streaming-setup/</guid><pubDate>Sun, 24 Apr 2016 00:00:00 +0000</pubDate></item><item><title>Hanami and Multi-Database Testing with Travis</title><link>https://www.devroom.io/2016/04/15/hanami-and-multi-database-testing-with-travis/</link><description>&lt;p>&lt;em>This is a re-post of my article over at &lt;a href="https://kabisa.nl">Kabisa&lt;/a>&amp;rsquo;s &lt;a href="https://www.theguild.nl/hanami-and-multi-database-testing-with-travis">The Guild&lt;/a>.&lt;/em>&lt;/p>
&lt;p>I&amp;rsquo;ve been busy rewriting &lt;a href="https://github.com/ariejan/firefly">Firefly&lt;/a> for a while now using
&lt;a href="http://hanamirb.org">Hanami&lt;/a>. Hanami is a fascinatingly fresh ruby web framework with a
strong opinion on &lt;em>Clean Architecture&lt;/em>. Me like!&lt;/p>
&lt;h2 id="why-test-against-different-databases">Why test against different databases&lt;/h2>
&lt;p>Initially I developed Firefly to use Sqlite for database storage. However, Sqlite
is not always the &lt;em>best&lt;/em> option. Running Firefly on &lt;a href="https://heroku.com">Heroku&lt;/a> for
instance would be impractical, since Heroku&amp;rsquo;s architecture assumes you use a &lt;em>real&lt;/em>
database, like Postgres.&lt;/p>
&lt;p>Firefly being open source also means that different users will want to use a
database that they&amp;rsquo;re already familiar with, or one that&amp;rsquo;s already running for
other apps.&lt;/p>
&lt;h2 id="supporting-multiple-databases">Supporting multiple databases&lt;/h2>
&lt;p>The &lt;em>big three&lt;/em> relational databases I want to support are Sqlite, MySQL and PostgreSQL.
Hanami uses &lt;a href="http://sequel.jeremyevans.net/">Sequel&lt;/a>. This means my code is already
abstracted from specific database implementation. As long as Sequel supports it, so
can Firefly.&lt;/p>
&lt;p>The only problem I encountered was the fact that MySQL &lt;code>datetime&lt;/code> fields do not store
fractions of seconds, which messed with some tests. This was easily taken care of.&lt;/p>
&lt;h2 id="travis">Travis&lt;/h2>
&lt;p>&lt;a href="https://travis-ci.org">Travis&lt;/a> is an awesome CI-as-a-Service provider. Open
source projects can even use their service for free! &amp;lt;3&lt;/p>
&lt;p>Whenever a new commit is made (on &lt;code>master&lt;/code> or in Pull Requests), Travis will
check out the code, do some setup specified in a &lt;code>.travis.yml&lt;/code> file and report
back the test status.&lt;/p>
&lt;p>The thing is that, with multiple databases, we need to tell Travis to run
multiple sub-builds for each database. We also need to tell Travis how
to configure / setup Firefly to use each database properly.&lt;/p>
&lt;h2 id="hanami-and-databases">Hanami and databases&lt;/h2>
&lt;p>Before setting up multiple databases, let&amp;rsquo;s check how Hanami configures a
database connection.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># lib/firefly.rb&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="no">Hanami&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="no">Model&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">configure&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># * SQL adapter&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># adapter type: :sql, uri: &amp;#39;sqlite://db/firefly_development.sqlite3&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># adapter type: :sql, uri: &amp;#39;postgres://localhost/firefly_development&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># adapter type: :sql, uri: &amp;#39;mysql://localhost/firefly_development&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">#&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">adapter&lt;/span> &lt;span class="ss">type&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="ss">:sql&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">uri&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="no">ENV&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="s1">&amp;#39;FIREFLY_DATABASE_URL&amp;#39;&lt;/span>&lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>So, the actual database URI is set using an environment variable. Hanami
makes use of the &lt;code>dotenv&lt;/code> gem, which will load a &lt;code>.env&lt;/code> or &lt;code>.env.test&lt;/code> file
depending on which environment Hanami runs in. Somehow we&amp;rsquo;d need different
&lt;code>.env.test&lt;/code> files for each database configuration&lt;/p>
&lt;p>For Sqlite:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># .env.test.sqlite&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">FIREFLY_DATABASE_URL&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;sqlite://db/firefly_development.sqlite3&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>For MySQL:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># .env.test.mysql&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">FIREFLY_DATABASE_URL&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;mysql://root@localhost/firefly_test&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>For Postgres:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># .env.test.postgresql&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">FIREFLY_DATABASE_URL&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;postgres://localhost/firefly_test&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>There&amp;rsquo;s also the issue of dependencies. For instance, when using PostgreSQL,
the &lt;code>pg&lt;/code> gem should be included in &lt;code>Gemfile&lt;/code>. If you&amp;rsquo;re running with Sqlite,
you do &lt;em>not&lt;/em> want that dependency there. Here I&amp;rsquo;d like to take the same
approach and create multiple Gemfiles that each specify their own
dependencies as needed.&lt;/p>
&lt;p>For Sqlite:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># gemfiles/Gemfile.sqlite&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">gem&lt;/span> &lt;span class="s1">&amp;#39;sqlite3&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>For MySQL:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># gemfiles/Gemfile.mysql&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">gem&lt;/span> &lt;span class="s1">&amp;#39;mysql&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>For Postgres:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># gemfiles/Gemfile.postgresql&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">gem&lt;/span> &lt;span class="s1">&amp;#39;pg&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="tying-it-all-together">Tying it all together&lt;/h2>
&lt;p>What&amp;rsquo;s left to is tell Travis about the different database and put the right
files in place at the right time.&lt;/p>
&lt;p>First, I setup the environment variables for each database. This triggers Travis
to run a build for each combination of variables:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="c"># .travis.yml&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">env&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">DB=sqlite&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">DB=mysql&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">DB=postgresql&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Travis will run the build three times, each time with a different &lt;code>DB&lt;/code> value set.&lt;/p>
&lt;p>The process of the Firefly build is like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="c"># .travis.yml&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">install&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">bundle install --jobs=3 --retry=3 --without production&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">script&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s1">&amp;#39;HANAMI_ENV=test bundle exec hanami db create&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s1">&amp;#39;HANAMI_ENV=test bundle exec hanami db migrate&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s1">&amp;#39;bundle exec rake test&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>What I want is hook into different places and setup the right &lt;code>.env&lt;/code> and &lt;code>Gemfile&lt;/code>
for the specified database. As it turns out Travis provides &lt;code>before_install&lt;/code> and
&lt;code>before_script&lt;/code> hooks. By making use of the specified &lt;code>DB&lt;/code> environment variable,
it really is just a matter of copying the right files into place.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="c"># .travis.yml&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">before_install&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">cp gemfiles/Gemfile.$DB Gemfile&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">install&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">bundle install --jobs=3 --retry=3 --without production&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">before_script&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">cp .env.test.$DB .env.test&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">script&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s1">&amp;#39;HANAMI_ENV=test bundle exec hanami db create&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s1">&amp;#39;HANAMI_ENV=test bundle exec hanami db migrate&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s1">&amp;#39;bundle exec rake test&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>That&amp;rsquo;s all it takes to run your tests against multiple databases with Hanami!&lt;/p>
&lt;h2 id="bonus-test-against-multiple-rubies">Bonus: test against multiple rubies&lt;/h2>
&lt;p>Testing against multiple databases is cool, but it&amp;rsquo;s also very well possible
that end-users will not be using the greatest and latest ruby version. Firefly
currently support the latest 2.2.x and 2.3. versions of Ruby. Travis supports
this out of the box:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="c"># .travis.yml&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">rvm&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="m">2.2.4&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="m">2.3.0&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This, in combination with our database setup, will trigger six builds. Sqlite,
MySQL and PostgreSQL builds on ruby-2.2.4 &lt;em>and&lt;/em> on ruby-2.3.0.&lt;/p>
&lt;p>&lt;img
src="https://www.devroom.io/img/travis-firefly-builds.png"
alt="Travis builds"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;p>I hope you liked this post. Happy coding and keep testing!&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2016/04/15/hanami-and-multi-database-testing-with-travis/</guid><pubDate>Fri, 15 Apr 2016 00:00:00 +0000</pubDate></item><item><title>Putting ariejan.net on a diet: a 69% reduction</title><link>https://www.devroom.io/2016/01/06/putting-ariejan-net-on-a-diet-a-69-percent-reduction/</link><description>&lt;p>You may have recently read &lt;a href="http://idlewords.com/talks/website_obesity.htm">Website Obesity&lt;/a> by
&lt;a href="http://idelwords.com/">Maciej Cegłowski&lt;/a>, as featured on &lt;a href="http://news.ycombinator.com">Hacker News&lt;/a>.
There&amp;rsquo;s a lot of great stuff in that article. The most obivous and striking thing is that there
is a lot of bloat on the web. Examples include a tweet (140 characters) that is served as a
900kB website. How to optimize a website and remove bloat is a topic in and of itself. I can
highly recommend the article mentioned above.&lt;/p>
&lt;h2 id="taking-a-look-at-ariejannet">Taking a look at ariejan.net&lt;/h2>
&lt;p>The article by Maciej triggered me to take another look at ariejan.net and see if I can make it
any better (bloat-wise). But first, let&amp;rsquo;s take a measurement of the current situation. I&amp;rsquo;m using
&lt;a href="http://yslow.org/">Yslow&lt;/a> as a Chrome plugin for this.&lt;/p>
&lt;p>&lt;img
src="https://www.devroom.io/img/ariejan-net-before.png"
alt=""
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;p>The frontpage, which a list of all my posts, weighs in at &lt;strong>256.8kB&lt;/strong>. 170kB for the actual HTML document,
but there&amp;rsquo;s also almost 100kB of CSS and JavaScript.&lt;/p>
&lt;h2 id="a-change-of-scenery">A change of scenery&lt;/h2>
&lt;p>The first thing I did was delete everything in my CSS file and remove all unneeded content from the
frontpage, including tons of specific dates, word counts and reading times. In general I simplified
the HTML quite a bit, removing unneeded elements. Feel free to take a look, you know you want to.&lt;/p>
&lt;p>On the article pages I also made a few changes, most notably removing the Disqus comments. The reason
for this is twofold: comments just don&amp;rsquo;t add much as most people tweet or mail anyway and it adds
a reasonable amount of extra JavaScript. I might change my mind on this, but for now comments are gone.&lt;/p>
&lt;p>I was also using Google Fonts to make things look pretty. Well, guess what, I don&amp;rsquo;t care that much
about pretty. Out with the webfonts. Instead, I opted to for the &amp;lsquo;retro look&amp;rsquo; with a nice web-safe
monospaced font.&lt;/p>
&lt;p>Because of this change in style, I also set up a new favicon, which turns to be 25% of its original
size as well.&lt;/p>
&lt;h2 id="the-result">The result&lt;/h2>
&lt;p>&lt;img
src="https://www.devroom.io/img/ariejan-net-after.png"
alt=""
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;p>From &lt;strong>256.8kB&lt;/strong> down to &lt;strong>78.8kB&lt;/strong>. If you have any kind of decent connection, you probably
won&amp;rsquo;t experience a big difference. Still, the bloat on ariejan.net&amp;rsquo;s frontpage has been
reduced by more than 69%.&lt;/p>
&lt;p>The document (HTML) size has been greatly reduced, simply by omitting data I don&amp;rsquo;t need and
removing unneeded (and over 300 times repeated) markup.&lt;/p>
&lt;p>JavaScript is still at 26kB because Google Analytics. I&amp;rsquo;m &lt;em>not&lt;/em> happy with that and I&amp;rsquo;m
looking at other options to monitor popular content and visitor numbers, suggestions are
welcome.&lt;/p>
&lt;h2 id="whats-next">What&amp;rsquo;s next?&lt;/h2>
&lt;p>Nothing much. I might look in to replacing Google Analytics. Maybe I&amp;rsquo;ll restore comments
if I get complaints about that. Other than that, I&amp;rsquo;m quite happy again with the new
&amp;ldquo;design&amp;rdquo; and most importantly, my site&amp;rsquo;s weight.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2016/01/06/putting-ariejan-net-on-a-diet-a-69-percent-reduction/</guid><pubDate>Wed, 06 Jan 2016 00:00:00 +0000</pubDate></item><item><title>El Capitan: Safari New Tab Slow Fix</title><link>https://www.devroom.io/2015/12/04/el-capitan-safari-new-tab-slow-fix/</link><description>&lt;p>Safari is a great browser. I know there are alternatives, like Firefox and Chrome, but I like Safari.&lt;/p>
&lt;p>Recently I noticed a lag of about 3-5 seconds when opening a new tab. I use tabs often, so this started to get in my way.
Because I did not feel like switching browsers, I did some digging around to see if I could get this problem fixed.&lt;/p>
&lt;p>Doing a quick Google search (after waiting a few seconds for that new tab), there was one quick suggestion:&lt;/p>
&lt;blockquote>
&lt;p>Disable iCloud for Safari&lt;/p>
&lt;/blockquote>
&lt;p>Alright, let&amp;rsquo;s try. No change. Still lag. This was the other common tip:&lt;/p>
&lt;blockquote>
&lt;p>Set &amp;lsquo;New tab opens with&amp;rsquo; to &amp;lsquo;Empty Page&amp;rsquo;&lt;/p>
&lt;/blockquote>
&lt;p>Ok, done. Still lag.&lt;/p>
&lt;p>I went digging further in Safari&amp;rsquo;s settings and on a hunch I disabled &amp;ldquo;Include search engine suggestions&amp;rdquo;. The magic button. This makes opening new tabs fast again.&lt;/p>
&lt;p>&lt;img
src="https://www.devroom.io/img/safari-slow-new-tab-fix.png"
alt=""
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;p>The reason why this fixes the issue is beyond me, unless the new tab automatically starts searching for suggestions even though it&amp;rsquo;s showing an empty page.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2015/12/04/el-capitan-safari-new-tab-slow-fix/</guid><pubDate>Fri, 04 Dec 2015 00:00:00 +0000</pubDate></item><item><title>Rails generate model: be specific</title><link>https://www.devroom.io/2015/11/27/rails-generate-model-be-specific/</link><description>&lt;p>Rails can generate a lot of things for you. Personally I use &lt;code>generate model&lt;/code> often to quickly setup a new model, including test files and a database migration.
In its simplest form it looks like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">rails generate product
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This will get you started with a &lt;em>naked&lt;/em> &lt;code>Product&lt;/code> model. To make things easier, you can also supply attribute names:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">rails generate product name description:text
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Optionally, you can tell the generator what &lt;em>type&lt;/em> of attribute you want. You can choose from the same types as you&amp;rsquo;d normally use in your migration:&lt;/p>
&lt;ul>
&lt;li>&lt;code>integer&lt;/code>&lt;/li>
&lt;li>&lt;code>primary_key&lt;/code>&lt;/li>
&lt;li>&lt;code>decimal&lt;/code>&lt;/li>
&lt;li>&lt;code>float&lt;/code>&lt;/li>
&lt;li>&lt;code>boolean&lt;/code>&lt;/li>
&lt;li>&lt;code>binary&lt;/code>&lt;/li>
&lt;li>&lt;code>string&lt;/code>&lt;/li>
&lt;li>&lt;code>text&lt;/code>&lt;/li>
&lt;li>&lt;code>date&lt;/code>&lt;/li>
&lt;li>&lt;code>time&lt;/code>&lt;/li>
&lt;li>&lt;code>datetime&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>Now, that&amp;rsquo;s not where it ends. Here are some more useful tricks:&lt;/p>
&lt;h3 id="associations">Associations&lt;/h3>
&lt;p>You can specify that your model &lt;em>references&lt;/em> another. Our &lt;code>Product&lt;/code> might &lt;em>belong_to&lt;/em> a &lt;code>Category&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">rails generate product category:references
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This generates the &lt;code>category_id&lt;/code> attribute automatically. In some cases you might want to use a &lt;em>polymorphic&lt;/em> relation instead, no problem:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">rails generate product supplier:references&lt;span class="o">{&lt;/span>polymorphic&lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="limits">Limits&lt;/h3>
&lt;p>For &lt;code>string&lt;/code>, &lt;code>text&lt;/code>, &lt;code>integer&lt;/code> and &lt;code>binary&lt;/code> fields, you can set the limit by supplying it in curly braces:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">rails generate product sku:string&lt;span class="o">{&lt;/span>12&lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>For &lt;code>decimal&lt;/code> you must supply two values for precision and scale:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">rails generate product price:decimal&lt;span class="o">{&lt;/span>10,2&lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="indices--indexes">Indices / Indexes&lt;/h3>
&lt;p>You can also set indices (unique or not) on specific fields as well:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">rails generate product name:string:index
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">rails generate product sku:string&lt;span class="o">{&lt;/span>12&lt;span class="o">}&lt;/span>:uniq
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">rails generate product supplier:references&lt;span class="o">{&lt;/span>polymorphic&lt;span class="o">}&lt;/span>:index
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="password-digests-and-tokens">Password digests and tokens&lt;/h3>
&lt;p>If you want to store password digests using &lt;code>has_secure_password&lt;/code>, you can also use the &lt;code>digest&lt;/code> type.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">rails generate user password:digest
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>And the same goes for &lt;code>has_secure_token&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">rails generate user auth_token:token
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>That&amp;rsquo;s it. Happy generating.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2015/11/27/rails-generate-model-be-specific/</guid><pubDate>Fri, 27 Nov 2015 00:00:00 +0000</pubDate></item><item><title>Building Golang CLI Tools Update</title><link>https://www.devroom.io/2015/10/12/building-golang-cli-tools-update/</link><description>&lt;p>In my &lt;a href="https://ariejan.net/2015/10/03/a-makefile-for-golang-cli-tools/">previous post&lt;/a> I discussed how to use a &lt;code>Makefile&lt;/code> to set version and
build information at compile time. Although this approach may work fine for you, it has
three drawbacks I want to discuss.&lt;/p>
&lt;h3 id="1-simplicity">1. Simplicity&lt;/h3>
&lt;p>Andrew responded on the &lt;a href="https://groups.google.com/forum/#!forum/golang-nuts">golang-nuts mailing list&lt;/a> with the following comment:&lt;/p>
&lt;blockquote>
&lt;p>To me it seems like you took something simple and cross platform &amp;ldquo;go generate&amp;rdquo; + &amp;ldquo;go install/build&amp;rdquo; and turned it into something more complicated and less portable.&lt;/p>
&lt;/blockquote>
&lt;p>Although I&amp;rsquo;m not sure &lt;code>go generate&lt;/code> is relevant in this case, I agree that on some level
a &lt;code>Makefile&lt;/code> is complicating things unnecessarily. Let&amp;rsquo;s remove it!&lt;/p>
&lt;h3 id="2-non-reproducable-builds">2. Non-reproducable builds&lt;/h3>
&lt;p>Guilio responded with:&lt;/p>
&lt;blockquote>
&lt;p>I only have an issue with buildTime: it makes the build not reproducible.&lt;/p>
&lt;/blockquote>
&lt;p>This is a valid point. The idea that if you compile a given version of your application,
the resulting binary&amp;rsquo;s hash (be it MD5 or whatever) should be equal to that of any other
binary build from that specific version.&lt;/p>
&lt;p>By using &lt;code>BuildTime&lt;/code> the binary is never the same.&lt;/p>
&lt;p>Build time is also irrelevant. It does not matter &lt;em>when&lt;/em> a binary was compiled, but it
&lt;em>does matter&lt;/em> which precise version was build.&lt;/p>
&lt;p>Let&amp;rsquo;s replace &lt;code>BuildTime&lt;/code> with the current git commit hash instead.&lt;/p>
&lt;h3 id="3-why-is-there-a-version-file">3. Why is there a &lt;code>VERSION&lt;/code> file?&lt;/h3>
&lt;p>If you&amp;rsquo;re going to store version information under version control, you might as well
put it right in the code, where it belongs.&lt;/p>
&lt;p>Let&amp;rsquo;s remove &lt;code>VERSION&lt;/code> and instead create a nice &lt;code>version.go&lt;/code> to handle everything.&lt;/p>
&lt;h2 id="lets-refactor">Let&amp;rsquo;s refactor&lt;/h2>
&lt;p>Okay, first things to go are &lt;code>Makefile&lt;/code> and &lt;code>VERSION&lt;/code>.&lt;/p>
&lt;p>Next, I created a &lt;code>core/version.go&lt;/code> which contains the necessary version information.
I&amp;rsquo;ve also taken the liberty of creating a proper struct for the version information,
including major, minor and patch numbers, as well as a label and release name.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">package&lt;/span> &lt;span class="nx">core&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="s">&amp;#34;fmt&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">type&lt;/span> &lt;span class="nx">version&lt;/span> &lt;span class="kd">struct&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Major&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">Minor&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">Patch&lt;/span> &lt;span class="kt">int&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Label&lt;/span> &lt;span class="kt">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Name&lt;/span> &lt;span class="kt">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">var&lt;/span> &lt;span class="nx">Version&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">version&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;dev&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;Chuck Norris&amp;#34;&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">var&lt;/span> &lt;span class="nx">Build&lt;/span> &lt;span class="kt">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">v&lt;/span> &lt;span class="nx">version&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">String&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">v&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Label&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="s">&amp;#34;&amp;#34;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Sprintf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Roll version %d.%d.%d-%s \&amp;#34;%s\&amp;#34;\nGit commit hash: %s&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">v&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Major&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">v&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Minor&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">v&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Patch&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">v&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Label&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">v&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">Build&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Sprintf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Roll version %d.%d.%d \&amp;#34;%s\&amp;#34;\nGit commit hash: %s&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">v&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Major&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">v&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Minor&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">v&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Patch&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">v&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">Build&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>As you can see, it&amp;rsquo;s quite easy to set and update the version numbers, label and release name.&lt;/p>
&lt;p>&lt;code>Build&lt;/code> is still set at compile time and contains the current git commit hash.&lt;/p>
&lt;p>Because the &lt;code>go build&lt;/code> command is quite long, I&amp;rsquo;ve put it in a nice &lt;code>build.sh&lt;/code> file that
makes building easier.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">go build -ldflags &lt;span class="s2">&amp;#34;-X github.com/ariejan/roll/core.Build=`git rev-parse HEAD`&amp;#34;&lt;/span> -o roll main.go
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This will result in a build that reports version information like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">$ ./roll version
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Roll version 1.2.3-dev &lt;span class="s2">&amp;#34;Chuck Norris&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Git commit hash: b72b076af8b18ef4f6b10296f12840f23258acec
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="check-that-sha">Check that SHA&lt;/h3>
&lt;p>If you want, you can &lt;a href="https://github.com/ariejan/roll">grab the code&lt;/a> and run &lt;code>./build.sh&lt;/code> yourself. The resulting binary
has a SHA-1 of &lt;code>3ad7509279690d99e4144332dc200ede732663fd&lt;/code>. Yay for reproducable builds!&lt;/p>
&lt;h3 id="naming-variables-in-ldlags">Naming variables in ldlags&lt;/h3>
&lt;p>A short note on Peter Kleiweg&amp;rsquo;s comment. He pointed out that I could use&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">&lt;span class="s2">&amp;#34;-X main.Build=`git rev-parse HEAD`&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This would be true if the &lt;code>Build&lt;/code> variable is in the &lt;code>main&lt;/code> package. But because it&amp;rsquo;s
not (it&amp;rsquo;s in &lt;code>core&lt;/code>) I have to specify the full package name.&lt;/p>
&lt;h2 id="thank-you">Thank you!&lt;/h2>
&lt;p>Thanks to all the awesome gophers responding to my previous post! It&amp;rsquo;s great to get
feedback and get to learn more about Golang. Keep the comments coming, please!&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2015/10/12/building-golang-cli-tools-update/</guid><pubDate>Mon, 12 Oct 2015 00:00:00 +0000</pubDate></item><item><title>A makefile for Golang CLI tools</title><link>https://www.devroom.io/2015/10/03/a-makefile-for-golang-cli-tools/</link><description>&lt;p>&lt;em>Note: I&amp;rsquo;ve received feedback on this post and written an update, which you can read &lt;a href="https://ariejan.net/2015/10/12/building-golang-cli-tools-update/">here&lt;/a>&lt;/em>&lt;/p>
&lt;p>It&amp;rsquo;s no secret I love the power and simplicity of Go. To further train my skills I wrote a simple app that will roll dice from the
command line, because you know, that&amp;rsquo;s very useful.&lt;/p>
&lt;p>There are two goals for me in this project right now: make it trivial to use compile time variables and have a &lt;code>Makefile&lt;/code> for easy
compilation, installation and clean up. I&amp;rsquo;m sure I&amp;rsquo;ll think of other features to try. These will get their own posts.&lt;/p>
&lt;h2 id="compile-time-variables">Compile time variables&lt;/h2>
&lt;p>The classic example for &lt;em>compile time variables&lt;/em> is setting a version number and build date for the binaries you compile. You could
manually edit code before each compile, because you would &lt;em>never&lt;/em> forget to do that.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">var&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Version&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#34;1.0.0&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">BuildTime&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#34;2015-10-03T11:08:49+0200&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Luckily there&amp;rsquo;s a nice alternative provided by Go: the &lt;code>link&lt;/code> &lt;a href="https://golang.org/cmd/link/">docs&lt;/a> command allows you to set
string variables at compile time with the &lt;code>-X&lt;/code> option. Let&amp;rsquo;s take a look at our code and build command.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">var&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Version&lt;/span> &lt;span class="kt">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">BuildTime&lt;/span> &lt;span class="kt">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Compilation would look like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">go build -ldflags &lt;span class="s2">&amp;#34;-X github.com/ariejan/roll/core.Version=1.0.0 -X github.com/ariejan/roll/core.BuildTime=2015-10-03T11:08:49+0200&amp;#34;&lt;/span> main.go
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;em>Note: this is the format used with Go 1.5.1, previous versions do not use the &lt;code>=&lt;/code> sign, instead separate the variable and value with a space.&lt;/em>&lt;/p>
&lt;h2 id="the-makefile">The Makefile&lt;/h2>
&lt;p>Makefiles have always been scary to me. Lot&amp;rsquo;s of magic and weird syntax and I&amp;rsquo;ve never had the need nor the desire to dive into them. As it turns out,
Makefiles can be very useful. Let&amp;rsquo;s start by building one for the &lt;code>roll&lt;/code> project.&lt;/p>
&lt;p>First, let&amp;rsquo;s start with the build command that passes in &lt;code>Version&lt;/code> and &lt;code>BuildTime&lt;/code> and refactor it so it becomes more managable and we can
easily set both variables to proper values.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-make" data-lang="make">&lt;span class="line">&lt;span class="cl">&lt;span class="c"># This is how we want to name the binary output
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">&lt;/span>&lt;span class="nv">BINARY&lt;/span>&lt;span class="o">=&lt;/span>roll
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c"># These are the values we want to pass for Version and BuildTime
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">&lt;/span>&lt;span class="nv">VERSION&lt;/span>&lt;span class="o">=&lt;/span>1.0.0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">BUILD_TIME&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="sb">`&lt;/span>date +%FT%T%z&lt;span class="sb">`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c"># Setup the -ldflags option for go build here, interpolate the variable values
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">&lt;/span>&lt;span class="nv">LDFLAGS&lt;/span>&lt;span class="o">=&lt;/span>-ldflags &lt;span class="s2">&amp;#34;-X github.com/ariejan/roll/core.Version=&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nv">VERSION&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> -X github.com/ariejan/roll/core.BuildTime=&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nv">BUILD_TIME&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nf">all&lt;/span>&lt;span class="o">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> go build &lt;span class="si">${&lt;/span>&lt;span class="nv">LDFLAGS&lt;/span>&lt;span class="si">}&lt;/span> -o &lt;span class="si">${&lt;/span>&lt;span class="nv">BINARY&lt;/span>&lt;span class="si">}&lt;/span> main.go
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now, if you run &lt;code>make all&lt;/code> your binary will be compiled with the proper variables passed in. There are a few caveats here, though.&lt;/p>
&lt;p>Each Make target (we only named &lt;code>all&lt;/code>) will check for an output file named &lt;code>all&lt;/code> and decide if it needs compiling or not. This is
how Make can speed up large builds - by not compiling things that don&amp;rsquo;t need compiling. In the case of our project I want to make two
changes: properly name our target after the binary we create and make sure we only re-compile if any of the Go files have changed.&lt;/p>
&lt;p>First, let&amp;rsquo;s rename our build target to the name of our binary. Also, set it as the default target and make sure &lt;code>make all&lt;/code> will also
compile the binary for us.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-make" data-lang="make">&lt;span class="line">&lt;span class="cl">&lt;span class="nf">.DEFAULT_GOAL&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="k">$(&lt;/span>&lt;span class="nv">BINARY&lt;/span>&lt;span class="k">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nf">$(BINARY)&lt;/span>&lt;span class="o">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> go build &lt;span class="si">${&lt;/span>&lt;span class="nv">LDFLAGS&lt;/span>&lt;span class="si">}&lt;/span> -o &lt;span class="si">${&lt;/span>&lt;span class="nv">BINARY&lt;/span>&lt;span class="si">}&lt;/span> main.go
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Next let&amp;rsquo;s get a list of all go source files we want Make to watch, for this we&amp;rsquo;ll rely on &lt;code>find&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-make" data-lang="make">&lt;span class="line">&lt;span class="cl">&lt;span class="nv">SOURCEDIR&lt;/span>&lt;span class="o">=&lt;/span>.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">SOURCES&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="k">$(&lt;/span>shell find &lt;span class="k">$(&lt;/span>SOURCEDIR&lt;span class="k">)&lt;/span> -name &lt;span class="s1">&amp;#39;*.go&amp;#39;&lt;/span>&lt;span class="k">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Remember how each Make target corresponds to a file on disk? Make will check if that target file exists or was changed. This means we can add the list
of source go files as dependencies to the build task. If any of the source files were changed, make will re-run the task.&lt;/p>
&lt;p>This is the Makefile so far:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-make" data-lang="make">&lt;span class="line">&lt;span class="cl">&lt;span class="nv">SOURCEDIR&lt;/span>&lt;span class="o">=&lt;/span>.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">SOURCES&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="k">$(&lt;/span>shell find &lt;span class="k">$(&lt;/span>SOURCEDIR&lt;span class="k">)&lt;/span> -name &lt;span class="s1">&amp;#39;*.go&amp;#39;&lt;/span>&lt;span class="k">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">BINARY&lt;/span>&lt;span class="o">=&lt;/span>roll
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">VERSION&lt;/span>&lt;span class="o">=&lt;/span>1.0.0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">BUILD_TIME&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="sb">`&lt;/span>date +%FT%T%z&lt;span class="sb">`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">LDFLAGS&lt;/span>&lt;span class="o">=&lt;/span>-ldflags &lt;span class="s2">&amp;#34;-X github.com/ariejan/roll/core.Version=&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nv">VERSION&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> -X github.com/ariejan/roll/core.BuildTime=&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nv">BUILD_TIME&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nf">.DEFAULT_GOAL&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="k">$(&lt;/span>&lt;span class="nv">BINARY&lt;/span>&lt;span class="k">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nf">$(BINARY)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="k">$(&lt;/span>&lt;span class="nv">SOURCES&lt;/span>&lt;span class="k">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> go build &lt;span class="si">${&lt;/span>&lt;span class="nv">LDFLAGS&lt;/span>&lt;span class="si">}&lt;/span> -o &lt;span class="si">${&lt;/span>&lt;span class="nv">BINARY&lt;/span>&lt;span class="si">}&lt;/span> main.go
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>For fun we can add two more tasks: &lt;code>install&lt;/code> and &lt;code>clean&lt;/code>. Because both of these do not result in a file in our repository (like the &lt;code>build&lt;/code> command), we
mark these targets with &lt;code>.PHONY&lt;/code>, telling Make not to expect a file to appear.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-make" data-lang="make">&lt;span class="line">&lt;span class="cl">&lt;span class="nv">SOURCEDIR&lt;/span>&lt;span class="o">=&lt;/span>.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">SOURCES&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="k">$(&lt;/span>shell find &lt;span class="k">$(&lt;/span>SOURCEDIR&lt;span class="k">)&lt;/span> -name &lt;span class="s1">&amp;#39;*.go&amp;#39;&lt;/span>&lt;span class="k">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">BINARY&lt;/span>&lt;span class="o">=&lt;/span>roll
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">VERSION&lt;/span>&lt;span class="o">=&lt;/span>1.0.0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">BUILD_TIME&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="sb">`&lt;/span>date +%FT%T%z&lt;span class="sb">`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">LDFLAGS&lt;/span>&lt;span class="o">=&lt;/span>-ldflags &lt;span class="s2">&amp;#34;-X github.com/ariejan/roll/core.Version=&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nv">VERSION&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> -X github.com/ariejan/roll/core.BuildTime=&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nv">BUILD_TIME&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nf">.DEFAULT_GOAL&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="k">$(&lt;/span>&lt;span class="nv">BINARY&lt;/span>&lt;span class="k">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nf">$(BINARY)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="k">$(&lt;/span>&lt;span class="nv">SOURCES&lt;/span>&lt;span class="k">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> go build &lt;span class="si">${&lt;/span>&lt;span class="nv">LDFLAGS&lt;/span>&lt;span class="si">}&lt;/span> -o &lt;span class="si">${&lt;/span>&lt;span class="nv">BINARY&lt;/span>&lt;span class="si">}&lt;/span> main.go
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nf">.PHONY&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="n">install&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nf">install&lt;/span>&lt;span class="o">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> go install &lt;span class="si">${&lt;/span>&lt;span class="nv">LDFLAGS&lt;/span>&lt;span class="si">}&lt;/span> ./...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nf">.PHONY&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="n">clean&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nf">clean&lt;/span>&lt;span class="o">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="o">[&lt;/span> -f &lt;span class="si">${&lt;/span>&lt;span class="nv">BINARY&lt;/span>&lt;span class="si">}&lt;/span> &lt;span class="o">]&lt;/span> &lt;span class="p">;&lt;/span> &lt;span class="k">then&lt;/span> rm &lt;span class="si">${&lt;/span>&lt;span class="nv">BINARY&lt;/span>&lt;span class="si">}&lt;/span> &lt;span class="p">;&lt;/span> &lt;span class="k">fi&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>It&amp;rsquo;s a basic &lt;code>Makefile&lt;/code> that makes compiling your Golang command line tools a whole lot easier. Enjoy, and stay tuned for more
posts on Golang and Makefiles.&lt;/p>
&lt;p>You can find the code for Roll at &lt;a href="https://github.com/ariejan/roll">https://github.com/ariejan/roll&lt;/a>.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2015/10/03/a-makefile-for-golang-cli-tools/</guid><pubDate>Sat, 03 Oct 2015 00:00:00 +0000</pubDate></item><item><title>Postgresq error: type 'hstore' does not exist</title><link>https://www.devroom.io/2015/08/05/postgresql-error-type-hstore-does-not-exist/</link><description>&lt;p>Today I was programming and messed up my database schema in such a way that I just wanted to restore a recent backup and start over from there.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">dropdb app_development
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">createdb app_development
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Unfortunately I ran into an error:&lt;/p>
&lt;blockquote>
&lt;p>PostgreSQL error: type &amp;lsquo;hstore&amp;rsquo; does not exist&lt;/p>
&lt;/blockquote>
&lt;p>hstore is a Postgresql extension (available since postgresql-9.x) that allows you store sets of key/value pairs within a single Postgresql value (read column). Read more on hstore in the &lt;a href="http://www.postgresql.org/docs/9.0/static/hstore.html">Postgresql documentation&lt;/a>.&lt;/p>
&lt;p>Anyway, the hstore extension is not loaded for my brand new database. No problem, simply add it with this command:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">psql app_development -c &lt;span class="s1">&amp;#39;create extension hstore;&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>That&amp;rsquo;s all.&lt;/p>
&lt;h2 id="bonus-points">Bonus points&lt;/h2>
&lt;p>If you use hstore for almost any database, you could add it to the &lt;code>template1&lt;/code> database.&lt;/p>
&lt;p>FYI: Postgresql creates a new databases by copying another. &lt;code>template1&lt;/code> is a default database provided by Postgresql for the purpose of creating new database.&lt;/p>
&lt;p>Adding hstore to &lt;code>template1&lt;/code> is just as easy as adding it to your own database.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">psql template1 -c &lt;span class="s1">&amp;#39;create extension hstore;&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Whenever your use &lt;code>createdb&lt;/code> now, the new database will have the hstore extension by default.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2015/08/05/postgresql-error-type-hstore-does-not-exist/</guid><pubDate>Wed, 05 Aug 2015 00:00:00 +0000</pubDate></item><item><title>Testing with MiniTest</title><link>https://www.devroom.io/2015/04/07/testing-with-minitest/</link><description>&lt;p>Ever since I started doing TDD I&amp;rsquo;ve used &lt;a href="http://rspec.info/">RSpec&lt;/a>. It&amp;rsquo;s a great tool, and for a long
time it was part of my standard testing stack. This stack also contains things
like &lt;a href="https://cukes.info/">Cucumber&lt;/a> and &lt;a href="https://github.com/thoughtbot/factory_girl">FactoryGirl&lt;/a>.&lt;/p>
&lt;p>Now, this stack works great. But that doesn&amp;rsquo;t mean it&amp;rsquo;s the best, it has its issues:&lt;/p>
&lt;h2 id="cucumber">Cucumber&lt;/h2>
&lt;p>Cucumber is, in almost every project, added complexity without any benefit. The idea
behind cucumber is that it allows you to write your features / user stories in
plain English and prove that those features are functional. The process of
writing plain English, and using a boat load of &lt;em>steps&lt;/em> (regular expressions, really) to
convert that to executable code is tedious. Unless those cucumber features actually
find their way to a customer, I don&amp;rsquo;t see any added value in writing them.
Developers know how to write code, why not omit the entire English-to-code conversion
entirely.&lt;/p>
&lt;h2 id="factorygirl">FactoryGirl&lt;/h2>
&lt;p>Factory Girl is a great way of generating object to work with. Unfortunately, more
often than not, these generated object are ActiveRecord models. When creating those,
you will most likely hit the database. For simple apps that isn&amp;rsquo;t as much of a problem.
When an application grows, models become dependent on each other. You can&amp;rsquo;t have an order
without a customer, with a valid address and now without order lines and a valid products
with sufficient stock. You could mock all these, but especially with integration and feature
tests, you want the whole thing.&lt;/p>
&lt;h2 id="rspec">RSpec&lt;/h2>
&lt;p>The first thing I fell in love with with RSpec was its clean syntax. The RSpec DSL allows
you to write your specs in such away the are very readable. This is how it&amp;rsquo;s suppost
to look:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="n">describe&lt;/span> &lt;span class="no">MyClass&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">context&lt;/span> &lt;span class="s2">&amp;#34;with a valid email&amp;#34;&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">let&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="ss">:email&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="s2">&amp;#34;john@example.com&amp;#34;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">subject&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="no">MyClass&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">new&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="ss">email&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">email&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">it&lt;/span> &lt;span class="s2">&amp;#34;reports number of mails sent&amp;#34;&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">expect&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">subject&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">email!&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">to&lt;/span> &lt;span class="n">eq&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>But when an application grows, so do the number of specs. And the complexity of the
boilerplate needed to set the stage for your test. I&amp;rsquo;ve seen spec files over over
2k lines where a spec on line 1982 gets setup by &lt;code>let&lt;/code> statements and &lt;code>before&lt;/code> blocks
spread over all the preceding 1981 lines. Yes, this can be resolved to an extend
by refactoring the test, splitting it up and what not. but still, it&amp;rsquo;s very
difficult to read a spec and know what it&amp;rsquo;s actually doing.&lt;/p>
&lt;h2 id="whats-the-alternative">What&amp;rsquo;s the alternative?&lt;/h2>
&lt;p>Curiosity drove me to investigate other testing frameworks and soon I discovered
Minitest. What I love about minitest is the lack of DSL (unless you use minitest/spec,
of course), it also feels much faster when running tests, but I have not run any
benchmarks to support that feeling.&lt;/p>
&lt;p>Here&amp;rsquo;s how I&amp;rsquo;d write that same test in Minitest:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">MyClassTest&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="no">MiniTest&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="no">Unit&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="no">TestCase&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">test_reports_number_of_mails_sent&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">assert_equal&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="no">MyClass&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">new&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="ss">email&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;john@example.com&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">email!&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Note that everything needed to perform this test is contained in that single
test method. You could do the same with RSpec, but it&amp;rsquo;s DSL seems to prefer
another convention.&lt;/p>
&lt;h2 id="assert-like-whatever">Assert, like, whatever&lt;/h2>
&lt;p>What I truely &lt;em>love&lt;/em> about Minitest are the simple &lt;code>assert&lt;/code> methods. In essence,
that&amp;rsquo;s all you need to know about Minitest: &lt;code>assert&lt;/code>.&lt;/p>
&lt;p>Did you ever see this in Rspec:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="n">expect&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">my_car&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">to&lt;/span> &lt;span class="n">be_drivable&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>There&amp;rsquo;s quite a lot going on here. Out of nowhere you have a &lt;code>my_car&lt;/code> instance,
which was probably declared in a &lt;code>let&lt;/code> somewhere in this file. You could assume
it&amp;rsquo;s of the type &lt;code>Car&lt;/code>, but who knows. You also don&amp;rsquo;t know what else was done
to this instance in some &lt;code>before&lt;/code> block. Furthermore, this line assumes that the
&lt;code>my_car&lt;/code> object responds to a method named &lt;code>driveable?&lt;/code>. Yes, &lt;em>with&lt;/em> a questionmark.
There is nothing explicit about what&amp;rsquo;s going on here. If you&amp;rsquo;re a novice Ruby
developer, you will probably have some difficulty figuring out what&amp;rsquo;s going on here.
If you&amp;rsquo;re an experience Ruby developer, you will as well.&lt;/p>
&lt;p>Consider the following Minitest alternative:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="n">my_car&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">Car&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">new&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="ss">wheels&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">4&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">my_car&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">fuel!&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="ss">type&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="ss">:diesel&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">litres&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">60&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">assert&lt;/span> &lt;span class="n">my_car&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">driveable?&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This is plain Ruby. If you know Ruby, you understand what&amp;rsquo;s going on here. After all,
your tests are meant to drive your internal design. Well, here it is, in all it&amp;rsquo;s glory.&lt;/p>
&lt;p>The only method you need to know, really, is &lt;code>assert&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="n">assert&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">test&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">msg&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kp">nil&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>You supply assert with a &lt;code>test&lt;/code> value. When true, the test passes. When false, it doesn&amp;rsquo;t.&lt;/p>
&lt;p>There are quite a few other &lt;code>assert*&lt;/code> methods to help you do common assertions. You don&amp;rsquo;t have
to use them, as the can all very easily be written with a common &lt;code>assert&lt;/code>. They are only there
for convience. The ones I use most are:&lt;/p>
&lt;ul>
&lt;li>&lt;code>assert_empty&lt;/code> - assert the supplied argument is empty&lt;/li>
&lt;li>&lt;code>assert_nil&lt;/code> - assert the supplied argument is &lt;code>nil&lt;/code>&lt;/li>
&lt;li>&lt;code>assert_equal&lt;/code> - assert the supplied arguments are equal (using &lt;code>==&lt;/code>)&lt;/li>
&lt;li>&lt;code>assert_match&lt;/code> - assert the supplied regex mathes with something else&lt;/li>
&lt;li>&lt;code>assert_includes&lt;/code> - assert the supplied collections includes a certain object&lt;/li>
&lt;/ul>
&lt;p>Then, for every &lt;code>assert&lt;/code> method, there&amp;rsquo;s also a &lt;code>refute&lt;/code> method that fail when true (instead
of passing). It is &lt;em>that&lt;/em> simple. You can read more about these in
the &lt;a href="http://docs.seattlerb.org/minitest/Minitest/Assertions.html">Minitest::Assertions&lt;/a> documentation.&lt;/p>
&lt;h2 id="fixtures-or-fixture-replacements">Fixtures or Fixture Replacements&lt;/h2>
&lt;p>Fixtures are nothing more than a pre-defined set of data to run your tests against. In
the case of Rails fixtures are mostly data that go into the database.
They get loaded once and each test is run inside a database transaction on that dataset.&lt;/p>
&lt;p>FactoryGirl and Machinist are &lt;em>fixture replacements&lt;/em> in the sense that you define
how your data should look and then generate what you need for each test.&lt;/p>
&lt;p>Using fixtures has two major benefits as opposed to FactoryGirl:&lt;/p>
&lt;ol>
&lt;li>All data is loaded only once, before the test suite runs. With FactoryGirl it&amp;rsquo;s not
uncommon that, for a specific test file, you need to create 10-20 records in the database,
everytime, for 20 specs. This soon adds up an is mostly what makes test suites slow.&lt;/li>
&lt;li>You test against a database full of data. This may sound strange, but with time you&amp;rsquo;ll
create an extensive database of data used for testing. It&amp;rsquo;s easy to add (data-wise) edge
cases and see how they behave in your application.&lt;/li>
&lt;/ol>
&lt;h2 id="capybara">Capybara&lt;/h2>
&lt;p>I already mentioned Cucumber for feature testing. Under the hood, most of the time, cucumber
will be using capybara to emulate a client browser. Minitest can handle this as well and it
works just as you&amp;rsquo;d expect:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">TestCarCRUD&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">test_branded_car_listing&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">visit&lt;/span> &lt;span class="s2">&amp;#34;/cars/toyota&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">within&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;#cars&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># &amp;#34;Toyota Corolla Verso&amp;#34; from fixtures&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">assert&lt;/span> &lt;span class="n">page&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">has_content?&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">cars&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="ss">:toyota_corolla&lt;/span>&lt;span class="o">].&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The &lt;a href="https://rubygems.org/gems/minitest-rails-capybara">minitest-rails-capybara&lt;/a> gem might be of help here to make
your setup easy.&lt;/p>
&lt;h2 id="getting-started-with-minitest">Getting started with Minitest&lt;/h2>
&lt;p>I don&amp;rsquo;t want to go into too much detail about using Minitest with Rails. It&amp;rsquo;s already
well documented elsewhere and basically all you need is the &lt;a href="https://rubygems.org/gems/minitest-rails">minitest-rails&lt;/a>
gem. For feature testing you&amp;rsquo;d also need &lt;a href="https://rubygems.org/gems/minitest-rails-capybara">minitest-rails-capybara&lt;/a>.&lt;/p>
&lt;h2 id="the-testing-silver-bullet">The Testing Silver Bullet&lt;/h2>
&lt;p>There probably isn&amp;rsquo;t a silver bullet when it comes to testing Rails applications. I love
Minitest for its ease of use, lightweightedness, explicitness, and the fact that&amp;rsquo;s just
Ruby. In combination with fixtures it&amp;rsquo;s possible to write a very fast test suite.&lt;/p>
&lt;p>I think that&amp;rsquo;s RSpec tries to do too much magic and DSL&amp;rsquo;ing which makes it much harder to
write clean tests. As a developer I see little benefit from using a specific DSL for writing
tests. Having paired up with a few junior developers, new to Ruby and Rails, they tend to
pickup Minitest tests much faster than RSpec and Cucumber.&lt;/p>
&lt;p>Be sure to give Minitest a try and let me know your thoughts!&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2015/04/07/testing-with-minitest/</guid><pubDate>Tue, 07 Apr 2015 00:00:00 +0000</pubDate></item><item><title>Why I dropped fish in favour of Zsh</title><link>https://www.devroom.io/2014/11/20/why-i-dropped-fish-in-favour-of-zsh/</link><description>&lt;p>Developers are religious about two things: their editor and their shell.&lt;/p>
&lt;p>After getting multiple recommendations from friends and co-workers I
gave &lt;a href="http://fishshell.com">fish&lt;/a> a try. It lists several benefits over other shells, among
other there&amp;rsquo;s autosuggetions, colour support and web based configuration.&lt;/p>
&lt;p>Installing it on a Mac is easy with &lt;a href="http://brew.sh">Homebrew&lt;/a>, so I figured why not
give it a fair try.&lt;/p>
&lt;p>And yes, fish has its benefits. I used the web based configurator. Once.
The only feature that really stood out to me was the autosuggestions and
more notably the fact that fish will suggest commonly used commands based
on the directory you&amp;rsquo;re currently in.&lt;/p>
&lt;p>Still, the most annoying thing is that fish is not POSIX compatible.
Although I&amp;rsquo;m not a purist that insists on POSIX compatibility, it imposed
some practical limitations.&lt;/p>
&lt;h2 id="environment-variables">Environment variables&lt;/h2>
&lt;p>The first and most annoying thing is environment variables. I&amp;rsquo;m not talking
about the globally set variables, but those you use to run a command once.&lt;/p>
&lt;p>Normally I&amp;rsquo;d do something like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">&lt;span class="nv">TIME_COP&lt;/span>&lt;span class="o">=&lt;/span>2016-02-24 rake app:send_daily_notifications
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Well, that doesn&amp;rsquo;t work in fish as it needs you to explicitly run the &lt;code>env&lt;/code> command:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">env &lt;span class="nv">TIME_COP&lt;/span>&lt;span class="o">=&lt;/span>2016-02-24 rake app:send_daily_notifications
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>It&amp;rsquo;s a subtle difference, but if you&amp;rsquo;re used to the old syntax and have
quite some documentation with snippets like this, it&amp;rsquo;s quite a nuisance.&lt;/p>
&lt;h2 id="-vs-">&amp;amp;&amp;amp; vs &amp;amp;&lt;/h2>
&lt;p>Running a second command conditionally is a construct I use often:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">./analyse_data.rb &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> rake app:send_daily_notifications
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>If &lt;code>./analyse_data.rb&lt;/code> fails (e..g it has a non-zero return value), then
the supplied rake task will not be executed. Again, this is something I
commonly use.&lt;/p>
&lt;p>Fish on the other hand uses a single &lt;code>&amp;amp;&lt;/code>, the &lt;code>&amp;amp;&amp;amp;&lt;/code> operation is invalid.&lt;/p>
&lt;h2 id="incompatibility-with-vim">Incompatibility with Vim&lt;/h2>
&lt;p>You&amp;rsquo;ll need to give Vim some extra love and attention when you&amp;rsquo;re running
fish. Namely, you&amp;rsquo;ll need to explicitly tell Vim to &lt;em>not&lt;/em> use fish, but bash
instead.&lt;/p>
&lt;p>It&amp;rsquo;s a simple fix, but one that should not be necessary.&lt;/p>
&lt;h2 id="back-to-zsh">Back to Zsh&lt;/h2>
&lt;p>The &lt;em>issues&lt;/em> I have with fish are minimal and due to my own personal
preference. I think fish is a nice shell, but it just doesn&amp;rsquo;t fit me.&lt;/p>
&lt;p>So, back to Zsh! But no more Oh-My-Zsh-crap, please! More on how I&amp;rsquo;ve
setup Vim/Tmux/Zsh in a later post.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2014/11/20/why-i-dropped-fish-in-favour-of-zsh/</guid><pubDate>Thu, 20 Nov 2014 00:00:00 +0000</pubDate></item><item><title>Rails: Prevent Accidental Debugging Commits</title><link>https://www.devroom.io/2014/10/15/rails-prevent-accidental-debugging-commits/</link><description>&lt;p>Your Rails app has grown over time, multiple developers have worked on the code and you are about to make a small change in the code.&lt;/p>
&lt;p>Unfortunately this change applies to code that allows you to view a financial overview of last year’s data. It’s legally not allowed to expose the current year’s data, but the change you need to make applies only to 2014. We should shift time to 2015. Problem?&lt;/p>
&lt;p>There are several things you could do.&lt;/p>
&lt;ol>
&lt;li>Change your system date to 2015. This has the downside that many apps get confused, including, most likely, your backup tools.&lt;/li>
&lt;li>Use a gem like TimeCop to fake time. This works great for tests, but it feels a bit like cheating in development.&lt;/li>
&lt;li>Change &lt;code>@financial_year = 1.year.ago&lt;/code> to &lt;code>@financial_year = 2014&lt;/code>.&lt;/li>
&lt;/ol>
&lt;p>&lt;em>Side note: This feature gets tested by both unit and integration tests using TimeCop. The problem here is viewing and testing the change in my browser, prototyping the change and showing it to the customer.&lt;/em>&lt;/p>
&lt;p>Option 3 seems like the easiest. In the scope of the financial overview, let’s pretend we’re always viewing data for 2014.&lt;/p>
&lt;p>The real issue here is that you forget you made this change and commit it. Although there are several process in place to detect this: peer-review, QA-testing and custom acceptance. There still is a chance this change makes it to production. &lt;em>Whoops!&lt;/em>&lt;/p>
&lt;p>Because this is Rails there are lots of awesome goodies around, like &lt;code>rake notes&lt;/code>. It finds comments that start with &lt;code>OPTIMIZE&lt;/code>, &lt;code>FIXME&lt;/code> or &lt;code>TODO&lt;/code> and lists them for you. It’s not unlike what many IDE’s do you as well.&lt;/p>
&lt;p>Instead of this change:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-diff" data-lang="diff">&lt;span class="line">&lt;span class="cl">&lt;span class="gd">- @financial_year = 1.year.ago
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gd">&lt;/span>&lt;span class="gi">+ @financial_year = 2014
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>I could make a change like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-diff" data-lang="diff">&lt;span class="line">&lt;span class="cl">&lt;span class="gd">- @financial_year = 1.year.ago
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gd">&lt;/span>&lt;span class="gi">+ # TODO: Change this back to 1.year.ago!!!
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gi">+ @financial_year = 2014
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Then, before committing, I should run &lt;code>rake notes&lt;/code> or &lt;code>rake notes:todo&lt;/code> and check if there’s anything that needs to be reverted back. But I’m a programmer, dammit. I can automate this! I &lt;em>should&lt;/em> automate this!&lt;/p>
&lt;p>Using a combination of a git pre-commit hook and a custom annotation using Rails’ &lt;code>SourceAnnotationExtractor&lt;/code>, the core of &lt;code>rake notes&lt;/code>, it should be possible to simply reject a commit. Rejecting a commit for pending &lt;code>TODO&lt;/code>’s doesn’t seem useful. Instead, let’s use a custom annotation: &lt;code>NOCOMMIT&lt;/code>. The code change is like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-diff" data-lang="diff">&lt;span class="line">&lt;span class="cl">&lt;span class="gd">- @financial_year = 1.year.ago
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gd">&lt;/span>&lt;span class="gi">+ # NOCOMMIT: Change this back to 1.year.ago!!!
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gi">+ @financial_year = 2014
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>And with the following in &lt;code>.git/hooks/pre-commit&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#!/bin/sh
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">nocommit_notes&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="k">$(&lt;/span>&lt;span class="nv">ANNOTATION&lt;/span>&lt;span class="o">=&lt;/span>NOCOMMIT rake notes:custom&lt;span class="k">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="o">[[&lt;/span> -n &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$nocommit_notes&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> &lt;span class="o">]]&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">then&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">echo&lt;/span> &lt;span class="s2">&amp;#34;Error: You have NOCOMMIT notes in your code.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">echo&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">echo&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$nocommit_notes&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">exit&lt;/span> &lt;span class="m">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">fi&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Using the NOCOMMIT tag will prevent you from accidentally committing changes you do not want committed. This is an extension to other practices, like interactively staging your changes and reviewing what goes into a commit, code reviews and acceptance testing. It’s yet another safe guard agains my own stupidity.&lt;/p>
&lt;p>Check out &lt;a href="https://github.com/rails/rails/blob/master/railties/lib/rails/tasks/annotations.rake">railties/lib/rails/tasks/annotations.rake&lt;/a> for details on the &lt;code>rake notes&lt;/code> task.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2014/10/15/rails-prevent-accidental-debugging-commits/</guid><pubDate>Wed, 15 Oct 2014 00:00:00 +0000</pubDate></item><item><title>Synchronize goroutines in your tests</title><link>https://www.devroom.io/2014/08/29/synchronize-goroutines-in-your-tests/</link><description>&lt;p>I have been working on an &lt;a href="https://github.com/ariejan/i6502">emulator for the MOS 6502 Microprocessor, written in Go&lt;/a>. As
part of this package I have also implemented a minimal 6551 Asynchronous Communication Interface
Adapter. The 6551 provides serial IO and is easy to use in combination with the 6502.&lt;/p>
&lt;p>When the microprocessor writes a byte to the 6551 it is stored in the &lt;code>tx&lt;/code> (transmit) register
where it&amp;rsquo;s available for other hardware components to read.&lt;/p>
&lt;p>In order to make my emulator available using websockets (more on that in a later post), I had
to &lt;em>know&lt;/em> when a new byte became available. In a real hardware design I&amp;rsquo;d probably have to use
the \CTS (Clear to Send) input pin to signal the 6551 I&amp;rsquo;m ready to read the next byte of data.&lt;/p>
&lt;p>But, my goal is to emulate the &lt;em>Microprocessor&lt;/em> not a complete computer hardware design.o&lt;/p>
&lt;p>The logical option in Go would be to use a channel that receives new bytes when they are
written by the Microprocessor.&lt;/p>
&lt;p>I quickly devised the following:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">type&lt;/span> &lt;span class="nx">Acia6551&lt;/span> &lt;span class="kd">struct&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// ... ommitted for brevity
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">output&lt;/span> &lt;span class="kd">chan&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="kt">byte&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This would allow me to create an instance of type Acia6551 and supply it with an output
channel.&lt;/p>
&lt;p>The calling code would start a goroutine waiting for output on this channel.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="nx">output&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nb">make&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kd">chan&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="kt">byte&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">acia&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">Acia6551&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">output&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">output&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">go&lt;/span> &lt;span class="kd">func&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="nx">_&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">b&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="k">range&lt;/span> &lt;span class="o">&amp;lt;-&lt;/span>&lt;span class="nx">output&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Handle output byte
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}()&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Awesome. This worked very well in my websockets prototype.&lt;/p>
&lt;p>The problem is: how do you test this? How do you test that bytes written by the
microcontroller to the Acia are actually sent out to the output channel?&lt;/p>
&lt;p>The solution was to create a goroutine in the test and use another channel to synchronize
the writing of the byte with the output appearing on the output channel.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">TestAciaOutputChannel&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">t&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">testing&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">T&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">var&lt;/span> &lt;span class="nx">value&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="kt">byte&lt;/span> &lt;span class="c1">// Used to store the output value from the channel
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">output&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nb">make&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kd">chan&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="kt">byte&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">// The output channel
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">done&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nb">make&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kd">chan&lt;/span> &lt;span class="kt">bool&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">// Channel signaling we&amp;#39;re done waitign
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">acia&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">Acia6551&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">output&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">output&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Create a goroutine, waiting for data on the output channel,
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="c1">// and sending a signal when data arrived.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">go&lt;/span> &lt;span class="kd">func&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">value&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="o">&amp;lt;-&lt;/span>&lt;span class="nx">output&lt;/span> &lt;span class="c1">// Block, waiting for []byte
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">done&lt;/span> &lt;span class="o">&amp;lt;-&lt;/span> &lt;span class="kc">true&lt;/span> &lt;span class="c1">// We&amp;#39;re done!
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="p">}()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">acia&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">WriteByte&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mh">0x00&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mh">0x42&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">// Writes a single byte of data to the 6551
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">&amp;lt;-&lt;/span>&lt;span class="nx">done&lt;/span> &lt;span class="c1">// Wait for the output to be received and stored in `value`
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">value&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="mh">0x42&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">t&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Errorf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Expected output not received through output channel&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This is an example of simple synchronization using a seperate channel, in this case &lt;code>chan bool&lt;/code>.
It worked great in my tests to verify data was actually written to the output channel.&lt;/p>
&lt;p>&lt;a href="http://ariejan.github.io/i6502/">Read more about my i6502 project&lt;/a>&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2014/08/29/synchronize-goroutines-in-your-tests/</guid><pubDate>Fri, 29 Aug 2014 00:00:00 +0000</pubDate></item><item><title>GPG Sign Your Git Commits</title><link>https://www.devroom.io/2014/06/04/gpg-sign-your-git-commits/</link><description>&lt;p>I have &lt;a href="https://ariejan.net/2014/04/03/pretty-difficult-privacy/">written&lt;/a> and &lt;a href="https://ariejan.net/talks/">talked&lt;/a> before about GPG and the need for trust on the internet.&lt;/p>
&lt;p>Getting started with GPG and using it on a daily basis is, when you&amp;rsquo;re using the right tools, not all that hard, but still quite technical. Today &lt;a href="http://googleonlinesecurity.blogspot.nl/2014/06/making-end-to-end-encryption-easier-to.html">Google announced&lt;/a> they are working on a Chrome extention to enable end-to-end encryption using OpenPGP.&lt;/p>
&lt;p>As a developer, I do more than dispatching emails all day. On occasion I write code. And that code gets committed to a repository that will remember that commit forever.&lt;/p>
&lt;p>Just as with emails it is remarkably easy to fake your identity when committing code.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">git commit -a -m &lt;span class="s2">&amp;#34;Meh&amp;#34;&lt;/span> --author &lt;span class="s2">&amp;#34;Chuck Norris &amp;lt;chuck@example.com&amp;gt;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>In theory, this would allow anyone to commit (malicious) code under your name. Meaning that &lt;em>you&lt;/em>&amp;rsquo;ll get the blame for the back door &lt;em>you&lt;/em> committed.&lt;/p>
&lt;p>Git seems to offer a resolution by adding a &lt;code>Signed-off-by&lt;/code> field, to allow a second developer to sign off on code that gets merged into the project. But this field suffers from the same trust issues as the &lt;code>Author&lt;/code> field.&lt;/p>
&lt;p>&lt;strong>You cannot trust the git Author and Signed-off-by fields.&lt;/strong>&lt;/p>
&lt;p>Again, GPG offers a solution to the problem of trust. By establishing trust based on public keys, wouldn&amp;rsquo;t it be cool if you could sign a git commit just the same way you&amp;rsquo;d sign an email?&lt;/p>
&lt;p>Well, you can.&lt;/p>
&lt;h2 id="signing-commits">Signing commits&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">git commit -a -m &lt;span class="s2">&amp;#34;Cool new feature&amp;#34;&lt;/span> --gpg-sign&lt;span class="o">=&lt;/span>F713697B
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This will attach your signature to the git commit message, allowing others to validate your signature. Validating this signature is quite easy as well.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">$ git log --show-signature
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">commit 3d53a4be3f6f955007dc056347d926067bbfa8de
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">gpg: Signature made zo &lt;span class="m">1&lt;/span> jun 20:42:58 &lt;span class="m">2014&lt;/span> CEST using RSA key ID F713697B
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">gpg: Good signature from &lt;span class="s2">&amp;#34;Ariejan de Vroom &amp;lt;ariejan@ariejan.net&amp;gt;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Author: Ariejan de Vroom &amp;lt;ariejan@ariejan.net&amp;gt;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Date: Sun Jun &lt;span class="m">1&lt;/span> 20:42:58 &lt;span class="m">2014&lt;/span> +0200
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Any other developer (or your CI) can now validate your commit as coming from you, based on the trust they assigned to your public key.&lt;/p>
&lt;p>Optionally to using &lt;code>--gpg-sign&lt;/code> you can use &lt;code>-S&lt;/code>. If you don&amp;rsquo;t specify a specific key, git will try to figure out what key to use based on your email address. It&amp;rsquo;s also possible to set a default signing key globally.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">git config --global user.signingkey F713697B
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="signing-tags">Signing tags&lt;/h2>
&lt;p>Besides signing every commit you make, it&amp;rsquo;s also good idea to sign tags. That way you can be sure that the created tag was actually created by a trusted person.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">git tag -s 1.2.3 -m &lt;span class="s2">&amp;#34;Release 1.2.3 including bug fixes.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="signing-merges">Signing merges&lt;/h2>
&lt;p>If you are responsible for integrating features and bug fixes into the main branch of a project, you&amp;rsquo;d probably like to sign the merges you make. You have two options here.&lt;/p>
&lt;p>The first is to merge and manually commit a sign the merge.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">git merge feature-awesome --no-commit
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">git commit -m &lt;span class="s2">&amp;#34;Merge feature-awesome&amp;#34;&lt;/span> -S
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The second is merging and signing directly.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">git merge feature-awesome -S
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Be sure to check out &lt;a href="http://mikegerwitz.com/papers/git-horror-story">this in depth guide by Mike Gerwitz&lt;/a>.&lt;/p>
&lt;h2 id="where-to-go-from-here">Where to go from here?&lt;/h2>
&lt;p>Trust is a hard thing to come by on the internet and it really bites you when things go wrong. Just as with email, creating a web of trust can be helpful and someday save you from disaster.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2014/06/04/gpg-sign-your-git-commits/</guid><pubDate>Wed, 04 Jun 2014 00:00:00 +0000</pubDate></item><item><title>Testing $HOME with Cucumber and Aruba</title><link>https://www.devroom.io/2014/04/15/testing-home-with-cucumber-and-aruba/</link><description>&lt;p>&lt;a href="https://github.com/cucumber/cucumber">Cucumber&lt;/a> and &lt;a href="https://github.com/cucumber/aruba">Aruba&lt;/a> are awesome tools to write acceptance tests for your command line application. The allow you to do things like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-cucumber" data-lang="cucumber">&lt;span class="line">&lt;span class="cl">&lt;span class="k">Scenario:&lt;/span>&lt;span class="nf"> Exit with 0 when no examples are run
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nf">&lt;/span>&lt;span class="k"> Given &lt;/span>&lt;span class="nf">a file named &amp;#34;&lt;/span>&lt;span class="s">a_no_examples_spec.rb&lt;/span>&lt;span class="nf">&amp;#34; with:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nf"> &lt;/span>&lt;span class="k">&amp;#34;&amp;#34;&amp;#34;&lt;/span>&lt;span class="s">ruby
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> &lt;/span>&lt;span class="k">&amp;#34;&amp;#34;&amp;#34;&lt;/span>&lt;span class="nf">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nf"> &lt;/span>&lt;span class="k">When &lt;/span>&lt;span class="nf">I run `rspec a_no_examples_spec.rb`
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nf"> &lt;/span>&lt;span class="k">Then &lt;/span>&lt;span class="nf">the exit status should be &lt;/span>&lt;span class="s">0&lt;/span>&lt;span class="nf">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nf"> &lt;/span>&lt;span class="k">And &lt;/span>&lt;span class="nf">the output should contain &amp;#34;&lt;/span>&lt;span class="s">0 examples&lt;/span>&lt;span class="nf">&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;em>This example was taken from rspec-core.&lt;/em>&lt;/p>
&lt;p>Aruba basically does three things for you:&lt;/p>
&lt;ul>
&lt;li>Create and inspect files and directories&lt;/li>
&lt;li>Run commands&lt;/li>
&lt;li>Inspect output and exit status&lt;/li>
&lt;/ul>
&lt;h2 id="the-problem">The problem&lt;/h2>
&lt;p>Now, if you are writing a CLI that interacts with a configuration file in the user&amp;rsquo;s home directory, you&amp;rsquo;d write a cucumber like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-cucumber" data-lang="cucumber">&lt;span class="line">&lt;span class="cl">&lt;span class="k">Scenario:&lt;/span>&lt;span class="nf"> use .my-app configuration
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nf">&lt;/span>&lt;span class="k"> Given &lt;/span>&lt;span class="nf">a file named &amp;#34;&lt;/span>&lt;span class="s">~/.my-app&lt;/span>&lt;span class="nf">&amp;#34; with:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nf"> &lt;/span>&lt;span class="k">&amp;#34;&amp;#34;&amp;#34;&lt;/span>&lt;span class="s">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> awesome_enabled: true
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> &lt;/span>&lt;span class="k">&amp;#34;&amp;#34;&amp;#34;&lt;/span>&lt;span class="nf">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nf"> &lt;/span>&lt;span class="k">When &lt;/span>&lt;span class="nf">I run `my-app`
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nf"> &lt;/span>&lt;span class="k">Then &lt;/span>&lt;span class="nf">the output should contain &amp;#34;&lt;/span>&lt;span class="s">AWESOME&lt;/span>&lt;span class="nf">&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>But how does your app and Aruba differentiate between this generated test file and your actual configration file in your home directory? It doens&amp;rsquo;t.&lt;/p>
&lt;h2 id="the-answer">The answer&lt;/h2>
&lt;p>The solution is quite easy and elegant, in your &lt;code>Before&lt;/code> set the &lt;code>$HOME&lt;/code> environment variable to a custom location inside the &lt;code>tmp/aruba&lt;/code> directory.&lt;/p>
&lt;p>In &lt;code>features/support/env.rb&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="no">Before&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">set_env&lt;/span> &lt;span class="s1">&amp;#39;HOME&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="no">File&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">expand_path&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="no">File&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">join&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">current_dir&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;home&amp;#39;&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="no">FileUtils&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">mkdir_p&lt;/span> &lt;span class="no">ENV&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="s1">&amp;#39;HOME&amp;#39;&lt;/span>&lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This will set &lt;code>$HOME&lt;/code> to &lt;code>tmp/aruba/home&lt;/code> in the context of your cucumbers (and execute binary). &lt;code>current_dir&lt;/code> automatically points to the right location for the aruba temporary directory.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2014/04/15/testing-home-with-cucumber-and-aruba/</guid><pubDate>Tue, 15 Apr 2014 00:00:00 +0000</pubDate></item><item><title>Dealing With Technical Debt</title><link>https://www.devroom.io/2014/04/04/dealing-with-technical-debt/</link><description>&lt;p>The rails project I&amp;rsquo;m talking about is over three years old and has seen commits from 27 developers in that period. These developers were both co-workers, freelancers, off-shore developers and designers of different levels of expertise.&lt;/p>
&lt;h2 id="technical-debt-inventory">Technical Debt Inventory&lt;/h2>
&lt;p>Needless to say, as most projects of this size and age, this one has plenty of technical debt. Let&amp;rsquo;s make an inventory.&lt;/p>
&lt;img src="https://www.devroom.io/img/20140404-01.png" class="center bordered" />
&lt;p>&lt;strong>The test suite takes approximately eighty minutes to run.&lt;/strong> These are all RSpec tests, including features. Luckily we can split the entire suite up into smaller parts using Travis, but still the entire thing takes about forty minutes.&lt;/p>
&lt;p>The main cause for these slow specs is a lack of understanding about how to write &lt;em>good tests&lt;/em>. For example, testing if search and pagination works, someone thought it fine to create fifty ActiveRecord objects in a before block.&lt;/p>
&lt;p>These big offenders are easily remedied. Others are a bit harder, as some specs require an elaborate tree of models to test functionality.&lt;/p>
&lt;p>&lt;strong>This project has no cucumbers to discuss and review features with Product Owners.&lt;/strong> These cucumbers would have been awesome to discuss features both with product owners and team members. But at the start of the project cucumber was deemed &lt;em>too much hassle&lt;/em> and Rspec feature specs were used instead.&lt;/p>
&lt;p>The main issue now is that the line between features specs and unit specs is a thin one and people often mistake one for the other. This is probably one of the reasons so many specs create many models and make the entire suite slow.&lt;/p>
&lt;p>&lt;strong>There are parts of the code that are not well tested - or not at all.&lt;/strong> This is a dangerous one. Because, from the start, nobody monitored test coverage or did proper code reviews, there are patches of code that are not well tested, and there are some that are not tested at all.&lt;/p>
&lt;p>Pressure to finish a product has led to to cutting corners in code quality and test coverage.&lt;/p>
&lt;p>&lt;strong>Lots of smells and lots of noise.&lt;/strong> All the points above indicate there is a certain amount of technical debt in this project. Another pointer are &lt;em>code smells&lt;/em>. To name a few I&amp;rsquo;ve seen over the past few months:&lt;/p>
&lt;ul>
&lt;li>Long methods&lt;/li>
&lt;li>Conditional Complexity&lt;/li>
&lt;li>Data Clumps&lt;/li>
&lt;li>Alternative Classes With Different Interfaces&lt;/li>
&lt;li>Indecent Exposure&lt;/li>
&lt;li>Uncommunicative Names&lt;/li>
&lt;li>Divergent Change&lt;/li>
&lt;li>Shotgun Surgery&lt;/li>
&lt;li>Lazy Classes&lt;/li>
&lt;li>Inappropriate Intimacy&lt;/li>
&lt;li>Train Wrecks (or Message Chains)&lt;/li>
&lt;li>Feature Envy&lt;/li>
&lt;/ul>
&lt;p>All of these sneaked their way into the project.&lt;/p>
&lt;h2 id="why-does-this-matter">Why does this matter?&lt;/h2>
&lt;p>The product we ship works. Although the specs take a long time to run, and there are some code smells in there, the suite is actually green and we have confidence the product works as expected.&lt;/p>
&lt;p>So why complain about technical debt? In my opinion as a software craftsman, software should&lt;/p>
&lt;ol>
&lt;li>work&lt;/li>
&lt;li>be clean and readable&lt;/li>
&lt;li>allow for change&lt;/li>
&lt;li>be well tested&lt;/li>
&lt;/ol>
&lt;p>Well, the code works. But clean, readable, easy to change and well tested are debatable.&lt;/p>
&lt;p>Responding to change is still possible, but it&amp;rsquo;s a bit more painful than it should have been. There are some untested patches of code that we would really would like to see tested. And of course, code should be readable and easy to understand. We don&amp;rsquo;t always have these things and it&amp;rsquo;s holding back progress sometimes&lt;/p>
&lt;h2 id="so-you-have-some-technical-debt-on-your-hands">So, you have some Technical Debt on your hands&lt;/h2>
&lt;p>Recently two new developers joined this project and they immediately asked the right question:&lt;/p>
&lt;p>&lt;em>&lt;strong>What can we do to make this project better?&lt;/strong>&lt;/em>&lt;/p>
&lt;p>In my opinion, as a software developer, this is the one magic question. What can &lt;em>you&lt;/em> do, right now, to make this project &lt;em>better&lt;/em>.?&lt;/p>
&lt;p>Besides the fact that you ask yourself what you yourself can do, there&amp;rsquo;s another important component in that question: &lt;em>better&lt;/em>.&lt;/p>
&lt;p>It&amp;rsquo;s not about fixing all the things. It&amp;rsquo;s about improving the things you touch just a tiny bit. Over the course of a few weeks or months these small improvements start to add up and really make a difference.&lt;/p>
&lt;p>It&amp;rsquo;s often called the &lt;em>Boy Scout Mentality&lt;/em>. Leave things behind in a better state then how you found them when you arrived.&lt;/p>
&lt;p>So, although the projects works fine, we as developers are now constantly improving the code and specs we touch.&lt;/p>
&lt;h2 id="eliminating-technical-debt">Eliminating Technical Debt&lt;/h2>
&lt;p>Just like &lt;em>100% Test Coverage&lt;/em>, &lt;em>Low Technical Debt&lt;/em> is not and end goal, but
an ongoing mission.&lt;/p>
&lt;p>Your application will always have some form of technical debt. Even if you
have the best programmers in the world, it will happen.&lt;/p>
&lt;p>What you &lt;em>can&lt;/em> do is focus on minimizing technical debt.&lt;/p>
&lt;p>The team currently working on this project now has formulated a few
basic guide lines to help us bring down the technical debt of this project.
We are not going to do it over night, but in a few months time we will have
made quite a dent.&lt;/p>
&lt;h2 id="guidelines">Guidelines&lt;/h2>
&lt;p>The overall objective is this:&lt;/p>
&lt;p>&lt;em>&lt;strong>Whatever you do, leave it in a better shape than you found it in. This goes for code, documentation, specs, whatever.&lt;/strong>&lt;/em>&lt;/p>
&lt;p>When implementing features or changes, we use a few simple steps to help us along:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Is this code I&amp;rsquo;m about to change well tested?&lt;/strong>
If not, fix it now. Update those specs to be more efficient, handle the edge cases you see popping up right now. Write cucumbers.&lt;/li>
&lt;li>&lt;strong>With your improved specs green, refactor the existing code&lt;/strong>
Your new specs suite is fast and elegant. Use it to refactor the existing codebase to make change easier.&lt;/li>
&lt;/ul>
&lt;p>Congratulations, you have just made your life much easier. Note that you have not written a single line of code yet for you new feature or change. But the cleaning up you just did will greatly benefit you and the time it takes to write this feature.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Write specs for your change or feature&lt;/strong>
This is quite easy now, as it should add nicely to the changes you made in the first step.&lt;/li>
&lt;li>&lt;strong>Implement your features or change&lt;/strong>
Because you refactored most of the code smells out in the second step, you should be able to easily change your code.&lt;/li>
&lt;/ul>
&lt;p>These are four easy steps to minimize technical debt in your project.&lt;/p>
&lt;p>You should not wait until you hit the technical debt limit of your project. Minimizing technical debt is just as important as writing tests. It should be a team priority from &lt;em>Day 1&lt;/em>.&lt;/p>
&lt;p>Minimizing technical debt is not all too different from doing proper test driven development. It requires rigorous discipline and skills. And just like with tests, you will not handle that technical debt after this crunch period.&lt;/p>
&lt;p>Next time you open a project, ask your self, &lt;em>&amp;ldquo;How can I leave this project in a better state than I just found it?&amp;rdquo;&lt;/em>&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2014/04/04/dealing-with-technical-debt/</guid><pubDate>Fri, 04 Apr 2014 00:00:00 +0000</pubDate></item><item><title>Pretty Difficult Privacy</title><link>https://www.devroom.io/2014/04/03/pretty-difficult-privacy/</link><description>&lt;p>&lt;em>Disclaimer: I could write books about online privacy, freedom of speech
and how encryption fits into that picture. There are others out there who
can do a better job at that and who have way more experience in crypto
than I have.&lt;/em>&lt;/p>
&lt;p>So, I got a keybase.io invite. Awesome. But what problem is keybase trying
to solve?&lt;/p>
&lt;p>I created my first GPG key in 2000 by way of checking out shiny new things.
I&amp;rsquo;ve been happily generating GPG keys ever since, but I never really used GPG.
There were three reasons for this:&lt;/p>
&lt;ol>
&lt;li>The tools, like GnuPG, are difficult to use.&lt;/li>
&lt;li>Integration with popular mail clients sucks.&lt;/li>
&lt;li>I only know two to three people with a public key.&lt;/li>
&lt;/ol>
&lt;p>To start with that last point, there is barely anyone I know that uses
GPG. And I know quite a few technically oriented people, like developers
and sys admins. No body seems to bother for GPG and the hassle it entails.
Most people &lt;em>do&lt;/em> have a public key available, but they simple have not
setup anything to read and send encrypted or signed emails on a daily
basis.&lt;/p>
&lt;p>This brings us to points 1 and 2. The tooling, GnuPG, is not very
user friendly. I&amp;rsquo;m not saying it&amp;rsquo;s unusable, but compared to the CLI
interface that keybase provides, it&amp;rsquo;s arcane magic. Even with GPG setup
and integrated in [Sup][3], I routinely make mistakes and get feedback from
people I encrypted an email wrong and it&amp;rsquo;s unreadable by them.&lt;/p>
&lt;p>Using this arcane magic in conjunction with &lt;em>normal&lt;/em> applications, like
mail clients, is quite difficult as well.&lt;/p>
&lt;p>Luckily there&amp;rsquo;s &lt;a href="https://gpgtools.org/">GPGTools&lt;/a> for Mac, which provides you with everything
you need to get started, including a Mail.app plugin, key management and
a Mac version of GnuPG.&lt;/p>
&lt;p>With this software installed, as a normal user, you probably still have
no clue what you&amp;rsquo;re doing.&lt;/p>
&lt;p>It took me a few evenings to get up to speed again with how GPG works and,
more difficult, how to manage your keys. This last part turns out to be
the most difficult thing, mostly because the plenhora of options and
settings to choose from. All the terminology is not helping: private keys,
public keys, fingerprints, trust (of keys), trust (of users), key rings,
signing, ecrypting, cleartext, photoids, shortids and key ids, key length,
algorithms, validity, passphrases and expiration dates. Not to mention
subkeys, signing of keys, capabilities and revokation.&lt;/p>
&lt;p>That&amp;rsquo;s quite a lot of matter to understand before you can use GPG properly.&lt;/p>
&lt;p>I think what keybase is doing is great. There are also a few flaws in their
plan, and I think it&amp;rsquo;s not for the better of GPG and privacy. First and
foremose, Keybase is becoming a central repository that stores user
identities. That does not sound good, not even to mention they are running
a business and a closed-source site. Besides, there is already the
web of trust and public distributed key servers for GPG to use. Proving
identity with a twitter account or github account is also not very powerful
IMHO, as it&amp;rsquo;s easy to fake.&lt;/p>
&lt;p>These things aside, keybase does not solve the fundamental issue of
cryptography in general and GPG in particular: it&amp;rsquo;s difficult to use and
does mostly not integrate nicely with other software.&lt;/p>
&lt;p>For me, to get GPG to the general public, I think the tooling and
services around GPG should:&lt;/p>
&lt;ul>
&lt;li>be understandable and usable by the average user&lt;/li>
&lt;li>be open source&lt;/li>
&lt;li>be distributed in nature&lt;/li>
&lt;li>integrate easily with third party applications&lt;/li>
&lt;/ul>
&lt;p>If you can solve this problem, I thing you have gold. Get in touch with
me if you have ideas on this topic. You can find &lt;a href="http://ariejan.net/gpg/">info on my GPG key right
here&lt;/a>.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2014/04/03/pretty-difficult-privacy/</guid><pubDate>Thu, 03 Apr 2014 00:00:00 +0000</pubDate></item><item><title>To Blog or Not To Blog</title><link>https://www.devroom.io/2014/04/03/to-blog-or-not-to-blog/</link><description>&lt;img src="https://www.devroom.io/img/20140403-01.jpg" class="center bordered" />
&lt;p>My last post was 154 days ago. That&amp;rsquo;s about five months without writing a
single post on this blog. I&amp;rsquo;ve been thinking about what this means and
what happened, and here is what I discovered.&lt;/p>
&lt;p>First of all I love writing. It has something peaceful to write down your
experiences and thoughts. I also love that I can share these experiences
with you, like 90.000 times each month, if I&amp;rsquo;m to believe analytics.&lt;/p>
&lt;p>But I didn&amp;rsquo;t write. I barely looked at my site and only once a month
got reminded about it when the ad revenue email came in.&lt;/p>
&lt;p>Finding &lt;em>where it had gone wrong&lt;/em> has not been a high priority for me either.
I&amp;rsquo;m not sure why or how. Recently I came across the
&lt;a href="http://sach.ac/no-excuses-blogging">A No-Excuses Guide to Blogging&lt;/a> summary, which neatly lists all kinds of
excuses to &lt;em>not blog&lt;/em> and what to do about them.&lt;/p>
&lt;p>This got me thinking about why I hadn&amp;rsquo;t posted a blog in such a long time.&lt;/p>
&lt;img src="https://www.devroom.io/img/20140403-02.png" class="center bordered" />
&lt;p>I have been up to quite a lot lately, but still I struggled to write blog
posts. From the &lt;em>A No-Excuses Guide to Blogging&lt;/em> I picked up several tips
that got me going again.&lt;/p>
&lt;p>The three most important tips I got from &lt;em>A No-Excuses Guide to Bloggin&lt;/em> were
these:&lt;/p>
&lt;h2 id="1-write-about-what-youre-learning-while-you-learn">1. Write about what you&amp;rsquo;re learning, while you learn.&lt;/h2>
&lt;p>I&amp;rsquo;m learning constantly. If it&amp;rsquo;s not using Vim more proficiently, then it&amp;rsquo;s
about setting up servers with some shell script magic. Finding new tools
to pair program, or write better tests.&lt;/p>
&lt;p>Often I found myself not worthy of writing a post about a topic, like Bitcoin,
GPG or Vagrant and chef because I don&amp;rsquo;t feel at the top of my game on these
subjects. I&amp;rsquo;m no expert. I don&amp;rsquo;t want to look like a fool writing non-sense
about these topics.&lt;/p>
&lt;p>But I&amp;rsquo;m learning. I don&amp;rsquo;t have to be an expert to talk about what I
&lt;em>discovered&lt;/em> or &lt;em>learned&lt;/em>. I can even ask questions and leave them
unanswered in my posts, and who knows someone might find an answer.&lt;/p>
&lt;h2 id="2-keep-a-list-of-topics-and-create-focus">2. Keep a list of topics and create focus&lt;/h2>
&lt;p>The seconds problem I had was the volatility of my ideas. I often had a
great idea at night when learning or programming. The next day those ideas
were mostly gone and forgotten.&lt;/p>
&lt;p>Now, I&amp;rsquo;m keeping track of what I&amp;rsquo;m doing more actively using Evernote. This
way I can later recall what I did and what that great idea for a blog post
was all about.&lt;/p>
&lt;p>When I started doing this, the list with ideas grew quickly. Too quickly.
The list became quite long and I had no idea where to start or how to get
any writing done.&lt;/p>
&lt;p>Focussing on what&amp;rsquo;s most important sounds easy. But what is most important?
What is most important &lt;em>to me&lt;/em>? It turns out that there are subjects that
get me excited and happy. There is no clear box to put these ideas. Some
ideas are very techincal, others are more security or privacy oriented
while other are not technical at all (like this post).&lt;/p>
&lt;p>Optimizing for my own happiness was big factor in getting back to writing
for this blog again.&lt;/p>
&lt;h2 id="3-make-sharing-part-of-the-way-you-work">3. Make sharing part of the way you work&lt;/h2>
&lt;p>It may be hard to believe but sharing was never a part of my daily
workflow. And I honestly don&amp;rsquo;t know why it wasn&amp;rsquo;t.&lt;/p>
&lt;p>Sharing comes naturally to me. When I&amp;rsquo;ve figured something out I want to
talk to people about it. And more often than not they get excited as well.&lt;/p>
&lt;p>Wouldn&amp;rsquo;t it be great if I could make this sharing a part of my daily
workflow? I&amp;rsquo;m still experimenting with this (as I have to write a few new
posts) but I think this will be a great motivator to keep writing.&lt;/p>
&lt;h2 id="to-blog-or-not-to-blog">To Blog or Not To Blog&lt;/h2>
&lt;p>After reading &lt;a href="http://sach.ac/no-excuses-blogging">A No-Excuses Guide to Blogging&lt;/a> I got excited again about
my blog and writing. I&amp;rsquo;ve taken away a few tips from this guide that,
hopefully, will keep me interested and focus on writing what I care about.&lt;/p>
&lt;ul>
&lt;li>Keep a list of ideas&lt;/li>
&lt;li>Keep notes when learning or trying new things&lt;/li>
&lt;li>Technology doesn&amp;rsquo;t matter&lt;/li>
&lt;li>Focus on what makes you happy and excited&lt;/li>
&lt;li>Make sharing part of your workflow&lt;/li>
&lt;/ul>
&lt;p>With a fresh list of ideas for my blog I&amp;rsquo;m ready to start writing some blog
posts again!&lt;/p>
&lt;p>&lt;em>If you are a blogger and having some &lt;em>&amp;ldquo;issues&amp;rdquo;&lt;/em>, make sure to read
&lt;a href="http://sach.ac/no-excuses-blogging">A No-Excuses Guide to Blogging&lt;/a> by Sasha Chua.&lt;/em>&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2014/04/03/to-blog-or-not-to-blog/</guid><pubDate>Thu, 03 Apr 2014 00:00:00 +0000</pubDate></item><item><title>Deploying with git-deploy</title><link>https://www.devroom.io/2013/10/31/deploying-with-git-deploy/</link><description>&lt;p>I&amp;rsquo;ve blogged before about deploying Rails applications. Normally I opt for using capistrano, as it provides all the features I need and is pretty easy to customize if needed.&lt;/p>
&lt;p>One of my previous strategies was to use capistrano to checkout a branch on a remote server, and &lt;code>git fetch&lt;/code> that branch upon a new deployment.&lt;/p>
&lt;p>The problem with capistrano, however, is that it can be quite slow from time to time. It also has a lot of features I never or only rarely use, like multi-host deployments.&lt;/p>
&lt;p>A few days ago someone pointed out &lt;a href="https://github.com/mislav/git-deploy">git-deploy&lt;/a> to me. I tried it out and it is &lt;em>fantastic&lt;/em>. Let me explain that.&lt;/p>
&lt;h2 id="light-weight">Light-weight&lt;/h2>
&lt;p>What could be the easiest way to deploy an app?&lt;/p>
&lt;ul>
&lt;li>SSH to the server&lt;/li>
&lt;li>Fetch code changes&lt;/li>
&lt;li>&lt;code>bundle install&lt;/code>, &lt;code>rake db:migrate&lt;/code>, &lt;code>rake assets:precompile&lt;/code>&lt;/li>
&lt;li>Restart&lt;/li>
&lt;/ul>
&lt;p>Yes, you could write a script to do all that, but there&amp;rsquo;s an even easier way: git hooks.&lt;/p>
&lt;p>Git hooks are triggered after certain events on a git repository. In this case, the flow becomes:&lt;/p>
&lt;ul>
&lt;li>Push code to server&lt;/li>
&lt;/ul>
&lt;p>In the git hooks, we still need to handle:&lt;/p>
&lt;ul>
&lt;li>&lt;code>bundle install&lt;/code>, &lt;code>rake db:migrate&lt;/code>, &lt;code>rake assets:precompile&lt;/code>&lt;/li>
&lt;li>Restart&lt;/li>
&lt;/ul>
&lt;p>Now, here comes &lt;code>git-deploy&lt;/code>. What it does is add a few scripts to your project. Setup is rather easy:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Install the gem, no need for Gemfile.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">gem install git-deploy
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Add a git remote to your server&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">git remote add production &lt;span class="s2">&amp;#34;user@server.com:/home/myapp/app&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Setup deployments&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">git deploy setup -r production
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Generate deploy scripts (locally)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">git deploy init
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>After this, you should find the following files in &lt;code>deploy/&lt;/code>:&lt;/p>
&lt;ul>
&lt;li>&lt;code>after_push&lt;/code>&lt;/li>
&lt;li>&lt;code>before_restart&lt;/code>&lt;/li>
&lt;li>&lt;code>restart&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>The filenames are self explanatory, but here&amp;rsquo;s a quick run through:&lt;/p>
&lt;p>&lt;strong>&lt;code>after_push&lt;/code>&lt;/strong>&lt;/p>
&lt;p>This file is run after you&amp;rsquo;ve pushed your code to the remote server. It gathers some information about your changes, sets up the environment and kicks of the other two scripts.&lt;/p>
&lt;p>&lt;strong>&lt;code>before_restart&lt;/code>&lt;/strong>&lt;/p>
&lt;p>Before you can restart your server, you will probably need to update some things. By default git-deploy will check if it needs to run migrations, perform a &lt;code>bundle install&lt;/code> and precompile your assets.&lt;/p>
&lt;p>&lt;strong>&lt;code>restart&lt;/code>&lt;/strong>&lt;/p>
&lt;p>Does exactly what it says, it will restart your Rails app. By default it&amp;rsquo;s configured for Passenger, so it simply touches &lt;code>tmp/restart.txt&lt;/code>.&lt;/p>
&lt;h2 id="server-setup-rvm-and-sidekiq">Server setup, RVM and Sidekiq&lt;/h2>
&lt;p>What follows is a step-by-step guide of how I setup a new server. In this case for a project using ruby-2.0 with RVM and Sidekiq for background processing.&lt;/p>
&lt;p>&lt;strong>0. Set your RAILS_ENV&lt;/strong>&lt;/p>
&lt;p>I always declare &lt;code>RAILS_ENV&lt;/code> en &lt;code>/etc/environment&lt;/code>. This file gets loaded everywhere and saves you the headache of adding this env variable with every rails command you execute later.&lt;/p>
&lt;p>I also set my editor here, so I&amp;rsquo;m not surprised by nano when doing &lt;code>visudo&lt;/code> or &lt;code>crontab -e&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">&lt;span class="nv">RAILS_ENV&lt;/span>&lt;span class="o">=&lt;/span>production
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">RACK_ENV&lt;/span>&lt;span class="o">=&lt;/span>production
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">EDITOR&lt;/span>&lt;span class="o">=&lt;/span>vi
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>1. Create a user for the app&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">adduser myapp
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>2. Add user to sudoers&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">$ sudo vi /etc/sudoers
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Add the following line:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">myapp &lt;span class="nv">ALL&lt;/span>&lt;span class="o">=(&lt;/span>ALL&lt;span class="o">)&lt;/span> NOPASSWD: ALL
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>3. Setup SSH Keys&lt;/strong>&lt;/p>
&lt;p>In this case all you need to do is add your own public key(s) to &lt;code>/home/myapp/.ssh/authorized_keys&lt;/code>.&lt;/p>
&lt;p>&lt;strong>4. Setup RVM for &lt;code>myapp&lt;/code>&lt;/strong>&lt;/p>
&lt;p>I prefer to setup RVM for the specific user (instead of system-wide). The following command will directly install the latest ruby as well.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">&lt;span class="se">\c&lt;/span>url -L https://get.rvm.io &lt;span class="p">|&lt;/span> bash -s stable --ruby
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>5. Apache + Passenger&lt;/strong>&lt;/p>
&lt;p>Then I setup Apache with Passenger. It&amp;rsquo;s fine if you want to use Nginx instead.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">sudo apt-get install libcurl4-openssl-dev apache2-mpm-worker libapr1-dev libaprutil1-dev apache2-threaded-dev
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">gem install passenger
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">passenger-install-apache2-module
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Just follow the instructions of the Passenger install script. Depending on your system configuration, you may be asked to add some (temporary) swap space.&lt;/p>
&lt;p>Passenger will spit out three lines that you should add to the bottom of &lt;code>/etc/apache2/apache2.conf&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-apacheconf" data-lang="apacheconf">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">LoadModule&lt;/span> passenger_module &lt;span class="sx">/home/myapp/.rvm/gems/ruby-2.0.0-p247/gems/passenger-4.0.21/buildout/apache2/mod_passenger.so&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">PassengerRoot&lt;/span> &lt;span class="sx">/home/myapp/.rvm/gems/ruby-2.0.0-p247/gems/passenger-4.0.21&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">PassengerDefaultRuby&lt;/span> &lt;span class="sx">/home/myapp/.rvm/wrappers/ruby-2.0.0-p247/ruby&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>6. Configure Apache VirtualHost&lt;/strong>&lt;/p>
&lt;p>Next, create and enable an Apache virtual host for your app:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-apacheconf" data-lang="apacheconf">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;VirtualHost&lt;/span> &lt;span class="s">*:80&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">ServerName&lt;/span> myapp.example.com
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">DocumentRoot&lt;/span> &lt;span class="sx">/home/myapp/app/public&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Directory&lt;/span> &lt;span class="s">/home/myapp/app/public&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">AllowOverride&lt;/span> &lt;span class="k">all&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">Options&lt;/span> -MultiViews
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/Directory&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/VirtualHost&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>And enable it:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">sudo ln -sf /etc/apache2/sites-available/myapp.example.com /etc/apache2/sites-enabled/010-myapp.example.com
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">service apache2 restart
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>7. Install your database, tools, etc.&lt;/strong>&lt;/p>
&lt;p>Install postgres, redis or whatever rocks your boat. Make sure to also install the proper tools (like imagemagick) and development headers.&lt;/p>
&lt;p>Also make sure that you update &lt;code>config/database.yml&lt;/code> in your project and commit these changes and the files in &lt;code>deploy&lt;/code>.&lt;/p>
&lt;p>I&amp;rsquo;m assuming you also create a database now, as we won&amp;rsquo;t be running &lt;code>rake db:create&lt;/code> later on.&lt;/p>
&lt;p>&lt;strong>8. Add foreman for Sidekiq&lt;/strong>&lt;/p>
&lt;p>I want to use foreman to manage starting/stopping sidekiq. Add foreman to your &lt;code>Gemfile&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="n">gem&lt;/span> &lt;span class="s1">&amp;#39;foreman&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Optional, but works well with a Debian server&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">gem&lt;/span> &lt;span class="s1">&amp;#39;foreman-export-initscript&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>And add a line to &lt;code>Procfile&lt;/code>, of course, add any parameters you need for running sidekiq here.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">worker&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">bundle exec sidekiq&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>9. Enable RVM for git-deploy&lt;/strong>&lt;/p>
&lt;p>In order to use RVM and &lt;code>rvmsudo&lt;/code> properly, we need to set that up in &lt;code>deploy/after_push&lt;/code>. Add the following lines before the &lt;code>run&lt;/code> commands at the bottom.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">export&lt;/span> &lt;span class="nv">rvmsudo_secure_path&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">PATH&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="nv">$PATH&lt;/span>:&lt;span class="nv">$HOME&lt;/span>/.rvm/bin
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>10. Sidekiq deploy configuration&lt;/strong>&lt;/p>
&lt;p>There are two things we need to do. Firstly, we need to generate an initscript with foreman. We do this in &lt;code>deploy/before_restart&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="n">run&lt;/span> &lt;span class="s2">&amp;#34;rvmsudo bundle exec foreman export initscript /etc/init.d -a myapp -u myapp -l /var/log/myapp&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">run&lt;/span> &lt;span class="s2">&amp;#34;rvmsudo chmod +x /etc/init.d/myapp&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Secondly, we need to use this initscript to actually restart Sidekiq in &lt;code>deploy/restart&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">echo&lt;/span> &lt;span class="s2">&amp;#34;restarting foreman&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo /etc/init.d/myapp restart
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>11. Ready? Deploy it!&lt;/strong>&lt;/p>
&lt;p>Now, deploying is rather easy:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">git push production master
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>That&amp;rsquo;s it.&lt;/p>
&lt;h2 id="future-deployments">Future deployments&lt;/h2>
&lt;p>Well, from now on you can simply push your code with git and &lt;code>git-deploy&lt;/code> will take care of it all:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">git push production master
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>A few things to keep in mind:&lt;/p>
&lt;ul>
&lt;li>You don&amp;rsquo;t need the &lt;code>git-deploy&lt;/code> gem installed to deploy your project.&lt;/li>
&lt;li>Keep your &lt;code>deploy/&lt;/code> scripts up to date and under version control.&lt;/li>
&lt;li>Anyone with SSH access to your server can deploy the project.&lt;/li>
&lt;/ul>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>I think that capistrano is a fine tool, but for many projects it&amp;rsquo;s overkill. &lt;code>git-deploy&lt;/code> is light weight and easy to use and gets the job of deploying your app done quickly. It&amp;rsquo;s also easily extendable, if you need it.&lt;/p>
&lt;p>Give it a try. You can easily spin up a VPS over at &lt;a href="http://aj.gs/digitalocean">Digital Ocean&lt;/a> to play around or host your next awesome project. (Disclaimer: referral link). If you use Digital Ocean, you may also be interested in my iOS App: &lt;a href="http://aj.gs/binary-deep">Binary Deep&lt;/a>.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2013/10/31/deploying-with-git-deploy/</guid><pubDate>Thu, 31 Oct 2013 00:00:00 +0000</pubDate></item><item><title>Divide and Conquer</title><link>https://www.devroom.io/2013/05/23/divide-and-conquer/</link><description>&lt;p>A few days ago I wrote about getting up early and getting stuff done
(&lt;a href="https://ariejan.net/2013/05/21/early-birds/">link&lt;/a>).&lt;/p>
&lt;p>After giving this a some more thought I figured out that there&amp;rsquo;s something
else going on as well.&lt;/p>
&lt;p>Getting up early and working during the quiet hours in the morning is not
the only thing helping me be productive.&lt;/p>
&lt;p>The other factor is having limited time.&lt;/p>
&lt;p>The fact is that I have to make breakfast, shower and get out of the
door for work on time. This means that my time has a hard limit put
on it and I have to plan what I do accordingly.&lt;/p>
&lt;p>I&amp;rsquo;ve noticed that each morning I try to pick a single task to focus on.
This can be researching some new tech, or fixing a bug or implementing
a new feature. Because my time is so limited, it&amp;rsquo;s much easier to estimate
what task would fit into that time.&lt;/p>
&lt;p>So, why does this work so well for me?&lt;/p>
&lt;p>It forces me to break down features and bugs into smaller pieces that can
be completed in about ninety minutes.&lt;/p>
&lt;p>During a normal work day I don&amp;rsquo;t have these little units of time with hard
limits and it&amp;rsquo;s much easier to just keep on going.&lt;/p>
&lt;p>I&amp;rsquo;m going to undertake an experiment at work to slice my time into small
units with hard time limits on them and see if it helps me to be more
productive during a sprint.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2013/05/23/divide-and-conquer/</guid><pubDate>Thu, 23 May 2013 00:00:00 +0000</pubDate></item><item><title>Early Birds</title><link>https://www.devroom.io/2013/05/21/early-birds/</link><description>&lt;p>Over the years I&amp;rsquo;ve often found myself behind my computer at two in the morning trying to finish a feature for a side-project and trying to keep my eyes open and focussed.&lt;/p>
&lt;p>After a hack session until two or three in the morning, there would be a price to pay in the morning. Not even three alarms would wake me and I&amp;rsquo;d be grumpy all day long. The feature I wrote the evening before didn&amp;rsquo;t even look &lt;em>that&lt;/em> good, so I&amp;rsquo;d be re-written or removed altogether. Another night wasted.&lt;/p>
&lt;p>This rhythm was not rewarding and pretty hard to keep up. It tired me out and got me demotivated. It even caused me to give up on some projects because of it.&lt;/p>
&lt;p>Late april I was invited to an early morning bird walk. The idea was to hit the woods at 5:30 and listen to all the birds waking up and singing their songs. To my surprise it was rather easy getting up at five, and when I got home at eight I was full of energy and still had an entire day ahead of me.&lt;/p>
&lt;p>I went to bed early that evening – no late night hacking – and to my surprise I woke naturally at 5:30 the next morning. I got up, showered, got myself a cup of coffee and fired up my computer to hack on a feature I&amp;rsquo;ve been thinking about.&lt;/p>
&lt;p>At 6:45 the feature was done and I was still thirty minutes ahead of my normal morning routine.&lt;/p>
&lt;p>That day I had more energy. Knowing that I&amp;rsquo;d already completed that one feature put me at easy somehow. In the evening I didn&amp;rsquo;t do much – because I didn&amp;rsquo;t have to. I spend some time doing fun stuff and went to bed early.&lt;/p>
&lt;p>I&amp;rsquo;ve been doing this for two weeks now and it&amp;rsquo;s been awesome. In the morning I&amp;rsquo;m more focussed and I honestly write better code.&lt;/p>
&lt;p>It&amp;rsquo;s been a long while since I finished a side project, but the way things are currently progressing I might even finish this one and start another.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2013/05/21/early-birds/</guid><pubDate>Tue, 21 May 2013 00:00:00 +0000</pubDate></item><item><title>I'm here for an argument</title><link>https://www.devroom.io/2013/03/26/i-am-here-for-an-argument/</link><description>&lt;p>When people make a request or proposal I often see them present their request and arguments using the following structure:&lt;/p>
&lt;ol>
&lt;li>Make a request&lt;/li>
&lt;li>Give a plethora of arguments why that request is a good idea.&lt;/li>
&lt;/ol>
&lt;p>An example:&lt;/p>
&lt;ul>
&lt;li>I want to go to the zoo!&lt;/li>
&lt;li>Because I want to see animal&lt;/li>
&lt;li>Because I want to spend time with you&lt;/li>
&lt;li>Because it has been a long time&lt;/li>
&lt;/ul>
&lt;p>Here I think, meh, the zoo. And then I read some arguments that may or may not change my opinion on actually going to the zoo or not.&lt;/p>
&lt;p>You start with the &lt;em>what&lt;/em> and then continue on to the &lt;em>why&lt;/em>, but I&amp;rsquo;ve already lost interest.&lt;/p>
&lt;p>Consider this request:&lt;/p>
&lt;ul>
&lt;li>I want to spend time with you and see animals.&lt;/li>
&lt;li>It has been a long time since we went to the zoo&lt;/li>
&lt;li>Let&amp;rsquo;s go to the zoo!&lt;/li>
&lt;/ul>
&lt;p>Here you start with me agreeing on spending time together and it becomes easier for me to accept your request to go to the zoo. I&amp;rsquo;m not blinded by your request.&lt;/p>
&lt;p>You start with the &lt;em>why&lt;/em> and then continue to the &lt;em>what&lt;/em>.&lt;/p>
&lt;p>In addition to this article, please watch &lt;a href="http://www.ted.com/talks/simon_sinek_how_great_leaders_inspire_action.html">Simon Sinek&amp;rsquo;s TED Talk&lt;/a> [18m05] How great leaders inspire action.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2013/03/26/i-am-here-for-an-argument/</guid><pubDate>Tue, 26 Mar 2013 00:00:00 +0000</pubDate></item><item><title>Automated nanoc deployments</title><link>https://www.devroom.io/2013/03/25/automated-nanoc-deployments/</link><description>&lt;p>I&amp;rsquo;ve migrated &lt;em>ariejan.net&lt;/em> from a custom Ruby on Rails application to a statically generated site with Nanoc.&lt;/p>
&lt;p>Publishing my site now goes like this:&lt;/p>
&lt;ol>
&lt;li>Write stuff in Markdown&lt;/li>
&lt;li>Commit and push&lt;/li>
&lt;li>Watch how jenkins builds and publishes the site with nanoc and rsync.&lt;/li>
&lt;/ol>
&lt;h2 id="using-nanoc">Using nanoc&lt;/h2>
&lt;p>Nanoc is a very powerful static site generator. You can use ruby to create helpers, filters and what not. Fancy using Sass, Compass and Haml? No problem!&lt;/p>
&lt;p>Simply write pages in Haml or Markdown and Nanoc will generate a complete site, including sitemap, robots.txt and RSS feed.&lt;/p>
&lt;p>Once you&amp;rsquo;ve set everything up to your liking, simply run &lt;code>nanoc compile&lt;/code> and your site is ready.&lt;/p>
&lt;p>Nanoc also has some nice features to deploy your website through rsync, which will upload (or remove) files on a remote server as necessary.&lt;/p>
&lt;h2 id="automate-the-crap-out-of-it">Automate the crap out of it!&lt;/h2>
&lt;p>I&amp;rsquo;m a programmer. I like to automate repetitive tasks. So I did.&lt;/p>
&lt;p>I&amp;rsquo;ve setup Jenkins with the RVM plugin and that&amp;rsquo;s all I need to have Jenkins generate and deploy my Nanoc site.&lt;/p>
&lt;p>Here&amp;rsquo;s the build script I currently use:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#!/bin/bash
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&lt;/span>&lt;span class="nb">export&lt;/span> &lt;span class="nv">NANOC_ENV&lt;/span>&lt;span class="o">=&lt;/span>production
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">bundle install
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">nanoc compile
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">nanoc deploy -t public
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>If the compilation fails for some reason, the deployment is cancelled.&lt;/p>
&lt;h2 id="enter-gitlab">Enter gitlab&lt;/h2>
&lt;p>Now I use Gitlab to host tons of private repositories, including the one for &lt;em>ariejan.net&lt;/em>.&lt;/p>
&lt;p>I&amp;rsquo;ve setup a web hook that will trigger the Jenkins job when new commits are made or pushed.&lt;/p>
&lt;p>This allows me to quickly edit a file, commit it and Gitlab en Jenkins will make sure the change is compiled and published to &lt;em>ariejan.net&lt;/em>.&lt;/p>
&lt;h2 id="where-to-go-from-here">Where to go from here&lt;/h2>
&lt;p>For now, this setup suffices for me. I keep all my posts in version control, and backupped to S3 (by backing up my gitlab repositories). Adding a new story to git is sufficient for it to be published automatically on &lt;em>ariejan.net&lt;/em> and in the event something goes wrong with compilation, Jenkins will notify me of the build failure.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2013/03/25/automated-nanoc-deployments/</guid><pubDate>Mon, 25 Mar 2013 00:00:00 +0000</pubDate></item><item><title>Review commits in your feature branch</title><link>https://www.devroom.io/2013/03/08/review-commits-in-your-feature-branch/</link><description>&lt;p>Github pull requests are awesome, but you can&amp;rsquo;t use them all the time, mostly when working on code not hosted at github.&lt;/p>
&lt;p>The following snippet makes it easy to see the commits in your current (head) branch that are not yet in the base branch.&lt;/p>
&lt;p>To see what commits are made in your current feature branch, but which have not been merged into develop yet:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">$ gpr develop
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">* &lt;span class="m">5246248&lt;/span> &amp;lt;ariejan@ariejan.net&amp;gt; &lt;span class="o">(&lt;/span>HEAD, origin/feature-branch, feature-branch&lt;span class="o">)&lt;/span> Implements the awesome feature &lt;span class="o">(&lt;/span>&lt;span class="m">50&lt;/span> minutes ago&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">* 4f55b7c &amp;lt;ariejan@ariejan.net&amp;gt; Write specs &lt;span class="k">for&lt;/span> awesome feature &lt;span class="o">(&lt;/span>&lt;span class="m">2&lt;/span> hours ago&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="snippets">Snippets&lt;/h2>
&lt;p>To achieve this, add the following alias to &lt;code>~/.gitconfig&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ini" data-lang="ini">&lt;span class="line">&lt;span class="cl">&lt;span class="k">[alias]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">lg&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s">log --graph --pretty=format:&amp;#39;%Cred%h%Creset %Cblue&amp;lt;%ae&amp;gt;%C(yellow)%d%Creset %s %Cgreen(%cr)%Creset&amp;#39; --abbrev-commit --date=relative&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>And the following alias to your &lt;code>~/.bashrc&lt;/code> or &lt;code>~/.zshrc&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">alias&lt;/span> &lt;span class="nv">gpr&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;git --no-pager lg HEAD --not &lt;/span>&lt;span class="nv">$1&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="bonus-tip">Bonus tip&lt;/h2>
&lt;p>After reviewing the commits in your feature branch with &lt;code>gpr&lt;/code>, use &lt;a href="http://defunkt.io/hub/">&lt;code>hub&lt;/code>&lt;/a> to attach your code to a github issue:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">hub pull-request -i &lt;span class="m">42&lt;/span> -h ariejan:feature-branch -b you:develop
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2013/03/08/review-commits-in-your-feature-branch/</guid><pubDate>Fri, 08 Mar 2013 00:00:00 +0000</pubDate></item><item><title>Instagram, Governments and Cypherpunks</title><link>https://www.devroom.io/2012/12/18/instagram-governments-and-cypherpunks/</link><description>&lt;p>The whole internet collectively fell over Instagram earlier this week when they released their new &lt;em>Terms of Service&lt;/em>.&lt;/p>
&lt;blockquote>
&lt;p>Some or all of the Service may be supported by advertising revenue. To help us deliver interesting paid or sponsored content or promotions, you agree that a business or other entity may pay us to display your username, likeness, photos (along with any associated metadata), and/or actions you take, in connection with paid or sponsored content or promotions, without any compensation to you.&lt;/p>
&lt;/blockquote>
&lt;p>Today &lt;a href="http://blog.instagram.com/post/38252135408/thank-you-and-were-listening">Instagram officially responded&lt;/a> to all the screaming and shouting, trying to control the damage.&lt;/p>
&lt;blockquote>
&lt;p>From the start, Instagram was created to become a business.&lt;/p>
&lt;/blockquote>
&lt;p>Well, that was unexpected. It&amp;rsquo;s the same with Facebook, Twitter, Gmail, Hotmail, etc. All these services are here for you to use for free - awesome!&lt;/p>
&lt;p>But nobody ever got rich from giving stuff away for free, and these are businesses we&amp;rsquo;re talking about.&lt;/p>
&lt;p>Let&amp;rsquo;s see how this works over at Facebook.&lt;/p>
&lt;h2 id="exhibit-a-facebook">Exhibit A: Facebook&lt;/h2>
&lt;p>Facebook is a good example of how you are the product being sold. We all know Facebook tries to show you ads that you are likely interested in. Based on what you put in your profile advertisers can target specific audiences.&lt;/p>
&lt;p>That might sound all well and good to you, but did you also know that your friends might see a &lt;em>sponsored&lt;/em> post in their newsfeed? And that that &lt;em>sponsored&lt;/em> post appears as if it&amp;rsquo;s one posted by &lt;em>you&lt;/em>?&lt;/p>
&lt;p>&lt;img
src="https://s3-eu-west-1.amazonaws.com/ariejannet/images/related-post.png"
alt="Facebook sponsored post"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;p>Here you see a post by a friend of mine who apparently likes some mobile company&amp;rsquo;s ad. They didn&amp;rsquo;t like this add. I asked them.&lt;/p>
&lt;p>So basically Facebook and an advertiser are using you, your name and your profile picture to promote an advertisement to one of your friends.&lt;/p>
&lt;h2 id="exhibit-b-google">Exhibit B: Google&lt;/h2>
&lt;p>Another example, more in line with Facebook, is Google. All those free tools: Search, Gmail, Google+, Analytics, Web Master Tools, maps. They are all geared towards either exposing ads to you, or gathering data on you to expose more focussed ads.&lt;/p>
&lt;p>The plus side here, I would say, is that Google actually delivers usable services, like search, mail, calendars and such. But still, look at the boatloads of money Google is making selling ads based on your private details and online behaviour.&lt;/p>
&lt;h2 id="exhibit-c-twitter">Exhibit C: Twitter&lt;/h2>
&lt;p>Twitter is a company that has struggled for a long time to find a way to monetize itself. Hopes were high that they would find a new an innovative way, but they have not.&lt;/p>
&lt;p>It remains to be seen what&amp;rsquo;s going to happen exactly with twitter, but we can all assume that our tweets are being analyzed and that Twitter will do anything to sell you to their advertisers.&lt;/p>
&lt;h2 id="exhibit-d-instagram">Exhibit D: Instagram&lt;/h2>
&lt;p>To come back to Instagram, they&amp;rsquo;re in the same boat as Twitter and Facebook.&lt;/p>
&lt;p>They have been growing their user base for quite some time now and now comes the time to move in and cash on all those users.&lt;/p>
&lt;p>The new terms of service should not come as a surprise.&lt;/p>
&lt;p>Instagram wants to use your data to match you up with advertisers and it even wants to show ads endorsed by you, just like Facebook already does.&lt;/p>
&lt;h2 id="so-whats-happening-out-there">So what&amp;rsquo;s happening out there?&lt;/h2>
&lt;p>Social Media are nice, they connect people and can start revolutions. But that is not why those media exist. They are marketing and data mining platforms on a massive scale.&lt;/p>
&lt;p>Have you ever wondered why social media companies get multi million dollar investments? Because with a huge, world-wide user base, there is a huge advertising potential.&lt;/p>
&lt;p>&lt;em>If you are not buying a product or service, but are receiving one for free, then you are the product or service begin sold.&lt;/em>&lt;/p>
&lt;p>I think that companies like Facebook and Instagram are wading in a gray area of privacy. Their practices may be perfectly legal, but they &lt;em>are&lt;/em> ethically questionable.&lt;/p>
&lt;h2 id="on-government-spying-and-eavesdropping">On government spying and eavesdropping&lt;/h2>
&lt;p>You might sometimes worry about governments eavesdropping and keeping tabs on innocent citizens – and you should.&lt;/p>
&lt;p>If you look at the sheer amount of data social media gather on you, imagine what kind of data a government agency with the proper tools and hardware can do.&lt;/p>
&lt;p>But the issue is not really that companies and governments are gathering data - you are feeding it to them with every status update and photo you upload to the internet. With every email you sent.&lt;/p>
&lt;p>So, what&amp;rsquo;s to be done?&lt;/p>
&lt;h2 id="cypherpunks-to-the-rescue">Cypherpunks to the rescue?&lt;/h2>
&lt;p>Now, I won&amp;rsquo;t claim to know a whole lot about cypherpunks and the crypto wars, but these people have fought (and still are) fighting for our digital rights and freedom.&lt;/p>
&lt;p>We are collectively giving away our personal data to companies and governments and most of the time we don&amp;rsquo;t even know it. But then some image sharing company changes its terms that will allow them to display adds alongside your name and we freak out.&lt;/p>
&lt;p>Here&amp;rsquo;s quite from the Cypherpunks manifesto:&lt;/p>
&lt;blockquote>
&lt;p>Privacy is necessary for an open society in the electronic age. &amp;hellip; We cannot expect governments, corporations, or other large, faceless organizations to grant us privacy &amp;hellip; We must defend our own privacy if we expect to have any. &amp;hellip; Cypherpunks write code. We know that someone has to write software to defend privacy, and &amp;hellip; we&amp;rsquo;re going to write it.&lt;/p>
&lt;/blockquote>
&lt;p>To be clear, I&amp;rsquo;m not a cypherpunk by any means. Here&amp;rsquo;s a snippet from Wired magazine, 1993:&lt;/p>
&lt;blockquote>
&lt;p>The people in this room [cypherpunks] hope for a world where an individual&amp;rsquo;s informational footprints &amp;ndash; everything from an opinion on abortion to the medical record of an actual abortion &amp;ndash; can be traced only if the individual involved chooses to reveal them;&lt;/p>
&lt;/blockquote>
&lt;p>And this is where the problem lies. Yesterday Will Wheaton, of Star Trek fame, &lt;a href="https://plus.google.com/108176814619778619437/posts/3o79SJWv4kG">wrote about&lt;/a> this as well.&lt;/p>
&lt;p>Will has a great point that we&amp;rsquo;ve come to a point where it&amp;rsquo;s okay that somebody else shares your personal details with a third party on the internet – without your knowledge, without your consent. And there is nothing you can do about it.&lt;/p>
&lt;p>I&amp;rsquo;m not sure about you, but this sure frightens me.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2012/12/18/instagram-governments-and-cypherpunks/</guid><pubDate>Tue, 18 Dec 2012 00:00:00 +0000</pubDate></item><item><title>Binary debugging with git bisect</title><link>https://www.devroom.io/2012/11/29/binary-debugging-with-git-bisect/</link><description>&lt;p>Part of resolving a bug is finding where and when that bug was introduced into your code. Not so much for blaming a specific person, but more for an understanding of how and maybe why the bug was introduced; and more over which versions of your app are affected.&lt;/p>
&lt;p>Most of the time the bug was recently introduced and your CI notified you that stuff has been broken.&lt;/p>
&lt;p>In order to find out when, how and by whom the build was broken, you&amp;rsquo;ll have to dig into your git history and run your specs to see if they pass or not.&lt;/p>
&lt;p>Running your specs for every commit in your history manually is very time consuming and boring. Luckily there are better ways, using plain old git.&lt;/p>
&lt;h2 id="binary-search">Binary search&lt;/h2>
&lt;p>Before I dive into git, it&amp;rsquo;s important you understand how binary search works. If you already know this stuff, skip right to the next section.&lt;/p>
&lt;p>You have a sorted array. This means there is some order to the elements you have. Presume you have an array of ints:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="n">a&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">[&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">4&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">7&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">33&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">42&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">54&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">76&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">89&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">91&lt;/span>&lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now, we want to find the position (&lt;code>n&lt;/code>) of &lt;code>7&lt;/code> in this array using binary search so that &lt;code>a[n] == 7&lt;/code>.&lt;/p>
&lt;p>Binary search uses a divide and conquer strategy. You split the array in the middle. We have 10 elements, so a logical place would be to split the array at position &lt;code>n = 5&lt;/code>, which has the value &lt;code>42&lt;/code>.&lt;/p>
&lt;p>Comparing &lt;code>7 &amp;lt;=&amp;gt; 42&lt;/code> tells us that, because we have an ordered array, the value &lt;code>7&lt;/code> should be in the first half of the array.&lt;/p>
&lt;p>We can ignore the right half of the array for searching, and repeat this step for the left part, specifically:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">4&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">7&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">33&lt;/span>&lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>So, let&amp;rsquo;s split this part up again. We get &lt;code>4&lt;/code>. This is less than &lt;code>7&lt;/code>, so if we continue looking we should take the right part.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>&lt;span class="mi">7&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">33&lt;/span>&lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Okay, again, we continue our search. We know have to split the array one way or the other, and we end up picking &lt;code>n = 4&lt;/code>. We hit &lt;code>33&lt;/code>. Surely the value of &lt;code>7&lt;/code> must be on the left part of this.&lt;/p>
&lt;p>&lt;em>Note that although I don&amp;rsquo;t show the whole array, I&amp;rsquo;m still using the index positions for the entire &lt;code>a&lt;/code> array.&lt;/em>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>&lt;span class="mi">7&lt;/span>&lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now, there&amp;rsquo;s not much to pick for us. This is 7. Right here at &lt;code>n = 3&lt;/code>. Done!&lt;/p>
&lt;p>Notice that the last step does not involve checking the value of the last element. We only have 1 element left, so we&amp;rsquo;re finished.&lt;/p>
&lt;p>If we had been looking for a value of 18, we would have also found &lt;code>n = 3&lt;/code>. This means that we can search for non-existing values, which then return the index right before where that number should be inserted. This works because the array was ordered, so we can safely make such assumptions. Nice, huh?&lt;/p>
&lt;h2 id="how-does-binary-search-relate-to-finding-bugs">How does binary search relate to finding bugs?&lt;/h2>
&lt;p>Well, in the example above we were looking for an integer value. The test we use to evaluate is simple:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="n">value&lt;/span> &lt;span class="o">&amp;lt;=&amp;gt;&lt;/span> &lt;span class="n">a&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="n">n&lt;/span>&lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>When we want to find the commit that broke our build we need a more clever way of comparing values.&lt;/p>
&lt;p>This is where your test suite comes in. You have a failing spec now, so basically we&amp;rsquo;re looking for the point in git history where that spec failed for the first time.&lt;/p>
&lt;h2 id="git-bisect">Git bisect&lt;/h2>
&lt;p>Because it&amp;rsquo;s not feasible to do a linear search over your entire commit history, we&amp;rsquo;ll have to start by marking a &lt;em>good&lt;/em> and a &lt;em>bad&lt;/em> commit.&lt;/p>
&lt;p>First we&amp;rsquo;ll have to find a location in your git history where you know the app did not exhibit this bug.&lt;/p>
&lt;p>More than likely, this place is your last stable release. If you used &lt;code>git tag&lt;/code> to tag your release, you should be able to find out quickly if that release contains the bug.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">git checkout v1.2.1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">rspec spec/features/fancy_spec.rb
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">=&lt;/span>&amp;gt; &lt;span class="m">0&lt;/span> failures
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Good. We now our latest commit was broken, so let&amp;rsquo;s get started with that binary search!&lt;/p>
&lt;p>You don&amp;rsquo;t have to keep track of the git history and binary search position all by yourself: git does this for you. All you have to do is compare each commit that is presented to you with an expected result. E.g. does the &lt;code>fancy_spec.rb&lt;/code> spec pass or not?&lt;/p>
&lt;h2 id="bisecting-steps">Bisecting steps&lt;/h2>
&lt;p>First, let git know you want to do a binary search.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">git bisect start
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Next, let git know which commit is good, and which one is bad.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">git bisect good v1.2.1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">git bisect bad e8ab31
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Git will respond with something like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">Bisecting: &lt;span class="m">19&lt;/span> revisions left to &lt;span class="nb">test&lt;/span> after this &lt;span class="o">(&lt;/span>roughly &lt;span class="m">4&lt;/span> steps&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>0e70ee6aefe428fa897ec7e48273c3fe4d0bf7fb&lt;span class="o">]&lt;/span> Did some funky stuff in &lt;span class="sb">`&lt;/span>config/application.rb&lt;span class="sb">`&lt;/span>.
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Git has determined that commit &lt;code>0e70ee&lt;/code> is right in between &lt;code>v1.2.1&lt;/code> and &lt;code>ebab31&lt;/code>. Check if this revision is broken and report back to git.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">rspec spec/features/fancy_spec.rb
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;gt; &lt;span class="m">1&lt;/span> failure
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">git bisect bad
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>And git will respond:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">Bisecting: &lt;span class="m">9&lt;/span> revisions left to &lt;span class="nb">test&lt;/span> after this &lt;span class="o">(&lt;/span>roughly &lt;span class="m">3&lt;/span> steps&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>You can continue this until git tells you the commit that first broke this spec.&lt;/p>
&lt;p>When you&amp;rsquo;re done bisecting (or if you just feel like doing something else), just tell git:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">git bisect reset
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="usage-in-the-field">Usage in the field&lt;/h2>
&lt;p>Git bisect is a very powerful tool to find specific points in your code.&lt;/p>
&lt;p>In this example we were looking for a commit that broke a specific test. You can look for all kinds of things:&lt;/p>
&lt;ul>
&lt;li>When did the layout on that page break?&lt;/li>
&lt;li>At what point did we upgrade that gem to a buggy release?&lt;/li>
&lt;li>Who added this ugly piece of code?!&lt;/li>
&lt;/ul>
&lt;p>Doing a binary search over your git history is a fast and efficient way of finding these kind of things out.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2012/11/29/binary-debugging-with-git-bisect/</guid><pubDate>Thu, 29 Nov 2012 00:00:00 +0000</pubDate></item><item><title>CustoMac</title><link>https://www.devroom.io/2012/11/22/customac/</link><description>&lt;p>Ever since Apple decided to put Intel processors in their Macs there have been attempts by enthusiasts to run Mac OS X on commodity hardware – with mixed results.&lt;/p>
&lt;p>The key to installing Mac OS X on a non-Mac computer is using the right hardware. If your hardware is a close to Apple kit as possible, you have the best chance to succeed.&lt;/p>
&lt;p>The so-called &lt;em>Hackintosh&lt;/em> community has come a long way the past few years in making it easy for &amp;ldquo;normal people&amp;rdquo; to install Mac OS X on their &lt;em>PC&lt;/em>.&lt;/p>
&lt;p>Since I was tired of using Debian Linux on my Desktop, dual booting to Windows to play the occasional game of &lt;em>World of Warcraft&lt;/em>, I decided to give installing Mac OS X a try. But there was a problem.&lt;/p>
&lt;h2 id="the-hardware">The hardware&lt;/h2>
&lt;p>My current PC is powered by an AMD processor (AMD Phenom II X6 1055T, to be precise). Simply put, installing Mac OS X on an AMD cpu is not going to work.&lt;/p>
&lt;p>So, the first thing I did was go over to the &lt;a href="http://wiki.osx86project.org/wiki/index.php/HCL_10.8.2">OSx86 Project Hardware Compatibility List&lt;/a> and see what hardware is most compatible with Mac OS X 1.8.2 (the most recent version of OS X at this time).&lt;/p>
&lt;p>I already knew I&amp;rsquo;d need a shiny new Intel CPU, and thus also a new motherboard.&lt;/p>
&lt;h2 id="choosing-a-cpu">Choosing a CPU&lt;/h2>
&lt;p>Choosing a CPU for your CustoMac is not very difficult, because you are limited to Intel. Since the release of the new MacBooks Apple officially supports the &lt;a href="http://en.wikipedia.org/wiki/Ivy_Bridge_%28microarchitecture%29">Ivy Bridge&lt;/a> architecture, which means about a 50% lower power consumption and a 5-15% speed increase (&lt;a href="http://en.wikipedia.org/wiki/Ivy_Bridge_%28microarchitecture%29">link&lt;/a>).&lt;/p>
&lt;p>The main candidates were:&lt;/p>
&lt;ul>
&lt;li>Intel i5 3570&lt;/li>
&lt;li>Intel i5 3570K&lt;/li>
&lt;li>Intel i7 3770K&lt;/li>
&lt;/ul>
&lt;p>I won&amp;rsquo;t go into to too much detail here, but I chose the &lt;em>Intel i5 3570&lt;/em>.&lt;/p>
&lt;p>The i7 offers &lt;a href="http://en.wikipedia.org/wiki/Hyper-threading">Hyper Threading&lt;/a> at a price bump of about € 100,-. For me, this was not worth the money.&lt;/p>
&lt;p>Then there&amp;rsquo;s the choice between the &lt;em>3570&lt;/em> or the &lt;em>3570K&lt;/em>. The &amp;lsquo;K&amp;rsquo; version has less features, but is &lt;em>unlocked&lt;/em>, allow it to be easily over-clocked to higher speeds.&lt;/p>
&lt;p>The price difference between the &lt;em>3570&lt;/em> and the &lt;em>3570&lt;/em> are minimal, but I&amp;rsquo;m not planning to over-clock my CPU, so I went for the slightly cheaper &lt;em>Intel i5 3570&lt;/em> processor.&lt;/p>
&lt;h2 id="choosing-a-motherboard">Choosing a motherboard&lt;/h2>
&lt;p>Next came a more difficult decision. The motherboard. Again, the &lt;a href="http://wiki.osx86project.org/wiki/index.php/HCL_10.8.2">Hardware Compatibility List&lt;/a> was a great help here. In the end I chose the &lt;em>Gigabyte Z77-DS3H&lt;/em>.&lt;/p>
&lt;p>The pro&amp;rsquo;s of this board are that it&amp;rsquo;s well supported by Mac OS X and the OSx86 community. This board is special because it features Gigabyte&amp;rsquo;s 3D UEFI Bios. This bios would make it easy to install Mac OS X &lt;em>untouched&lt;/em> on your machine.&lt;/p>
&lt;p>I didn&amp;rsquo;t end up using this UEFI feature, but nonetheless, support for this board is incredible.&lt;/p>
&lt;h2 id="the-full-hardware-list">The full hardware list&lt;/h2>
&lt;p>So, recycling other parts of my current &lt;em>PC&lt;/em> I build the following CustoMac configuration:&lt;/p>
&lt;ul>
&lt;li>Gigabyte Z77-DS3H Motherboard (&lt;a href="http://www.amazon.com/gp/product/B007R21JWC/ref=as_li_ss_tl?ie=UTF8&amp;camp=1789&amp;creative=390957&amp;creativeASIN=B007R21JWC&amp;linkCode=as2&amp;tag=ariejannet-20">But at Amazon&lt;/a>)&lt;/li>
&lt;li>Intel i5 3570 CPU @3.4Ghz (&lt;a href="http://www.amazon.com/gp/product/B0087EVHTE/ref=as_li_ss_tl?ie=UTF8&amp;camp=1789&amp;creative=390957&amp;creativeASIN=B0087EVHTE&amp;linkCode=as2&amp;tag=ariejannet-20">Buy at Amazon&lt;/a>)&lt;/li>
&lt;li>16GB RAM at 1333Mhz&lt;/li>
&lt;li>1x 120 GB OCZ Agility 3 SSD&lt;/li>
&lt;li>1x 1TB Western Digital HD&lt;/li>
&lt;li>1x 2TB Western Digital HD&lt;/li>
&lt;li>XFX ATI Radeon HD 6870 1GB (&lt;a href="http://www.amazon.com/gp/product/B0047ZH7GE/ref=as_li_ss_tl?ie=UTF8&amp;camp=1789&amp;creative=390957&amp;creativeASIN=B0047ZH7GE&amp;linkCode=as2&amp;tag=ariejannet-20">Buy at Amazon&lt;/a>)&lt;/li>
&lt;/ul>
&lt;p>&lt;em>Note 1: The above Amazon links are affiliate links.&lt;/em>&lt;/p>
&lt;p>&lt;em>Note 2: I could have upgraded my memory to 1600Mhz units, which would be faster. But I have no use for the old memory, so I chose to re-use it for now.&lt;/em>&lt;/p>
&lt;h2 id="the-preparation">The preparation&lt;/h2>
&lt;p>Before you get started, you should prepare an installation USB drive on another Mac. You&amp;rsquo;ll need at least 8GB of space on the drive.&lt;/p>
&lt;ul>
&lt;li>Buy Mountain Lion from the App Store on you Mac and download the installer. If you already purchased Mountain Lion, re-download it.&lt;/li>
&lt;li>Download &lt;a href="http://www.tonymacx86.com/downloads">UniBeast&lt;/a> for Mountain Lion and follow step 1 and 2 from &lt;a href="http://tonymacx86.blogspot.nl/2011/10/unibeast-install-mac-os-x-lion-using.html">this guide&lt;/a>.&lt;/li>
&lt;/ul>
&lt;p>At this point you have a bootable USB drive with the Mountain Lion install on it.&lt;/p>
&lt;p>To prepare the actual installation, remove any devices you don&amp;rsquo;t need, like extra hard drivers, DVD/BluRay drives, etc. In my case I also pulled out the ATI 6870 and used the onboard Intel HD Graphics during installation.&lt;/p>
&lt;p>Plug your USB drive into a USB 2.0 (black, not blue) port on the motherboard. Make sure to use one of the ports on the back of your computer, those are directly attached to the motherboard and have the greatest chance of succes.&lt;/p>
&lt;p>Now, boot up the computer and enter the BIOS. There are two important changes you need to make.&lt;/p>
&lt;ul>
&lt;li>Set SATA to &lt;code>AHCI&lt;/code> mode.&lt;/li>
&lt;li>Disable &lt;code>VT-d&lt;/code>.&lt;/li>
&lt;/ul>
&lt;p>Then select the USB drive as the bootable device and boot.&lt;/p>
&lt;h2 id="booting-the-installer">Booting the installer&lt;/h2>
&lt;p>You&amp;rsquo;ll see the UniBeast boot screen which show a &amp;lsquo;USB&amp;rsquo; option (and possibly other, depending on what&amp;rsquo;s on your disks). Choose &amp;lsquo;USB&amp;rsquo; - but don&amp;rsquo;t press &lt;code>ENTER&lt;/code> just yet. Instead, type &lt;code>-x&lt;/code>, which will show up on the screen. Then, press &lt;code>ENTER&lt;/code>.&lt;/p>
&lt;p>After a few minutes you should have the Mac OS X Installer in front of you. Go ahead, install this baby.&lt;/p>
&lt;h2 id="notes-on-fusion-drive">Notes on Fusion drive&lt;/h2>
&lt;p>It&amp;rsquo;s possible to create a CustoMac Fusion Drive using an SSD and regular harddisk.&lt;/p>
&lt;p>When you&amp;rsquo;re in the installer, choose &amp;lsquo;Terminal&amp;rsquo; to open a terminal window and follow the steps in &lt;a href="http://jollyjinx.tumblr.com/post/34638496292/fusion-drive-on-older-macs-yes-since-apple-has">this fusion drive guide&lt;/a>.&lt;/p>
&lt;p>I was able to create and install Mac OS X on a Fusion Drive without problems. The only knack was that the custom bootloader you need is not Fusion Drive aware, which makes it difficult to use.&lt;/p>
&lt;p>In the end I decided to not use a Fusion drive setup, and just install everything on the SSD.&lt;/p>
&lt;h2 id="completing-the-installation">Completing the installation&lt;/h2>
&lt;p>Now comes the tricky part. The installation is done and you&amp;rsquo;re CustoMac wants to reboot. Let it, but leave the USB drive connected.&lt;/p>
&lt;p>Your machine will boot up with the same boot menu as before, but instead of &amp;lsquo;USB&amp;rsquo; you should now be able to select &amp;lsquo;Macintosh HD&amp;rsquo;.&lt;/p>
&lt;p>Select that, enter &lt;code>-x&lt;/code> followed by &lt;code>ENTER&lt;/code>.&lt;/p>
&lt;p>You&amp;rsquo;ll now be taken through the final steps of installation, like setting up iCloud and creating a user account.&lt;/p>
&lt;p>When finished, you should be on your new Mac desktop.&lt;/p>
&lt;h2 id="installing-custom-kexts">Installing custom kexts&lt;/h2>
&lt;p>Now, your CustoMac can only boot with the USB drive. Let&amp;rsquo;s change that by installing a bootloader and some kernel extensions.&lt;/p>
&lt;ul>
&lt;li>Download &lt;a href="http://www.tonymacx86.com/downloads">MultiBeast&lt;/a> for Mountain Lion.&lt;/li>
&lt;li>Run the installer and select the following options:
** UserDSDT or DSDT-Free Installation
** Miscellaneous =&amp;gt; FakeSMC
** Audio =&amp;gt; Realtek ALC8xx =&amp;gt; Without DSDT =&amp;gt; Latest version for ALC8887/888b.
** Network =&amp;gt; maolj&amp;rsquo;s AtherosL1cEthernet
** Disk =&amp;gt; TRIM fix for 1.8.1+
** Bootloaders =&amp;gt; Chimera&lt;/li>
&lt;/ul>
&lt;p>That&amp;rsquo;s all. Install that stuff. Now, you should be able to reboot your CustoMac and boot it without the USB drive.&lt;/p>
&lt;p>If things don&amp;rsquo;t work out (like a black or white screen, kernel panics, whatever), just plugin your USB drive again, boot from it and select your &amp;lsquo;Macintosh HD&amp;rsquo;.&lt;/p>
&lt;h2 id="graphics">Graphics&lt;/h2>
&lt;p>At this point, you should have a working CustoMac with sound and network working. The only thing missing is a proper graphics card.&lt;/p>
&lt;p>You&amp;rsquo;ll need to make some tweaks to the chameleon plist file. Then shutdown your computer and install the graphics card.&lt;/p>
&lt;p>&lt;em>Note: this works for my XFX ATI Radeon HD 6870 card. There may be subtle differences for different version and brands. Just use the Google to find hints, boot with the USB drive to get to your system and make updates as needed.&lt;/em>&lt;/p>
&lt;p>In &lt;code>/Extra/org.chameleon.Boot.plist&lt;/code> make sure you have the following entries:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;key&amp;gt;&lt;/span>AtiConfig&lt;span class="nt">&amp;lt;/key&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;string&amp;gt;&lt;/span>Duckweed&lt;span class="nt">&amp;lt;/string&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;key&amp;gt;&lt;/span>AtiPorts&lt;span class="nt">&amp;lt;/key&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;string&amp;gt;&lt;/span>5&lt;span class="nt">&amp;lt;/string&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;key&amp;gt;&lt;/span>Graphics Mode&lt;span class="nt">&amp;lt;/key&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;string&amp;gt;&lt;/span>1920x1080x32&lt;span class="nt">&amp;lt;/string&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;key&amp;gt;&lt;/span>GraphicsEnabler&lt;span class="nt">&amp;lt;/key&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;string&amp;gt;&lt;/span>Yes&lt;span class="nt">&amp;lt;/string&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;key&amp;gt;&lt;/span>PciRoot&lt;span class="nt">&amp;lt;/key&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;string&amp;gt;&lt;/span>1&lt;span class="nt">&amp;lt;/string&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Depending on which slot you used for your graphics card, you may have to set &lt;code>PciRoot&lt;/code> to &lt;code>0&lt;/code>.&lt;/p>
&lt;p>I also had to add &lt;code>npci=0x2000&lt;/code> to &lt;code>Kernel Flags&lt;/code>, but you may or may not need it.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;key&amp;gt;&lt;/span>Kernel Flags&lt;span class="nt">&amp;lt;/key&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="boot-up">Boot up!&lt;/h2>
&lt;p>Now, reboot one last time and everything should go smoothly.&lt;/p>
&lt;p>If your system comes up without any troubles, start attaching those others disks and drives you had disconnected during the installation. There shouldn&amp;rsquo;t be any issues here.&lt;/p>
&lt;h2 id="youre-done">You&amp;rsquo;re done!&lt;/h2>
&lt;p>Congratulations. You now have a CustoMac!&lt;/p>
&lt;p>Keep in mind that you should not blindly install any update you see. Installing an update my change the bootloader or change kernel extensions that break your system.&lt;/p>
&lt;p>A good tip is to create a full disk image of your SSD using a tool like &lt;a href="http://www.shirt-pocket.com/SuperDuper/SuperDuperDescription.html">Super Duper&lt;/a>. In case of shit hitting the fan after an update, you can easily restore your disk to working order.&lt;/p>
&lt;p>In my case, I&amp;rsquo;ve attached an old 500GB drive to store this disk image. It works great.&lt;/p>
&lt;h2 id="shiny-and-fast">Shiny and fast!&lt;/h2>
&lt;p>Just as a side-note, my CustoMac is blazingly fast. It&amp;rsquo;s the combination of the fast &lt;em>Ivy Bridge&lt;/em> architecture, the i5 processor, and the SSD.&lt;/p>
&lt;p>I&amp;rsquo;ve measured boot-up time from pressing the power button to the Mac OS X login screen at about 11 seconds.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2012/11/22/customac/</guid><pubDate>Thu, 22 Nov 2012 00:00:00 +0000</pubDate></item><item><title>A Call to all CI Service Providers</title><link>https://www.devroom.io/2012/11/05/a-call-to-all-ci-service-providers/</link><description>&lt;p>As a professional developer I test my code. Every check-in I do is tested either on &lt;a href="http://kabisa.nl">Kabisa&lt;/a>&amp;rsquo;s &lt;a href="http://jenkins-ci.org">Jenkins&lt;/a> server or on &lt;a href="http://travis-ci.com">Travis CI Pro&lt;/a>.&lt;/p>
&lt;p>For open source projects there&amp;rsquo;s &lt;a href="http://travis-ci.org">Travis CI&lt;/a>. It&amp;rsquo;s free and a great way to get to know Travis.&lt;/p>
&lt;p>Now, as an individual I have some side projects, most notably my own site, &lt;a href="http://ariejan.net">Ariejan.net&lt;/a>. I value well written and well tested code as much for my own stuff as for my professional work. However, I don&amp;rsquo;t have a Jenkins CI server in my basement.&lt;/p>
&lt;p>Looking around the internets there are a few CI services that can test my Ariejan.net Rails application, among them &lt;a href="https://circleci.com">Cirlce CI&lt;/a>, &lt;a href="http://www.atlassian.com/software/bamboo/">Atlassian Bamboo&lt;/a> and &lt;a href="https://semaphoreapp.com">Semaphore&lt;/a>.&lt;/p>
&lt;p>Now, the thing here is that to test the few projects I have (about four) it will cost me more to have the hosted in a CI services than actually running these apps.&lt;/p>
&lt;ul>
&lt;li>Travis - $129/mo for 2 concurrent builds.&lt;/li>
&lt;li>Circle CI - $49/mo for 10 private projects.&lt;/li>
&lt;li>Semaphore - $39/mo for 5 private projects.&lt;/li>
&lt;li>Bamboo - $20/mo for 10 jobs.¹&lt;/li>
&lt;/ul>
&lt;p>&lt;em>¹ Bamboo advertises $10/mo, but a $10/mo Jira subscription is also required.&lt;/em>&lt;/p>
&lt;p>As an individual, these prices are pretty steep for the few commits I make during the weekend.&lt;/p>
&lt;h3 id="looking-at-github-and-heroku">Looking at Github and Heroku&lt;/h3>
&lt;p>Let&amp;rsquo;s take a look at github and heroku here. At heroku I pay nothing to host a site. Performance is limited, but it more often than not is sufficient for a side-project. Adding a worker or extra dyno costs about $38/mo. Still less than half of the CI services I mentioned.&lt;/p>
&lt;p>Having private repos with Github is also quite cheap. You&amp;rsquo;ll get 5 for $7/mo. Which, converted, is about €1 per repo a month.&lt;/p>
&lt;p>Now, as an individual developer, I&amp;rsquo;m more than happy to use these services for these relatively low fees.&lt;/p>
&lt;h3 id="where-are-the-cheap-individual-ci-services">Where are the cheap, individual CI services?&lt;/h3>
&lt;p>As an individual developer, this is what I need:&lt;/p>
&lt;ul>
&lt;li>Unlimited, or at least 5-10 jobs/private projects.&lt;/li>
&lt;li>Low monthly cost: $5 - $10.&lt;/li>
&lt;/ul>
&lt;p>What I don&amp;rsquo;t need is:&lt;/p>
&lt;ul>
&lt;li>Concurrency and paralellisation&lt;/li>
&lt;li>Immediate execution&lt;/li>
&lt;li>24/7 Support&lt;/li>
&lt;/ul>
&lt;p>I can do with a single worker doing one build at a time. I don&amp;rsquo;t write enterprise CRM software in my weekends, you know.&lt;/p>
&lt;p>Also, I don&amp;rsquo;t expect my build to execute immediately. I don&amp;rsquo;t mind if there&amp;rsquo;s a job queue and I have to wait a bit longer to get back results.&lt;/p>
&lt;h3 id="business-opportunity">Business opportunity?&lt;/h3>
&lt;p>IMHO there&amp;rsquo;s a business opportunity here. Github and Heroku are great examples of how giving something away for free (or cheap) to individual developers creates a market for their product.&lt;/p>
&lt;p>I understand that CI takes a considerable amount of system resources and time, but having a good experience as an individual is more than likely to attrack people to your platform when they need a more profession plan.&lt;/p>
&lt;p>Who&amp;rsquo;s stepping up to the plate?&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2012/11/05/a-call-to-all-ci-service-providers/</guid><pubDate>Mon, 05 Nov 2012 00:00:00 +0000</pubDate></item><item><title>Decorating Sorcery's current_user with Draper</title><link>https://www.devroom.io/2012/11/02/decorating_sorcery_current_user_with_draper/</link><description>&lt;p>I already &lt;a href="http://ariejan.net/2012/04/14/decorating-devise-s-current_user-with-draper">wrote&lt;/a> about how to apply your decorator to the &lt;code>current_user&lt;/code> when you&amp;rsquo;re using &lt;a href="https://github.com/plataformatec/devise">Devise&lt;/a>. However, the trick is a bit different when applied to &lt;a href="https://github.com/NoamB/sorcery">Sorcery&lt;/a>.&lt;/p>
&lt;p>Instead of being &lt;code>nil&lt;/code> when no user is signed in, Sorcery uses an explicit &lt;code>false&lt;/code> value, no &lt;code>nil&lt;/code>. In your &lt;code>ApplicationController&lt;/code> at &lt;code>app/controllers/application_controller.rb&lt;/code> add this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">current_user&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="no">UserDecorator&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">decorate&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">super&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">unless&lt;/span> &lt;span class="k">super&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="kp">false&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>I&amp;rsquo;m using the most recent version of &lt;a href="https://github.com/drapergem/draper">Draper&lt;/a> by the way.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2012/11/02/decorating_sorcery_current_user_with_draper/</guid><pubDate>Fri, 02 Nov 2012 00:00:00 +0000</pubDate></item><item><title>A static file server in Go</title><link>https://www.devroom.io/2012/10/04/a-static-file-server-in-go/</link><description>&lt;p>If you don&amp;rsquo;t know Go, you should really look into it. Today I was trying to figure out how to write a simple (and fast) static file server in Go.&lt;/p>
&lt;p>As it turns out, this is very easy to do. Go contains (in the &lt;code>net/http&lt;/code> package) a nice &lt;code>FileServer&lt;/code> type that can server files from the directory you point it to.&lt;/p>
&lt;p>Here&amp;rsquo;s a sweet and short example:&lt;/p>
&lt;pre>&lt;code>package main
import (
&amp;quot;net/http&amp;quot;
&amp;quot;log&amp;quot;
)
func main() {
err := http.ListenAndServe(&amp;quot;:4242&amp;quot;, http.FileServer(http.Dir(&amp;quot;public&amp;quot;)))
if err != nil {
log.Printf(&amp;quot;Error running web server for static assets: %v&amp;quot;, err)
}
}
&lt;/code>&lt;/pre>
&lt;p>By itself this is not very useful, but you can easily integrate this into any other http server you create, maybe for handling dynamic requests or doing web sockets.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2012/10/04/a-static-file-server-in-go/</guid><pubDate>Thu, 04 Oct 2012 00:00:00 +0000</pubDate></item><item><title>Migrate git repositories</title><link>https://www.devroom.io/2012/10/01/migrate-git-repositories/</link><description>&lt;p>Sometimes you have to move your git repository to another host. In this case I want to move a privately hosted git repository to a brand spanking new github repository.&lt;/p>
&lt;p>These are four easy steps to get that done:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">git clone --bare git@yourserver.com:project.git
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> project.git
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">git push --mirror git@github.com:ariejan/project.git
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> .. &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> rm -rf project.git
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>That&amp;rsquo;s it. Don&amp;rsquo;t forget to update the &lt;code>remote&lt;/code> of your working copy accordingly:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">git remote set-url origin git@github.com:ariejan/project.git
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Of course, this works with any git server or service, not just Github, although Github is awesome and you should use it.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2012/10/01/migrate-git-repositories/</guid><pubDate>Mon, 01 Oct 2012 00:00:00 +0000</pubDate></item><item><title>Git: Interactive Revert</title><link>https://www.devroom.io/2012/09/04/git-interactive-revert/</link><description>&lt;p>I recently made a commit in a project that, mistakenly, included changes to &lt;code>db/schema.rb&lt;/code>. My local schema was out of date and this could cause trouble for the others in my team.&lt;/p>
&lt;p>Luckily we use &lt;a href="http://nvie.com/posts/a-successful-git-branching-model/">a successful git branching model&lt;/a> so my changes were still up for review by the team.&lt;/p>
&lt;p>The change I made was part of larger commit. But all I wanted was to revert serveral chunks from &lt;code>db/schema.rb&lt;/code> that would cause trouble.&lt;/p>
&lt;h2 id="interactive-add">Interactive add&lt;/h2>
&lt;p>Now, there&amp;rsquo;s &lt;code>git add -i&lt;/code> which allows you to selectively stage chunks for the next commit. Unfortunately, the process for reversing interactively is a bit more complicated, but not much.&lt;/p>
&lt;h2 id="interactive-revert">Interactive revert&lt;/h2>
&lt;p>Well, let&amp;rsquo;s say you want to revert changes you made in commit &lt;code>f1e11c&lt;/code>. Go to your feature branch and revert your changes for that commit &lt;em>but do not commit them yet&lt;/em>, hence the &lt;code>-n&lt;/code> option:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">git revert -n f1e11c
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Your working copy now contains staged changes to revert the entire &lt;code>f1e11c&lt;/code> commit. You don&amp;rsquo;t want to revert everything, so unstage all those changes.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">git reset
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The key now is to interactively &lt;em>stage the changes you want to revert&lt;/em>. You can do this like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">git add -p
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>or&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">git add -i
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Choose whichever suits you.&lt;/p>
&lt;p>In my case I staged only the chunks that related to the changes in &lt;code>db/schema.rb&lt;/code> that I wanted to revert.&lt;/p>
&lt;p>With your reverting changes staged, commit them.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">git commit
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>You&amp;rsquo;re now left with reverting changes you didn&amp;rsquo;t want to make. Just throw them away.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">git reset --hard
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>And that&amp;rsquo;s all. You have now selectively reverted a commit.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2012/09/04/git-interactive-revert/</guid><pubDate>Tue, 04 Sep 2012 00:00:00 +0000</pubDate></item><item><title>Rails migrations: decimal precision and scale</title><link>https://www.devroom.io/2012/08/28/rails-migrations-decimal-precision-and-scale/</link><description>&lt;p>I&amp;rsquo;m always confused when using &lt;code>decimal&lt;/code> in a Rails migration. Normally I need to store a value that has 2 or 3 numbers behind the comma (or dot), or decimals.&lt;/p>
&lt;p>Let&amp;rsquo;s say you have a &lt;code>Product&lt;/code> model with a &lt;code>discount_percentage&lt;/code> attribute. This attribute is currently an integer, only allowing non-decimal values. To allow 2 digit decimal values (e.g. &lt;code>12.54&lt;/code>), you can mak the following migration:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="n">change_column&lt;/span> &lt;span class="ss">:products&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:discount_percentage&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:decimal&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">precision&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">5&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">scale&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">2&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This will allow you to store values like &lt;code>80.00&lt;/code>, &lt;code>99.99&lt;/code> and &lt;code>100.00&lt;/code>. There are five digits in the entire number and of those five, two decimals.&lt;/p>
&lt;p>It&amp;rsquo;s not necessary so specify &lt;code>precision&lt;/code> and &lt;code>scale&lt;/code> as your database will then allow any decimal value it can store.&lt;/p>
&lt;p>Read more about the &lt;code>decimal&lt;/code> data type in PostgreSQL here: &lt;a href="http://www.postgresql.org/docs/9.1/static/datatype-numeric.html">http://www.postgresql.org/docs/9.1/static/datatype-numeric.html&lt;/a>&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2012/08/28/rails-migrations-decimal-precision-and-scale/</guid><pubDate>Tue, 28 Aug 2012 00:00:00 +0000</pubDate></item><item><title>Getting started with Arduino</title><link>https://www.devroom.io/2012/08/27/getting-started-with-arduino/</link><description>&lt;p>I&amp;rsquo;m a software engineer. I don&amp;rsquo;t do hardware.&lt;/p>
&lt;p>Well, that used to be the case. I did build some PC&amp;rsquo;s back in the day, but that&amp;rsquo;s where my involvement with electronics hardware ended. That&amp;rsquo;s all changed after I purchased an &lt;a href="http://arduino.cc/">Arduino&lt;/a>.&lt;/p>
&lt;h2 id="arduino">Arduino&lt;/h2>
&lt;p>Arduino is an easy to use hardare &lt;em>and&lt;/em> software platform for designers, developers and tinkerers. What?&lt;/p>
&lt;p>Well, Arduino is more than just a small piece of electronics hardware. It does include hardware. This is the Arduino Uno, the latest incarnation and the board you&amp;rsquo;ll most likely purchase if you want to break into the world of Arduino&lt;/p>
&lt;p>&lt;img
src="https://ariejannet.s3.amazonaws.com/images/arduino/impression.jpeg"
alt="The Arduino Uno"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;p>This Arduino has some nice features you should know about. At its core it has the &lt;a href="http://www.atmel.com/devices/atmega328.aspx">Atmel Atmega328&lt;/a> chip. It has 32Kb of flashmemory and usually runs at 16MHz. Now this may not sound like much, but note that this is not like your average Intel Core i7 processor. This chip does several things for you, but the most important part is that you can program it to interact with the outside word through its 14 I/O pins. It gets its power over USB or from a battery that can supply 7-12V.&lt;/p>
&lt;p>As I said earlier, Arduino is more than just the hardware board. It also contains software. Software on the Atmega328 chip, called the bootloader, allows you to easily interact with the Arduino from your computer. Then there is the Arduino IDE, which you can install on Mac, Linux and Windows. This is the tool you write your code with and which allows you to upload that code to the Arduino.&lt;/p>
&lt;p>Now the great part is that both the hardware designs and the software are open-source. This means that you&amp;rsquo;re free to buy all the separate components for an Arduino board and solder one yourself . You may even sell it if you like. The only catch is that you can&amp;rsquo;t use the name &lt;em>Arduino&lt;/em>.&lt;/p>
&lt;h2 id="an-example">An example&lt;/h2>
&lt;p>The most basic thing you can do with Arduino is hook up a &lt;abbr title="Light Emitting Diode">LED&lt;abbr> and resistor and make the LED blink.&lt;/p>
&lt;p>This is what that would look like if you&amp;rsquo;d build it:&lt;/p>
&lt;p>&lt;img
src="https://ariejannet.s3.amazonaws.com/images/arduino/ExampleCircuit_bb.png"
alt="Arduino Blink Setup"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;p>This is the electrical schematic for this setup. It&amp;rsquo;s very straight forward, as you can see, but enough to get you started. Even if you don&amp;rsquo;t yet know how LED&amp;rsquo;s work or what a resistor does, you can make this easily enough.&lt;/p>
&lt;p>&lt;em>Note: if you do want to build this, I recommend you check out the &lt;a href="http://arduino.cc/en/Tutorial/blink">Blink tutorial&lt;/a> and buy an Arduino Starter Kit, which includes all the components to get started.&lt;/em>&lt;/p>
&lt;p>For those a bit tech-savvy, here&amp;rsquo;s the electrical schematic for the Blink tutorial. I&amp;rsquo;m not going to explain it in to detail, but note that the led and resistor are connected to &lt;code>D13&lt;/code> or pin 13 on the Arduino.&lt;/p>
&lt;p>&lt;img
src="https://ariejannet.s3.amazonaws.com/images/arduino/ExampleCircuit_sch.png"
alt="Arduino Blink Schematic"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;p>With the following, relatively simple code, you can make that LED blink.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-arduino" data-lang="arduino">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">int&lt;/span> &lt;span class="n">led&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">13&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// This refers to pin 13.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// setup() is run once during start-up
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kt">void&lt;/span> &lt;span class="nf">setup&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// initialize the digital pin as an output.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nf">pinMode&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">led&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kc">OUTPUT&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// the loop routine runs over and over again forever:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kt">void&lt;/span> &lt;span class="nf">loop&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nf">digitalWrite&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">led&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kc">HIGH&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// turn the LED on (HIGH is the voltage level)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nf">delay&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1000&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// wait for a second
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nf">digitalWrite&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">led&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kc">LOW&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// turn the LED off by making the voltage LOW
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nf">delay&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1000&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// wait for a second
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>After making this first step you&amp;rsquo;ll get to know more components and features of Arduino. You&amp;rsquo;ll start using sensors (buttons, light sensors, sound sensors, GPS, magnetometers, gas sensors) and you&amp;rsquo;ll start using actuators (motors, steppers, relais, LED matrixes). The possibilities are endless.&lt;/p>
&lt;h2 id="the-next-step">The next step&lt;/h2>
&lt;p>The great thing about Arduino is that it&amp;rsquo;s designed to be easy to use. But is also offers great possibilities.&lt;/p>
&lt;p>For one, the Arduino Uno has only got 14 digital and 6 analog I/O pins. When you start using more other components you&amp;rsquo;ll run out of I/O pins quickly. Enter the Arduino Mega which sports 54 digital and 16 analog I/O pins. Need something smaller: Arduino Nano, Need something to integrate into clothing: Arduino Lilypad. Need something custom: it&amp;rsquo;s frickin&amp;rsquo; open-source, so design your own &lt;abbr title="Printed Circuit Board">PCB&lt;/abbr>, add the Arduino components you need and you&amp;rsquo;re good to go.&lt;/p>
&lt;p>The things that are being doing with Arduino are amazing. The most important thing is that it makes electronics tinkering available to more people. It&amp;rsquo;s pretty cheap to get started and learn about electronics. Then there are hundreds (if not more) possibilities to use Arduino, ranging for fancy LED matrix animations to the full-grown &lt;a href="http://www.instructables.com/id/Arduino-RC-Lawnmower/">Lawn Bot 400&lt;/a>:&lt;/p>
&lt;p>&lt;img
src="https://ariejannet.s3.amazonaws.com/images/arduino/lawnbot.jpeg"
alt="Lawn Bot 400"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;h2 id="whats-got-all-this-to-do-with-ariejannet">What&amp;rsquo;s got all this to do with Ariejan.net?&lt;/h2>
&lt;p>Of course, I&amp;rsquo;m a Rails web developer, but Arduino has sparked my interest.&lt;/p>
&lt;p>Over at Kabisa we&amp;rsquo;ve already done a small side-project using Arduino to scan and log RFID-tags - we&amp;rsquo;ll be deploying this at a Rails conference later this year (and we&amp;rsquo;ll be using it to give away some awesome prizes).&lt;/p>
&lt;p>In the scarce spare-time I have I&amp;rsquo;m planning on going beyond the blinking LED and build a 4WD Robot. I&amp;rsquo;ll post my progress here at Ariejan.net. Keep an eye on the &lt;a href="http://ariejan.net/posts/tagged/arduino">Arduino section&lt;/a> for updates.&lt;/p>
&lt;p>It&amp;rsquo;ll take me some months to get my robot driving and doing the stuff I want it to do. But my dream project is building a Quadcopter.&lt;/p>
&lt;h2 id="what-about-you">What about you?&lt;/h2>
&lt;p>You are still reading this? And you didn&amp;rsquo;t get started with Arduino yet? Come on, find &lt;a href="http://arduino.cc/en/Main/Buy">your local Arduino distributor&lt;/a> or head over &lt;a href="http://arduino.cc/">http://arduino.cc&lt;/a> and get one. Start doing the tutorials and be ready to be pulled into the world of tinkering and hobby electronics. It&amp;rsquo;s fun!&lt;/p>
&lt;p>&lt;em>Images have been taken from &lt;a href="http://arduino.cc/">arduino.cc&lt;/a> with not so much permission.&lt;/em>&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2012/08/27/getting-started-with-arduino/</guid><pubDate>Mon, 27 Aug 2012 00:00:00 +0000</pubDate></item><item><title>Move your latest commits to a separate branch</title><link>https://www.devroom.io/2012/08/14/move-your-latest-commits-to-a-separate-branch/</link><description>&lt;p>The situation is pretty straightforward. You have been making commits for that new feature in your &lt;code>master&lt;/code> branch. Naughty you!&lt;/p>
&lt;p>Let&amp;rsquo;s assume you want to have this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">A - B - (C) - D - E - F
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>C&lt;/code> was the last commit you pulled from &lt;code>origin&lt;/code> and D, E and F are commits you just made but should have been in their own branch. This is what you wanted:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">A - B - (C)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> \ D - E F
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Step 1: Assuming you&amp;rsquo;re at &lt;code>F&lt;/code> on &lt;code>master&lt;/code>, create a new branch with those commits:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">git branch my_feature_branch
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Then, still on &lt;code>master&lt;/code>, reset back to commit &lt;code>C&lt;/code>. This is 3 commits back.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">git reset --hard HEAD~3
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Okay, you&amp;rsquo;re &lt;code>master&lt;/code> is now back at &lt;code>C&lt;/code>, which you lasted pulled, and the &lt;code>my_feature_branch&lt;/code> includes D, E and F. Just checkout &lt;code>my_feature_branch&lt;/code> and continue work as usual. I&amp;rsquo;m sure no one saw what you just did.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2012/08/14/move-your-latest-commits-to-a-separate-branch/</guid><pubDate>Tue, 14 Aug 2012 00:00:00 +0000</pubDate></item><item><title>VPN too complicated? Use a IP-over-SSH tunnel instead</title><link>https://www.devroom.io/2012/07/11/vpn-too-complicated-use-a-ip-over-ssh-tunnel-instead/</link><description>&lt;p>Some times you find yourself in a place where your Mac is safely tucked away behind a firewall. That&amp;rsquo;s great, but sometimes it is annoying as hell, because you &lt;em>need&lt;/em> to access resources over FTP or contact people who&amp;rsquo;re on IRC.&lt;/p>
&lt;p>The normal solution would be to setup a VPN with one of your servers elsewhere and connect to the outside world that way. Unfortunately, in all their wisdom, sys admins have probably closed up the proper ports to access your VPN server as well.&lt;/p>
&lt;p>As a last resort you might consider setting up a SSH Tunnel for a specific service like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">ssh -N user@server -L 3306:127.0.0.1:3306
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>But, this only works for a single port, and thus application. It may help, but it can become tedious pretty quickly. You also have to rewrite any configuration you had for connection to a remote host to use your localhost, most likely on some strange port.&lt;/p>
&lt;p>Luckily for us, there&amp;rsquo;s this awesome tool called &lt;a href="https://github.com/apenwarr/sshuttle/">sshuttle&lt;/a>.&lt;/p>
&lt;p>Sshuttle allows you to setup what&amp;rsquo;s called IP-over-SSH. Basically it runs a local proxy server to a remote server over SSH and changes the routing for your machine to send everything through that proxy.&lt;/p>
&lt;p>Besides giving you access to all the services you need, you also encrypt (e.g. hide) all your traffic from the prying eyes of any sys admins on the local network.&lt;/p>
&lt;p>Installing sshuttle on your Mac is a breeze&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">brew install sshuttle
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Then you can setup an IP-over-SSH connection to any remote server you have SSH access to. You&amp;rsquo;ll need your local admin password in order to setup routing properly.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">sshuttle -r username@server 0/0 -vv
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This routes all traffic over the tunnel towards &lt;code>server&lt;/code>. Use on of those online ip checkers to see that you&amp;rsquo;re actually using your &lt;code>server&lt;/code>&amp;rsquo;s IP address.&lt;/p>
&lt;p>In the future you may want to change the &lt;code>-vv&lt;/code> verbose option out and swap in &lt;code>-D&lt;/code> to run in daemon mode.&lt;/p>
&lt;p>The one thing this does &lt;em>not&lt;/em> do is DNS. DNS is still done using your locally configured DNS server, mostly for speed.&lt;/p>
&lt;p>Not to worry, you can go &amp;lsquo;full stealth&amp;rsquo; with the &lt;code>--dns&lt;/code> options, which also routes DNS over to the remote server:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">sshuttle --dns -r username@server 0/0 -vv
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>To stop using your IP-over-SSH connection, simply press CTRL-C twice and sshuttle should restore your normal networking connections.&lt;/p>
&lt;p>If sshuttle does not restore the connection properly, you can do so manually:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">sudo ipfw -q -f flush
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>I&amp;rsquo;ve already create a few aliases in my &lt;code>~/.zshrc&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">alias&lt;/span> &lt;span class="nv">tunnel&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;sshuttle -r ariejan@server 0/0 -vv&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">alias&lt;/span> &lt;span class="nv">tunnel_dns&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;sshuttle --dns -r ariejan@server 0/0 -vv&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">alias&lt;/span> &lt;span class="nv">reset_tunnel&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;sudo ipfw -q -f flush&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>So, no need to setup complicated VPN contraptions, just use plain old SSH and off you go.&lt;/p>
&lt;p>Bonus: you can also connect to a non-standard SSH port, in case port 22 has been blocked in the firewall as well:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">sshuttle --dns -r username@server:port 0/0 -vv
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2012/07/11/vpn-too-complicated-use-a-ip-over-ssh-tunnel-instead/</guid><pubDate>Wed, 11 Jul 2012 00:00:00 +0000</pubDate></item><item><title>Search and Replace in multiple files with Vim</title><link>https://www.devroom.io/2012/06/18/search-and-replace-in-multiple-files-with-vim/</link><description>&lt;p>I recently learned a nice VimTrick™ when pairing with &lt;a href="http://arjanvandergaag.nl">Arjan&lt;/a>. We upgrade an app to Rails 3.2.6 and got the following deprecation message:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">DEPRECATION WARNING: :confirm option is deprecated and will be removed from Rails 4.0.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Use &amp;#39;:data =&amp;gt; { :confirm =&amp;gt; &amp;#39;Text&amp;#39; }&amp;#39; instead.
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Well, nothing difficult about that, but we have quite a few &lt;code>:confirm&lt;/code> in this app.&lt;/p>
&lt;p>Firstly we checked where we used them (note we use ruby 1.9 hash syntax everywhere):&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">ack -l &lt;span class="s2">&amp;#34;confirm:&amp;#34;&lt;/span> app
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now you have a listing of all the files that contain the &lt;code>:confirm&lt;/code> hash key. You can leave out the &lt;code>-l&lt;/code> option to get some context for each find.&lt;/p>
&lt;p>Now, we need to open Vim with those files:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">ack -l &lt;span class="s2">&amp;#34;confirm:&amp;#34;&lt;/span> app &lt;span class="p">|&lt;/span> xargs -o vim
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Vim will open the first of these files. Here&amp;rsquo;s a snippet of what you may find:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-haml" data-lang="haml">= link_to &amp;#34;Delete&amp;#34;, something_path, confirm: &amp;#34;Are you sure?&amp;#34;
&lt;/code>&lt;/pre>&lt;p>Now, search and replace is easy:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-vim" data-lang="vim">&lt;span class="line">&lt;span class="cl">&lt;span class="p">:&lt;/span>%&lt;span class="nx">s&lt;/span>&lt;span class="sr">/confirm: &amp;#34;.*&amp;#34;/&lt;/span>&lt;span class="nx">data&lt;/span>: { &amp;amp; }/&lt;span class="nx">g&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This will surround the current confirm with the &lt;code>data&lt;/code> hash. Just the way Rails likes it. The &lt;code>&amp;amp;&lt;/code> character will be replaced with whatever text matched the search pattern.&lt;/p>
&lt;p>You could repeat this for every file manually. But, you&amp;rsquo;re using Vim.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-vim" data-lang="vim">&lt;span class="line">&lt;span class="cl">&lt;span class="p">:&lt;/span>&lt;span class="nx">argdo&lt;/span> %&lt;span class="nx">s&lt;/span>&lt;span class="sr">/confirm: &amp;#34;.*&amp;#34;/&lt;/span>&lt;span class="nx">data&lt;/span>: { &amp;amp; }/&lt;span class="nx">g&lt;/span> &lt;span class="p">|&lt;/span> &lt;span class="nx">update&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This will perform the search and replace on each of the supplied arguments (in this case the files selected with &lt;code>ack&lt;/code>) and update (e.g. save) those files.&lt;/p>
&lt;p>Now you can quit Vim and enjoy the glory of all the disappearing deprecation warnings.&lt;/p>
&lt;p>Note: to do this with the ruby 1.8 hash syntax, just update the search and replace texts accordingly.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2012/06/18/search-and-replace-in-multiple-files-with-vim/</guid><pubDate>Mon, 18 Jun 2012 00:00:00 +0000</pubDate></item><item><title>SEO is bullshit</title><link>https://www.devroom.io/2012/05/24/seo-is-bullshit/</link><description>&lt;p>I get quite a lot of &amp;ldquo;oh we can SEO optimize your site for you&amp;rdquo; emails lately. I don&amp;rsquo;t know why, but &lt;strong>SEO is bullshit&lt;/strong>. Really. SEO is one of those &lt;em>areas of expertise&lt;/em> that are total rubbish.&lt;/p>
&lt;h2 id="common-seo-advice">Common SEO advice&lt;/h2>
&lt;p>The most common advise regarding SEO includes:&lt;/p>
&lt;ul>
&lt;li>Getting links to your site&lt;/li>
&lt;li>Add tons of keywords so the Google Crawler knows what you&amp;rsquo;re talking about&lt;/li>
&lt;li>Add META data, lots of it&lt;/li>
&lt;li>Structure your page in some way for crawlers to understand better&lt;/li>
&lt;li>Add an XML sitemap&lt;/li>
&lt;/ul>
&lt;p>There might be some merit in this, but you are optimizing your site for a &lt;em>crawler&lt;/em>, a &lt;em>computer program&lt;/em> that interprets your site. Are crawlers your primary audience? If so, you have a sad site.&lt;/p>
&lt;p>&lt;em>SEO Hacking&lt;/em> is another common term I find in emails. SEO hacking tries to trick search engines to list your site higher for certain keywords. I won&amp;rsquo;t go into the details, but basically you try to cheat a search engine&amp;rsquo;s indexing rules.&lt;/p>
&lt;p>Some shady SEO companies even offer you a guarantee that you&amp;rsquo;ll be the number 1 listen for certain keywords. Really? But how valuable is that?&lt;/p>
&lt;p>The problem with doing a lot of SEO hacks on your site is this: search engines and crawlers are continuiously updated to prevent hacking. Although this is not disclosed by Google, there are rumours that if you do actually use &amp;lsquo;SEO hacks&amp;rsquo;, you&amp;rsquo;re site will be ranked lower, instead of higher.&lt;/p>
&lt;h2 id="the-best-seo-advice-you-can-get">The best SEO advice you can get&lt;/h2>
&lt;p>So, what can you do to make people find your site? Simple:&lt;/p>
&lt;p>&lt;strong>Write. Good. Content.&lt;/strong>&lt;/p>
&lt;p>Use headers like &lt;code>&amp;lt;h2&amp;gt;&lt;/code> and &lt;code>&amp;lt;strong&amp;gt;&lt;/code> and &lt;code>&amp;lt;em&amp;gt;&lt;/code> tags to highlight import terms or words in your text — this is valuable to your readers as well.&lt;/p>
&lt;p>If your content is good, readers will find it. Link to it. Tweet about it. And crawlers will &lt;em>always&lt;/em> be able to index your site properly. Of course, it helps to tell people about your article using social media and other news sites for your niche.&lt;/p>
&lt;h2 id="so-not-meta-tags-you-say">So, not META-tags you say?&lt;/h2>
&lt;p>No, that&amp;rsquo;s not whaty I&amp;rsquo;m saying. META tags &lt;em>do&lt;/em> prodide information to crawlers, you should not assume they use that information in any way. For example, Ariejan.net has these META-tags:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-html" data-lang="html">&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">meta&lt;/span> &lt;span class="na">content&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#39;About Software Engineering, Ruby on Rails, Java, Git and the Cloud - by Ariejan&amp;#39;&lt;/span> &lt;span class="na">name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#39;description&amp;#39;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">meta&lt;/span> &lt;span class="na">content&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#39;ruby, rubyonrails, rails, git, svn, mysql, mac, ios, apple, web, web2.0, development, dev&amp;#39;&lt;/span> &lt;span class="na">name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#39;keywords&amp;#39;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>In Google, this results in a view like this:&lt;/p>
&lt;p>&lt;img
src="https://ariejannet.s3.amazonaws.com/images/ariejan-seo-example.jpg"
alt="Google Search Result for Ariejan.net"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;p>Do note that the posts listed are considered popular by Google. Those are the post that are most visited and linked to on my site.&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>If you get an email, promising you that for $200 you&amp;rsquo;re site will be ranked nr. 1 for specific keywords: throw it away.&lt;/p>
&lt;p>The best SEO is writing awesome content people want to read.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2012/05/24/seo-is-bullshit/</guid><pubDate>Thu, 24 May 2012 00:00:00 +0000</pubDate></item><item><title>How Star Trek has shaped our technology and future</title><link>https://www.devroom.io/2012/05/23/how-star-trek-has-shaped-our-technology-and-future/</link><description>&lt;p>You have probably watched Star Trek. Even if you don&amp;rsquo;t like it, I bet you&amp;rsquo;ve seen at least a few episodes of &lt;em>The Next Generation&lt;/em> or a movie.&lt;/p>
&lt;p>Regardless of you opinion on Star Trek, its cool to see that current technology was only Science-Fiction ten or twenty years ago.&lt;/p>
&lt;p>Let&amp;rsquo;s start with a simple example - the Communicator. Press a button and talk to anyone wirelessly, wherever they are. Reminds you of that mobile phone you carry around all day, doesn&amp;rsquo;t it?&lt;/p>
&lt;p>Well, if that doesn&amp;rsquo;t convince you, then check out the &lt;a href="http://www.vocera.com/">Vocera B3000 Communication Bagde&lt;/a>&lt;/p>
&lt;p>But there&amp;rsquo;s more. Are you afraid of needles? Wouldn&amp;rsquo;t it be totally cool if they didn&amp;rsquo;t have to stick metal tubes into your arm to give you meds? Well, the can! Enter &lt;a href="http://www.sontra.com/technology/">SonoPrep&lt;/a> (Sorry, their site appears to be down). It&amp;rsquo;ll weaking your sking in 15 seconds and you&amp;rsquo;re ready to receive meds. You won&amp;rsquo;t feel a thing.&lt;/p>
&lt;p>Warp Speeds? Try &lt;a href="http://en.wikipedia.org/wiki/Hyperdrive">Hyperdrive&lt;/a>.&lt;/p>
&lt;p>Cloaking devices? &lt;a href="http://www.networkworld.com/community/node/13348?nwwpkg=slideshows">Got it&lt;/a>&lt;/p>
&lt;p>Phasers? &lt;a href="http://latimesblogs.latimes.com/technology/2011/04/navy-laser-weapon-fire-.html">Check&lt;/a>&lt;/p>
&lt;p>Medical Tricorder? &lt;a href="http://www.networkworld.com/community/node/18946?nwwpkg=slideshows">Sure, we can&lt;/a>&lt;/p>
&lt;p>Transparent Aluminium? &lt;a href="http://en.wikipedia.org/wiki/Aluminium_oxynitride">Yes, please&lt;/a>&lt;/p>
&lt;p>Okay, tractor beams then? &lt;a href="http://www.stanford.edu/group/blocklab/Optical%20Tweezers%20Introduction.htm">Oh, yeah&lt;/a>&lt;/p>
&lt;p>A huge library where you can just play about any tune in history? &lt;a href="http://www.spotify.com">Sweet mother of god, yes&lt;/a>&lt;/p>
&lt;p>Hand-Held devices that tell you your current location? &lt;a href="https://buy.garmin.com/shop/shop.do?pID=63349&amp;ra=true">We have those too&lt;/a>&lt;/p>
&lt;p>PADD&amp;rsquo;s? &lt;a href="http://www.apple.com/ipad/">Oh, yes!&lt;/a>&lt;/p>
&lt;p>Warp Core&amp;rsquo;s and photon torpedo&amp;rsquo;s? We&amp;rsquo;d need &lt;a href="http://en.wikipedia.org/wiki/Antimatter">Antimatter&lt;/a> for that, so we&amp;rsquo;re getting there.&lt;/p>
&lt;p>I must admit that I was quite surprised to find some of these things already exist in current technology! I wonder how long it will take before we build our first &lt;em>&amp;ldquo;USS Enterprise&amp;rdquo;&lt;/em>.&lt;/p>
&lt;p>Well, about 20 years.&lt;/p>
&lt;p>&lt;a href="http://www.buildtheenterprise.org/">Build the Enterprise&lt;/a> has thought about building a huge, Enterprise like, star ship. It&amp;rsquo;s entirely feasable to built on in the next 20 years with proper funding (about half of what NASA got during its Apollo missions).&lt;/p>
&lt;p>Theoretically the Enterprise could be ready in 20 years. It could then take you to Mars in 90 days. The moon would take you &amp;lsquo;only&amp;rsquo; 3.&lt;/p>
&lt;p>The future is now.&lt;/p>
&lt;p>Note: Probably no one can tell if a specific technology or device was inspired by Star Trek or not. But it&amp;rsquo;s very fascinating to see that stuff that was once thought-up by &lt;a href="http://en.wikipedia.org/wiki/Gene_Roddenberry">Gene Roddenberry&lt;/a> (and others) are already a reality.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2012/05/23/how-star-trek-has-shaped-our-technology-and-future/</guid><pubDate>Wed, 23 May 2012 00:00:00 +0000</pubDate></item><item><title>Ruby: regex scanning in a case statement</title><link>https://www.devroom.io/2012/05/22/ruby-regex-scanning-in-a-case-statement/</link><description>&lt;p>Here&amp;rsquo;s a handy ruby snippet that might come in handy one day.&lt;/p>
&lt;p>When the regex matches (input should end with &amp;quot; today&amp;quot;), you can directly grab the matched value using the special &lt;code>$1&lt;/code> variable.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="k">case&lt;/span> &lt;span class="n">input&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">when&lt;/span> &lt;span class="sr">/(.*)\stoday$/i&lt;/span> &lt;span class="k">then&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">puts&lt;/span> &lt;span class="s2">&amp;#34;Today: &lt;/span>&lt;span class="si">#{&lt;/span>&lt;span class="vg">$1&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>I think you can see how you can bend this to your own needs.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2012/05/22/ruby-regex-scanning-in-a-case-statement/</guid><pubDate>Tue, 22 May 2012 00:00:00 +0000</pubDate></item><item><title>What's causing all that disk I/O on my Mac?</title><link>https://www.devroom.io/2012/05/21/what-s-causing-all-that-disk-i-o-on-my-mac/</link><description>&lt;p>After doing a full re-install of my MacBook Pro a few weeks back to combat system slowness, I&amp;rsquo;m again struck with a slow system.&lt;/p>
&lt;p>The problem is that there&amp;rsquo;s a lot of disk I/O going on. CPU and memory are fine. I&amp;rsquo;m just not sure what&amp;rsquo;s causing this trouble.&lt;/p>
&lt;p>Luckily, there&amp;rsquo;s an easy solution to find out what&amp;rsquo;s doing disk I/O on you mac.&lt;/p>
&lt;p>Open up a terminal and run&lt;/p>
&lt;pre>&lt;code>$ sudo fs_usage
09:08:45.749208 read F=12 B=0x400 0.000210 W Sparrow.3845544
09:08:45.749274 lseek F=12 O=0x0602e000 &amp;lt;UNKNOWN&amp;gt; 0.000002 Sparrow.3845544
09:08:45.749279 read F=12 B=0x400 0.000005 Sparrow.3845544
09:08:45.749341 lseek F=12 O=0x06038c00 &amp;lt;UNKNOWN&amp;gt; 0.000002 Sparrow.3845544
09:08:45.750358 RdData[async] D=0x025aaf38 B=0x1000 /dev/disk0s2 0.028830 W Sparrow.3845547
09:08:45.750379 pread F=16 B=0x30 O=0x00b32730 0.028895 W Sparrow.3845547
09:08:45.750390 pread F=16 B=0x30 O=0x0de9a810 0.000008 Sparrow.3845547
09:08:45.750396 pread F=16 B=0x30 O=0x1f5ec5c0 0.000006 W Sparrow.3845547
09:08:45.750399 pread F=16 B=0x30 O=0x236c84a0 0.000003 Sparrow.3845547
09:08:45.750405 pread F=16 B=0x4b O=0x236c84b6 0.000002 Sparrow.3845547
09:08:45.750701 pread F=16 B=0x30 O=0x00c95f00 0.000007 Sparrow.3845547
09:08:45.758502 RdData[async] D=0x04f0b608 B=0x1000 /dev/disk0s2 0.009148 W
&lt;/code>&lt;/pre>
&lt;p>What you see here is Sparrow doing mad reads ot my disk. I&amp;rsquo;m not sure what it&amp;rsquo;s doing, but it must stop. After killing Sparrow disk I/O goes down to normal, workable levels again.&lt;/p>
&lt;p>Now, to pick another mail client for Mac&amp;hellip;&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2012/05/21/what-s-causing-all-that-disk-i-o-on-my-mac/</guid><pubDate>Mon, 21 May 2012 00:00:00 +0000</pubDate></item><item><title>Why Diablo 3's DRM is the best kind there is</title><link>https://www.devroom.io/2012/05/20/why-diablo-3-s-drm-is-the-best-kind-there-is/</link><description>&lt;p>I just tried to play Diablo 3, but could not log on due to server maintenance. Blizzard requires you to be &lt;em>always online&lt;/em> to play even single player campaigns. I&amp;rsquo;ve seen a lot of people rage about this, but Blizzard actually has very good reasons to do this. And they even kept you, as a player, in mind!&lt;/p>
&lt;p>&lt;img
src="https://s3-eu-west-1.amazonaws.com/ariejannet/images/diablo_3_rage.jpg"
alt="Diable 3 Rage"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;p>First of all, what Blizzard is doing is not really DRM.&lt;/p>
&lt;p>Digital Rights Management is making sure you have authorization (from the providing party) to do something off-line. The best example is music. After &lt;em>authorizing&lt;/em> your computer you can play your DRM protected music.&lt;/p>
&lt;p>What Blizzard requires is you logging on to one of their servers to play Diablo 3. This sounds like DRM, but you are actually using Blizzard&amp;rsquo;s servers to play. And for good reason!&lt;/p>
&lt;p>Diablo 3 contains several features that allow you to interact with other players. For example, you can easily interact with your friends and you can even join their games. There&amp;rsquo;s also the Auction House, where you can trade items.&lt;/p>
&lt;p>These features are nothing new to World of Warcraft players, and it&amp;rsquo;s not surprising they require a central server to make sure everything is fair.&lt;/p>
&lt;p>An added benefit of this &lt;em>always online&lt;/em> model is that Blizzard can closely monitor how players behave in the game, allowing them to fine-tune game balance with every update the release.&lt;/p>
&lt;p>So, there are many &lt;em>wins&lt;/em> for the player here. The big win for Blizzard is that they can be selective about whom they allow to enter the server: only people with a valid license key.&lt;/p>
&lt;p>I know it&amp;rsquo;s frustrating that you can&amp;rsquo;t play Diablo 3 on a rainy sunday afternoon when you want to. But with a modern, social game like Diablo 3, being online is an absolute must.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2012/05/20/why-diablo-3-s-drm-is-the-best-kind-there-is/</guid><pubDate>Sun, 20 May 2012 00:00:00 +0000</pubDate></item><item><title>What happened to downloading games from The Pirate Bay?</title><link>https://www.devroom.io/2012/05/19/what-happened-to-downloading-games-from-the-pirate-bay/</link><description>&lt;p>This post is on piracy. Piracy of software, games, movies, music and other virtual goods.&lt;/p>
&lt;p>Before I continue writing this post let me confess to you that I used to use pirated software. I played games downloaded from the internet. And movies I watches &amp;lsquo;on demand&amp;rsquo; by downloading them when I wanted to. There were times when there were no legally purchased songs on my iPod.&lt;/p>
&lt;p>After reinstalling my Mac a few weeks ago I noticed something remarkable. There is not a single piece of pirated software on my Mac. Everything has been paid for or is free / open-source. The same goes for my gaming rig, all games are legally purchased.&lt;/p>
&lt;p>This is in shrill contrast to a few years ago. What the fuck happened?&lt;/p>
&lt;p>Then I saw Diablo 3 was released. I&amp;rsquo;m not a huge fan of the franchise, but wanted to play Diablo 3 anyway. So, what to do.&lt;/p>
&lt;p>Checking with local and online retailers I can get a fair price of €49 euro - but the game is either sold out or will not be delivered in the next few days. I want to play Diablo 3 ** RIGHT THE FUCK NOW**.&lt;/p>
&lt;p>Okay, let&amp;rsquo;s check with Blizzard then, the Battle.net store. They have the digital download version of Diablo 3, but for €59. It is instantly available, though.&lt;/p>
&lt;p>Clickety-Click, Diablo 3 purchased. Download ready in 16 minutes. Playing 5 minutes later.&lt;/p>
&lt;p>It&amp;rsquo;s been said many times before, and it will be said many times again: &lt;em>buying digital goods should be easy, fast and secure.&lt;/em>&lt;/p>
&lt;p>I&amp;rsquo;ve amazed myself by forking out an additional €10 to get easy access to Diablo 3. But I&amp;rsquo;m a happy customer and I bet Blizzard is also happy to have sold another Diablo 3 copy.&lt;/p>
&lt;p>Hopefully the entertainment industry will get this message soon. Because movies are the only thing that are still available to me when I want them through The Pirate Bay.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2012/05/19/what-happened-to-downloading-games-from-the-pirate-bay/</guid><pubDate>Sat, 19 May 2012 00:00:00 +0000</pubDate></item><item><title>Running a different ruby with Passenger 3.2 and RVM</title><link>https://www.devroom.io/2012/05/11/running-a-different-ruby-with-passenger-3-2-and-rvm/</link><description>&lt;p>Passenger 3.2 will have quite some nice new features. &lt;a href="http://blog.phusion.nl/2012/04/13/a-sneak-preview-of-phusion-passenger-3-2/">1&lt;/a> &lt;a href="http://blog.phusion.nl/2012/04/25/a-sneak-preview-of-phusion-passenger-3-2-part-2/">2&lt;/a>&lt;/p>
&lt;p>The features I&amp;rsquo;m looking forward to most is the ability to specify - per virtual server - which ruby to use.&lt;/p>
&lt;p>Before, you installed passenger and specified the required ruby version using &lt;code>passenger_ruby&lt;/code>, like this in your &lt;code>nginx.conf&lt;/code>:&lt;/p>
&lt;pre>&lt;code>http {
passenger_root /opt/passenger;
passenger_ruby /usr/local/bin/ruby;
server {
server_name ariejan.net;
passenger_enabled on;
}
}
&lt;/code>&lt;/pre>
&lt;p>Now, if you added another server it would be forced to use the same ruby version. This might be okay for most servers, but for me not so much. I have several side-projects running on a single machine, and using only one ruby version is not optimal or even impossible.&lt;/p>
&lt;p>Now, with the upcoming Passenger 3.2 you can select a ruby version &lt;em>per server&lt;/em>. This is awesome. All you have to do is move the &lt;code>passenger_ruby&lt;/code>directive into the &lt;code>server&lt;/code> block and all is set. Of course, you can leave the globally set ruby just as is.&lt;/p>
&lt;pre>&lt;code>http {
passenger_root /opt/passenger;
passenger_ruby /usr/local/bin/ruby;
server {
server_name ariejan.net;
passenger_enabled on;
passenger_ruby /home/deployer/.rvm/rubies/ruby-1.9.3-p194/bin/ruby;
}
}
&lt;/code>&lt;/pre>
&lt;p>As you can see in the example above, I&amp;rsquo;m referencing ruby-1.9.3-p194, installed with RVM.&lt;/p>
&lt;h3 id="installing-experimental-passenger">Installing &amp;ldquo;experimental&amp;rdquo; Passenger&lt;/h3>
&lt;p>The installation is easy, as usual, but you must checkout the passenger source from Github and use the &lt;code>experimental&lt;/code> branch.&lt;/p>
&lt;p>&lt;strong>Warning: do not install the &lt;code>experimental&lt;/code> branch of Passenger on your production server unless you are absolutely sure what you&amp;rsquo;re doing and you know how to rollback quickly and easily to a stable version of passenger.&lt;/strong>&lt;/p>
&lt;pre>&lt;code>cd /opt
git clone https://github.com/FooBarWidget/passenger.git
cd /opt/passenger
git checkout -b experimental origin/experimental
./bin/passenger-install-nginx-module
&lt;/code>&lt;/pre></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2012/05/11/running-a-different-ruby-with-passenger-3-2-and-rvm/</guid><pubDate>Fri, 11 May 2012 00:00:00 +0000</pubDate></item><item><title>Decorating Devise's current_user with Draper</title><link>https://www.devroom.io/2012/04/14/decorating-devise-s-current_user-with-draper/</link><description>&lt;p>I&amp;rsquo;ve become a big fan of decorators, especially &lt;a href="https://github.com/jcasimir/draper">Draper&lt;/a>.&lt;/p>
&lt;p>Decorators allow you to move view related functionality for your models in to separate decorator classes. This keeps both your models and views clean and readable.&lt;/p>
&lt;p>Anyway, if you use Devise you&amp;rsquo;re provided with a &lt;code>current_user&lt;/code> helper. However, this helper returns an instance of &lt;code>User&lt;/code> - without your decorators. To enable decorators for your &lt;code>current_user&lt;/code> by default, simple add this to &lt;code>app/controllers/application_controller.rb&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">current_user&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="no">UserDecorator&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">decorate&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">super&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">unless&lt;/span> &lt;span class="k">super&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">nil?&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now, anywhere in your views where you call &lt;code>current_user&lt;/code> you&amp;rsquo;ll get a decorated version instead.&lt;/p>
&lt;p>&lt;a href="http://ariejan.net/2012/11/02/decorating_sorcery_current_user_with_draper">Check here to see how to use Draper with Sorcery&lt;/a>&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2012/04/14/decorating-devise-s-current_user-with-draper/</guid><pubDate>Sat, 14 Apr 2012 00:00:00 +0000</pubDate></item><item><title>Eindhoven.rb Lightning Talk: Gitlab</title><link>https://www.devroom.io/2012/04/06/eindhoven-rb-lightning-talk-gitlab/</link><description>&lt;p>These are the slides of my &lt;a href="http://eindhovenrb.nl">Eindhoven.rb&lt;/a> lightning talk. The topic is Gitlab, why it&amp;rsquo;s awesome and how it relates to Github.&lt;/p>
&lt;p>If you have any questions about Gitlab, feel free to contact me or visit the &lt;a href="https://groups.google.com/forum/#!forum/gitlabhq">Gitlab Mailinglist&lt;/a>&lt;/p>
&lt;script async class="speakerdeck-embed" data-id="4f7da2d94156d9001f032da4" data-ratio="1.7777777777777777" src="//speakerdeck.com/assets/embed.js">&lt;/script></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2012/04/06/eindhoven-rb-lightning-talk-gitlab/</guid><pubDate>Fri, 06 Apr 2012 00:00:00 +0000</pubDate></item><item><title>Showing Ruby, Rails and git info in your app</title><link>https://www.devroom.io/2012/04/02/showing-ruby-rails-and-git-info-in-your-app/</link><description>&lt;p>Some people&amp;rsquo;ve asked me how I show rendering information on &lt;a href="http://ariejan.net">ariejan.net&lt;/a>.&lt;/p>
&lt;p>&lt;img
src="https://ariejannet.s3.amazonaws.com/images/render_stats.jpg"
alt=""
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;p>There are a few things going on here, let me explain them one by one.&lt;/p>
&lt;h3 id="rails-version">Rails version&lt;/h3>
&lt;p>The current Rails version is probably the easiest you see here. Rails exposes its version information like this:&lt;/p>
&lt;pre>&lt;code>Rails.version
&lt;/code>&lt;/pre>
&lt;h3 id="ruby-version">Ruby version&lt;/h3>
&lt;p>Ruby also exposes version information, albeit using constants:&lt;/p>
&lt;pre>&lt;code>RUBY_VERSION
=&amp;gt; &amp;quot;1.9.3&amp;quot;
&lt;/code>&lt;/pre>
&lt;p>You may know that ruby also has different patch levels for each release. You can also retrieve that information:&lt;/p>
&lt;pre>&lt;code>RUBY_PATCHLEVEL
=&amp;gt; 125
&lt;/code>&lt;/pre>
&lt;p>The you may also want to know which engine you&amp;rsquo;re using. This may be &amp;ldquo;ruby&amp;rdquo; or something different:&lt;/p>
&lt;pre>&lt;code>RUBY_ENGINE
=&amp;gt; &amp;quot;ruby&amp;quot;
&lt;/code>&lt;/pre>
&lt;p>Combine these to get a sexy ruby version string:&lt;/p>
&lt;pre>&lt;code>puts &amp;quot;#{RUBY_ENGINE}-#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}&amp;quot;
=&amp;gt; &amp;quot;ruby-1.9.3-p125&amp;quot;
&lt;/code>&lt;/pre>
&lt;h3 id="current-process-id">Current process ID&lt;/h3>
&lt;p>I like to know which Unicorn produced a certain page. Retrieving the process ID is easy:&lt;/p>
&lt;pre>&lt;code>Process.pid
=&amp;gt; 9473
&lt;/code>&lt;/pre>
&lt;h3 id="current-git-revision-sha">Current git revision SHA&lt;/h3>
&lt;p>Knowing which version of your app is currently running can be useful information. To do this I created the following initializer:&lt;/p>
&lt;pre>&lt;code># config/initializers/git_revision.rb
module AppName
REVISION = `git log --pretty=format:'%h' -n 1`
end
&lt;/code>&lt;/pre>
&lt;p>This will expose the current short SHA in your application&amp;rsquo;s namespace:&lt;/p>
&lt;pre>&lt;code>AppName::REVISION
=&amp;gt; &amp;quot;ac6d3a0&amp;quot;
&lt;/code>&lt;/pre>
&lt;p>You can now use this info through-out your app to show version information.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2012/04/02/showing-ruby-rails-and-git-info-in-your-app/</guid><pubDate>Mon, 02 Apr 2012 00:00:00 +0000</pubDate></item><item><title>From 11.34s to 0.625s for opening a .rb file in Vim</title><link>https://www.devroom.io/2012/03/21/from-11-34s-to-0-625s-for-opening-a-rb-file-in-vim/</link><description>&lt;p>Would you believe me if I told you that opening a simple Ruby file on my 2011 MacBook Pro takes 11.34 seconds?&lt;/p>
&lt;p>To test this, I&amp;rsquo;ve used this command:&lt;/p>
&lt;pre>&lt;code>$ vim --startuptime log-before.txt app/models/user.rb
&lt;/code>&lt;/pre>
&lt;p>This command will time everything that Vim does until the file is ready for you to edit down to the millisecond. This is a great way to find out what&amp;rsquo;s slowing things down.&lt;/p>
&lt;p>I&amp;rsquo;ll highlight the most interesting parts of &lt;a href="https://gist.github.com/2147190#file_log_before.txt">&lt;code>log-before.txt&lt;/code>&lt;/a> here:&lt;/p>
&lt;pre>&lt;code> 000.028 000.028: --- VIM STARTING ---
6643.597 5496.976 5496.976: sourcing /usr/share/vim/vim73/ftplugin/ruby.vim
11262.686 3963.993 3963.993: sourcing /Users/ariejan/.vim/bundle/vim-css-color/after/syntax/css.vim
11263.907 3976.912 004.334: sourcing /usr/share/vim/vim73/syntax/html.vim
11263.997 3977.361 000.449: sourcing /usr/share/vim/vim73/syntax/xhtml.vim
11340.533 000.004: --- VIM STARTED ---
&lt;/code>&lt;/pre>
&lt;p>These are the &lt;em>big spenders&lt;/em> of loading a ruby file. Firstly there is &lt;code>ruby.vim&lt;/code> taking about 5.4 seconds to load. Then there is &lt;code>css.vim&lt;/code> taking another 3.9 seconds - and this file doesn&amp;rsquo;t even include CSS!&lt;/p>
&lt;p>These two time sinking hogs are keeping me back – 11 seconds at a time.&lt;/p>
&lt;p>Let&amp;rsquo;s see, &lt;code>vim-css-color&lt;/code>. This plugin shows color hashes in their actual colour. So &lt;code>#00f&lt;/code> will have a blue background. Great when editing CSS files, but not all that import. I removed &lt;code>vim-css-color&lt;/code>.&lt;/p>
&lt;p>&lt;em>Note: the reason &lt;code>vim-css-color&lt;/code> is slow with terminal vim is that is has to pre-compile colour hashes to ther xterm escape code equivalents. This is pretty time consuming.&lt;/em>&lt;/p>
&lt;p>Next up: &lt;code>ruby.vim&lt;/code>. Why is this so bloody slow?&lt;/p>
&lt;p>As it turns out, Vim has trouble finding the right &lt;code>ruby&lt;/code> for me. This can be remedied by adding the following snippet to your &lt;code>~/.vimrc&lt;/code>. It sets a logical search path for &lt;code>ruby&lt;/code>:&lt;/p>
&lt;pre>&lt;code>if !empty($MY_RUBY_HOME)
let g:ruby_path = join(split(glob($MY_RUBY_HOME.'/lib/ruby/*.*').&amp;quot;\n&amp;quot;.glob($MY_RUBY_HOME.'/lib/ruby/site_ruby/*'),&amp;quot;\n&amp;quot;),',')
endif
&lt;/code>&lt;/pre>
&lt;p>Again I ran my timer command (&lt;a href="https://gist.github.com/2147190#file_log_after.txt">full output&lt;/a>):&lt;/p>
&lt;pre>&lt;code>$ vim --startuptime log-after app/models/user.rb
&lt;/code>&lt;/pre>
&lt;p>Now look at that:&lt;/p>
&lt;pre>&lt;code>000.034 000.034: --- VIM STARTING ---
107.182 000.834 000.834: sourcing /usr/share/vim/vim73/ftplugin/ruby.vim
625.001 000.003: --- VIM STARTED ---
&lt;/code>&lt;/pre>
&lt;p>Yes, that is 0.625 seconds! I&amp;rsquo;m a happy ruby coder again.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2012/03/21/from-11-34s-to-0-625s-for-opening-a-rb-file-in-vim/</guid><pubDate>Wed, 21 Mar 2012 00:00:00 +0000</pubDate></item><item><title>Redis using 2GB of memory on 70MB data set - the fix</title><link>https://www.devroom.io/2012/03/21/redis-using-2gb-of-memory-on-70mb-data-set-the-fix/</link><description>&lt;p>For &lt;a href="http://ariejan.net">Ariejan.net&lt;/a> I use &lt;a href="http://redis.io">redis&lt;/a> to cache pages and shards. This works great and all, but today I noticed something alarming:&lt;/p>
&lt;pre>&lt;code>redis Running 3d 12h 4m 0.0% 45.2% [1829556 kB]
&lt;/code>&lt;/pre>
&lt;p>Yes, that&amp;rsquo;s about 1.7 GB of RAM. That&amp;rsquo;s way too much for what I cache. Let&amp;rsquo;s see what redis has to say for itself:&lt;/p>
&lt;pre>&lt;code>$ redis-cli
redis 127.0.0.1:6379&amp;gt; info
redis_version:2.2.12
...
connected_clients:5
connected_slaves:0
used_memory:71626608
used_memory_human:68.31M
used_memory_rss:1873465344
mem_fragmentation_ratio:26.16
&lt;/code>&lt;/pre>
&lt;p>Well, that&amp;rsquo;s awkward. The OS is reporting 1.7GB memory usage, while Redis claims to store a mere 68MB. What&amp;rsquo;s happening here!&lt;/p>
&lt;p>You may have noticed that I included &lt;code>mem_fragmentation_ratio&lt;/code> in the snippet above as well. It&amp;rsquo;s at a whopping 26.16, meaning that for every byte I store, 26.16 bytes of memory are used. This explains the 1.7GB memory usage.&lt;/p>
&lt;p>But, how do I get rid of this? My system has enough RAM to cope redis as is, but it&amp;rsquo;s not a comforting thought to leave Redis running like this.&lt;/p>
&lt;p>As it turns out, there isn&amp;rsquo;t a lot you can do about this. Redis 2.2 uses &lt;em>malloc&lt;/em> which causes the fragmentation. One alternative is to add a slave redis-server, migrate your data and then switch the slave to master. Although this is reported to work well, it&amp;rsquo;s not a good solution to the problem.&lt;/p>
&lt;p>Fortunately, Redis 2.4 on Linux by default does not use &lt;em>malloc&lt;/em> anymore. Instead it uses &lt;em>jemalloc&lt;/em>. From the redis README:&lt;/p>
&lt;blockquote>
&lt;p>Redis is compiled and linked against libc malloc by default, with the exception of jemalloc being the default on Linux systems. This default was picked because jemalloc has proven to have fewer fragmentation problems than libc malloc.&lt;/p>
&lt;/blockquote>
&lt;p>The only logical step to take is to upgrade to Redis 2.4.&lt;/p>
&lt;p>If you&amp;rsquo;re already running (or just upgraded to) 2.4 you can easily check if your redis is using &lt;em>jemalloc&lt;/em>:&lt;/p>
&lt;pre>&lt;code>$ redis-cli
redis 127.0.0.1:6379&amp;gt; info
redis_version:2.4.9
mem_fragmentation_ratio:1.11
mem_allocator:jemalloc-2.2.5
&lt;/code>&lt;/pre>
&lt;p>I&amp;rsquo;ve taken this step and the &lt;code>mem_fragmentation_ratio&lt;/code> samples I&amp;rsquo;ve measured have all been in the 1.1-1.4 regions.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2012/03/21/redis-using-2gb-of-memory-on-70mb-data-set-the-fix/</guid><pubDate>Wed, 21 Mar 2012 00:00:00 +0000</pubDate></item><item><title>Open Source is a privilege. Not a right.</title><link>https://www.devroom.io/2012/03/20/open-source-is-a-privilege-not-a-right/</link><description>&lt;p>&lt;em>Edit: this is not a Gitlab specific issue. I&amp;rsquo;ve seen it before and I&amp;rsquo;m seeing it again at the Gitlab project.&lt;/em>&lt;/p>
&lt;p>The past few months I&amp;rsquo;ve been contributing to the &lt;a href="https://github.com/gitlabhq/gitlabhq">Gitlab&lt;/a> project. This has been a great experience, mostly because Gitlab has become a very popular project with over 2.3k watchers right now.&lt;/p>
&lt;p>It gives me great satisfaction knowing that my code is being used by hundreds if not thousands of people right now. But there is also this little thing that has been bugging me lately.&lt;/p>
&lt;p>There is a small core team of developers devoting time to the development of Gitlab. They do this passionatly and they have been cranking out releases and new features every 22nd of month for the past six months.&lt;/p>
&lt;p>A lot of effort has gone into making Gitlab what it is today.&lt;/p>
&lt;p>But with the rise in popularity there has also been an increased number issues, bugs and feature requests. The mailing list is also more active than ever. Of course, all of this is a good thing for an open source project, you would think.&lt;/p>
&lt;p>Most users appreciate the effort that goes into developing an application as complex as Gitlab. They appreciate the fact it&amp;rsquo;s available for &lt;em>free&lt;/em>. The appreciate somebody made time to develop a tool they can put to use on a daily basis.&lt;/p>
&lt;p>This appreciation is great motivator for the developers. Knowing their code is being put to good use only drives them write more code, more features.&lt;/p>
&lt;p>Unfortunately, I read tickets and threads of this kind too often lately:&lt;/p>
&lt;blockquote>
&lt;p>Add [this feature]. My company needs this and if you don&amp;rsquo;t add this we won&amp;rsquo;t use Gitlab.&lt;/p>
&lt;/blockquote>
&lt;p>Also this:&lt;/p>
&lt;blockquote>
&lt;p>We are expecting [this feature] in the next release.&lt;/p>
&lt;/blockquote>
&lt;p>Or:&lt;/p>
&lt;blockquote>
&lt;p>We must have [this feature]. Gitlab is useless without it and we&amp;rsquo;ll go back to using Github.&lt;/p>
&lt;/blockquote>
&lt;p>Why do people think they can make &lt;em>demands&lt;/em> like these?&lt;/p>
&lt;p>Is it too hard to read the installation guide that comes with the project?&lt;/p>
&lt;p>Don&amp;rsquo;t get me wrong, it&amp;rsquo;s awesome people are willing to try and install Gitlab. It&amp;rsquo;s even more awesome people post their ideas and feature requests to further improve Gitlab.&lt;/p>
&lt;p>However, the &lt;strong>lack of respect&lt;/strong> for people who spend their &lt;strong>free time&lt;/strong> developing an open source application for you &lt;strong>to use for free&lt;/strong> is sickening.&lt;/p>
&lt;p>No, really. Sickening.&lt;/p>
&lt;p>There really is no reason to &lt;em>threaten&lt;/em> or &lt;em>pressure&lt;/em> the people who are dedicating the free time, parts of the lives, to an open source project to implement something for you.&lt;/p>
&lt;p>Of course, some of you will say, &amp;ldquo;Just ignore those ass holes. They&amp;rsquo;re not worth your time.&amp;rdquo; I agree, but after reading two or three of these responses I don&amp;rsquo;t feel like spending my evening off coding some awesomeness.&lt;/p>
&lt;p>This is a request to anyone who&amp;rsquo;s ever going to post a feature request, a bug report or even a post to the mailing list of an open source project:&lt;/p>
&lt;ul>
&lt;li>be polite,&lt;/li>
&lt;li>don&amp;rsquo;t make threaths or demands, and&lt;/li>
&lt;li>show some respect for the souls who dedicate their time to making something awesome.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Open Source is a privilege. Not a right.&lt;/strong>&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2012/03/20/open-source-is-a-privilege-not-a-right/</guid><pubDate>Tue, 20 Mar 2012 00:00:00 +0000</pubDate></item><item><title>Removing untracked files and directories with git</title><link>https://www.devroom.io/2012/01/10/removing-untracked-files-and-directories-with-git/</link><description>&lt;p>I just tried writing some new code, but it was no success. This happens, but it left me with a working copy littered with new and changed files.&lt;/p>
&lt;pre>&lt;code># Changes not staged for commit:
# (use &amp;quot;git add &amp;lt;file&amp;gt;...&amp;quot; to update what will be committed)
# (use &amp;quot;git checkout -- &amp;lt;file&amp;gt;...&amp;quot; to discard changes in working directory)
#
# modified: config/routes.rb
#
# Untracked files:
# (use &amp;quot;git add &amp;lt;file&amp;gt;...&amp;quot; to include in what will be committed)
#
# db/migrate/20111231131752_create_validations.rb
# vendor/assets/images/
# vendor/assets/javascripts/
# vendor/assets/stylesheets/custom.sass
&lt;/code>&lt;/pre>
&lt;p>So, how do I get rid of this mess?&lt;/p>
&lt;h2 id="1---be-absolutely-sure-you-want-to-delete-your-work">1 - Be absolutely sure you want to delete your work&lt;/h2>
&lt;p>I sometimes commit my work although it&amp;rsquo;s of no use to me at the moment. It&amp;rsquo;s in a separate branch anyway and can be easily ignored until I come back to it.&lt;/p>
&lt;p>So, if you&amp;rsquo;re really sure you want to delete your changes and files, continue to step 2.&lt;/p>
&lt;h2 id="2---delete-untracked-files-and-directories">2 - Delete untracked files and directories&lt;/h2>
&lt;p>Now, delete all files that are not yet tracked by git. You could do it like this:&lt;/p>
&lt;pre>&lt;code>rm -r db/migrate/20111231131752_create_validations.rb vendor/assets/images \
vendor/assets/javascripts/ vendor/assets/stylesheets/custom.sass
&lt;/code>&lt;/pre>
&lt;p>This requires you to copy/paste or type all the files and directories you want to delete. There&amp;rsquo;s a much easier way: ask git to do it for you:&lt;/p>
&lt;pre>&lt;code>git clean -df
&lt;/code>&lt;/pre>
&lt;p>The &lt;code>d&lt;/code> option tells git to include directories, &lt;code>f&lt;/code> says that you really want to perform the delete. Optionally, you can first run with &lt;code>n&lt;/code> instead of &lt;code>f&lt;/code> to see what&amp;rsquo;s going to happen - a so called dry-run.&lt;/p>
&lt;p>If you use this command frequently and don&amp;rsquo;t want to specify the &lt;code>f&lt;/code> options every time you can set &lt;code>clean.requireForce&lt;/code> in your &lt;code>~/.gitconfig&lt;/code> to &lt;code>true&lt;/code> to omit the &lt;code>f&lt;/code> options.&lt;/p>
&lt;h2 id="3---reset-changes-to-tracked-files">3 - Reset changes to tracked files&lt;/h2>
&lt;p>With all the new stuff out of the way, let&amp;rsquo;s clean up the files that are tracked by git. You want to revert everything back to the last commit you made:&lt;/p>
&lt;pre>&lt;code>git reset HEAD --hard
&lt;/code>&lt;/pre>
&lt;p>That&amp;rsquo;s all. You&amp;rsquo;re now back to a clean working directory at &lt;code>HEAD&lt;/code>. Start over now.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2012/01/10/removing-untracked-files-and-directories-with-git/</guid><pubDate>Tue, 10 Jan 2012 00:00:00 +0000</pubDate></item><item><title>Recursively fixing file and directory permissions</title><link>https://www.devroom.io/2011/12/13/recursively-fixing-file-and-directory-permissions/</link><description>&lt;p>While working on a &lt;a href="http://gitlabhq.com/">Gitlab&lt;/a> installation I noticed that all repository file permissions were off. Fixing recursive file and directory permissions can be quite hard. Or so I thought.&lt;/p>
&lt;p>Using the following commands (in plain Bash) allow you to recursively set permissions for files and directories. So, to fix the proper read permissions on your Gitlab repositories you can use this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Go to your git repositories directory (as git or the gitlab user)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> /home/git/repositories
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Fix ownership&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo chown -R git:git *
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Fix directory permissions&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo find -type d -exec chmod &lt;span class="m">770&lt;/span> &lt;span class="o">{}&lt;/span> &lt;span class="se">\;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Fix file permissions&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo find -type f -exec chmod &lt;span class="m">660&lt;/span> &lt;span class="o">{}&lt;/span> &lt;span class="se">\;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>After this, your Gitlab should have no trouble accessing your code (e.g. in the tree browser).&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2011/12/13/recursively-fixing-file-and-directory-permissions/</guid><pubDate>Tue, 13 Dec 2011 00:00:00 +0000</pubDate></item><item><title>Upgrade postgresql-8.4 to postgresql-9.1 on debian</title><link>https://www.devroom.io/2011/11/22/upgrade-postgresql-8-4-to-postgresql-9-1-on-debian/</link><description>&lt;p>Today I upgraded a production PostgreSQL 8.4 database to version 9.1. This was on a Debian server.
~
The first step is to make a full dump of your data. I personally like to store that dump somewhere safe before upgrading. As root:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">su - postgres
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">pg_dumpall &amp;gt; dump.sql
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">exit&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cp ~postgres/dump.sql /root/
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now you can safely remove the postgresql-8.4 and install postgresql-9.1:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">aptitude purge postgresql-8.4
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">aptitude install postgresql-9.1
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Next check the postgresql configuration in &lt;code>/etc/postgresql/9.1/main&lt;/code>. If you make any changes, make sure to restart postgres with &lt;code>/etc/init.d/postgresql restart&lt;/code>.&lt;/p>
&lt;p>Postgresql 9.1 is now up and running, let&amp;rsquo;s import our data back into it.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">su - postgres
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">psql &amp;lt; dump.sql
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>That&amp;rsquo;s all. You&amp;rsquo;re now fully upgraded to PostgreSQL 9.1.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2011/11/22/upgrade-postgresql-8-4-to-postgresql-9-1-on-debian/</guid><pubDate>Tue, 22 Nov 2011 00:00:00 +0000</pubDate></item><item><title>Deploying a third-party Rails application - like Gitlab</title><link>https://www.devroom.io/2011/11/18/deploying-a-third-party-rails-application-like-gitlab/</link><description>&lt;p>We all know how to deploy our own Rails projects. (If not, &lt;a href="http://ariejan.net/2011/09/14/lighting-fast-zero-downtime-deployments-with-git-capistrano-nginx-and-unicorn">read this guide&lt;/a>.) But how do you handle deploying a third-party application that may require some customisation on your part?&lt;/p>
&lt;p>A good example would be &lt;a href="http://www.gitlabhq.com">Gitlab&lt;/a>&lt;/p>
&lt;p>Gitlab is an open source Github clone, build using Ruby on Rails. It&amp;rsquo;s a nice project that uses Gitosis under the hood to manage your git repositories. There are &lt;a href="http://www.ryanwersal.com/blog/2011/10/18/installing-gitlab-on-ubuntu-server/">several&lt;/a> &lt;a href="http://nepalonrails.tumblr.com/post/12603081685/setup-gitlab-github-clone-using-vagrant-and-chef">good&lt;/a> installation guides available on the web, but they all assume you want to deploy gitlab verbatim - without any modification or configuration&lt;/p>
&lt;p>I have also setup Gitlab, but I want to use capistrano and unicorn. I also want to tweak some configuration. In some rare cases I want to fix an annoying bug and not wait for the Gitlab team to pull it.&lt;/p>
&lt;p>Doing a &lt;code>git pull&lt;/code> on my remote server just won&amp;rsquo;t cut it.&lt;/p>
&lt;h2 id="fork-fork">Fork! Fork!&lt;/h2>
&lt;p>What I did was clone Gitlab and use the same principles describe in my &lt;a href="http://ariejan.net/2011/11/09/contributing-to-open-source-with-github">&lt;em>Contributing to open source with Github&lt;/em>&lt;/a> to apply my own changes and merge any upstream changes when they are available.&lt;/p>
&lt;p>Here&amp;rsquo;s how I set everything up.&lt;/p>
&lt;p>First, clone the official Gitlab repository and name it &lt;code>upstream&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">git clone --origin upstream https://github.com/gitlabhq/gitlabhq.git my_git_server
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Next I made all the changes I want. I updated &lt;code>config/gitosis.yml&lt;/code> and &lt;code>unicorn&lt;/code> to &lt;code>Gemfile&lt;/code> and setup Capistrano.&lt;/p>
&lt;p>I then pushed this to my own git server. This is the same server Capistrano will use to pull changes from.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">git remote add origin git@git.ariejan.net:my_git_server.git
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">git push origin master
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cap deploy
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>That&amp;rsquo;s all there is to deploying Gitlab from my own repository.&lt;/p>
&lt;h2 id="merging-upstream-changes">Merging upstream changes&lt;/h2>
&lt;p>Now, the Gitlab crew is pushing out new features at an amazing rate. So, how do I get those new features (and the occasional bug fix) into my copy of Gitlab for deploying?&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">git fetch upstream
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Remember how we named the official Gitlab repository &lt;code>upstream&lt;/code> earlier? With this &lt;code>fetch&lt;/code> we get all changes from their repository (but we don&amp;rsquo;t apply them to anything yet).&lt;/p>
&lt;p>Then, merge the upstream changes with your own branch.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">git merge upstream/master
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>There may be merge conflicts, just resolve them and commit your merge. Then again to deploy:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">git push origin master
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cap deploy
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="why-do-this">Why do this?&lt;/h2>
&lt;p>The reason I use this approach is that it&amp;rsquo;s easy to merge upstream changes and have my own customizations and deployment tools at the same time.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2011/11/18/deploying-a-third-party-rails-application-like-gitlab/</guid><pubDate>Fri, 18 Nov 2011 00:00:00 +0000</pubDate></item><item><title>Contributing to Open-Source with Github</title><link>https://www.devroom.io/2011/11/09/contributing-to-open-source-with-github/</link><description>&lt;p>You want to contribute to an open-source project, but are scared away by all the git-complexity involved? This small guide will help you out.&lt;/p>
&lt;img src="https://ariejannet.s3.amazonaws.com/content/github.png" alt="Octocat" align="right" />
&lt;h2 id="step-1---fork-fork-fork">Step 1 - Fork, fork, fork&lt;/h2>
&lt;p>First create a fork of the original project. You can do this easily by clicking the &amp;ldquo;Fork&amp;rdquo; button on the top of the Github project page. This will give you your own copy (fork) of the entire repository.&lt;/p>
&lt;p>Then, check out your fork:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">git clone git@github.com:ariejan/repo-name.git
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="step-2---contribute">Step 2 - Contribute&lt;/h2>
&lt;p>Before you start writing code there are a few tasks you need to perform:&lt;/p>
&lt;ul>
&lt;li>Are there tests for this project? Are they all green? If not, fix this first. (using this same step, of course)&lt;/li>
&lt;li>Create a new branch: &lt;code>git checkout -b fix_for_this_or_that&lt;/code>&lt;/li>
&lt;li>Red-Green-Refactor (e.g. write code)&lt;/li>
&lt;li>Commit your changes (in your &lt;code>fix_for_this_or_that&lt;/code> branch).&lt;/li>
&lt;/ul>
&lt;h2 id="step-3---sharing-your-contribution">Step 3 - Sharing your contribution&lt;/h2>
&lt;p>With your contribution done, don&amp;rsquo;t merge it back into &lt;code>master&lt;/code>. &lt;code>master&lt;/code> is your way of receiving changes from the original (upstream) repo (see step 4).&lt;/p>
&lt;p>First push your branch to Github, so you can share it with others.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">git push origin fix_for_this_or_that
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>You should now see this branch in your Github project page. You&amp;rsquo;ll also notice there&amp;rsquo;s a &amp;ldquo;Pull Request&amp;rdquo; button at the top. Click it if you want the project maintainer to pull your &lt;code>fix_for_this_or_that&lt;/code> branch into the main project.&lt;/p>
&lt;h2 id="step-4---keeping-up-to-date">Step 4 - Keeping up-to-date&lt;/h2>
&lt;p>Over time the &lt;code>master&lt;/code> of your fork will start lagging behind. Because you did not merge any of your code changes into the &lt;code>master&lt;/code> of your fork, you can update it easily.&lt;/p>
&lt;p>Before you can pull in changes you must add a git remote for it. You can use the &lt;em>Git Read-only&lt;/em> URL for this.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">git remote add upstream https://github.com/some_one/some-repo.git
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now, for raking in the changes;&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">git checkout master
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">git fetch upstream
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">git merge upstream/master
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="step-45---keeping-your-feature-branch-up-to-date">Step 4.5 - Keeping your feature branch up-to-date&lt;/h2>
&lt;p>If you pulled in changes from &lt;code>upstream&lt;/code> and did not yet share your feature branch, you can rebase your feature branch. This makes sure you have the latest code from &lt;code>master&lt;/code>. This also makes merging your pull request easier.&lt;/p>
&lt;p class="important">Do &lt;strong>not&lt;/strong> rebase if you already pushed your branch to Github. &lt;a href="http://progit.org/book/ch3-6.html#the_perils_of_rebasing">Read why&lt;/a>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">git checkout fix_for_this_or_that
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">git rebase master
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>If any conflict arise, fix them. And continue your rebase:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">git add conflicting_file
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">git rebase --continue
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>You may also abort the rebase:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">git rebase --abort
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="big-picture">Big picture&lt;/h2>
&lt;p>This small guide will help you to fork repositories on Github and use them to contribute code. By using the approach you lighten the task of the project maintainer who can easily checkout your specific changes and decide to include them into the main project or not.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2011/11/09/contributing-to-open-source-with-github/</guid><pubDate>Wed, 09 Nov 2011 00:00:00 +0000</pubDate></item><item><title>Fixing a slow starting Terminal or iTerm2 on Mac OS X</title><link>https://www.devroom.io/2011/11/08/fixing-a-slow-starting-terminal-or-iterm2-on-mac-os-x/</link><description>&lt;p>For some time I have been annoyed with how slow my Terminal (in my case iTerm2) starts up. It would take 5-10 seconds before I was presented a prompt. Being in the console for the better part of the day, this was unacceptable.&lt;/p>
&lt;p>The first thing I did was &lt;em>upgrade&lt;/em> Bash to Zsh. I&amp;rsquo;d heard great things about Zsh, so I thought I&amp;rsquo;d give it a try. Zsh is really awesome, but it did not fix the start-up delay I was experiencing previously with Bash.&lt;/p>
&lt;p>Doing a little digging around I found a working solution. Apparently Apple keeps system logs in &lt;code>/private/var/log/asl/*.asl&lt;/code>. Removing these files made my shell fast again.&lt;/p>
&lt;p class="important">Do &lt;strong>not&lt;/strong> remove all files in &lt;code>/private/var/log/asl&lt;/code> as there are other files there that are not related to the shell. Leave them in there.&lt;/p>
&lt;p>With the above warning in the back of your mind, open your terminal (slowly) and issue the following command:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">sudo rm /private/var/log/asl/*.asl
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now quit and restart Terminal or iTerm2 and your prompt should present itself quickly again.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2011/11/08/fixing-a-slow-starting-terminal-or-iterm2-on-mac-os-x/</guid><pubDate>Tue, 08 Nov 2011 00:00:00 +0000</pubDate></item><item><title>Installing Node.js and NPM on Ubuntu/Debian</title><link>https://www.devroom.io/2011/10/24/installing-node-js-and-npm-on-ubuntu-debian/</link><description>&lt;p>This is just short snippet on how to install Node.js (any version) and NPM (Node Package Manager) on your Ubuntu/Debian system.&lt;/p>
&lt;h2 id="step-1---update-your-system">Step 1 - Update your system&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">sudo apt-get update
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo apt-get install git-core curl build-essential openssl libssl-dev python
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="step-2---install-nodejs">Step 2 - Install Node.js&lt;/h2>
&lt;p>First, clone the Node.js repository:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">git clone https://github.com/nodejs/node.git
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> node
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now, if you require a specific version of Node:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">git tag &lt;span class="c1"># Gives you a list of released versions&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">git checkout v13.10.1
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Then compile and install Node like this:&lt;/p>
&lt;p>&lt;small>This might take a while, depending on your hardware.&lt;/small>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">./configure
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">make
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo make install
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Then, check if node was installed correctly:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">node -v
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="step-3---install-npm">Step 3 - Install NPM&lt;/h2>
&lt;p>Simply run the NPM install script:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">curl -L https://npmjs.org/install.sh &lt;span class="p">|&lt;/span> sudo sh
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>And then check it works:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">npm -v
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>That&amp;rsquo;s all.&lt;/p>
&lt;p>&lt;em>Updated 2020-03-11&lt;/em>&lt;/p>
&lt;ul>
&lt;li>Use the new nodejs/node repo&lt;/li>
&lt;li>Install correct dependencies, including python\&lt;/li>
&lt;li>Update example node version to v13.10.1_&lt;/li>
&lt;/ul></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2011/10/24/installing-node-js-and-npm-on-ubuntu-debian/</guid><pubDate>Mon, 24 Oct 2011 00:00:00 +0000</pubDate></item><item><title>Automatically switch between SSL and non-SSL with Nginx+Unicorn+Rails</title><link>https://www.devroom.io/2011/10/22/automatically-switch-between-ssl-and-non-ssl-with-nginx-unicorn-rails/</link><description>&lt;p>&lt;em>Scroll down for setup instructions. Or, read this bit about SSL in the real world first.&lt;/em>&lt;/p>
&lt;p>SSL or Secure Socket Layer is a nice way to secure sensitive parts of your Rails application. It achieves to goals.&lt;/p>
&lt;p>Firstly is encrypts all traffic between you and the remote server. Consider the passwords and personal information you submit to websites. When unencrypted (using HTTP), all this data is sent over the internet for all to read. With SSL (HTTPS) enabled, all traffic is encrypted, making it very hard for a third-party to eavesdrop.&lt;/p>
&lt;p>Secondly, a proper SSL connection can give you trust in that you&amp;rsquo;re communicating with the right people.&lt;/p>
&lt;p>For example, Rabobank (a Dutch bank) uses SSL for its website. When you open their site, you&amp;rsquo;ll notice the green &amp;lsquo;Rabobank Nederland&amp;rsquo; in the address bar.&lt;/p>
&lt;p>&lt;img
src="https://ariejannet.s3.amazonaws.com/content/rabobank-ssl.jpg"
alt="image"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;p>This tells me I am communicating with Rabobank Nederland. This is why SSL certificates are so expensive - the SSL authority needs to verify the identify of Rabobank before they issue the certificate.&lt;/p>
&lt;p>In the example above Rabobank uses an &lt;a href="http://en.wikipedia.org/wiki/Extended_Validation_Certificate">EV SSL Certificate&lt;/a>. EV stands for Extended Validation. This means that the SSL authority has verified (among other things) that Rabobank is a legitimate business and that they are the legal owner of the domain rabobank.nl&lt;/p>
&lt;p>The cost for such an EV SSL Certificate is $200 - $1000 per year. You probably don&amp;rsquo;t need it for your site.&lt;/p>
&lt;h2 id="ssl-for-you-and-me">SSL for you and me&lt;/h2>
&lt;p>When you are looking to secure the back-end of your site (where you login etc.), you only require the encryption part of SSL. There are two routes you can take&lt;/p>
&lt;h3 id="self-signed-ssl">Self signed SSL&lt;/h3>
&lt;p>You are able to create a working SSL certificate yourself. This will give you encryption, but no identity validation. When you use a self-signed SSL certificate all browsers will warn you about this.&lt;/p>
&lt;ul>
&lt;li>Encryption&lt;/li>
&lt;li>No validation&lt;/li>
&lt;li>Warnings from your browser&lt;/li>
&lt;li>Free&lt;/li>
&lt;/ul>
&lt;p>For me, that&amp;rsquo;s a reason not to use self signed SSL for any other than development and testing purposes.&lt;/p>
&lt;h3 id="standard-ssl">Standard SSL&lt;/h3>
&lt;p>Most SSL authorities provide you with a &lt;em>Standard SSL&lt;/em> product. These certificates only check if you own the domainname. They also offer encryption and work (without warnings) in your browser. You can get one of these for as little as $9 a year.&lt;/p>
&lt;ul>
&lt;li>Encryption&lt;/li>
&lt;li>Domain validation / trust&lt;/li>
&lt;li>No warnings from your browser&lt;/li>
&lt;li>Cheap ($10 - $20)&lt;/li>
&lt;/ul>
&lt;h2 id="setting-up-ssl-for-your-rails-application">Setting up SSL for your Rails application&lt;/h2>
&lt;p>Setting up SSL is a web server thing. It does not involve your Rails appliation directly (but more on that in a moment).&lt;/p>
&lt;p>If you followed my &lt;a href="http://ariejan.net/2011/09/14/lighting-fast-zero-downtime-deployments-with-git-capistrano-nginx-and-unicorn">nginx+unicorn&lt;/a> guide, you&amp;rsquo;ll have Nginx and Unicorn setup already.&lt;/p>
&lt;h3 id="create-your-private-ssl-key-key">Create your private SSL key (key)&lt;/h3>
&lt;p>First you need to create a private key. Do this on you server. The following command will generate a 2048 bit key. When it asks you to set a passphrase, do so.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">$ openssl genrsa -des3 -out example.com.key &lt;span class="m">2048&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Generating RSA private key, &lt;span class="m">2048&lt;/span> bit long modulus
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">..................................+++
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">.................................................................................+++
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">e is &lt;span class="m">65537&lt;/span> &lt;span class="o">(&lt;/span>0x10001&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Enter pass phrase &lt;span class="k">for&lt;/span> example.com.key:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Verifying - Enter pass phrase &lt;span class="k">for&lt;/span> example.com.key:
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="creating-a-key-and-certificate-sign-request-csr">Creating a key and Certificate Sign Request (csr)&lt;/h3>
&lt;p>Okay, you now have your key. Next step, create the Certificate Sign Request. The sign request is the part you&amp;rsquo;ll send to the SSL authority to sign. You will need to provide some information here. Make sure you enter everything correctly.&lt;/p>
&lt;ul>
&lt;li>&lt;code>Country Name&lt;/code> - Your 2 letter country code. I.e. NL, UK, BE&lt;/li>
&lt;li>&lt;code>State or Povince Name&lt;/code>. I.e. Noord-Brabant, New York&lt;/li>
&lt;li>&lt;code>Locality Name&lt;/code> - Your city. I.e. Eindhoven, London&lt;/li>
&lt;li>&lt;code>Organization Name&lt;/code> - Your company or site name: Ariejan.net, Apple Inc.&lt;/li>
&lt;li>&lt;code>Organization Unit Name&lt;/code> - The section of your company. You may leave this blank. I.e. Online Services, Finance&lt;/li>
&lt;li>&lt;code>Common Name&lt;/code> - The domain this SSL certificate will be used for. If you want to run this on &lt;a href="https://www.example.com">https://www.example.com&lt;/a>, you enter &lt;code>www.example.com&lt;/code> here. This is &lt;em>not&lt;/em> a wildcard. &lt;code>example.com&lt;/code> will &lt;strong>not&lt;/strong> work on &lt;code>www.example.com&lt;/code> and vice versa.&lt;/li>
&lt;li>&lt;code>Email Address&lt;/code>, &lt;code>challenge password&lt;/code> and &lt;code>optional company name&lt;/code> should normally be left blank. Just hit enter.&lt;/li>
&lt;/ul>
&lt;p>Here&amp;rsquo;s the full version:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">$ openssl req -new -key example.com.key -out example.com.csr
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Enter pass phrase &lt;span class="k">for&lt;/span> example.com.key: &lt;span class="s">&amp;lt;&amp;lt;passphrase&amp;gt;&amp;gt;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">You are about to be asked to enter information that will be incorporated
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">into your certificate request.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">What you are about to enter is what is called a Distinguished Name or a DN.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">There are quite a few fields but you can leave some blank
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">For some fields there will be a default value,
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">If you enter &amp;#39;.&amp;#39;, the field will be left blank.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">-----
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">Country Name (2 letter code) [AU]:NL
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">State or Province Name (full name) [Some-State]:Noord-Brabant
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">Locality Name (eg, city) []:Eindhoven
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">Organization Name (eg, company) [Internet Widgits Pty Ltd]:Ariejan.net
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">Organizational Unit Name (eg, section) []:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">Common Name (eg, YOUR name) []:example.com
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">Email Address []:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">Please enter the following &amp;#39;extra&amp;#39; attributes
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">to be sent with your certificate request
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">A challenge pass&lt;/span>word &lt;span class="o">[]&lt;/span>:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">An optional company name &lt;span class="o">[]&lt;/span>:
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Great, you now have two files:&lt;/p>
&lt;ul>
&lt;li>&lt;code>example.com.key&lt;/code> - your private key. Keep it secret, keep it safe!&lt;/li>
&lt;li>&lt;code>example.com.csr&lt;/code> - sign request&lt;/li>
&lt;/ul>
&lt;h3 id="get-your-certificate-crt">Get your certificate (crt)&lt;/h3>
&lt;p>Now, go to your selected SSL authority, order your Standard SSL Certificate and upload the contents of &lt;code>example.com.csr&lt;/code> when requested.&lt;/p>
&lt;p>After doing some validations, which may require you to click some links in emails, you&amp;rsquo;re certificate should be ready for download. Save this file as &lt;code>example.com.crt&lt;/code>.&lt;/p>
&lt;h3 id="intermediate-certificates">Intermediate certificates&lt;/h3>
&lt;p>Some SSL authorities work with so called intermediate certificates. This requires you to include an intermediate certificate with your own certificate. If your SSL provider requires this, save the intermediate certificate as &lt;code>intermediate.crt&lt;/code>.&lt;/p>
&lt;p>For usage with nginx, you must place both your own and the intermediate certificates in a single file. This is easy:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">cat example.com.crt intermediate.crt &amp;gt; sslchain.crt
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="remove-the-passphrase-from-your-key">Remove the passphrase from your key&lt;/h3>
&lt;p>Now, this is not recommended, but many people do this. The reason is that when your private key has passphrase, your server requires that passphrase everytime your (re)start it. This could mean that your server cannot boot up without manual interaction from your part.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">cp example.com.key example.com.key.orig
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">openssl rsa -in example.com.key.orig -out example.com.key
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>You now have &lt;code>example.com.key.orig&lt;/code>, which is your original private key &lt;em>with&lt;/em> the passphrase. And you have &lt;code>example.com.key&lt;/code>, which is the same private key, but &lt;em>without&lt;/em> the passphrase.&lt;/p>
&lt;h3 id="setup-nginx-for-ssl">Setup nginx for SSL&lt;/h3>
&lt;p>Finally, you can setup Nginx for SSL. Normally I add both a SSL and non-SSL configuration. Setup is very easy.&lt;/p>
&lt;p>First of all, become root and copy your keys and certificate to &lt;code>/etc/ssl&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">cp example.com.key example.com.crt /etc/ssl
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>or if you use an intermediate certificate:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">cp example.com.key sslchain.crt /etc/ssl
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Next, you take your non-SSL &lt;code>server&lt;/code> configuration and duplicate it. Then you add the following lines.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-nginx" data-lang="nginx">&lt;span class="line">&lt;span class="cl">&lt;span class="k">listen&lt;/span> &lt;span class="mi">443&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1"># Instead of Listen 80
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">ssl&lt;/span> &lt;span class="no">on&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">ssl_certificate&lt;/span> &lt;span class="s">/etc/ssl/sslchain.crt&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1"># or /etc/ssl/example.com.crt
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">ssl_certificate_key&lt;/span> &lt;span class="s">/etc/ssl/example.com.key&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">location&lt;/span> &lt;span class="s">/&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># Add this to the location directive.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kn">proxy_set_header&lt;/span> &lt;span class="s">X-Forwarded-Proto&lt;/span> &lt;span class="s">https&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Most of this is pretty straight forward. The &lt;code>proxy_set_header&lt;/code> directive is needed to let your Rails application know if the request came in over SSL or not. Normally this shouldn&amp;rsquo;t matter, but you&amp;rsquo;ll need it for the next part of this guide.&lt;/p>
&lt;p>Save, restart nginx and your SSL connection should be available.&lt;/p>
&lt;h2 id="automatically-switch-between-ssl-and-non-ssl-with-rails">Automatically switch between SSL and non-SSL with Rails&lt;/h2>
&lt;p>To take this site as an example, I don&amp;rsquo;t want to run the front-end through SSL. First of all, you can&amp;rsquo;t submit any data to my server. Second, I include several external resources (Disqus, Twitter, AdSense), that will give you warnings about using &amp;ldquo;insecure&amp;rdquo; content on an encrypted page.&lt;/p>
&lt;p>What I &lt;em>do&lt;/em> want is to encrypt traffic to the backend, where I log in and write posts like these.&lt;/p>
&lt;p>I need to make sure that your browser knows exactly when and when not to switch to SSL. This is where the &lt;code>rack-ssl-enforcer&lt;/code> gem comes in.&lt;/p>
&lt;p>First, update your Gemfile:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Gemfile&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">gem&lt;/span> &lt;span class="s1">&amp;#39;rack-ssl-enforcer&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>After you&amp;rsquo;ve run &lt;code>bundle install&lt;/code>, update &lt;code>config/application.rb&lt;/code> (or &lt;code>config/environments/production.rb&lt;/code> is you only want to configure this for your production environment).&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># config/application.rb or config/environments/production.rb&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">config&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">middleware&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">use&lt;/span> &lt;span class="no">Rack&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="no">SslEnforcer&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="ss">:redirect_to&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s1">&amp;#39;https://example.com&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># For when behind a proxy, like nginx&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="ss">:only&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="o">[&lt;/span>&lt;span class="sr">/^\/admin\//&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="sr">/^\/authors\//&lt;/span>&lt;span class="o">]&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># Force SSL on everything behind /admin and /authors&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="ss">:strict&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="kp">true&lt;/span> &lt;span class="c1"># Force no-SSL for everything else&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>With the following statement, you achieve the following:&lt;/p>
&lt;ul>
&lt;li>When you access any URL with a path that starts with &lt;code>/admin&lt;/code> or &lt;code>/authors&lt;/code>, you&amp;rsquo;ll be redirected to the SSL site.&lt;/li>
&lt;li>Because we&amp;rsquo;re behind a proxy and want to do proper redirects, we specify the correct SSL domain.&lt;/li>
&lt;li>We set &lt;code>strict&lt;/code> to true. If you access something that is &lt;em>not&lt;/em> &lt;code>/admin&lt;/code> or &lt;code>/authors&lt;/code>, you will be redirected to the non-SSL version of that page.&lt;/li>
&lt;/ul>
&lt;p>There&amp;rsquo;s a lot more possible with the &lt;a href="http://rubygems.org/gems/rack-ssl-enforcer">rack-ssl-enforcer&lt;/a> gem. Checkout their &lt;a href="https://github.com/tobmatth/rack-ssl-enforcer#readme">README on Github&lt;/a> for details.&lt;/p>
&lt;p>&lt;em>Note: if you find yourself getting into an infinite redirect loop, make sure have the &lt;code>proxy_set_header&lt;/code> directive set correctly in your Nginx configuration.&lt;/em>&lt;/p>
&lt;h2 id="wrapping-up">Wrapping up&lt;/h2>
&lt;p>You now know how you can setup an SSL certificate with Nginx and how you can make your Rails application automatically switch between SSL and non-SSL whenever you want to.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2011/10/22/automatically-switch-between-ssl-and-non-ssl-with-nginx-unicorn-rails/</guid><pubDate>Sat, 22 Oct 2011 00:00:00 +0000</pubDate></item><item><title>Rails 3: Customized exception handling</title><link>https://www.devroom.io/2011/10/14/rails-3-customized-exception-handling/</link><description>&lt;p>Exceptions happen. There&amp;rsquo;s no way around that. But not all exceptions are created equally.&lt;/p>
&lt;p>For instance, a 404 &amp;ldquo;Not found&amp;rdquo; error can (and should) be handled correctly in your application.&lt;/p>
&lt;p>Let me give you an example of how to handle a &lt;code>ActiveRecord::RecordNotFound&lt;/code> exception. Let&amp;rsquo;s assume you have an application that could show a user profile:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># GET /p/:name&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">show&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="vi">@profile&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">Profile&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">find&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">params&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="ss">:name&lt;/span>&lt;span class="o">]&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now, it may happen that the &lt;code>:name&lt;/code> paramater contains a value that cannot be found in our database, most likely because someone made a typo in the URL.&lt;/p>
&lt;p>If &lt;code>Profile#find&lt;/code> cannot get a proper result it will throw &lt;code>ActiveRecord::RecordNotFound&lt;/code>.&lt;/p>
&lt;p>Now, instead of showing the user the (by default ugly) 404 page from &lt;code>public/404.html&lt;/code> we want to do something more fancy.&lt;/p>
&lt;h2 id="action-specific-exception-handling">Action-specific exception handling&lt;/h2>
&lt;p>Here&amp;rsquo;s one solution:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># GET /p/:name&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">show&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="vi">@profile&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">Profile&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">find&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">params&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="ss">:name&lt;/span>&lt;span class="o">]&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">rescue&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">render&lt;/span> &lt;span class="ss">:template&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s1">&amp;#39;application/profile_not_found&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:status&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="ss">:not_found&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>You can now create &lt;code>app/views/applicaiton/profile_not_found.html.haml&lt;/code> and give a nice custom error message to your user.&lt;/p>
&lt;p>You may try to find some matching profiles to &lt;code>:name&lt;/code> or show a search box.&lt;/p>
&lt;h2 id="global-exception-handling">Global exception handling&lt;/h2>
&lt;p>The above example only works for the specific profile &lt;code>show&lt;/code> action. It&amp;rsquo;s also possible to hanlde exceptions on the application level.&lt;/p>
&lt;p>Your &lt;code>show&lt;/code> action still looks like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># GET /p/:name&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">show&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="vi">@profile&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">Profile&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">find&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">params&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="ss">:name&lt;/span>&lt;span class="o">]&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Then, in your &lt;code>app/controllers/application_controller.rb&lt;/code> add this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">ApplicationController&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="no">ActionController&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="no">Base&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">rescue_from&lt;/span> &lt;span class="no">ActiveRecord&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="no">RecordNotFound&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:with&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="ss">:rescue_not_found&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kp">protected&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">rescue_not_found&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">render&lt;/span> &lt;span class="ss">:template&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s1">&amp;#39;application/not_found&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:status&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="ss">:not_found&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Whenever an &lt;code>ActiveRecord::RecordNotFound&lt;/code> exception is thrown (and not handled by the action itself), it will be handled by your &lt;code>ApplicationController&lt;/code>.&lt;/p>
&lt;h2 id="custom-exceptions">Custom exceptions&lt;/h2>
&lt;p>It&amp;rsquo;s possible to throw your own custom exceptions and handle them in different ways. Like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Define your own error&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">MyApp&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="no">ProfileNotFoundError&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="no">StandardError&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># GET /p/:name&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">show&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="vi">@profile&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">Profile&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">find_by_name&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">params&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="ss">:name&lt;/span>&lt;span class="o">]&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">raise&lt;/span> &lt;span class="no">MyApp&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="no">ProfileNotFoundError&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="vi">@profile&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">nil?&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>And add this to your &lt;code>ApplicationController&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="n">rescue_from&lt;/span> &lt;span class="no">MyApp&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="no">ProfileNotFoundError&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:with&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="ss">:profile_not_found&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Optionally, if you don&amp;rsquo;t want to write that custom &lt;code>profile_not_found&lt;/code> method, you may also supply a block:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="n">rescue_from&lt;/span> &lt;span class="no">MyApp&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="no">ProfileNotFoundError&lt;/span> &lt;span class="k">do&lt;/span> &lt;span class="o">|&lt;/span>&lt;span class="n">exception&lt;/span>&lt;span class="o">|&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">render&lt;/span> &lt;span class="ss">:nothing&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s2">&amp;#34;Profile not found.&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:status&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="mi">404&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2011/10/14/rails-3-customized-exception-handling/</guid><pubDate>Fri, 14 Oct 2011 00:00:00 +0000</pubDate></item><item><title>Fast specs - Run your specs in less than 1 second</title><link>https://www.devroom.io/2011/10/13/fast-specs-run-your-specs-in-less-than-1-second/</link><description>&lt;p>Okay, let me clarify that title first. I, as most of you, have two sets of tests for my Rails application: rspec and cucumber. rspec heavily focusses on testing models and business logic while cucumber focusses on testing the entire application stack and user interaction.&lt;/p>
&lt;p>The problem is that as your app grows, your test set grows - and so does the time it takes to run those tests.&lt;/p>
&lt;p>&lt;em>This post is inspired by &lt;a href="http://coreyhaines.com">Corey Haines&lt;/a>&amp;rsquo; talk at &lt;a href="http://arrrrcamp.be">Arrrrcamp&lt;/a> (Oct 2011) and my own experience with writing fast specs.&lt;/em>&lt;/p>
&lt;h2 id="red---green---refactor">Red - Green - Refactor&lt;/h2>
&lt;p>If you do TDD/BDD you most likely follow the Red-Green-Refactor pattern:&lt;/p>
&lt;ol>
&lt;li>Write one test, and see it fail (red)&lt;/li>
&lt;li>Write the most minimal implementation to satisfy that test, and see it pass (green)&lt;/li>
&lt;li>Refactor your code to look/perform better (refactor)&lt;/li>
&lt;li>Repeat&lt;/li>
&lt;/ol>
&lt;p>But what if your test suite takes &amp;gt;30 seconds to run. You write a test, then wait 30 seconds to see the test fail. You then write the simple implementation, wait 30 seconds. Oops, you made a typo - fix it, wait 30 seconds. Now it passes. Refactor, again wait 30 seconds.&lt;/p>
&lt;p>I think this scenario is very familiar for many rails developers.&lt;/p>
&lt;h2 id="take-a-closer-look">Take a closer look&lt;/h2>
&lt;p>So, let&amp;rsquo;s take a closer look at a real-world example.&lt;/p>
&lt;p>This is a &lt;code>Post&lt;/code> model, it &lt;code>belongs_to&lt;/code> and author and it can give you a summary of an article by returning the text above a &amp;lsquo;~&amp;rsquo; marker.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># app/models/post.rb&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">Post&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="no">ActiveRecord&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="no">Base&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="no">DELIMITER&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;~&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">belongs_to&lt;/span> &lt;span class="ss">:author&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">summary&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">summary&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="n">body&lt;/span> &lt;span class="o">=~&lt;/span> &lt;span class="sr">/&lt;/span>&lt;span class="si">#{&lt;/span>&lt;span class="no">DELIMITER&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sr">/i&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">body&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">split&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sr">/&lt;/span>&lt;span class="si">#{&lt;/span>&lt;span class="no">DELIMITER&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sr">/i&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">first&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">strip&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">else&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">body&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This is a spec that&amp;rsquo;s defined for the &lt;code>Post&lt;/code> model:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># spec/models/post_spec.rb&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">require&lt;/span> &lt;span class="s1">&amp;#39;spec/helper&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">describe&lt;/span> &lt;span class="no">Post&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">context&lt;/span> &lt;span class="s2">&amp;#34;summary&amp;#34;&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">it&lt;/span> &lt;span class="s2">&amp;#34;should return the summary&amp;#34;&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">post&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">FactoryGirl&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">build&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="ss">:post&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:body&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s2">&amp;#34;Summary&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s2">~&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s2">No summary.&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">post&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">summary&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">should&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">&amp;#34;Summary&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Running this spec would take at least 10 seconds. Running &lt;code>time rspec spec/models/post_spec.rb&lt;/code> woudl output something like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">Finished in 0.05523 seconds
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">real 0m10.387s
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This means that running the actual spec took 0.05s, but running the entire command took 10 seconds. What is slowing us down?&lt;/p>
&lt;h2 id="dependencies">Dependencies&lt;/h2>
&lt;p>As you see in the spec above I use &lt;code>FactoryGirl.build&lt;/code> instead of &lt;code>FactoryGirl.create&lt;/code> to prevent interacting with the database. I do this because hitting the database slows down your test.&lt;/p>
&lt;p>I removed the database dependency in order for my test to run faster.&lt;/p>
&lt;p>What other dependencies are there that could be removed in order to test the summary functionality?&lt;/p>
&lt;p>Any idea?&lt;/p>
&lt;p>Yes!&lt;/p>
&lt;p>Rails.&lt;/p>
&lt;h2 id="rails-is-a-dependency-to-your-app">Rails is a dependency to your app&lt;/h2>
&lt;p>You have a Rails-app. But Rails is not your app, it&amp;rsquo;s a dependency, just like the &lt;code>pg&lt;/code> and &lt;code>haml&lt;/code> gems are dependencies.&lt;/p>
&lt;p>Loading the entire Rails stack takes quite some time. And just like with the database dependency we must ask ourselves: do we really need Rails to perform this test?&lt;/p>
&lt;p>There are a lot of scenarios where the answer to that question is &lt;em>NO&lt;/em>.&lt;/p>
&lt;h2 id="specs-without-rails">Specs without Rails&lt;/h2>
&lt;p>This may seem a bit weird at first, but let&amp;rsquo;s take another look at the &lt;code>Post&lt;/code> model:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># app/models/post.rb&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">Post&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="no">ActiveRecord&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="no">Base&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="no">DELIMITER&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;~&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">belongs_to&lt;/span> &lt;span class="ss">:author&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">summary&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">summary&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="n">body&lt;/span> &lt;span class="o">=~&lt;/span> &lt;span class="sr">/&lt;/span>&lt;span class="si">#{&lt;/span>&lt;span class="no">DELIMITER&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sr">/i&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">body&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">split&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sr">/&lt;/span>&lt;span class="si">#{&lt;/span>&lt;span class="no">DELIMITER&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sr">/i&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">first&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">strip&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">else&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">body&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The &lt;code>summary&lt;/code> method does not interact with the &lt;code>Post&lt;/code> model at all, except that it access the &lt;code>body&lt;/code> attribute. But the &lt;code>body&lt;/code> attribute is just a &lt;code>String&lt;/code>.&lt;/p>
&lt;p>So, if we wanted to test the &lt;code>summary&lt;/code> method and remove all Rails dependencies, we&amp;rsquo;d have to remove ActiveRecord.&lt;/p>
&lt;p>Consider this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># app/logic/myapp/summary.rb&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">module&lt;/span> &lt;span class="nn">MyApp&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">class&lt;/span> &lt;span class="nc">Summary&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nc">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="nf">for&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">text&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">delimiter&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">summary&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="n">text&lt;/span> &lt;span class="o">=~&lt;/span> &lt;span class="sr">/&lt;/span>&lt;span class="si">#{&lt;/span>&lt;span class="n">delimiter&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sr">/i&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">text&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">split&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sr">/&lt;/span>&lt;span class="si">#{&lt;/span>&lt;span class="n">delimiter&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sr">/i&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">first&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">strip&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">else&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">text&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>I think you can quickly see that this method does exactly the same as &lt;code>Post#summary&lt;/code>. But it does not have any dependency to ActiveRecord.&lt;/p>
&lt;p>Could we rewrite our test for this new class?&lt;/p>
&lt;pre>&lt;code># fast_spec/myapp/summary_spec.rb
require 'myapp/summary'
describe MyApp::Summary do
it &amp;quot;should return the summary&amp;quot; do
MyApp::Summary.for(&amp;quot;Summary\n~\nNo summary.&amp;quot;, &amp;quot;~\n&amp;quot;).should == &amp;quot;Summary&amp;quot;
end
end
&lt;/code>&lt;/pre>
&lt;p>That looks good! Note that this spec &lt;em>does not&lt;/em> include &lt;code>spec_helper&lt;/code>. &lt;code>spec_helper&lt;/code> is responsible for loading up your test environment, which normally includes all your app dependencies, including Rails.&lt;/p>
&lt;h2 id="your-new-post-model">Your new Post model&lt;/h2>
&lt;p>The &lt;code>Post&lt;/code> model should also be updated, of course to utilise this new class.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># app/models/post.rb&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">Post&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="no">ActiveRecord&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="no">Base&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="no">DELIMITER&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;~&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">belongs_to&lt;/span> &lt;span class="ss">:author&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">summary&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="no">MyApp&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="no">Summary&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">for&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">body&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="no">DELIMITER&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The spec for &lt;code>Post&lt;/code> should also be changed. Since we already have tested that &lt;code>MyApp::Summary#for&lt;/code> returns the right summary for a given text and delimiter, all we have left to do is make sure that &lt;code>Post#summary&lt;/code> calls it correctly.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># spec/models/post_spec.rb&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">require&lt;/span> &lt;span class="s1">&amp;#39;spec/helper&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">describe&lt;/span> &lt;span class="no">Post&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">context&lt;/span> &lt;span class="s2">&amp;#34;summary&amp;#34;&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">it&lt;/span> &lt;span class="s2">&amp;#34;should return the summary&amp;#34;&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">post&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">FactoryGirl&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">build&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="ss">:post&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="no">MyApp&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="no">Summary&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">should_receive&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="ss">:for&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">with&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">post&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">body&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="no">Post&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="no">DELIMITER&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">post&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">summary&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="running-fast-specs">Running fast specs&lt;/h2>
&lt;p>The files above are located in &lt;code>fast_spec&lt;/code> and &lt;code>app/logic&lt;/code>. I do this because I want to separate my fast_specs so I can run them independtly from my normal specs.&lt;/p>
&lt;p>Running fast specs works like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">rspec -I app/logic fast_spec
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Try it out with &lt;code>time rspec -I app/logic fast_spec&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">Finished in 0.03223 seconds
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">real 0m0.421s
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>That&amp;rsquo;s your same spec, down from about 10 seconds to 0.5 second.&lt;/p>
&lt;h2 id="the-big-picture">The big picture&lt;/h2>
&lt;p>By now you have seen that you can extract business logic into a seperate class. You&amp;rsquo;ve also seen that you can write tests that don&amp;rsquo;t depend on Rails and run amazingly fast.&lt;/p>
&lt;p>Of course you ask, how does this relate to normal RSpec and Cucumber tests?&lt;/p>
&lt;p>My opinion is that your fast_specs test business logic that does not relate to anything Rails specific, like making calculations, processing text, et cetera.&lt;/p>
&lt;p>When you make changes to your business logic, you can test is very quickly. This shortens your TDD cycle and allows you to focus more on the task at hand (instead of waiting for your tests).&lt;/p>
&lt;p>RSpec tests are there to test integration with your dependencies like Rails. This is the place where test scopes and mailers.&lt;/p>
&lt;p>Cucumber still has its place to test user interaction with your app. Can a user still click the &amp;lsquo;Order&amp;rsquo; button and get the proper response from our app.&lt;/p>
&lt;p>RSpec and Cucumber will now be ran less often. Maybe only before you commit code - or maybe have your CI run them for you?&lt;/p>
&lt;h2 id="added-benefit---design">Added benefit - Design&lt;/h2>
&lt;p>An added bonus of TDD is that your tests dictate the design of your app. By using fast_spec you force your business logic into separate classes. This makes them even more re-usable than normal Rails models.&lt;/p>
&lt;p>Your ActiveRecord models now become cleaner and are more a &lt;em>configuration of Rails&lt;/em> than anything else.&lt;/p>
&lt;p>By doing this your app will become way more maintainable, easier to test, faster to test and your code will be more re-usable. What&amp;rsquo;s not to like?&lt;/p>
&lt;h2 id="where-to-go-from-here">Where to go from here?&lt;/h2>
&lt;p>As Corey Haines put it:&lt;/p>
&lt;blockquote>
&lt;p>Try to extract one single method from your app into a fast_spec. Just one. When you have that one spec running so fast it&amp;rsquo;s easy to add more.&lt;/p>
&lt;/blockquote>
&lt;p>This would also be my advise to you. Try to extract one piece of functionality from a model into a fast_spec. It may be difficult at first, but try it and see the results.&lt;/p>
&lt;p>Then, when you have that one example working, add more.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2011/10/13/fast-specs-run-your-specs-in-less-than-1-second/</guid><pubDate>Thu, 13 Oct 2011 00:00:00 +0000</pubDate></item><item><title>Testing Rails 3 scopes revisited</title><link>https://www.devroom.io/2011/10/09/testing-rails-3-scopes-revisited/</link><description>&lt;p>In my &lt;a href="http://ariejan.net/2011/09/25/properly-testing-rails-3-scopes">previous article&lt;/a> I told you about how I like to tests my scope. There was a fair amount of criticism on that post and after considering it all (and hearing Corey Haines&amp;rsquo; talk on Arrrrcamp last friday), I&amp;rsquo;m convinced it&amp;rsquo;s the wrong path.&lt;/p>
&lt;p>In essence Rails allows you to create a scope to generate a custom database query for you. Now, if you only test your configuration of Rails (as I did in my previous post), you don&amp;rsquo;t know if that scope works or not. If a piece directly relies on your database, you must test it against your database.&lt;/p>
&lt;p>Also, your configuration may be correct, but what happens when you upgrade to a newer version of Rails or PostgreSQL? Does that configuration still work as advertised?&lt;/p>
&lt;p>So, this is the proper way of testing your scopes:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">Post&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="no">ActiveRecord&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="no">Base&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">scope&lt;/span> &lt;span class="ss">:latest&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">order&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;created_at DESC&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">describe&lt;/span> &lt;span class="no">Post&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">context&lt;/span> &lt;span class="s1">&amp;#39;scopes&amp;#39;&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">before&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="ss">:all&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="vi">@first&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">FactoryGirl&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">create&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="ss">:post&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:created_at&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">day&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">ago&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="vi">@last&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">FactoryGirl&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">create&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="ss">:post&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:created_at&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="mi">4&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">day&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">ago&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">it&lt;/span> &lt;span class="s2">&amp;#34;should return posts in the correct order&amp;#34;&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="no">Post&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">latest&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">should&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="o">[&lt;/span>&lt;span class="vi">@first&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="vi">@last&lt;/span>&lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="a-note-on-speed">A note on speed&lt;/h2>
&lt;p>So, you now have to utilize the whole Rails stack &lt;em>and&lt;/em> hit your database to perform this test, which is slow. But then you don&amp;rsquo;t upgrade Rails or your database very often, so you don&amp;rsquo;t have run this spec with every TDD Baby Step you take. (More on that in a later post.)&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2011/10/09/testing-rails-3-scopes-revisited/</guid><pubDate>Sun, 09 Oct 2011 00:00:00 +0000</pubDate></item><item><title>Properly testing Rails 3 scopes</title><link>https://www.devroom.io/2011/09/25/properly-testing-rails-3-scopes/</link><description>&lt;p class="important">The content of this post is no longer correct. Please read &lt;a href="http://ariejan.net/2011/10/09/testing-rails-3-scopes-revisited">this article&lt;/a> for details.&lt;/p>
&lt;p>Testing scopes has always felt a bit weird to me. Normally I&amp;rsquo;d do something like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">Post&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="no">ActiveRecord&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="no">Base&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">scope&lt;/span> &lt;span class="ss">:published&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">where&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="ss">:published&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="kp">true&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">scope&lt;/span> &lt;span class="ss">:latest&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">order&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;created_at DESC&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">describe&lt;/span> &lt;span class="no">Post&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">context&lt;/span> &lt;span class="s1">&amp;#39;scopes&amp;#39;&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">before&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="ss">:all&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="vi">@first&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">FactoryGirl&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">create&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="ss">:post&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:created_at&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">day&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">ago&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:published&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="kp">true&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="vi">@last&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">FactoryGirl&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">create&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="ss">:post&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:created_at&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="mi">4&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">day&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">ago&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:published&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="kp">false&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">it&lt;/span> &lt;span class="s2">&amp;#34;should only return published posts&amp;#34;&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="no">Post&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">published&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">should&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="o">[&lt;/span>&lt;span class="vi">@first&lt;/span>&lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">it&lt;/span> &lt;span class="s2">&amp;#34;should return posts in the correct order&amp;#34;&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="no">Post&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">latest&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">should&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="o">[&lt;/span>&lt;span class="vi">@first&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="vi">@last&lt;/span>&lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This test is okay. It tests if the named scope does what it needs to do. And there&amp;rsquo;s also the problem. Scopes are part of ActiveRecord and are already extensively tested there. All we need to do is check if we &lt;em>configure&lt;/em> the scope correctly.&lt;/p>
&lt;p>What we need is a way to inspect what &lt;code>where&lt;/code> and &lt;code>order&lt;/code> rules are set for a particular scope and make sure those are the correct ones. We can then trust ActiveRecord to keep its promise to execute the proper SQL query for us.&lt;/p>
&lt;p>Here&amp;rsquo;s another test that utilizes some Rails 3 methods you may not have heard of before.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="n">describe&lt;/span> &lt;span class="no">Post&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">context&lt;/span> &lt;span class="s1">&amp;#39;scopes&amp;#39;&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">it&lt;/span> &lt;span class="s2">&amp;#34;should only return published posts&amp;#34;&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="no">Post&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">published&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">where_values_hash&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">should&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="ss">:published&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="kp">true&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">it&lt;/span> &lt;span class="s2">&amp;#34;should return posts in the correct order&amp;#34;&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="no">Post&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">latest&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">order_values&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">should&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="o">[&lt;/span>&lt;span class="s2">&amp;#34;created_at DESC&amp;#34;&lt;/span>&lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The &lt;code>where_values_hash&lt;/code> and &lt;code>order_values&lt;/code> allow you to inspect what a scopes doing. By writing your test this way you achieve two import goals:&lt;/p>
&lt;ol>
&lt;li>You test &lt;em>your&lt;/em> code (instead of the ActiveRecord)&lt;/li>
&lt;li>You don&amp;rsquo;t use the database, which is a significant speed boost&lt;/li>
&lt;/ol>
&lt;p>Happy testing to you all!&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2011/09/25/properly-testing-rails-3-scopes/</guid><pubDate>Sun, 25 Sep 2011 00:00:00 +0000</pubDate></item><item><title>RSpec speed-up (24.6%) by tweaking ruby garbage collection</title><link>https://www.devroom.io/2011/09/24/rspec-speed-up-by-tweaking-ruby-garbage-collection/</link><description>&lt;p>[Today I learned][1] that Ruby garbage collection can be of huge importance to performance. More precisely, if Ruby does a lot of garbage collection it may slow down your code. Running garbage collection only every 10 or 20 seconds when running specs may increase performance dramatically.&lt;/p>
&lt;p>At this time specs for Ariejan.net take an average of 25.29s to run. This is not bad, but in my opinion faster specs are better. After tweaking ruby garbage collection I got my specs to run in 19.05s, a &lt;strong>24.6% speed increase&lt;/strong>!
[1]: &lt;a href="http://37signals.com/svn/posts/2742-the-road-to-faster-tests">http://37signals.com/svn/posts/2742-the-road-to-faster-tests&lt;/a>
~
So, how do &lt;em>you&lt;/em> speed up &lt;em>your&lt;/em> tests?&lt;/p>
&lt;p>The key is that you don&amp;rsquo;t want ruby to decide when to do garbage collection for you. Instead, we&amp;rsquo;ll tell Ruby when to do this, and we&amp;rsquo;ll let it do it every 15 seconds.&lt;/p>
&lt;p>To set this up for RSpec create a file &lt;code>spec/support/deferred_garbage_collection.rb&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">DeferredGarbageCollection&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="no">DEFERRED_GC_THRESHOLD&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="no">ENV&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="s1">&amp;#39;DEFER_GC&amp;#39;&lt;/span>&lt;span class="o">]&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="mi">15&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">to_f&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="vc">@@last_gc_run&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">Time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">now&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nc">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="nf">start&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="no">GC&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">disable&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="no">DEFERRED_GC_THRESHOLD&lt;/span> &lt;span class="o">&amp;gt;&lt;/span> &lt;span class="mi">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nc">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="nf">reconsider&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="no">DEFERRED_GC_THRESHOLD&lt;/span> &lt;span class="o">&amp;gt;&lt;/span> &lt;span class="mi">0&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="no">Time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">now&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="vc">@@last_gc_run&lt;/span> &lt;span class="o">&amp;gt;=&lt;/span> &lt;span class="no">DEFERRED_GC_THRESHOLD&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="no">GC&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">enable&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="no">GC&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">start&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="no">GC&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">disable&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="vc">@@last_gc_run&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">Time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">now&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Next, add the following to your &lt;code>spec/spec_helper.rb&lt;/code> so rspec will use our deferred garbage collection.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="n">config&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">before&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="ss">:all&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="no">DeferredGarbageCollection&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">start&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">config&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">after&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="ss">:all&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="no">DeferredGarbageCollection&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">reconsider&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now, when you run &lt;code>rake spec&lt;/code> you should see a nice speed increase. Try to alter the threshold value a bit to see what gives your best performance:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="no">DEFER_GC&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">20&lt;/span> &lt;span class="n">rake&lt;/span> &lt;span class="n">spec&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Enjoy!&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2011/09/24/rspec-speed-up-by-tweaking-ruby-garbage-collection/</guid><pubDate>Sat, 24 Sep 2011 00:00:00 +0000</pubDate></item><item><title>Capistrano and the custom maintenance page</title><link>https://www.devroom.io/2011/09/19/capistrano-and-the-custom-maintenance-page/</link><description>&lt;p>Randuin posted a comment on my previous &lt;a href="http://ariejan.net/2011/09/14/lighting-fast-zero-downtime-deployments-with-git-capistrano-nginx-and-unicorn">&lt;em>Lighting fast, zero-downtime deployments with git, capistrano, nginx and Unicorn&lt;/em>&lt;/a> post asking how I handle database migrations. This is a good question.&lt;/p>
&lt;p>Database migrations, especially with large datasets, take a long time to run. They also lock your database tables which may cause all kinds of trouble.&lt;/p>
&lt;p>There&amp;rsquo;s a quite an easy solution for this, offered to us by Capistrano. Unfortunately it will cause downtime for your site while the migration is running.&lt;/p>
&lt;p>~&lt;/p>
&lt;h2 id="setup-nginx">Setup nginx&lt;/h2>
&lt;p>The first thing you should do it update your Nginx configuration in &lt;code>/etc/nginx/sites-available/default&lt;/code> and add the following snippet, just before the &lt;code>location /&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-nginx" data-lang="nginx">&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="s">(-f&lt;/span> &lt;span class="nv">$document_root/system/maintenance.html&lt;/span>&lt;span class="s">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kn">return&lt;/span> &lt;span class="mi">503&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">error_page&lt;/span> &lt;span class="mi">503&lt;/span> &lt;span class="s">@maintenance&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">location&lt;/span> &lt;span class="s">@maintenance&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kn">rewrite&lt;/span> &lt;span class="s">^(.*)&lt;/span>$ &lt;span class="s">/system/maintenance.html&lt;/span> &lt;span class="s">last&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kn">break&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>As &lt;a href="http://igels.net">iGEL&lt;/a> and &lt;a href="http://twitter.com/anlek">Anlek Consulting&lt;/a> pointed out in the comments, it&amp;rsquo;s good practice to send a &lt;em>503 Service Temporarily Unavailable&lt;/em> HTTP code back to the client. A normal user won&amp;rsquo;t notice the difference, but spiders do. Sending the 503 code will tell search engines, like Google, that your site is not available and that they should not re-index your maintenance message as the new site content. Instead, they&amp;rsquo;ll come back later when your site returns a &lt;em>HTTP 200&lt;/em> code again.&lt;/p>
&lt;p>You can try this fairly easily by putting your site in maintenance mode (per the instructions that follow) and do a HEAD request:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">$ curl -I http://ariejan.net
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">HTTP/1.1 &lt;span class="m">503&lt;/span> Service Temporarily Unavailable
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Server: nginx/0.8.54
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Date: Tue, &lt;span class="m">20&lt;/span> Sep &lt;span class="m">2011&lt;/span> 18:22:35 GMT
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Content-Type: text/html
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Content-Length: &lt;span class="m">1276&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Connection: keep-alive
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="use-capistrano">Use Capistrano&lt;/h2>
&lt;p>Now, you can run (from your own machine):&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">cap deploy:web:disable
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This task will upload a generic placeholder and place it in &lt;code>public/system/maintenance.html&lt;/code>. If nginx sees this file exists, it will render it and abort further processing. So, as long as the maintenance.html file is present, your app is not accessible.&lt;/p>
&lt;p>When you&amp;rsquo;re migrations are done, you can run:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">cap deploy:web:enable
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This will have remove the &lt;code>maintenance.html&lt;/code> file, and thus making your app accessible again.&lt;/p>
&lt;h2 id="piecing-it-all-together">Piecing it all together&lt;/h2>
&lt;p>What you probably want is a separate task to deploy new code &lt;em>and&lt;/em> run migrations. Here&amp;rsquo;s the task I used:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="n">namespace&lt;/span> &lt;span class="ss">:deploy&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">desc&lt;/span> &lt;span class="s2">&amp;#34;Deploy and migrate the database - this will cause downtime during migrations&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">task&lt;/span> &lt;span class="ss">:migrations&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">transaction&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">update_code&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="ss">web&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="n">disable&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">migrate&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="ss">web&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="n">enable&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">restart&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="customize-your-maintenance-page">Customize your maintenance page&lt;/h2>
&lt;p>Of course you want to customize the maintenance page, because, frankly, it&amp;rsquo;s kind of ugly by default. This does require you write your own &lt;code>deploy:web:disable&lt;/code> task:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="n">namespace&lt;/span> &lt;span class="ss">:deploy&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">namespace&lt;/span> &lt;span class="ss">:web&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">task&lt;/span> &lt;span class="ss">:disable&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:roles&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="ss">:web&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:except&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="ss">:no_release&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="kp">true&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">require&lt;/span> &lt;span class="s1">&amp;#39;erb&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">on_rollback&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="n">run&lt;/span> &lt;span class="s2">&amp;#34;rm &lt;/span>&lt;span class="si">#{&lt;/span>&lt;span class="n">shared_path&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">/system/maintenance.html&amp;#34;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">reason&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">ENV&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="s1">&amp;#39;REASON&amp;#39;&lt;/span>&lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">deadline&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">ENV&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="s1">&amp;#39;UNTIL&amp;#39;&lt;/span>&lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">template&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">File&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">read&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;./app/views/layouts/maintenance.html.erb&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">ERB&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">new&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">template&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">result&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">binding&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">put&lt;/span> &lt;span class="n">result&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">#{&lt;/span>&lt;span class="n">shared_path&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">/system/maintenance.html&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:mode&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="mo">0644&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now you can create a maintenance page for your app at &lt;code>app/views/layouts/maintentance.html.erb&lt;/code>. See &lt;a href="https://github.com/capistrano/capistrano/blob/master/lib/capistrano/recipes/deploy/templates/maintenance.rhtml">the original maintenance template&lt;/a> for inspiration.&lt;/p>
&lt;p>Note that you can use the environment variables &lt;code>REASON&lt;/code> and &lt;code>UNTIL&lt;/code> to give some info about why the app is down.&lt;/p>
&lt;p>&lt;em>Keep in mind that when you put up the &lt;code>maintenance.html&lt;/code> none of your files is accessible. This includes stylesheets and js files. Make sure to host any images and other assets on a remote server like Amazon S3.&lt;/em>&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2011/09/19/capistrano-and-the-custom-maintenance-page/</guid><pubDate>Mon, 19 Sep 2011 00:00:00 +0000</pubDate></item><item><title>Lighting fast, zero-downtime deployments with git, capistrano, nginx and Unicorn</title><link>https://www.devroom.io/2011/09/14/lighting-fast-zero-downtime-deployments-with-git-capistrano-nginx-and-unicorn/</link><description>&lt;p>Everyone who has ever developed a web app has had to deploy it. Back in the day you simply uploaded your files with FTP and everything would be good. Today we have to clone git repositories, restart servers, set permissions, create symlinks to our configuration files, clean out caches and what not.&lt;/p>
&lt;h2 id="doctor-whats-wrong">Doctor, what&amp;rsquo;s wrong?&lt;/h2>
&lt;p>In my opinion there are two critical problems with deployments today:&lt;/p>
&lt;ul>
&lt;li>They are slow&lt;/li>
&lt;li>They cause downtime&lt;/li>
&lt;/ul>
&lt;p>Both topics have been discussed by the likes of Twitter and Github. They have optimized their deployment process to allow for fast and &lt;em>continuous deployments&lt;/em>. But, you are probably stuck with a default Capistrano install. As it turns out, with a little work, it&amp;rsquo;s quite easy to setup the same deployment process as Github and Twitter use.&lt;/p>
&lt;p>For Ariejan.net, I&amp;rsquo;ve managed to get zero-downtime deployments that run in under 10 seconds. Yes, you read that right.
~&lt;/p>
&lt;h2 id="lets-go">Let&amp;rsquo;s go!&lt;/h2>
&lt;p>This guide will help you setup your server and Rails 3.1 project for fast, zero-downtime deployments. I&amp;rsquo;ll be using Nginx+Unicorn to serve the application, git+capistrano for fast deployments.
~&lt;/p>
&lt;h2 id="the-shopping-list">The shopping list&lt;/h2>
&lt;p>Here&amp;rsquo;s a list of ingredients you&amp;rsquo;ll need:&lt;/p>
&lt;ul>
&lt;li>A recent Ubuntu server (I used 11.04 Netty)&lt;/li>
&lt;li>Your Rails 3.1 app&lt;/li>
&lt;li>A remote git repository that contains your app&lt;/li>
&lt;/ul>
&lt;h2 id="assumptions">Assumptions&lt;/h2>
&lt;p>I&amp;rsquo;m making some assumptions about your app:&lt;/p>
&lt;ul>
&lt;li>Ruby 1.9.2&lt;/li>
&lt;li>Rails 3.1 app using Postgres named &lt;code>my_site&lt;/code>&lt;/li>
&lt;li>You want to use RVM and Bundler&lt;/li>
&lt;/ul>
&lt;h2 id="setting-up-your-server">Setting up your server&lt;/h2>
&lt;p>There are a few things you need to setup before diving in. The first bit is run under the &lt;code>root&lt;/code> user.&lt;/p>
&lt;p>Here&amp;rsquo;s the full &lt;code>apt-get&lt;/code> command list I used.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">apt-get update
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">apt-get upgrade -y
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">apt-get install build-essential ruby-full libmagickcore-dev imagemagick libxml2-dev &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> libxslt1-dev git-core postgresql postgresql-client postgresql-server-dev-8.4 nginx curl
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">apt-get build-dep ruby1.9.1
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>You&amp;rsquo;ll also need a separate user account to run your app. Believe me, you don&amp;rsquo;t want to run your app as &lt;code>root&lt;/code>. I call my user &lt;code>deployer&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">useradd -m -g staff -s /bin/bash deployer
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">passwd deployer
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>To allow &lt;code>deployer&lt;/code> to execute commands with super-user privileges, add the following to &lt;code>/etc/sudoers&lt;/code>. This required &lt;code>deployer&lt;/code> to enter his password before allowing super-user access.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># /etc/sudoers&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">%staff &lt;span class="nv">ALL&lt;/span>&lt;span class="o">=(&lt;/span>ALL&lt;span class="o">)&lt;/span> ALL
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="ruby-and-rvm">Ruby and RVM&lt;/h2>
&lt;p>With that done, you&amp;rsquo;re ready to install &lt;code>rvm&lt;/code>, I performed a system-wide install, so make sure you run this as root.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">bash &amp;lt; &amp;lt;&lt;span class="o">(&lt;/span>curl -s https://rvm.beginrescueend.com/install/rvm&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Next up install the required ruby, in this case ruby-1.9.2-p290 and rubygems:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">rvm install ruby-1.9.2-p290
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">wget http://production.cf.rubygems.org/rubygems/rubygems-1.8.10.tgz
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">tar zxvf rubygems-1.8.10.tgz
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> rubygems-1.8.10
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ruby setup.rb
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Create a &lt;code>~/.gemrc&lt;/code> file, this sets some sane defaults for your production server:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># ~/.gemrc&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">---&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="ss">:verbose&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kp">true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="ss">:bulk_threshold&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">1000&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="ss">install&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="o">--&lt;/span>&lt;span class="n">no&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">ri&lt;/span> &lt;span class="o">--&lt;/span>&lt;span class="n">no&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">rdoc&lt;/span> &lt;span class="o">--&lt;/span>&lt;span class="n">env&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">shebang&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="ss">:sources&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">-&lt;/span> &lt;span class="ss">http&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="sr">//&lt;/span>&lt;span class="n">gemcutter&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">org&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">-&lt;/span> &lt;span class="ss">http&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="sr">//&lt;/span>&lt;span class="n">gems&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">rubyforge&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">org&lt;/span>&lt;span class="o">/&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">-&lt;/span> &lt;span class="ss">http&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="sr">//&lt;/span>&lt;span class="n">gems&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">github&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">com&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="ss">:benchmark&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kp">false&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="ss">:backtrace&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kp">false&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="ss">update&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="o">--&lt;/span>&lt;span class="n">no&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">ri&lt;/span> &lt;span class="o">--&lt;/span>&lt;span class="n">no&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">rdoc&lt;/span> &lt;span class="o">--&lt;/span>&lt;span class="n">env&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">shebang&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="ss">:update_sources&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kp">true&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Also create this &lt;code>~/.rvmrc&lt;/code> file to auto-trust your .rvmrc project files:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># ~/.rvmrc&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">rvm_trust_rvmrcs_flag&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;em>Note: do this for both &lt;code>root&lt;/code> and the &lt;code>deployer&lt;/code> user to avoid confusion later on.&lt;/em>&lt;/p>
&lt;p>Because you&amp;rsquo;ll be running your app in production-mode all the time, add the following line to &lt;code>/etc/environment&lt;/code> so you don&amp;rsquo;t have to repeat it with every Rails related command you use:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">&lt;span class="nv">RAILS_ENV&lt;/span>&lt;span class="o">=&lt;/span>production
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="postgres">Postgres&lt;/h2>
&lt;p>I know not everybody uses Postgres, but I do. I love it and it beats the living crap out of MySQL. If you use MySQL, you&amp;rsquo;ll know what to do. Here are instructions for setting up Postgres. First create the database and login as the &lt;code>postgres&lt;/code> user:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">sudo -u postgres createdb my_site
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo -u postgres psql
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Then execute the following SQL:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">CREATE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">USER&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">my_site&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">WITH&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">PASSWORD&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;password&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">GRANT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">ALL&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">PRIVILEGES&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">ON&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">DATABASE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">my_site&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">TO&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">my_site&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="nginx">Nginx&lt;/h2>
&lt;p>Nginx is a great piece of Russian engineering. You&amp;rsquo;ll need some configuration though:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-nginx" data-lang="nginx">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># /etc/nginx/sites-available/default
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">upstream&lt;/span> &lt;span class="s">my_site&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># fail_timeout=0 means we always retry an upstream even if it failed
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="c1"># to return a good HTTP response (in case the Unicorn master nukes a
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="c1"># single worker for timing out).
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># for UNIX domain socket setups:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kn">server&lt;/span> &lt;span class="s">unix:/tmp/my_site.socket&lt;/span> &lt;span class="s">fail_timeout=0&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">server&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># if you&amp;#39;re running multiple servers, instead of &amp;#34;default&amp;#34; you should
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="c1"># put your main domain name here
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kn">listen&lt;/span> &lt;span class="mi">80&lt;/span> &lt;span class="s">default&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># you could put a list of other domain names this application answers
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kn">server_name&lt;/span> &lt;span class="s">my_site.example.com&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kn">root&lt;/span> &lt;span class="s">/home/deployer/apps/my_site/current/public&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kn">access_log&lt;/span> &lt;span class="s">/var/log/nginx/my_site_access.log&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kn">rewrite_log&lt;/span> &lt;span class="no">on&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kn">location&lt;/span> &lt;span class="s">/&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">#all requests are sent to the UNIX socket
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kn">proxy_pass&lt;/span> &lt;span class="s">http://my_site&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kn">proxy_redirect&lt;/span> &lt;span class="no">off&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kn">proxy_set_header&lt;/span> &lt;span class="s">Host&lt;/span> &lt;span class="nv">$host&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kn">proxy_set_header&lt;/span> &lt;span class="s">X-Real-IP&lt;/span> &lt;span class="nv">$remote_addr&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kn">proxy_set_header&lt;/span> &lt;span class="s">X-Forwarded-For&lt;/span> &lt;span class="nv">$proxy_add_x_forwarded_for&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kn">client_max_body_size&lt;/span> &lt;span class="mi">10m&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kn">client_body_buffer_size&lt;/span> &lt;span class="mi">128k&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kn">proxy_connect_timeout&lt;/span> &lt;span class="mi">90&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kn">proxy_send_timeout&lt;/span> &lt;span class="mi">90&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kn">proxy_read_timeout&lt;/span> &lt;span class="mi">90&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kn">proxy_buffer_size&lt;/span> &lt;span class="mi">4k&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kn">proxy_buffers&lt;/span> &lt;span class="mi">4&lt;/span> &lt;span class="mi">32k&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kn">proxy_busy_buffers_size&lt;/span> &lt;span class="mi">64k&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kn">proxy_temp_file_write_size&lt;/span> &lt;span class="mi">64k&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># if the request is for a static resource, nginx should serve it directly
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="c1"># and add a far future expires header to it, making the browser
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="c1"># cache the resource and navigate faster over the website
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="c1"># this probably needs some work with Rails 3.1&amp;#39;s asset pipe_line
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kn">location&lt;/span> &lt;span class="p">~&lt;/span> &lt;span class="sr">^/(images|javascripts|stylesheets|system)/&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kn">root&lt;/span> &lt;span class="s">/home/deployer/apps/my_site/current/public&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kn">expires&lt;/span> &lt;span class="s">max&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kn">break&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>All dandy! One more then:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-nginx" data-lang="nginx">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># /etc/nginx/nginx.conf
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">user&lt;/span> &lt;span class="s">deployer&lt;/span> &lt;span class="s">staff&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Change this depending on your hardware
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">worker_processes&lt;/span> &lt;span class="mi">4&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">pid&lt;/span> &lt;span class="s">/var/run/nginx.pid&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">events&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kn">worker_connections&lt;/span> &lt;span class="mi">1024&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kn">multi_accept&lt;/span> &lt;span class="no">on&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">http&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kn">sendfile&lt;/span> &lt;span class="no">on&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kn">tcp_nopush&lt;/span> &lt;span class="no">on&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kn">tcp_nodelay&lt;/span> &lt;span class="no">off&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># server_tokens off;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># server_names_hash_bucket_size 64;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="c1"># server_name_in_redirect off;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kn">include&lt;/span> &lt;span class="s">/etc/nginx/mime.types&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kn">default_type&lt;/span> &lt;span class="s">application/octet-stream&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kn">access_log&lt;/span> &lt;span class="s">/var/log/nginx/access.log&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kn">error_log&lt;/span> &lt;span class="s">/var/log/nginx/error.log&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kn">gzip&lt;/span> &lt;span class="no">on&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kn">gzip_disable&lt;/span> &lt;span class="s">&amp;#34;msie6&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># gzip_vary on;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kn">gzip_proxied&lt;/span> &lt;span class="s">any&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kn">gzip_min_length&lt;/span> &lt;span class="mi">500&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># gzip_comp_level 6;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="c1"># gzip_buffers 16 8k;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="c1"># gzip_http_version 1.1;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kn">gzip_types&lt;/span> &lt;span class="s">text/plain&lt;/span> &lt;span class="s">text/css&lt;/span> &lt;span class="s">application/json&lt;/span> &lt;span class="s">application/x-javascript&lt;/span> &lt;span class="s">text/xml&lt;/span> &lt;span class="s">application/xml&lt;/span> &lt;span class="s">application/xml+rss&lt;/span> &lt;span class="s">text/javascript&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">##
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="c1"># Virtual Host Configs
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="c1">##
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kn">include&lt;/span> &lt;span class="s">/etc/nginx/conf.d/*.conf&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kn">include&lt;/span> &lt;span class="s">/etc/nginx/sites-enabled/*&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Okay, that&amp;rsquo;s Nginx for you. You should start it now, although you&amp;rsquo;ll get a 500 or proxy error now:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">/etc/init.d/nginx start
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="unicorn">Unicorn&lt;/h2>
&lt;p>The next part involves setting up Capistrano and unicorn for your project. This is where the real magic will happen.&lt;/p>
&lt;p>You&amp;rsquo;ll be doing &lt;code>cap deploy&lt;/code> 99% of the time. This command needs to be &lt;em>fast&lt;/em>. To accomplish this I want to utilize the power of git. Instead of having Capistrano juggle around a bunch of release directories, which is painfully slow, I want to use git to switch to the correct version of my app. This means I&amp;rsquo;ll have just &lt;em>one&lt;/em> directory that is updated by git when it needs to be.&lt;/p>
&lt;p>Let&amp;rsquo;s get started by adding some gems to your app. When done run &lt;code>bundle install&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># Gemfile&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">gem&lt;/span> &lt;span class="s2">&amp;#34;unicorn&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">group&lt;/span> &lt;span class="ss">:development&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">gem&lt;/span> &lt;span class="s2">&amp;#34;capistrano&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The next step is adding a configuration file for Unicorn in &lt;code>config/unicorn.rb&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># config/unicorn.rb&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Set environment to development unless something else is specified&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">env&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">ENV&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="s2">&amp;#34;RAILS_ENV&amp;#34;&lt;/span>&lt;span class="o">]&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="s2">&amp;#34;development&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># See http://unicorn.bogomips.org/Unicorn/Configurator.html for complete&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># documentation.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">worker_processes&lt;/span> &lt;span class="mi">4&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># listen on both a Unix domain socket and a TCP port,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># we use a shorter backlog for quicker failover when busy&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">listen&lt;/span> &lt;span class="s2">&amp;#34;/tmp/my_site.socket&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:backlog&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="mi">64&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Preload our app for more speed&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">preload_app&lt;/span> &lt;span class="kp">true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># nuke workers after 30 seconds instead of 60 seconds (the default)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">timeout&lt;/span> &lt;span class="mi">30&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">pid&lt;/span> &lt;span class="s2">&amp;#34;/tmp/unicorn.my_site.pid&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Production specific settings&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="n">env&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">&amp;#34;production&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># Help ensure your application will always spawn in the symlinked&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># &amp;#34;current&amp;#34; directory that Capistrano sets up.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">working_directory&lt;/span> &lt;span class="s2">&amp;#34;/home/deployer/apps/my_site/current&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># feel free to point this anywhere accessible on the filesystem&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">user&lt;/span> &lt;span class="s1">&amp;#39;deployer&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;staff&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">shared_path&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;/home/deployer/apps/my_site/shared&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">stderr_path&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">#{&lt;/span>&lt;span class="n">shared_path&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">/log/unicorn.stderr.log&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">stdout_path&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">#{&lt;/span>&lt;span class="n">shared_path&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">/log/unicorn.stdout.log&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">before_fork&lt;/span> &lt;span class="k">do&lt;/span> &lt;span class="o">|&lt;/span>&lt;span class="n">server&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">worker&lt;/span>&lt;span class="o">|&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># the following is highly recomended for Rails + &amp;#34;preload_app true&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># as there&amp;#39;s no need for the master process to hold a connection&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">defined?&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="no">ActiveRecord&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="no">Base&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="no">ActiveRecord&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="no">Base&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">connection&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">disconnect!&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># Before forking, kill the master process that belongs to the .oldbin PID.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># This enables 0 downtime deploys.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">old_pid&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;/tmp/unicorn.my_site.pid.oldbin&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="no">File&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">exists?&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">old_pid&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="n">server&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">pid&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="n">old_pid&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">begin&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="no">Process&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">kill&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;QUIT&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="no">File&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">read&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">old_pid&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">to_i&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">rescue&lt;/span> &lt;span class="no">Errno&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="no">ENOENT&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="no">Errno&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="no">ESRCH&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># someone else did our job for us&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">after_fork&lt;/span> &lt;span class="k">do&lt;/span> &lt;span class="o">|&lt;/span>&lt;span class="n">server&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">worker&lt;/span>&lt;span class="o">|&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># the following is *required* for Rails + &amp;#34;preload_app true&amp;#34;,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">defined?&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="no">ActiveRecord&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="no">Base&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="no">ActiveRecord&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="no">Base&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">establish_connection&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># if preload_app is true, then you may also want to check and&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># restart any other shared sockets/descriptors such as Memcached,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># and Redis. TokyoCabinet file handles are safe to reuse&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># between any number of forked children (assuming your kernel&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># correctly implements pread()/pwrite() system calls)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Okay, as you can see there&amp;rsquo;s some nice stuff in there to accomplish zero-downtime restarts. Let me tell you a bit more about that.&lt;/p>
&lt;p>Unicorn starts as a &lt;code>master&lt;/code> process and then spawns several workers (we configured four). When you send Unicorn the &amp;lsquo;USR2&amp;rsquo; signal it will rename itself to &lt;code>master (old)&lt;/code> and create a new master process. The old master will keep running.&lt;/p>
&lt;p>Now, when the new master starts and forks a worker it checks the PID files of the new and old Unicorn masters. If those are different, the new master was started correctly. We can now send the old master the QUIT signal, shutting it down gracefully (e.g. let it handle open requests, but not new ones).&lt;/p>
&lt;p>All the while, you have restarted your app, without taking it down: zero downtime!&lt;/p>
&lt;h2 id="capistrano">Capistrano&lt;/h2>
&lt;p>Now for Capistrano, add the following to your &lt;code>Gemfile&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Gemfile&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">group&lt;/span> &lt;span class="ss">:development&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">gem&lt;/span> &lt;span class="s2">&amp;#34;capistrano&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>And generate the necessary Capistrano files.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">capify .
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Open up &lt;code>config/deploy.rb&lt;/code> and replace it with the following.&lt;/p>
&lt;p>This deploy script does all the usual, but the special part is where you reset the release paths to the current path, making the whole release directory unnecessary.&lt;/p>
&lt;p>Also not that the &lt;code>update_code&lt;/code> is overwritten to do a simple &lt;code>git fetch&lt;/code> and &lt;code>git reset&lt;/code> - this is very fast indeed!&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># config/deploy.rb&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">require&lt;/span> &lt;span class="s2">&amp;#34;bundler/capistrano&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">set&lt;/span> &lt;span class="ss">:scm&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:git&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">set&lt;/span> &lt;span class="ss">:repository&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;git@codeplane.com:you/my_site.git&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">set&lt;/span> &lt;span class="ss">:branch&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;origin/master&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">set&lt;/span> &lt;span class="ss">:migrate_target&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:current&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">set&lt;/span> &lt;span class="ss">:ssh_options&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="ss">:forward_agent&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="kp">true&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">set&lt;/span> &lt;span class="ss">:rails_env&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;production&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">set&lt;/span> &lt;span class="ss">:deploy_to&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;/home/deployer/apps/my_site&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">set&lt;/span> &lt;span class="ss">:normalize_asset_timestamps&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kp">false&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">set&lt;/span> &lt;span class="ss">:user&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;deployer&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">set&lt;/span> &lt;span class="ss">:group&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;staff&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">set&lt;/span> &lt;span class="ss">:use_sudo&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kp">false&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">role&lt;/span> &lt;span class="ss">:web&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;123.456.789.012&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">role&lt;/span> &lt;span class="ss">:app&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;123.456.789.012&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">role&lt;/span> &lt;span class="ss">:db&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;123.456.789.012&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:primary&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="kp">true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">set&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="ss">:latest_release&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="n">fetch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="ss">:current_path&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">set&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="ss">:release_path&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="n">fetch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="ss">:current_path&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">set&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="ss">:current_release&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="n">fetch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="ss">:current_path&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">set&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="ss">:current_revision&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="n">capture&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;cd &lt;/span>&lt;span class="si">#{&lt;/span>&lt;span class="n">current_path&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">; git rev-parse --short HEAD&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">strip&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">set&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="ss">:latest_revision&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="n">capture&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;cd &lt;/span>&lt;span class="si">#{&lt;/span>&lt;span class="n">current_path&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">; git rev-parse --short HEAD&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">strip&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">set&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="ss">:previous_revision&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="n">capture&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;cd &lt;/span>&lt;span class="si">#{&lt;/span>&lt;span class="n">current_path&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">; git rev-parse --short HEAD@{1}&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">strip&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">default_environment&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="s2">&amp;#34;RAILS_ENV&amp;#34;&lt;/span>&lt;span class="o">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;production&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Use our ruby-1.9.2-p290@my_site gemset&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">default_environment&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="s2">&amp;#34;PATH&amp;#34;&lt;/span>&lt;span class="o">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;--&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">default_environment&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="s2">&amp;#34;GEM_HOME&amp;#34;&lt;/span>&lt;span class="o">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;--&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">default_environment&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="s2">&amp;#34;GEM_PATH&amp;#34;&lt;/span>&lt;span class="o">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;--&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">default_environment&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="s2">&amp;#34;RUBY_VERSION&amp;#34;&lt;/span>&lt;span class="o">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;ruby-1.9.2-p290&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">default_run_options&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="ss">:shell&lt;/span>&lt;span class="o">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;bash&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">namespace&lt;/span> &lt;span class="ss">:deploy&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">desc&lt;/span> &lt;span class="s2">&amp;#34;Deploy your application&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">task&lt;/span> &lt;span class="ss">:default&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">update&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">restart&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">desc&lt;/span> &lt;span class="s2">&amp;#34;Setup your git-based deployment app&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">task&lt;/span> &lt;span class="ss">:setup&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:except&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="ss">:no_release&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="kp">true&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">dirs&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">[&lt;/span>&lt;span class="n">deploy_to&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">shared_path&lt;/span>&lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">dirs&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="n">shared_children&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">map&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="o">|&lt;/span>&lt;span class="n">d&lt;/span>&lt;span class="o">|&lt;/span> &lt;span class="no">File&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">join&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">shared_path&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">d&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">run&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">#{&lt;/span>&lt;span class="n">try_sudo&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> mkdir -p &lt;/span>&lt;span class="si">#{&lt;/span>&lt;span class="n">dirs&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">join&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39; &amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> &amp;amp;&amp;amp; &lt;/span>&lt;span class="si">#{&lt;/span>&lt;span class="n">try_sudo&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> chmod g+w &lt;/span>&lt;span class="si">#{&lt;/span>&lt;span class="n">dirs&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">join&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39; &amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">run&lt;/span> &lt;span class="s2">&amp;#34;git clone &lt;/span>&lt;span class="si">#{&lt;/span>&lt;span class="n">repository&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> &lt;/span>&lt;span class="si">#{&lt;/span>&lt;span class="n">current_path&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">task&lt;/span> &lt;span class="ss">:cold&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">update&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">migrate&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">task&lt;/span> &lt;span class="ss">:update&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">transaction&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">update_code&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">desc&lt;/span> &lt;span class="s2">&amp;#34;Update the deployed code.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">task&lt;/span> &lt;span class="ss">:update_code&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:except&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="ss">:no_release&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="kp">true&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">run&lt;/span> &lt;span class="s2">&amp;#34;cd &lt;/span>&lt;span class="si">#{&lt;/span>&lt;span class="n">current_path&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">; git fetch origin; git reset --hard &lt;/span>&lt;span class="si">#{&lt;/span>&lt;span class="n">branch&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">finalize_update&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">desc&lt;/span> &lt;span class="s2">&amp;#34;Update the database (overwritten to avoid symlink)&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">task&lt;/span> &lt;span class="ss">:migrations&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">transaction&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">update_code&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">migrate&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">restart&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">task&lt;/span> &lt;span class="ss">:finalize_update&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:except&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="ss">:no_release&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="kp">true&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">run&lt;/span> &lt;span class="s2">&amp;#34;chmod -R g+w &lt;/span>&lt;span class="si">#{&lt;/span>&lt;span class="n">latest_release&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="n">fetch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="ss">:group_writable&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kp">true&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># mkdir -p is making sure that the directories are there for some SCM&amp;#39;s that don&amp;#39;t&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># save empty folders&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">run&lt;/span> &lt;span class="s">&amp;lt;&amp;lt;-CMD
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">&lt;/span> &lt;span class="n">rm&lt;/span> &lt;span class="o">-&lt;/span>&lt;span class="n">rf&lt;/span> &lt;span class="c1">#{latest_release}/log #{latest_release}/public/system #{latest_release}/tmp/pids &amp;amp;&amp;amp;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">mkdir&lt;/span> &lt;span class="o">-&lt;/span>&lt;span class="nb">p&lt;/span> &lt;span class="c1">#{latest_release}/public &amp;amp;&amp;amp;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">mkdir&lt;/span> &lt;span class="o">-&lt;/span>&lt;span class="nb">p&lt;/span> &lt;span class="c1">#{latest_release}/tmp &amp;amp;&amp;amp;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">ln&lt;/span> &lt;span class="o">-&lt;/span>&lt;span class="n">s&lt;/span> &lt;span class="c1">#{shared_path}/log #{latest_release}/log &amp;amp;&amp;amp;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">ln&lt;/span> &lt;span class="o">-&lt;/span>&lt;span class="n">s&lt;/span> &lt;span class="c1">#{shared_path}/system #{latest_release}/public/system &amp;amp;&amp;amp;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">ln&lt;/span> &lt;span class="o">-&lt;/span>&lt;span class="n">s&lt;/span> &lt;span class="c1">#{shared_path}/pids #{latest_release}/tmp/pids &amp;amp;&amp;amp;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">ln&lt;/span> &lt;span class="o">-&lt;/span>&lt;span class="n">sf&lt;/span> &lt;span class="c1">#{shared_path}/database.yml #{latest_release}/config/database.yml&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="no">CMD&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">fetch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="ss">:normalize_asset_timestamps&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kp">true&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">stamp&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">Time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">now&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">utc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">strftime&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;%Y%m%d%H%M.%S&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">asset_paths&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">fetch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="ss">:public_children&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="sx">%w(images stylesheets javascripts)&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">map&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="o">|&lt;/span>&lt;span class="nb">p&lt;/span>&lt;span class="o">|&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">#{&lt;/span>&lt;span class="n">latest_release&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">/public/&lt;/span>&lt;span class="si">#{&lt;/span>&lt;span class="nb">p&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> &lt;span class="p">}&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">join&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34; &amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">run&lt;/span> &lt;span class="s2">&amp;#34;find &lt;/span>&lt;span class="si">#{&lt;/span>&lt;span class="n">asset_paths&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> -exec touch -t &lt;/span>&lt;span class="si">#{&lt;/span>&lt;span class="n">stamp&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> {} &amp;#39;;&amp;#39;; true&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:env&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="s2">&amp;#34;TZ&amp;#34;&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s2">&amp;#34;UTC&amp;#34;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">desc&lt;/span> &lt;span class="s2">&amp;#34;Zero-downtime restart of Unicorn&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">task&lt;/span> &lt;span class="ss">:restart&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:except&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="ss">:no_release&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="kp">true&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">run&lt;/span> &lt;span class="s2">&amp;#34;kill -s USR2 `cat /tmp/unicorn.my_site.pid`&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">desc&lt;/span> &lt;span class="s2">&amp;#34;Start unicorn&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">task&lt;/span> &lt;span class="ss">:start&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:except&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="ss">:no_release&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="kp">true&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">run&lt;/span> &lt;span class="s2">&amp;#34;cd &lt;/span>&lt;span class="si">#{&lt;/span>&lt;span class="n">current_path&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> ; bundle exec unicorn_rails -c config/unicorn.rb -D&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">desc&lt;/span> &lt;span class="s2">&amp;#34;Stop unicorn&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">task&lt;/span> &lt;span class="ss">:stop&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:except&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="ss">:no_release&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="kp">true&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">run&lt;/span> &lt;span class="s2">&amp;#34;kill -s QUIT `cat /tmp/unicorn.my_site.pid`&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">namespace&lt;/span> &lt;span class="ss">:rollback&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">desc&lt;/span> &lt;span class="s2">&amp;#34;Moves the repo back to the previous version of HEAD&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">task&lt;/span> &lt;span class="ss">:repo&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:except&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="ss">:no_release&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="kp">true&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">set&lt;/span> &lt;span class="ss">:branch&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;HEAD@{1}&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">deploy&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">default&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">desc&lt;/span> &lt;span class="s2">&amp;#34;Rewrite reflog so HEAD@{1} will continue to point to at the next previous release.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">task&lt;/span> &lt;span class="ss">:cleanup&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:except&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="ss">:no_release&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="kp">true&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">run&lt;/span> &lt;span class="s2">&amp;#34;cd &lt;/span>&lt;span class="si">#{&lt;/span>&lt;span class="n">current_path&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">; git reflog delete --rewrite HEAD@{1}; git reflog delete --rewrite HEAD@{1}&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">desc&lt;/span> &lt;span class="s2">&amp;#34;Rolls back to the previously deployed version.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">task&lt;/span> &lt;span class="ss">:default&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">rollback&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">repo&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">rollback&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">cleanup&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">run_rake&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">cmd&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">run&lt;/span> &lt;span class="s2">&amp;#34;cd &lt;/span>&lt;span class="si">#{&lt;/span>&lt;span class="n">current_path&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">; &lt;/span>&lt;span class="si">#{&lt;/span>&lt;span class="n">rake&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> &lt;/span>&lt;span class="si">#{&lt;/span>&lt;span class="n">cmd&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now there is one little thing you&amp;rsquo;ll need to do. I like to run my apps, even on the server, to use their own gemset. This keeps everything clean and isolated. Login to the &lt;code>deployer&lt;/code> account and create your gemset. Next run &lt;code>rvm info&lt;/code> and fill the &lt;code>PATH&lt;/code>, &lt;code>GEM_HOME&lt;/code> and &lt;code>GEM_PATH&lt;/code> variables accordingly.&lt;/p>
&lt;blockquote>
&lt;p>Don&amp;rsquo;t forget to install &lt;code>bundler&lt;/code> in your new gemset&lt;/p>
&lt;/blockquote>
&lt;h2 id="database-configuration">Database configuration&lt;/h2>
&lt;p>I always like to keep the database configuration out of git. I&amp;rsquo;ll place it in the shared directory.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="c"># /home/deployer/apps/my_site/shared/database.yml&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">production&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">adapter&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">postgresql&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">encoding&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">unicode&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">database&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">my_site_production&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">pool&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">5&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">username&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">my_site&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">password&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">password&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="first-setup">First setup&lt;/h2>
&lt;p>Now setup your deployment like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">cap deploy:setup
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This will clone your repo and link your &lt;code>database.yml&lt;/code> file. Optionally, you may want to run migrations or upload an SQL dump to get started quickly with your app.&lt;/p>
&lt;h2 id="deployments">Deployments&lt;/h2>
&lt;p>Whenever you have a new feature developed in a feature branch, this is the process of deploying it:&lt;/p>
&lt;ol>
&lt;li>Merge &lt;code>feature_branch&lt;/code> into &lt;code>master&lt;/code>&lt;/li>
&lt;li>Run your tests to make sure everything is dandy.&lt;/li>
&lt;li>Push &lt;code>master&lt;/code>&lt;/li>
&lt;li>Run &lt;code>cap deploy&lt;/code>&lt;/li>
&lt;/ol>
&lt;p>For Ariejan.net, step 4 takes less than 10 seconds. Unicorn is restarted with zero-downtime so users don&amp;rsquo;t even notice the site was updated.&lt;/p>
&lt;h2 id="whats-next">What&amp;rsquo;s next?&lt;/h2>
&lt;p>You now have fast, zero-downtime deployments working for your app. There are still some things you should to (which I might cover in some later post):&lt;/p>
&lt;ul>
&lt;li>Tweak the nginx and Unicorn settings (especially number of workers); Perform some tests and see what works best for your app/server combination.&lt;/li>
&lt;li>Add caching to nginx (or add Varnish)&lt;/li>
&lt;li>Enable some monitoring, maybe Monit&lt;/li>
&lt;li>Crank up security a notch&lt;/li>
&lt;li>Integrate other deployment tasks like &lt;code>whenever&lt;/code> or prepare your assets&lt;/li>
&lt;/ul></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2011/09/14/lighting-fast-zero-downtime-deployments-with-git-capistrano-nginx-and-unicorn/</guid><pubDate>Wed, 14 Sep 2011 00:00:00 +0000</pubDate></item><item><title>Git: checkout a single file from another commit or branch</title><link>https://www.devroom.io/2011/09/13/git-checkout-a-single-file-from-another-commit-or-branch/</link><description>&lt;p>I recently worked on a new feature in a separate branch. It didn&amp;rsquo;t work out well, so I branched master again and tried another solution. However, I needed a specific filesI committed in the first feature branch. To avoid placing those files back in my working copy by hand, I used git to checkout the specific file from the other branch.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">git checkout feature_1 -- path/to/file/iwant
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This will &lt;em>not&lt;/em> checkout the &lt;code>feature_1&lt;/code> branch, but instead checkout the most recent version of &lt;code>path/to/file/iwant&lt;/code> in the &lt;code>feature_1&lt;/code> branch. Very handy indeed!&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2011/09/13/git-checkout-a-single-file-from-another-commit-or-branch/</guid><pubDate>Tue, 13 Sep 2011 00:00:00 +0000</pubDate></item><item><title>Git: remove, reset and rollback commits</title><link>https://www.devroom.io/2011/09/08/git-remove-reset-and-rollback-commits/</link><description>&lt;p>We&amp;rsquo;ve all been there, you committed changes you now regret. If you didn&amp;rsquo;t share those commits with anyone yet, you&amp;rsquo;re safe. Let me show you how to remove commits from your local repository. I&amp;rsquo;ll also include an example how to roll back commits you already did share with others.
~
Use &lt;code>git log&lt;/code> to see your most recent commits. Let&amp;rsquo;s say you want to revert the last three commits, you can run the following command:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">git reset --hard HEAD~3
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>If you only want the last commit to be removed:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">git reset --hard HEAD~1
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>HEAD~1 is a shorthand for the commit before head. But, it&amp;rsquo;s also possible to roll back to a specific commit using the SHA hash.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">git reset --hard d3f1a8
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;blockquote>
&lt;p>Please note that all your uncommitted changes will be lost when you perform &lt;code>git reset --hard&lt;/code>. You might want to use &lt;a href="http://ariejan.net/2008/04/23/git-using-the-stash/">git stash&lt;/a> to save your uncommitted changes.&lt;/p>
&lt;/blockquote>
&lt;p>In case you already pushed your changes to a remote repository, you can&amp;rsquo;t use &lt;code>git reset&lt;/code>, because it will wreak havoc with other people&amp;rsquo;s repositories later. Instead, you could revert your commit (e.g. create a new commit, undoing a previous one).&lt;/p>
&lt;p>Note that git revert does not walk back into history, but only works on a specific commit or range of commits. To use my previous examples:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">git revert HEAD~3..HEAD
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">git revert HEAD~1..HEAD
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">git revert d3f1a8..master
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Optionally specify the &lt;code>--no-commit&lt;/code> option to see what&amp;rsquo;s being reverted.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2011/09/08/git-remove-reset-and-rollback-commits/</guid><pubDate>Thu, 08 Sep 2011 00:00:00 +0000</pubDate></item><item><title>Git Log: What did I do yesterday, exactly?</title><link>https://www.devroom.io/2011/08/24/git-log-what-did-i-do-yesterday-exactly/</link><description>&lt;p>Sometimes you have to take your git repository&amp;rsquo;s log to see what you did the day before (ideal in preparation for the daily stand-up). What I want is a clean overview of each commit messages, their author and the time. The output result should be easily grep-able so I can filter stuff I don&amp;rsquo;t need out.
~
To do this, I use the following custom &lt;code>git log&lt;/code> command:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">git log --pretty&lt;span class="o">=&lt;/span>format:&lt;span class="s1">&amp;#39;%Cred%h%Creset - %C(yellow)%ae%Creset - %Cgreen%cd%Creset - %s%Creset&amp;#39;&lt;/span> --abbrev-commit --date&lt;span class="o">=&lt;/span>iso
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The result:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">5d6ef1e - ariejan@ariejan.net - 2011-05-02 10:36:43 +0200 - Bumped to version 1.5.2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">afede9e - ariejan@ariejan.net - 2011-05-02 10:35:53 +0200 - Fixed #29 - Sharing to facebook without a title now works properly.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">d9985a1 - ariejan@ariejan.net - 2011-04-23 00:13:06 -0700 - Added Travis build status to README
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">3e1149c - ariejan@ariejan.net - 2011-04-22 13:44:21 +0200 - Bumped version to 1.5.1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">fcab3bf - ariejan@ariejan.net - 2011-04-22 13:43:41 +0200 - Don&amp;#39;t test on ruby 1.9.2 for now. See issue #27
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">93843ec - ariejan@ariejan.net - 2011-04-22 13:42:29 +0200 - Fixed issue #28 - Share-to-* double-encodes titles
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ec56315 - ariejan@ariejan.net - 2011-04-22 12:43:41 +0200 - Fixed up utf-8 encoding for ruby 1.9.2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2146134 - ariejan@ariejan.net - 2011-04-22 12:39:37 +0200 - Bump Sinatra to 1.2.3
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">c5efbf4 - ariejan@ariejan.net - 2011-04-22 12:07:41 +0200 - Truncate long URLs in the back-end to maintain a correct layout.
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now, it&amp;rsquo;s easy to see what I did on april 22nd using grep&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">git log &amp;lt;snip&amp;gt; &lt;span class="p">|&lt;/span> grep 2011-04-22
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>You can also filter on email address to select only specific user etc. Of course, all this could be done with git, but I&amp;rsquo;m way more comfortable using grep.&lt;/p>
&lt;p>To make life easy, you can create a shortcut for this log command. Add this to you &lt;code>~/.gitconfig&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ini" data-lang="ini">&lt;span class="line">&lt;span class="cl">&lt;span class="k">[alias]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">timelog&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s">log --pretty=format:&amp;#39;%Cred%h%Creset - %C(yellow)%ae%Creset - %Cgreen%cd%Creset - %s%Creset&amp;#39; --abbrev-commit --date=iso&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>You can now use &lt;code>git timelog&lt;/code> in any git repository.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2011/08/24/git-log-what-did-i-do-yesterday-exactly/</guid><pubDate>Wed, 24 Aug 2011 00:00:00 +0000</pubDate></item><item><title>Git: Squash your latests commits into one</title><link>https://www.devroom.io/2011/07/05/git-squash-your-latests-commits-into-one/</link><description>&lt;p>With git it&amp;rsquo;s possible to squash previous commits into one. This is a great way to group certain changes together before sharing them with others.
~
Here&amp;rsquo;s how to squash some commits into one. Let&amp;rsquo;s say this is your current &lt;code>git log&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">* df71a27 - (HEAD feature_x) Updated CSS for new elements (4 minutes ago)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">* ba9dd9a - Added new elements to page design (15 minutes ago)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">* f392171 - Added new feature X (1 day ago)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">* d7322aa - (origin/feature_x) Proof of concept for feature X (3 days ago)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>You have a branch &lt;code>feature_x&lt;/code> here. You&amp;rsquo;ve already pushed &lt;code>d7322aa&lt;/code> with the proof of concept of the new feature X. After that you&amp;rsquo;ve been working to add new element to the feature, including some changes in CSS. Now, you want to squash your last three commits in one to make your history look pretty.&lt;/p>
&lt;p>The command to accomplish that is:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">git rebase -i HEAD~3
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This will open up your editor with the following:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">pick f392171 Added new feature X
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">pick ba9dd9a Added new elements to page design
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">pick df71a27 Updated CSS for new elements
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now you can tell git what to do with each commit. Let&amp;rsquo;s keep the commit &lt;code>f392171&lt;/code>, the one were we added our feature. We&amp;rsquo;ll squash the following two commits into the first one - leaving us with one clean commit with features X in it, including the added element and CSS.&lt;/p>
&lt;p>Change your file to this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">pick f392171 Added new feature X
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">squash ba9dd9a Added new elements to page design
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">squash df71a27 Updated CSS for new elements
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>When done, save and quit your editor. Git will now squash the commits into one. All done!&lt;/p>
&lt;p>Note: do not squash commits that you&amp;rsquo;ve already shared with others. You&amp;rsquo;re changing history and it will cause trouble for others.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2011/07/05/git-squash-your-latests-commits-into-one/</guid><pubDate>Tue, 05 Jul 2011 00:00:00 +0000</pubDate></item><item><title>Your Mac slow? Disable Spotlight in Snow Leopard</title><link>https://www.devroom.io/2011/06/27/your-mac-slow-disable-spotlight-in-snow-leopard/</link><description>&lt;p>For some time now I&amp;rsquo;ve experience my mac to be very slow. Opening Vim would take minutes. Creating a new Tab in iTerm would take more than 20 seconds. What the hell is going on - this is a brand new MacBook Pro!
~
After investigating, using &lt;em>Activity Monitor&lt;/em> I discovered the following:&lt;/p>
&lt;ul>
&lt;li>CPU&amp;rsquo;s idling at 3-5% usage&lt;/li>
&lt;li>+500M free RAM&lt;/li>
&lt;li>+200G free disk space&lt;/li>
&lt;li>Negligible amount of network traffic&lt;/li>
&lt;li>Very high amount of disk I/O writes (&amp;gt; 450w/s)&lt;/li>
&lt;/ul>
&lt;h3 id="whats-causing-disk-io">What&amp;rsquo;s causing disk I/O?&lt;/h3>
&lt;p>So, something is using my disk. But what? The solution is to use the &lt;code>iotop&lt;/code> utility:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">sudo iotop -C &lt;span class="m">5&lt;/span> &lt;span class="m">12&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>A common entry here is the &lt;code>mds&lt;/code> procoess, which has an insane amount of &lt;code>BYTES&lt;/code>. So, this &lt;code>mds&lt;/code> process is causing a lot of I/O, causing things to get slow.&lt;/p>
&lt;h3 id="whats-the-mds-process-do">What&amp;rsquo;s the &lt;code>mds&lt;/code> process do?&lt;/h3>
&lt;p>A quick Google search reveals that the &lt;code>mds&lt;/code> process is actually the Spotlight indexer service.&lt;/p>
&lt;h3 id="disable-spotlight">Disable Spotlight&lt;/h3>
&lt;p>I don&amp;rsquo;t use Spotlight at all, so let&amp;rsquo;s disable it - preventing the disk I/O.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">sudo mdutil -a -i off
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>That&amp;rsquo;s all. Spotlight indexing disabled. After a few seconds the disk I/O dropped from ± 450w/s to 0w/s. Vim starts ups again within a seconds. I&amp;rsquo;m happy.&lt;/p>
&lt;h3 id="enabling-spotlight">Enabling Spotlight&lt;/h3>
&lt;p>If, for some obscure reason, you want to re-enable Spotlight, use the following command:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">sudo mdutil -a -i on
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2011/06/27/your-mac-slow-disable-spotlight-in-snow-leopard/</guid><pubDate>Mon, 27 Jun 2011 00:00:00 +0000</pubDate></item><item><title>Git: What files were changed since the last release?</title><link>https://www.devroom.io/2011/06/24/git-what-files-were-changed-since-the-last-release/</link><description>&lt;p>Sometimes it handy to get a list out of &lt;code>git log&lt;/code> that tells you which files were changed since your last release. It&amp;rsquo;s not straight forward, but very doable with the help of &lt;code>git log&lt;/code> and &lt;code>grep&lt;/code>.
~
Let&amp;rsquo;s say you want to view all the changed files since the last tagged release, &lt;code>v1.3.1&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">git log --reverse --name-status HEAD...v1.3.1 &lt;span class="p">|&lt;/span> grep -e ^&lt;span class="o">[&lt;/span>MAD&lt;span class="o">][[&lt;/span>:space:&lt;span class="o">]]&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>As you&amp;rsquo;re used to, this shows each files that &lt;em>A&lt;/em>dded, &lt;em>M&lt;/em>odified or &lt;em>D&lt;/em>eleted. This command does not squash file changes. So it&amp;rsquo;s possible for a file to first be added, the deleted, then added again and later modified. The &lt;code>--reverse&lt;/code> option shows file changes historically, so the first file changed after the v1.3.1 release is shown first.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2011/06/24/git-what-files-were-changed-since-the-last-release/</guid><pubDate>Fri, 24 Jun 2011 00:00:00 +0000</pubDate></item><item><title>Crowd sourcing your BitCoin mining</title><link>https://www.devroom.io/2011/06/23/crowd-sourcing-your-bitcoin-mining/</link><description>&lt;p>[BitCoin][1] is a hip new currency using peer-to-peer networking to process transactions. You can either buy it from others, or &lt;em>mine&lt;/em> it by solving math puzzles. Would it be possible to crowd source the &lt;em>mining&lt;/em> process to visitors of your website?
[1]: &lt;a href="http://bitcoin.org">http://bitcoin.org&lt;/a>
~&lt;/p>
&lt;h3 id="whats-bitcoin-anyway">What&amp;rsquo;s BitCoin anyway?&lt;/h3>
&lt;div style="float:right;margin:0 0 10px 10px" markdown="1">
&lt;img src="https://ariejannet.s3.amazonaws.com/images/bitcoin-logo.png" width="120" />
&lt;/div>
&lt;p>BitCoin (BTC) is not distributed by a central bank, but is generated by its users. In order to &lt;em>mine&lt;/em> BTC, your computer must solve math puzzles. When you solve the puzzle, you are awarded 50BTC. How difficult the puzzles are and how big the reward is, is all determined by the network itself - it cannot be influenced by a single entity or person.&lt;/p>
&lt;p>So, let&amp;rsquo;s turn on that computer and start generating BitCoin! With a current exchange rate between $8 - $30 &lt;em>per&lt;/em> BitCoin, gaining 50 BTC would be quite lucrative.&lt;/p>
&lt;p>So, how long would it take to solve that math puzzle and score 50BTC, or $400 - $1500?&lt;/p>
&lt;p>Given the current difficulty (how difficult is the puzzle to solve) of &lt;em>877226&lt;/em> and a hash rate (the speed at which you&amp;rsquo;re trying to solve the puzzle) of 6 mHashes/s you can calculate how long it would take on average before you are rewarded 50BTC.&lt;/p>
&lt;p>Using a &lt;a href="http://bitcoinx.com/profit/index.php">calculator&lt;/a> I can compute that with my current AMD Phenom II 1055T X6 (6 cores @ 2.8Ghz) it would take an average of about 20 years to generated those 50BTC. That&amp;rsquo;s 0.007 BTC a day, which translates to $0.056 - $0.21 a day. That doesn&amp;rsquo;t even cover the power bill for running your computer each day.&lt;/p>
&lt;h3 id="can-we-do-better-than-a-6-core-cpu">Can we do better than a 6 core CPU?&lt;/h3>
&lt;p>Yes, we can! My system is also equipped with an ATI Radeon HD 6870 video card. I love playing games, you know ;-). The thing is, a video card is basically a very large and very fast calculator. It can perform simple operations, like the hashing required for BitCoin mining, very vast. It can do 1120 hashes &lt;em>&lt;em>at the same time&lt;/em>&lt;/em>. Yes, that is in parallel.&lt;/p>
&lt;p>Where the CPU could do 6 mHashes/s my GPU can do about 270 mHashes/s. Yes, my GPU is about 45 times faster at calculating hashes than my CPU.&lt;/p>
&lt;p>But, what does that mean for the time it takes to get those 50BTC? It will take 161.5 days on average. Yielding about $2.48 - $15.50 per day. That&amp;rsquo;s more like it! Still, not worth leaving your day job for.&lt;/p>
&lt;h3 id="more-power-please">More power, please!&lt;/h3>
&lt;p>It&amp;rsquo;s possible to rig up a machine that uses even faster video cards to squeeze more mHashes/s out of it. Also, a system is not even limited to one video card, you can add up to two or four in the same computer! Imagine having four ATI Radeon 6870&amp;rsquo;s. That&amp;rsquo;s a whopping 1080 mHashes/s.&lt;/p>
&lt;p>1080 mHashes/s gives you an average of 40 days and 9 hours, which translates to about $9.90 - $37.14 per day. You could be making $1100 a month with that (excluding the cost for purchase and power)&lt;/p>
&lt;h3 id="how-about-the-cloud">How about The Cloud?&lt;/h3>
&lt;p>You might wonder about the cloud. That big virtual pool of unlimited hardware. How about throwing some very big machines at this problem and get rich instantly.&lt;/p>
&lt;p>Well, it won&amp;rsquo;t work. As we saw, CPUs are bad a generating those hashes and are thus not really a viable option for mining.&lt;/p>
&lt;p>Amazon does offer an instance type that provides two Nvidia Tesla GPUs. Unfortunately Nvidia cards don&amp;rsquo;t perform well generating hashes. The cost of running one of those instances at about $50 a day. Because it performs worse than a single ATI Radeon HD 6870 you&amp;rsquo;ll only lose money with this approach.&lt;/p>
&lt;h3 id="team-up">Team up!&lt;/h3>
&lt;p>It&amp;rsquo;s also possible to team up with others to mine BitCoin faster. It&amp;rsquo;s been done before and the most famous example would be &lt;a href="http://setiathome.berkeley.edu/">Seti@Home&lt;/a>.&lt;/p>
&lt;p>Users run a program in the time their CPU is idle, contributing in the analysis of signals from space. If enough people contribute their spare CPU time, you&amp;rsquo;ll get the processing power of multiple time the fastest super-computer in the world.&lt;/p>
&lt;p>Great, so let&amp;rsquo;s do that for BitCoin! In the BitCoin world these are called &lt;em>pools&lt;/em>. You can join a pool and try to solve the math puzzle together. When that happens, every participant gets a proportional share of the 50BTC for their work done.&lt;/p>
&lt;p>Imagine there are a hundred people with an ATI Radeon HD 6870 (thus doing equal amounts of work). This would result in 2.7 gHashes/s. 50 BTC would be mined in about 1 day and 14 hours. The result per day would be about the same as a single person mining on his own.&lt;/p>
&lt;p>The big difference is that when mining in a pool you get BitCoins on your balance much faster. After just a day an a half you have 0.5 BTC sitting in your account, yours to spend. A single person would have to wait 162 days for that to happen.&lt;/p>
&lt;h3 id="use-the-crowd-luke">Use the Crowd, Luke!&lt;/h3>
&lt;p>Now, this got me thinking. What if you could harness the power of a thousands or hundreds of thousands of CPUs, even for a short period of time? How fast would mining be? Would it be profitable?&lt;/p>
&lt;p>Then I noticed there is a website that offers a &lt;em>web miner&lt;/em>. This is a Java applet that runs in the background of a web page. While a visitor is on your site, the applet will use spare CPU power from that users computer to generate hashes and effectively participate in a private pool.&lt;/p>
&lt;p>I ran an experiment on Ariejan.net during june 2011. I have implemented the web miner and generated BitCoin for a month using my visitors as (mostly unknowingly) test subjects.&lt;/p>
&lt;p>Ariejan.net gets between 55.000 - 65.000 page views each month. The average time per page is 40 seconds. This would yield me more than 700 hours of computing time each month.&lt;/p>
&lt;p>Using the web miner I got between 60 and 100 mHashes/s for an entire month. Doing all the mining on my own it would take 1 year and 2 months to generate 50 BTC. That&amp;rsquo;s $0.75 - $2.84 per day on average.&lt;/p>
&lt;p>Because that would be too long, I participated in another pool, contributing the immense power of my websites visitors. During june you generated 1.98 BTC. About $15.85 - $59.40 when exchanged right now.&lt;/p>
&lt;p>In comparison, my ATI Radeon HD 6870 could have made between $75 and $280 in the same period of time.&lt;/p>
&lt;p>This is a nice chunk in the monthly revenue stream generated by Ariejan.net, but it would not be enough to dedicate time on writing articles on a more regular basis.&lt;/p>
&lt;p>Also, &lt;em>stealing&lt;/em> a visitors CPU time is ethically debatable. Personally I think it would be okay to ask a visitor to opt-in. Running the miner secretly in the background as I did for this test would not be acceptable.&lt;/p>
&lt;h3 id="conclusion">Conclusion&lt;/h3>
&lt;p>If you&amp;rsquo;re in the business of mining BitCoin, crowd sourcing most likely is not an option for you. You probably don&amp;rsquo;t have a web site big enough to generate enough CPU power. Using commodity hardware would yield the same or better results.&lt;/p>
&lt;p>I would be interested to see others continue this &lt;em>research&lt;/em>. What would be the results:&lt;/p>
&lt;ul>
&lt;li>if you asked users to opt-in only&lt;/li>
&lt;li>if you asked users to opt-out only&lt;/li>
&lt;li>on a site with 1+ million visitors&lt;/li>
&lt;li>if a miner were integrated in a game or piece of software&lt;/li>
&lt;li>if Google put a JavaScript miner on google.com that performed only 10 hashes/view.&lt;/li>
&lt;/ul>
&lt;p>&lt;em>Got an opinion, feel free to leave it below.&lt;/em>&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2011/06/23/crowd-sourcing-your-bitcoin-mining/</guid><pubDate>Thu, 23 Jun 2011 00:00:00 +0000</pubDate></item><item><title>Vows and CoffeeScript</title><link>https://www.devroom.io/2011/06/10/vows-and-coffeescript/</link><description>&lt;p>CoffeeScript is a really nice way to write JavaScript code. Combined
with NodeJS you are empowered by a very fast platform to develop
server-side applications. Of course, you want to test these apps as well. &lt;a href="http://vowsjs.org">Vows&lt;/a> is really
great way to do this. Unfortunately it&amp;rsquo;s not straight forward (yet) to
set up NodeJS + CoffeeScript + Vows.&lt;/p>
&lt;p>~
First off, make sure you have CoffeeScript and Vows installed. Here I
install them &lt;em>globally&lt;/em> so you can use the &lt;code>coffee&lt;/code> and &lt;code>vows&lt;/code> command
line utilities.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">npm install -g coffee-script
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">npm install -g vows
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Next up, in your product directory, create a directory named &lt;code>test&lt;/code>.
Here we&amp;rsquo;ll create (classic) example: &lt;code>division-test.coffee&lt;/code>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-coffee" data-lang="coffee">&lt;span class="line">&lt;span class="cl">&lt;span class="nv">vows = &lt;/span>&lt;span class="nx">require&lt;/span> &lt;span class="s">&amp;#39;vows&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">assert = &lt;/span>&lt;span class="nx">require&lt;/span> &lt;span class="s">&amp;#39;assert&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">vows&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="nx">describe&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#39;Division by zero&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="nx">addBatch&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#39;when dividing a number by zero&amp;#39;&lt;/span>&lt;span class="o">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">topic: &lt;/span>&lt;span class="nf">-&amp;gt;&lt;/span> &lt;span class="mi">42&lt;/span>&lt;span class="o">/&lt;/span> &lt;span class="mi">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#39;we get Infinity&amp;#39;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nf">(topic) -&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">assert&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">equal&lt;/span> &lt;span class="nx">topic&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kc">Infinity&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#39;but when dividing zero by zero&amp;#39;&lt;/span>&lt;span class="o">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">topic: &lt;/span>&lt;span class="nf">-&amp;gt;&lt;/span> &lt;span class="mi">0&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="mi">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#39;we get a value which&amp;#39;&lt;/span>&lt;span class="o">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#39;is not a number&amp;#39;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nf">(topic) -&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">assert&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nb">isNaN&lt;/span> &lt;span class="nx">topic&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#39;is not equal to itself&amp;#39;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nf">(topic) -&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">assert&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">notEqual&lt;/span> &lt;span class="nx">topic&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">topic&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="nx">export&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">module&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>I&amp;rsquo;m not going to explain the intimate details of Vows here, suffice it
to say that you calculate a value and store it in &lt;code>topic&lt;/code>. Then you
perform a set of expectations.&lt;/p>
&lt;p>The magic part is the last line, &lt;code>.export(module)&lt;/code>. In all other
examples you&amp;rsquo;ll see the last command is &lt;code>.run()&lt;/code>. This &lt;code>run()&lt;/code> command
runs your vows immediately.&lt;/p>
&lt;p>When using coffee-script, you don&amp;rsquo;t want to directly run you vows, but
gather them all together, convert them to JavaScript and then have vows
run them.&lt;/p>
&lt;p>With the &lt;code>test/division-test.coffee&lt;/code> saved, try running &lt;code>vows&lt;/code> from
your console. Here&amp;rsquo;s the output from &lt;code>vows --spec&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">vows --spec
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">♢ Division by zero
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> when dividing a number by zero
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ✓ we get Infinity
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> but when dividing zero by zero we get a value which
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ✓ is not a number
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ✓ is not equal to itself
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">✓ OK » &lt;span class="m">3&lt;/span> honored &lt;span class="o">(&lt;/span>0.002s&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Another great tip is &lt;code>vows -w&lt;/code>. This will keep vows running and monitor
your test files for changes. When a file changes, it will re-run your
vows for you.&lt;/p>
&lt;p>Happy testing!&lt;/p>
&lt;p>&lt;em>Yes, I know there are issues with Coderay parsing UTF-8 characters.&lt;/em>&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2011/06/10/vows-and-coffeescript/</guid><pubDate>Fri, 10 Jun 2011 00:00:00 +0000</pubDate></item><item><title>Rake with namespaces and default tasks</title><link>https://www.devroom.io/2011/04/04/rake-with-namespaces-and-default-tasks/</link><description>&lt;p>Rake is an awesome tool to automate tasks for your Ruby (or Rails)
application. In this short article I&amp;rsquo;ll show you how to use namespaces
and set default tasks for them.
~
Let me first tell you what I want to accomplish. I have a Rails
application that needs to be cleaned up occasionally. Essetially we
delete old data from the database.&lt;/p>
&lt;p>There are several models that each implement a &lt;code>cleanup!&lt;/code> method which
takes care of cleaning up data. Now all I need is a few rake tasks to
kick off the clean up process.&lt;/p>
&lt;p>This is what I&amp;rsquo;d like:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">rake cleanup &lt;span class="c1"># Cleanup everything&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">rake cleanup:clicks &lt;span class="c1"># Aggregate click stats&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">rake cleanup:logs &lt;span class="c1"># Clean old logs&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Here&amp;rsquo;s what I put in &lt;code>lib/tasks/cleanup.rake&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="n">namespace&lt;/span> &lt;span class="ss">:cleanup&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">desc&lt;/span> &lt;span class="s2">&amp;#34;Aggregate click stats&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">task&lt;/span> &lt;span class="ss">:clicks&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="ss">:environment&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="no">Click&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">cleanup!&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">desc&lt;/span> &lt;span class="s2">&amp;#34;Clean old logs&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">task&lt;/span> &lt;span class="ss">:logs&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="ss">:environment&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="no">Log&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">cleanup!&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">task&lt;/span> &lt;span class="ss">:all&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="o">[&lt;/span>&lt;span class="ss">:clicks&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:logs&lt;/span>&lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">desc&lt;/span> &lt;span class="s2">&amp;#34;Cleanup everything&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">task&lt;/span> &lt;span class="ss">:cleanup&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s1">&amp;#39;cleanup:all&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Notice that the &lt;code>cleanup:all&lt;/code> task does not have a description. Without
it, it won&amp;rsquo;t show up when you do a &lt;code>rake -T&lt;/code> to view available tasks.&lt;/p>
&lt;p>The task &lt;code>cleanup&lt;/code> has the same name as the namespace and calls the
(undocumentend) &lt;code>cleanup:all&lt;/code> task, kicking off the entire cleanup
process.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2011/04/04/rake-with-namespaces-and-default-tasks/</guid><pubDate>Mon, 04 Apr 2011 00:00:00 +0000</pubDate></item><item><title>Rails 3 + Devise + Uploadify = No Flash Session Hacks</title><link>https://www.devroom.io/2011/03/27/rails-3-devise-uploadify-no-flash-session-hacks/</link><description>&lt;p>Uploadify is a great project to provide file uploads in your project.
The problem is, it&amp;rsquo;s written in flash.&lt;/p>
&lt;p>Besides the point that &lt;em>it is flash&lt;/em>, there&amp;rsquo;s something else that has
been bothering me a lot: &lt;strong>sessions&lt;/strong>.
~
The problem is like this. When a browsers opens a connection to your
Rails app, it has a session. Normally, session information is stored a
cookie that is sent with every request. This session also contains
information needed for you to stay logged in as a particular user.&lt;/p>
&lt;p>If you embed Flash on your site and have it communicate with your Rails
application, it will start a new session. Basically, Flash just acts
as a whole other browser.&lt;/p>
&lt;p>Because of this behaviour, when a logged in user uploads files with
Uploadify, the uploads will appear to come from an unauthenticated
user. There is no session information provided by flash to identify
that a user is signed in.&lt;/p>
&lt;p>&lt;strong>The &lt;em>old&lt;/em> solution&lt;/strong>&lt;/p>
&lt;p>The old solution I&amp;rsquo;ve found around the web is to work around this
session problem by forcing flash to send the browser&amp;rsquo;s session
information. This solution also requires you to add a piece of &lt;em>rack
middleware&lt;/em> to your app that takes any requests from a flash client and
hacks the session to look like it came from the browser.&lt;/p>
&lt;p>Besides the fact that I couldn&amp;rsquo;t get this hack to work, it still is
a big hack. So, what to do?&lt;/p>
&lt;p>&lt;strong>A word on devise&lt;/strong>&lt;/p>
&lt;p>I happen to use Devise in my app to authenticate users. As you may know,
Devise is &lt;em>very&lt;/em> complete. It includes everything ranging from sign up,
forgotten passwords and even account locking. It also features &lt;em>token
authentication&lt;/em>.&lt;/p>
&lt;p>Token authentication means that a user can authenticate himself by
providing a unique token, in most cases a string of random characters.&lt;/p>
&lt;p>A common use case for this token authentication is to provide users with
a &lt;em>secret link&lt;/em> to an RSS feed, or to allow quick access to the
application from link sent in an email. You click the link and you&amp;rsquo;re
automagically logged in.&lt;/p>
&lt;p>&lt;strong>The &lt;em>new&lt;/em> solution&lt;/strong>&lt;/p>
&lt;p>So, this token authentication got me thinking. Instead of sending an
encoded string with session information to flash, which in turn sends it
to the server, which in turn hacks it into an actual session, I could
just send the user&amp;rsquo;s authentication token along! No sever-side hacks
required - it&amp;rsquo;s all built in into devise already!&lt;/p>
&lt;p>Let me show you. First check that your (devise-powered) user has an
authentitication token:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="vi">@user&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">authentication_token&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s2">&amp;#34;4R2bzzQRdoT_iz-ND4Bb&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>In case your &lt;code>authentication_token&lt;/code> is nil, you should generate one with
&lt;code>@user.reset_authentication_token!&lt;/code>&lt;/p>
&lt;p>The next step is to tell Flash to use this authentication token in its
request to the server (while uploading files). Nothing fancy here
either. Not that this is a snippet from JavaScript, embedded in a HAML
template:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="line">&lt;span class="cl">&lt;span class="nx">$&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;#image_file&amp;#39;&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">uploadify&lt;/span>&lt;span class="p">({&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// I omitted all other config options, since they&amp;#39;re not relevant.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="s1">&amp;#39;script&amp;#39;&lt;/span> &lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;#{images_path(:auth_token =&amp;gt; current_user.authentication_token, :format =&amp;gt; :json)}&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Rails will generate a URL like this:
&lt;code>/images.json?auth_token=4R2bzzQRdoT_iz-ND4Bb&lt;/code>.&lt;/p>
&lt;p>The final step is to protect your &lt;code>ImagesController#create&lt;/code> action with
devise.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">ImagesController&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="no">ApplicationController&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">before_filter&lt;/span> &lt;span class="ss">:authenticate_user!&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">create&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># Handle your upload&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>That&amp;rsquo;s all. You dont&amp;rsquo; even need to add rack middleware or hack Uploadify
to allow an authenticated devise user to upload images through flash.&lt;/p>
&lt;p>Easy, huh?&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2011/03/27/rails-3-devise-uploadify-no-flash-session-hacks/</guid><pubDate>Sun, 27 Mar 2011 00:00:00 +0000</pubDate></item><item><title>Narf: A Ruby Micro Test Framework</title><link>https://www.devroom.io/2011/02/11/narf-a-ruby-micro-test-framework/</link><description>&lt;p>I&amp;rsquo;m a happy user of RSpec, Cucumber and sometimes Steak. Great tools to
write specs and features and prove my application does what it&amp;rsquo;s
supposed to do. But sometimes I have the need for something more &lt;em>light
weight&lt;/em>.
~
For example, sometimes I need to write a single ruby method. Just something
&amp;lsquo;quick&amp;rsquo; to import a file, convert some data or whatever. Being a good
citizen I want to test that method.&lt;/p>
&lt;p>Using RSpec or Cucumber here just seems wrong here. So, I&amp;rsquo;ve implemented
my own &lt;em>Ruby Micro Test Framework&lt;/em>: &lt;em>Narf&lt;/em>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">assert&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">message&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="n">block&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">puts&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">#{&lt;/span>&lt;span class="s2">&amp;#34;%6s&amp;#34;&lt;/span> &lt;span class="o">%&lt;/span> &lt;span class="p">((&lt;/span>&lt;span class="k">yield&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="kp">false&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">?&lt;/span> &lt;span class="s1">&amp;#39; PASS&amp;#39;&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="s1">&amp;#39;! FAIL&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> - &lt;/span>&lt;span class="si">#{&lt;/span>&lt;span class="n">message&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>(Yes, that&amp;rsquo;s it.)&lt;/p>
&lt;p>With this simple method, added to your ruby file, you can test your
method. And example:&lt;/p>
&lt;p>Let&amp;rsquo;s say you&amp;rsquo;re going to write a method that counts how often the word
&amp;lsquo;ruby&amp;rsquo; occurs in a given String:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">count_rubies&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">text&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># TODO&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">#--- Ruby Micro Test Framework&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">assert&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">message&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="n">block&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">puts&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">#{&lt;/span>&lt;span class="s2">&amp;#34;%6s&amp;#34;&lt;/span> &lt;span class="o">%&lt;/span> &lt;span class="p">((&lt;/span>&lt;span class="k">yield&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="kp">false&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">?&lt;/span> &lt;span class="s1">&amp;#39; PASS&amp;#39;&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="s1">&amp;#39;! FAIL&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> - &lt;/span>&lt;span class="si">#{&lt;/span>&lt;span class="n">message&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">#---&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">#--- Tests&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">assert&lt;/span> &lt;span class="s2">&amp;#34;Count zero rubies&amp;#34;&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">count_rubies&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;this is Sparta!&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">assert&lt;/span> &lt;span class="s2">&amp;#34;Count one ruby&amp;#34;&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">count_rubies&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;This is one ruby&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">assert&lt;/span> &lt;span class="s2">&amp;#34;Count one RuBy&amp;#34;&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">count_rubies&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;This is one RuBy&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now, simple save this file and feed it to ruby:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">$ ruby my_method.rb
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">! FAIL - Count zero rubies
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">! FAIL - Count one ruby
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">! FAIL - Count one RuBy
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now, implement your method&amp;hellip;&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">count_rubies&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">text&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">text&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">match&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sr">/(ruby)/i&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">size&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>And re-run your tests:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">$ ruby my_method.rb
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> PASS - Count zero rubies
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> PASS - Count one ruby
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> PASS - Count one RuBy
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>So with the addition of just a single method you can fully TDD/BDD your
single method Ruby code. Pretty need, huh?&lt;/p>
&lt;p>&lt;a href="http://github.com/ariejan/narf">Need codez?&lt;/a>&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2011/02/11/narf-a-ruby-micro-test-framework/</guid><pubDate>Fri, 11 Feb 2011 00:00:00 +0000</pubDate></item><item><title>Using your Firefly URL Shortener with Twitter for iPhone</title><link>https://www.devroom.io/2011/02/07/using-your-firefly-url-shortener-with-twitter-for-iphone/</link><description>&lt;p>The Twitter for iPhone app (&lt;a href="http://itunes.apple.com/nl/app/twitter/id333903271?mt=8">itunes&lt;/a>) can be configured to use your very
own Firefly URL shortener! Here&amp;rsquo;s how.&lt;/p>
&lt;p>~
&lt;img src="https://ariejannet.s3.amazonaws.com/images/custom-urlshortening.png" alt="Custom URL server..." border="0" align="right" />&lt;/p>
&lt;p>Actually, it&amp;rsquo;s quite simple. First, &lt;a href="https://github.com/ariejan/firefly#readme">setup your own Firefly instance&lt;/a>,
possibly &lt;a href="http://ariejan.net/2010/06/06/setup-your-own-firefly-url-shortener-in-25-minutes/">using Heroku&lt;/a>.&lt;/p>
&lt;p>When that&amp;rsquo;s done, simple open Twitter for iPhone, then follow this path:&lt;/p>
&lt;ul>
&lt;li>Tap the three dots&lt;/li>
&lt;li>Open &lt;em>&amp;ldquo;Accounts &amp;amp; Settings&amp;rdquo;&lt;/em>&lt;/li>
&lt;li>Tap the &lt;em>&amp;ldquo;Settings&amp;rdquo;&lt;/em> button at the bottom&lt;/li>
&lt;li>Open &lt;em>&amp;ldquo;Services&amp;rdquo;&lt;/em>&lt;/li>
&lt;li>Choose &lt;em>&amp;ldquo;URL Shortening&amp;rdquo;&lt;/em>&lt;/li>
&lt;li>Then pick &lt;em>&amp;ldquo;Custom&amp;hellip;&amp;rdquo;&lt;/em>&lt;/li>
&lt;/ul>
&lt;p>All right, you&amp;rsquo;re now ready to enter your magic URL. Simply replace the
&lt;code>YOUR_DOMAIN&lt;/code> and &lt;code>YOUR_API_KEY&lt;/code> placeholders with your actual
domain name and API key (you can find them in your &lt;code>config.ru&lt;/code>)&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">http://YOUR_DOMAIN/api/add?api_key=YOUR_API_KEY&amp;amp;url=%@
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Then hit &lt;em>&amp;ldquo;Save&amp;rdquo;&lt;/em> and you&amp;rsquo;re set! Twitter for iPhone will now
automatically use your Firefly instance to shorten URLs!&lt;/p>
&lt;p>Looking for the &lt;a href="https://github.com/ariejan/firefly">Firefly source code&lt;/a>?&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2011/02/07/using-your-firefly-url-shortener-with-twitter-for-iphone/</guid><pubDate>Mon, 07 Feb 2011 00:00:00 +0000</pubDate></item><item><title>Hot: Firefly 1.3.0 URL Shortener released</title><link>https://www.devroom.io/2011/02/01/hot-firefly-130-url-shortener-released/</link><description>&lt;p>I&amp;rsquo;ve been doing some work on Firefly lately and tonight I&amp;rsquo;ve released
version 1.3.0. If you&amp;rsquo;re using Firefly it&amp;rsquo;s recommended you upgrade to
the latest and greatest version.
~
For those who missed it: Firefly is a simple URL shortener gem
that can be used to host your own personal (or corporate) URL shortner
on your own domain. Since Firefly is rack-based, it&amp;rsquo;s easy to deploy to
Heroku or other ruby hosting providers.&lt;/p>
&lt;h3 id="whats-new">What&amp;rsquo;s new&lt;/h3>
&lt;p>Since version 1.1.0 (released october 2010) these features have been
added. I&amp;rsquo;ll just summarize those and ignore the 1.2.x release I made.&lt;/p>
&lt;ul>
&lt;li>Make Barby optional to save some gems for those of us who don&amp;rsquo;t need QR code generation. [mboeh]&lt;/li>
&lt;li>Added a CodeFactory to generate unique short codes (and prevent collisions with custom short codes) [ariejan]&lt;/li>
&lt;li>Support for specifying one&amp;rsquo;s own preferred short codes via the API. Also, invalid URL creations return a 422 code, not 200. [mboeh]&lt;/li>
&lt;li>Added GUI support for custom short codes. [ariejan]&lt;/li>
&lt;li>Improved error handling / reporting [ariejan]&lt;/li>
&lt;li>Minor tweaks to the web GUI [ariejan]&lt;/li>
&lt;li>Use dm-mysql-adapater as default, dm-sqlite-adapter is optional [ariejan]&lt;/li>
&lt;/ul>
&lt;h3 id="how-to-upgrade">How to upgrade&lt;/h3>
&lt;p>&lt;strong>IMPORTANT&lt;/strong>: Always make a backup of your data before you upgrade.&lt;/p>
&lt;p>Simple install the latest gem version and restart your server.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">gem install firefly -v1.3.0
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Then restart your server. DataMapper will take care of migrating your
database for you.&lt;/p>
&lt;p>&lt;strong>NOTE&lt;/strong>: Firefly is now using MySQL by default. If you were using
sqlite3 before, please install the appropriate DataMapper adapater: &lt;code>gem install dm-sqlite-adapter&lt;/code>&lt;/p>
&lt;h3 id="code-issues-forking">Code, Issues, Forking&lt;/h3>
&lt;p>Want to fork? &lt;a href="http://github.com/ariejan/firefly">http://github.com/ariejan/firefly&lt;/a>&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2011/02/01/hot-firefly-130-url-shortener-released/</guid><pubDate>Tue, 01 Feb 2011 00:00:00 +0000</pubDate></item><item><title>Rake task to sync your assets to Amazon S3/Cloudfront</title><link>https://www.devroom.io/2011/01/01/rake-task-to-sync-your-assets-to-amazon-s3cloudfront/</link><description>&lt;p>With my move to Heroku I felt bad about having Heroku&amp;rsquo;s app servers serve static content for me. It&amp;rsquo;s not really a problem, but I just like to use the best tool available for the job.&lt;/p>
&lt;p>Because &lt;em>Ariejan.net&lt;/em> is a rack app, it has a &lt;code>public&lt;/code> directory with all static assets in once place. There are, however, a few problems that need adressing.
~&lt;/p>
&lt;p>These are the problems I want to resolve:&lt;/p>
&lt;h4 id="keep-my-s3-bucket-in-sync-with-my-public-directory">Keep my S3 Bucket in sync with my public directory&lt;/h4>
&lt;p>The first and foremost is to keep my S3 bucket in sync with the content of &lt;code>public&lt;/code>. I don&amp;rsquo;t care about file deletions, but I do care about new and updated files. Those should be synced with every deployment to S3.&lt;/p>
&lt;h4 id="dont-re-upload-the-entire-public-directory-with-every-deployment">Don&amp;rsquo;t re-upload the entire public directory with every deployment&lt;/h4>
&lt;p>Over time the size of &lt;code>public&lt;/code> has grown. New images are added all the time. I don&amp;rsquo;t want to re-upload them with every deployment. So, my sync script must be smart enough to not upload unchanged files.&lt;/p>
&lt;h4 id="hook-the-s3-sync-into-my-current-deployment-rake-task">Hook the S3 sync into my current deployment rake task&lt;/h4>
&lt;p>My current rake deploy task should be able to call &lt;code>assets:deploy&lt;/code> or something to trigger an asset sync.&lt;/p>
&lt;h4 id="minimal-configuration">Minimal configuration&lt;/h4>
&lt;p>I don&amp;rsquo;t want to configure anything, if possible.&lt;/p>
&lt;h3 id="the-script">The script&lt;/h3>
&lt;p>Well, this is the rake task I currently use:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">require&lt;/span> &lt;span class="s1">&amp;#39;s3&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">require&lt;/span> &lt;span class="s1">&amp;#39;digest/md5&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">require&lt;/span> &lt;span class="s1">&amp;#39;mime/types&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">## These are some constants to keep track of my S3 credentials and&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">## bucket name. Nothing fancy here.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="no">AWS_ACCESS_KEY_ID&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;xxxxx&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="no">AWS_SECRET_ACCESS_KEY&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;yyyyy&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="no">AWS_BUCKET&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;my_bucket&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">## This defines the rake task `assets:deploy`.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">namespace&lt;/span> &lt;span class="ss">:assets&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">desc&lt;/span> &lt;span class="s2">&amp;#34;Deploy all assets in public/**/* to S3/Cloudfront&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">task&lt;/span> &lt;span class="ss">:deploy&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:env&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:branch&lt;/span> &lt;span class="k">do&lt;/span> &lt;span class="o">|&lt;/span>&lt;span class="n">t&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">args&lt;/span>&lt;span class="o">|&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">## Minify all CSS files&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="no">Rake&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="no">Task&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="ss">:minify&lt;/span>&lt;span class="o">].&lt;/span>&lt;span class="n">execute&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">## Use the `s3` gem to connect my bucket&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">puts&lt;/span> &lt;span class="s2">&amp;#34;== Uploading assets to S3/Cloudfront&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">service&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">S3&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="no">Service&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">new&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="ss">:access_key_id&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="no">AWS_ACCESS_KEY_ID&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="ss">:secret_access_key&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="no">AWS_SECRET_ACCESS_KEY&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">bucket&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">service&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">buckets&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">find&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="no">AWS_BUCKET&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">## Needed to show progress&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="no">STDOUT&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">sync&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kp">true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">## Find all files (recursively) in ./public and process them.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="no">Dir&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">glob&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;public/**/*&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">each&lt;/span> &lt;span class="k">do&lt;/span> &lt;span class="o">|&lt;/span>&lt;span class="n">file&lt;/span>&lt;span class="o">|&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">## Only upload files, we&amp;#39;re not interested in directories&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="no">File&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">file?&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">file&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">## Slash &amp;#39;public/&amp;#39; from the filename for use on S3&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">remote_file&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">file&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">gsub&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;public/&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">## Try to find the remote_file, an error is thrown when no&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">## such file can be found, that&amp;#39;s okay.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">begin&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">obj&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">bucket&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">objects&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">find_first&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">remote_file&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">rescue&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">obj&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kp">nil&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">## If the object does not exist, or if the MD5 Hash / etag of the&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">## file has changed, upload it.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="o">!&lt;/span>&lt;span class="n">obj&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">obj&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">etag&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="no">Digest&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="no">MD5&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">hexdigest&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="no">File&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">read&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">file&lt;/span>&lt;span class="p">)))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span> &lt;span class="s2">&amp;#34;U&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">## Simply create a new object, write the content and set the proper&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">## mime-type. `obj.save` will upload and store the file to S3.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">obj&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">bucket&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">objects&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">build&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">remote_file&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">obj&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">content&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">open&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">file&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">obj&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">content_type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">MIME&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="no">Types&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">type_for&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">file&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">to_s&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">obj&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">save&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">else&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span> &lt;span class="s2">&amp;#34;.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="no">STDOUT&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">sync&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kp">false&lt;/span> &lt;span class="c1"># Done with progress output.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">puts&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">puts&lt;/span> &lt;span class="s2">&amp;#34;== Done syncing assets&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This rake task is hooked into my &lt;code>rake deploy:production&lt;/code> script and generates the following output (I added a new file just to show you what happens.)&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl"> $ rake deploy:production
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">(&lt;/span>in /Users/ariejan/Code/Sites/ariejannet&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Deploying master to &lt;span class="nv">production&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">==&lt;/span> Minifying &lt;span class="nv">CSS&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">==&lt;/span> &lt;span class="nv">Done&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">==&lt;/span> Uploading assets to S3/Cloudfront
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ......................................U.........
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">==&lt;/span> Done syncing assets
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Updating ariejannet-production with branch master
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Counting objects: 40, &lt;span class="k">done&lt;/span>.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Delta compression using up to &lt;span class="m">4&lt;/span> threads.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Compressing objects: 100% &lt;span class="o">(&lt;/span>27/27&lt;span class="o">)&lt;/span>, &lt;span class="k">done&lt;/span>.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Writing objects: 100% &lt;span class="o">(&lt;/span>30/30&lt;span class="o">)&lt;/span>, 4.24 KiB, &lt;span class="k">done&lt;/span>.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Total &lt;span class="m">30&lt;/span> &lt;span class="o">(&lt;/span>delta 17&lt;span class="o">)&lt;/span>, reused &lt;span class="m">0&lt;/span> &lt;span class="o">(&lt;/span>delta 0&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -----&amp;gt; Heroku receiving push
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="conclusion">Conclusion&lt;/h3>
&lt;p>It&amp;rsquo;s very easy to write your own S3 sync script. My version has still has some issues/missing features that I may or may not add at some later time. There&amp;rsquo;s no support for file deletions and error handling is very poor at this time. Also, &lt;code>public&lt;/code> is still under version control (where I want it), and is pushed to Heroku. This is non-sense, because most of the assets in &lt;code>public&lt;/code> are not used (except &lt;code>robots.txt&lt;/code> and &lt;code>favicon.ico&lt;/code>)&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2011/01/01/rake-task-to-sync-your-assets-to-amazon-s3cloudfront/</guid><pubDate>Sat, 01 Jan 2011 00:00:00 +0000</pubDate></item><item><title>Now powered by Heroku</title><link>https://www.devroom.io/2010/12/31/now-powered-by-heroku/</link><description>&lt;p>This is just a quick note to let everyone know &lt;em>Ariejan.net&lt;/em> is now proudly hosted by &lt;a href="http://heroku.com">Heroku&lt;/a>. Since this site is powered by a &lt;a href="https://github.com/cloudhead/toto">toto&lt;/a> (a rack-based blog engine) and no database, Heroku is the perfect hosting solution.&lt;/p>
&lt;p>~&lt;/p>
&lt;p>All articles are simple text files (with some YAML front-matter to specify title, tags, etc.) and are kept under Git version control. Deploying &lt;em>Ariejan.net&lt;/em> is a simple &lt;code>git push&lt;/code> to Heroku.&lt;/p>
&lt;p>Want to run your own toto-powered blog on Heroku? Checkout &lt;a href="http://cloudhead.io/toto">Dorothy&lt;/a> (&lt;a href="https://github.com/cloudhead/dorothy">code&lt;/a>) to get started in &amp;ldquo;10 seconds&amp;rdquo;.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2010/12/31/now-powered-by-heroku/</guid><pubDate>Fri, 31 Dec 2010 00:00:00 +0000</pubDate></item><item><title>Public Readable Amazon S3 Bucket Policy</title><link>https://www.devroom.io/2010/12/24/public-readable-amazon-s3-bucket-policy/</link><description>&lt;p>Amazon S3 allows you to set per-file permissions to grant read and/or write access. This is nice, but sometimes you just want to share your whole bucket with the world.&lt;/p>
&lt;p>Luckily, Amazon features &lt;em>bucket policies&lt;/em>, which allow you to define permissions for an entire bucket.
~
This example will give &lt;em>read&lt;/em> access to &lt;em>Everyone&lt;/em> on &lt;em>all files&lt;/em> in your bucket.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;Version&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="s2">&amp;#34;2008-10-17&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;Statement&amp;#34;&lt;/span>&lt;span class="p">:[{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;Sid&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="s2">&amp;#34;AllowPublicRead&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;Effect&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="s2">&amp;#34;Allow&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;Principal&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;AWS&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;*&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;Action&amp;#34;&lt;/span>&lt;span class="p">:[&lt;/span>&lt;span class="s2">&amp;#34;s3:GetObject&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;Resource&amp;#34;&lt;/span>&lt;span class="p">:[&lt;/span>&lt;span class="s2">&amp;#34;arn:aws:s3:::bucket/*&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Make sure you replace &lt;code>bucket&lt;/code> in &lt;code>arn:aws:s3:::bucket/*&lt;/code> with your bucket name.&lt;/strong>&lt;/p>
&lt;p>After setting this bucket policy (see &amp;lsquo;Bucket -&amp;gt; Properties -&amp;gt; Add Bucket Policy&amp;rsquo;), all your files will be publicly readable.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2010/12/24/public-readable-amazon-s3-bucket-policy/</guid><pubDate>Fri, 24 Dec 2010 00:00:00 +0000</pubDate></item><item><title>Why did error_messages_for disappear from Rails 3?</title><link>https://www.devroom.io/2010/12/15/why-did-errormessagesfor-disappear-from-rails-3/</link><description>&lt;p>Today I learned that &lt;code>error_messages_for&lt;/code> has disappear from Rails 3. When I tried using it I got the following deprecation warning:&lt;/p>
&lt;p>&lt;strong>DEPRECATION WARNING: form.error_messages was removed from Rails and is now available as a plugin.&lt;/strong>&lt;/p>
&lt;p>What happened? Why was this pulled from Rails 3?
~
The reason &lt;code>error_messages_for&lt;/code> was pulled from Rails 3 is a new guideline that says that nothing in Rails Core should dictate the look and feel of an app.&lt;/p>
&lt;p>Okay, that&amp;rsquo;s all well and good. How do you fix this problem? There are two ways. The first is to implement your own error handling. A HAML snippet:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="o">-&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="vi">@post&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">errors&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">any?&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">.&lt;/span>&lt;span class="n">errors&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">%&lt;/span>&lt;span class="n">h2&lt;/span> &lt;span class="no">There&lt;/span> &lt;span class="n">was&lt;/span> &lt;span class="n">a&lt;/span> &lt;span class="n">problem&lt;/span> &lt;span class="n">saving&lt;/span> &lt;span class="n">this&lt;/span> &lt;span class="n">post&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">%&lt;/span>&lt;span class="n">ul&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">-&lt;/span> &lt;span class="vi">@post&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">errors&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">full_messages&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">each&lt;/span> &lt;span class="k">do&lt;/span> &lt;span class="o">|&lt;/span>&lt;span class="n">msg&lt;/span>&lt;span class="o">|&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">%&lt;/span>&lt;span class="n">li&lt;/span>&lt;span class="o">=&lt;/span> &lt;span class="n">msg&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This is by far the best way to go about it. However, if you have a current Rails 2 app you&amp;rsquo;re upgrading, writing your own error handling may be rather difficult or time consuming. In that case you can install a plugin that restores the original functionality of &lt;code>error_messages_for&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">rails plugin install git://github.com/rails/dynamic_form.git
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Just make sure to restart your server.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2010/12/15/why-did-errormessagesfor-disappear-from-rails-3/</guid><pubDate>Wed, 15 Dec 2010 00:00:00 +0000</pubDate></item><item><title>Hide 'Last login:' on bash login</title><link>https://www.devroom.io/2010/11/25/hide-last-login-on-bash-login/</link><description>&lt;p>Everytime I open a new Terminal on my Mac, I get a line like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">Last login: Thu Nov 25 09:07:55 on ttys004
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This annoys me. I don&amp;rsquo;t care when I last opened a local Terminal.&lt;/p>
&lt;p>To hide this &amp;ldquo;Last login&amp;rdquo; message when logging in to bash you need to create a file in your homedirectory.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">touch ~/.hushlogin
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>With this &lt;code>.hushlogin&lt;/code> file in place you won&amp;rsquo;t see the &amp;ldquo;Last login&amp;rdquo; message and go directly to your prompt, where you want to be.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2010/11/25/hide-last-login-on-bash-login/</guid><pubDate>Thu, 25 Nov 2010 00:00:00 +0000</pubDate></item><item><title>Clear your MySQL password</title><link>https://www.devroom.io/2010/10/26/clear-your-mysql-password/</link><description>&lt;p>Most people need their MySQL database protected with at least a decent password. I agree, but in development this often causes conflicts - and I prefer to work with my MySQL datbase without all the password-hassle.&lt;/p>
&lt;p>On Ubuntu I recently installed MySQL and set a password. Here&amp;rsquo;s how to remove that password so you can skip all the password stuff during development.
~
First, connect to MySQL and check what permissions are currently set:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">$ mysql -u root -p
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">use mysql&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">select&lt;/span> Host, User, Password from user&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>You&amp;rsquo;ll probably see three entries for the &lt;code>root&lt;/code> user: &lt;code>localhost&lt;/code>, &lt;code>127.0.0.1&lt;/code> and your hostname like &lt;code>hostname&lt;/code>. To clear the password for the root user issue the following query:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">update&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">user&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">set&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">password&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;&amp;#39;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">where&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">user&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;root&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Optionally, you may only want to reset the password for &lt;code>localhost&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">update&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">user&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">set&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">password&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;&amp;#39;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">where&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">user&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;root&amp;#39;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">and&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">host&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;localhost&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Keep in mind that using no MySQL password is insecure. Always protected your MySQL database with at least a strong password in production.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2010/10/26/clear-your-mysql-password/</guid><pubDate>Tue, 26 Oct 2010 00:00:00 +0000</pubDate></item><item><title>Setup a PPTP VPN connection on Mac OS X Snow Leopard</title><link>https://www.devroom.io/2010/10/12/setup-a-pptp-vpn-connection-on-mac-os-x-snow-leopard/</link><description>&lt;p>In my &lt;a href="http://ariejan.net/2010/10/11/setup-a-ubuntu-vpn-server/">previous tutorial&lt;/a> I explained how to setup a PPTP based VPN server on
Ubuntu. Now I&amp;rsquo;ll show you how to configure Mac OS X Snow Leopard to use that
VPN connection.&lt;/p>
&lt;p>~
Just watch the movie.&lt;/p>
&lt;iframe src="http://player.vimeo.com/video/15752843" width="620" height="349" frameborder="0">&lt;/iframe>&lt;p>&lt;a href="http://vimeo.com/15752843">How to setup a PPTP VPN connection from Mac OS X 10.6&lt;/a> from &lt;a href="http://vimeo.com/ariejan">Ariejan de Vroom&lt;/a> on &lt;a href="http://vimeo.com">Vimeo&lt;/a>.&lt;/p>
&lt;p>&lt;em>Note: You may open port 1723 (TCP) on your router to
allow you to connect to your VPN server from the Internets. In that case, use
your public IP Address.&lt;/em>&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2010/10/12/setup-a-pptp-vpn-connection-on-mac-os-x-snow-leopard/</guid><pubDate>Tue, 12 Oct 2010 00:00:00 +0000</pubDate></item><item><title>Setup a Ubuntu VPN server</title><link>https://www.devroom.io/2010/10/11/setup-a-ubuntu-vpn-server/</link><description>&lt;p>I recently installed Ubuntu Linux on a home server (I hate that word, but it
best describes what it is, so). Anyway, I&amp;rsquo;d like to be able to create a VPN
network between my home server and my MacbookPro, which might be anywhere in
the world.&lt;/p>
&lt;p>This first part of the tutorial describes how to setup a VPN server in Ubuntu.
~
First, install the &lt;code>pptpd&lt;/code> package. &lt;code>pptpd&lt;/code> offers a &lt;code>PPTP&lt;/code>-type VPN which is
supported by Microsoft and other network vendors. This is also the easiest to
setup.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">sudo apt-get install pptpd
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Next up, edit &lt;code>/etc/pptpd.conf&lt;/code> with &lt;code>sudo vi /etc/pptp.conf&lt;/code>. At the bottom add the following lines:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">localip 192.168.1.10
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">remoteip 192.168.1.230-239
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Here &lt;code>localip&lt;/code> references the IP of my home server. The &lt;code>remoteip&lt;/code> variable
configures which IPs remote clients may use when the connect through VPN to my
network. In this case I reserve 10 IP address: 192.168.1.230 through
192.168.1.239.&lt;/p>
&lt;p>With that out of the way, let&amp;rsquo;s tell &lt;code>PPTP&lt;/code> which users to allow. Edit
&lt;code>/etc/ppp/chap-secrets&lt;/code>, just like you did before using &lt;code>sudo&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl"># client server secret IP Address
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ariejan pptpd somepassword *
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>That&amp;rsquo;s all! Yes, seriously. Just restart the &lt;code>pptpd&lt;/code> daemon and you&amp;rsquo;re good to
go.&lt;/p>
&lt;p>Now, go and watch my &lt;a href="http://ariejan.net/2010/10/12/setup-a-pptp-vpn-connection-on-mac-os-x-snow-leopard/">PPTP VPN setup for Mac OS X Snow Leopard&lt;/a>&lt;/p>
&lt;p>&lt;em>Note: This was tested on Ubuntu 10.10-amd64&lt;/em>&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2010/10/11/setup-a-ubuntu-vpn-server/</guid><pubDate>Mon, 11 Oct 2010 00:00:00 +0000</pubDate></item><item><title>Firefly 1.1.0 adds QR Codes for your shortened URLs</title><link>https://www.devroom.io/2010/10/02/firefly-110-adds-qr-codes-for-your-shortened-urls/</link><description>&lt;p>&lt;a href="http://aj.gs/2Y.png">&lt;img src="http://aj.gs/2Y.png" width="100" height="100" alt='Example QR Code' title='Example QR Code' />&lt;/a>&lt;/p>
&lt;p>I just pushed Firefly 1.1.0 &lt;a href="http://github.com/ariejan/firefly/tree/v1.1.0">(code)&lt;/a> to &lt;a href="http://rubygems.org/gems/firefly">Rubygems&lt;/a> containing a very nice new feature: QR Codes.&lt;/p>
&lt;p>Why would you care? Well, almost anything can scan QR Codes nowadays. Simple add &lt;code>.png&lt;/code> to the end of your shortened URL and you&amp;rsquo;ll
get a nice QR Code that you can print or embed on somewhere on the web. Give it a try: &lt;a href="http://aj.gs/2Y.png">http://aj.gs/2Y.png&lt;/a>&lt;/p>
&lt;p>Simply scan the image and it will yield your short URL, ready for use.&lt;/p>
&lt;p>Updating your current Firefly installation is easy, simply run &lt;code>gem install firefly -v1.1.0&lt;/code> and restart your server.&lt;/p>
&lt;p>~&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2010/10/02/firefly-110-adds-qr-codes-for-your-shortened-urls/</guid><pubDate>Sat, 02 Oct 2010 00:00:00 +0000</pubDate></item><item><title>Precompile SASS to CSS for deployment to Heroku</title><link>https://www.devroom.io/2010/09/28/precompile-sass-to-css-for-deployment-to-heroku/</link><description>&lt;p>If you have deployed apps to Heroku you know that you cannot write to the read-only file system that Heroku offers. For file uploads you have to use some storage provider like Amazon S3 or RackSpace CloudFiles.&lt;/p>
&lt;p>Now, if your application (I&amp;rsquo;m assuming you&amp;rsquo;re already on Rails 3), is using Haml + Sass, you&amp;rsquo;re in some trouble. Sass is set to generate CSS files on the fly and save them to &lt;code>public/stylesheets&lt;/code> so the can be served like static content. On Heroku, that is not possible.&lt;/p>
&lt;p>Luckily, there are a few solutions to this problem. I&amp;rsquo;ll describe one of them.
~&lt;/p>
&lt;p>&lt;strong>Use the Force (of git)&lt;/strong>&lt;/p>
&lt;p>In my solution I&amp;rsquo;m using the power of git to generate the necessary CSS files and commit them automatically. Here&amp;rsquo;s how it works.&lt;/p>
&lt;p>In a normal situation the Sass plugin will compile the SASS or SCSS files in &lt;code>public/stylesheets/sass/*.scss&lt;/code> and store the generated CSC files in &lt;code>public/stylesheets&lt;/code>. What we need to do is generate those CSS files by hand and commit them just like other versioned files in our repository. To do this, you need to write a &lt;code>pre-commit&lt;/code> hook for git. This sounds more difficult than it really is.&lt;/p>
&lt;p>Here&amp;rsquo;s the &lt;code>pre-commit&lt;/code> hook I&amp;rsquo;m currently using to take all &lt;code>public/stylesheets/sass/*.scss&lt;/code> files and store the resulting CSS files in &lt;code>public/stylesheets&lt;/code>.&lt;/p>
&lt;pre>&lt;code>#!/usr/bin/env ruby
Dir['public/stylesheets/**/*.scss'].each do |sass|
basename = sass.gsub(/public\/stylesheets\/sass\//, '').gsub(/\.scss$/, '')
next if basename.match(/^_/) # skip includes
css = &amp;quot;public/stylesheets/#{basename}.css&amp;quot;
puts &amp;quot;Compiling #{sass} -&amp;gt; #{css}&amp;quot;
system &amp;quot;sass #{sass} #{css}&amp;quot;
system &amp;quot;git add #{css}&amp;quot;
end
&lt;/code>&lt;/pre>
&lt;p>Store the above Ruby script in &lt;code>.git/hooks/pre-commit&lt;/code>, then give is execute permissions with &lt;code>chmod 755 .git/hooks/pre-commit&lt;/code>.&lt;/p>
&lt;p>This script will find all &lt;code>*.scss&lt;/code> files and save their &lt;code>*.css&lt;/code> equivalents. Then it also stages those files for the commit. Since this is a &lt;code>pre-commit&lt;/code> script, the scenario is like this:&lt;/p>
&lt;ol>
&lt;li>You stage your files to commit, like usual and run the &lt;code>git commit&lt;/code> command.&lt;/li>
&lt;li>Before git makes the actual commit, it runs the &lt;code>pre-commit&lt;/code> script, which generates the necessare CSS files. You&amp;rsquo;ll a message like &lt;code>Compiling public/stylesheets/sass/app.scss -&amp;gt; public/stylesheets/app.css&lt;/code>.&lt;/li>
&lt;li>With any changes to the CSS stages, your commit is made.&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>Won&amp;rsquo;t this spam a lot of CSS commits?&lt;/strong>&lt;/p>
&lt;p>No, it won&amp;rsquo;t. Git is smart enough to see there are not changes to the content of the CSS file.&lt;/p>
&lt;p>&lt;strong>Does it work?&lt;/strong>&lt;/p>
&lt;p>Yes, it works. The generated CSS files are deployed to Heroku like normal, static CSS files and will be served as such.&lt;/p>
&lt;p>&lt;strong>One more thing&amp;hellip;&lt;/strong>&lt;/p>
&lt;p>By default, Sass is set to generate CSS files when needed. Since the CSS file is already there Sass &lt;em>probably&lt;/em> won&amp;rsquo;t try to generate it again in production. But, it might try so anyway and cause an exception.&lt;/p>
&lt;p>To prevent Sass for generating CSS in production completely, add the following line to your &lt;code>config/environments/production.rb&lt;/code>.&lt;/p>
&lt;pre>&lt;code>Sass::Plugin.options[:never_update] = true
&lt;/code>&lt;/pre>
&lt;p>&lt;strong>Notes&lt;/strong>&lt;/p>
&lt;p>There is one problem with this approach. If you have multiple developers or machines you work on, each and every one must have this &lt;code>pre-commit&lt;/code> script installed to make it work. My advise would be to include the script in your project&amp;rsquo;s doc directory.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2010/09/28/precompile-sass-to-css-for-deployment-to-heroku/</guid><pubDate>Tue, 28 Sep 2010 00:00:00 +0000</pubDate></item><item><title>Mass convert WMA to MP3 using ffmpeg and ruby</title><link>https://www.devroom.io/2010/09/11/mass-convert-wma-to-mp3-using-ffmpeg-and-ruby/</link><description>&lt;p>Today I found myself in a situation where I have a few (200+) WMA audio files. Due to personal preference I want MP3, not WMA. So, let&amp;rsquo;s convert that lot.&lt;/p>
&lt;p>What I want to do is convert all those WMA files to constant bitrate 192kbps, stereo mp3 files. (In my case, the WMA files have the required quality to use this settings).
~
The first tool you need is ffmpeg. If you&amp;rsquo;re on Mac, simple run &lt;code>brew install ffmpeg&lt;/code>. The second tool is &lt;code>irb&lt;/code> or _Interactive Ruby.&lt;/p>
&lt;p>With &lt;code>ffmpeg&lt;/code> setup and in your path, create a directory and stuff you WMA&amp;rsquo;s there. Then open &lt;code>irb&lt;/code> and run the following Ruby command:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="n">ext&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;.wma&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="no">Dir&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">glob&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;*&lt;/span>&lt;span class="si">#{&lt;/span>&lt;span class="n">ext&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">each&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="n">f&lt;/span>&lt;span class="o">|&lt;/span> &lt;span class="n">m&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">f&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">gsub&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ext&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;.mp3&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="sb">`ffmpeg -i &amp;#39;&lt;/span>&lt;span class="si">#{&lt;/span>&lt;span class="n">f&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">&amp;#39; -ab 192k -ac 2 -ar 44100 &amp;#39;&lt;/span>&lt;span class="si">#{&lt;/span>&lt;span class="n">m&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">&amp;#39;`&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>When done, you&amp;rsquo;ll find the WMA files converted to MP3 (having the same filename, except for the extention).&lt;/p>
&lt;p>Happy transcoding!&lt;/p>
&lt;p>Note: If you need to add ID3 tags to your files, I can highly recommend &lt;a href="http://musicbrainz.org/doc/MusicBrainz_Picard">MusicBrainz Picard&lt;/a>. It can fingerprint your music and find the proper data online. It&amp;rsquo;s free!&lt;/p>
&lt;p>&lt;em>Update: Added a separte &lt;code>ext&lt;/code> variable, this works great for converting FLAC to MP3 as well! Just use &lt;code>ext = '.flac'&lt;/code>.&lt;/em>&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2010/09/11/mass-convert-wma-to-mp3-using-ffmpeg-and-ruby/</guid><pubDate>Sat, 11 Sep 2010 00:00:00 +0000</pubDate></item><item><title>Resque: how to requeue failed jobs</title><link>https://www.devroom.io/2010/08/23/resque-how-to-requeue-failed-jobs/</link><description>&lt;p>Today I found about 100k Resque jobs in the &lt;em>failed&lt;/em> queue. Due to a small error in some user content, those jobs all failed. After fixing the problem, how do you reprocess all those jobs?&lt;/p>
&lt;p>Option one: go to the &lt;code>resque-web&lt;/code> backend and click &lt;em>retry&lt;/em> about 100.000 times.&lt;/p>
&lt;p>Option two: do some cool ruby commands.
~
Resque offers you direct access to the &lt;em>failed&lt;/em> queue and also provides a method to &lt;code>requeue&lt;/code> jobs. How easy can it be, right?&lt;/p>
&lt;p>With &lt;code>Resque::Failure.all(0,1)&lt;/code> you retrieve the first job from the &lt;em>failed&lt;/em> queue. &lt;code>Resque::Failure.all&lt;/code> is simply a ruby Array.&lt;/p>
&lt;p>To requeue jobs, you can use &lt;code>Resque::Failure.requeue(index)&lt;/code> where index corresponds to the index of the job in the &lt;code>Resque::Failure.all&lt;/code> Array.&lt;/p>
&lt;p>To requeue all jobs in the &lt;em>failed&lt;/em> queue, you can simply run the following commands:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Requeue all jobs in the failed queue&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">(&lt;/span>&lt;span class="no">Resque&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="no">Failure&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">count&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">downto&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">each&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="o">|&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="o">|&lt;/span> &lt;span class="no">Resque&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="no">Failure&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">requeue&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Clear the failed queue&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="no">Resque&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="no">Failure&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">clear&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>That&amp;rsquo;s all there is to it, really. Happy processing!&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2010/08/23/resque-how-to-requeue-failed-jobs/</guid><pubDate>Mon, 23 Aug 2010 00:00:00 +0000</pubDate></item><item><title>Rename a git branch</title><link>https://www.devroom.io/2010/08/09/rename-a-git-branch/</link><description>&lt;p>In git, branching is cheap and easy. You do it all the time (you&amp;rsquo;re not? Well, you should). Sometimes, though, you create a new feature branch, only to come to the conclusion later that the name you gave it does not cover the stuff you&amp;rsquo;ve been doing.&lt;/p>
&lt;p>No problem for git! Renaming a branch is really easy:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">git branch -m old_branch new_branch
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>That&amp;rsquo;s all.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2010/08/09/rename-a-git-branch/</guid><pubDate>Mon, 09 Aug 2010 00:00:00 +0000</pubDate></item><item><title>Using multiple clipboards in Vim</title><link>https://www.devroom.io/2010/08/09/using-multiple-clipboards-in-vim/</link><description>&lt;p>On of the first things you learn when using Vim activly is that when you delete something using &lt;code>x&lt;/code> or &lt;code>dd&lt;/code> that content is actually cut and put on a clipboard. Later you can retrieve that content using the &lt;code>p&lt;/code> or &lt;code>P&lt;/code> commanands.&lt;/p>
&lt;p>One thing that makes Vim more awesome than let&amp;rsquo;s say, uhm TextMate, is that it actually has more than one clipboard! Yeah! In fact, in Vim terminology those clipboards are called &lt;em>registers&lt;/em>.
~&lt;/p>
&lt;p>To view your current registers type &lt;code>:reg&lt;/code>. You&amp;rsquo;ll notice that every register is prefixed with the symbol &lt;code>&amp;quot;&lt;/code>.&lt;/p>
&lt;p>How do you use these things? Let&amp;rsquo;s say you want to copy a line into a specific register: &lt;code>&amp;quot;kyy&lt;/code> will yank the current line into register &lt;code>&amp;quot;k&lt;/code>. If you later want to paste register &lt;code>&amp;quot;k&lt;/code> you can do this: &lt;code>&amp;quot;kp&lt;/code>. Easy, huh?&lt;/p>
&lt;p>The beauty of registers is that they keep around until you explicitly copy something else into them. The normal clipboard or &lt;em>paste buffer&lt;/em> is replaced everytime you cut or copy something, registers are not affected unless you specify to use them.&lt;/p>
&lt;p>Now, go on, code more productively.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2010/08/09/using-multiple-clipboards-in-vim/</guid><pubDate>Mon, 09 Aug 2010 00:00:00 +0000</pubDate></item><item><title>How to enable SSH Forwarding on Mac OS X Snow Leopard</title><link>https://www.devroom.io/2010/07/29/how-to-enable-ssh-forwarding-on-mac-os-x-snow-leopard/</link><description>&lt;p>The other day I was toying with Rubber to deploy a Rails3 app to Amazon EC2. I host the project code in a private Github repository, accessible only with my own SSH key.&lt;/p>
&lt;p>In order to checkout your code an any EC2 instance you can do one of two things:&lt;/p>
&lt;ol>
&lt;li>
&lt;p>Copy your private SSH key to the instance - This sounds easy enough, but has serious security implications. You do &lt;em>not&lt;/em> want to be sending out your private SSH key, do you? That leaves you with option 2.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Let SSH forward the authentication request to your local machine. This is call &lt;em>Forwarding&lt;/em> and requires &lt;code>ssh-agent&lt;/code> to be running on your system. You&amp;rsquo;re in luck, &lt;code>ssh-agent&lt;/code> is started automatically on your mac.&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>Now, the problem is that in Leopard (10.5) SSH Forwarding was enabled by default. You guessed it, in Snow Leopard it has been disabled by default. So, it&amp;rsquo;s up to you to enable SSH Forwarding manually. Here goes:&lt;/p>
&lt;ol>
&lt;li>Open Terminal.app.&lt;/li>
&lt;li>&lt;code>sudo vi /etc/ssh_config&lt;/code> &lt;br>
You will be asked for your password now. Feel free to use your preferred editor here.&lt;/li>
&lt;li>Add the following two lines to the top of the file:&lt;br>
&lt;code>Host *&lt;/code>&lt;br>
&lt;code>ForwardAgent yes&lt;/code>&lt;br>&lt;/li>
&lt;li>Save the file and exit your editor.&lt;/li>
&lt;/ol>
&lt;p>All right sparky, you now have enabled SSH Forwarding. Have fun!&lt;/p>
&lt;p>PS. If you have enabled &amp;ldquo;Remote Login&amp;rdquo; under Sharing Preferences, make sure to stop and start that service to notify it of the changes you just made.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2010/07/29/how-to-enable-ssh-forwarding-on-mac-os-x-snow-leopard/</guid><pubDate>Thu, 29 Jul 2010 00:00:00 +0000</pubDate></item><item><title>Screencast: Firefly URL shortener in less than 2.5 minutes</title><link>https://www.devroom.io/2010/07/12/screencast-firefly-url-shortener-in-less-than-25-minutes/</link><description>&lt;p>Firefly is a light-weight URL Shortener app. It&amp;rsquo;s written in Ruby/Sinatra and can be easily deployed to Heroku, &lt;a href="https://ariejannet.local/2010/06/06/setup-your-own-firefly-url-shortener-in-25-minutes/">as I&amp;rsquo;ve written before&lt;/a>. This screencast illustrates how easy it really is to setup Firefly and give it a spin. Did I mention that both Firefly and Heroku can be used for free?&lt;/p>
&lt;p>&lt;object width="620" height="349">&lt;param name="allowfullscreen" value="true" />&lt;param name="allowscriptaccess" value="always" />&lt;param name="movie" value="http://vimeo.com/moogaloop.swf?clip_id=13997287&amp;server=vimeo.com&amp;show_title=1&amp;show_byline=1&amp;show_portrait=1&amp;color=00ADEF&amp;fullscreen=1&amp;autoplay=0&amp;loop=0" />&lt;embed src="http://vimeo.com/moogaloop.swf?clip_id=13997287&amp;server=vimeo.com&amp;show_title=1&amp;show_byline=1&amp;show_portrait=1&amp;color=00ADEF&amp;fullscreen=1&amp;autoplay=0&amp;loop=0" type="application/x-shockwave-flash" allowfullscreen="true" allowscriptaccess="always" width="620" height="349">&lt;/embed>&lt;/object>&lt;p>&lt;a href="http://vimeo.com/13997287">Firefly URL shortener in less than 2.5 minutes&lt;/a> from &lt;a href="http://vimeo.com/ariejan">Ariejan de Vroom&lt;/a> on &lt;a href="http://vimeo.com">Vimeo&lt;/a>.&lt;/p>&lt;/p>
&lt;p>~
As always I&amp;rsquo;m keen to hear how you are using Firefly. Got some coding talents? Feel free to contribute features and fixes through &lt;a href="http://github.com/ariejan/firefly">github&lt;/a>.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2010/07/12/screencast-firefly-url-shortener-in-less-than-25-minutes/</guid><pubDate>Mon, 12 Jul 2010 00:00:00 +0000</pubDate></item><item><title>Cherry-Picking specific commits from another branch</title><link>https://www.devroom.io/2010/06/10/cherry-picking-specific-commits-from-another-branch/</link><description>&lt;p>I&amp;rsquo;m often asked how to merge only specific commits from another branch into the current one. The reason you&amp;rsquo;d want to do this is to merge specific changes you need now, leaving other code changes you&amp;rsquo;re not interested in right now behind.&lt;/p>
&lt;p>First of all, use &lt;code>git log&lt;/code> or the awesome &lt;a href="http://gitx.frim.nl/">GitX&lt;/a> tool to see exactly which commit you want to pick. An example:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">dd2e86 - 946992 - 9143a9 - a6fd86 - 5a6057 [master]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 76cada - 62ecb3 - b886a0 [feature]
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Let&amp;rsquo;s say you&amp;rsquo;ve written some code in commit &lt;code>62ecb3&lt;/code> of the &lt;code>feature&lt;/code> branch that is very important right now. It may contain a bug fix or code that other people need to have access to now. Whatever the reason, you want to have commit &lt;code>62ecb3&lt;/code> in the master branch right now, but not the other code you&amp;rsquo;ve written in the &lt;code>feature&lt;/code> branch.&lt;/p>
&lt;p>Here comes &lt;code>git cherry-pick&lt;/code>. In this case, &lt;code>62ecb3&lt;/code> is the cherry and you want to pick it!&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">git checkout master
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">git cherry-pick 62ecb3
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>That&amp;rsquo;s all. &lt;code>62ecb3&lt;/code> is now applied to the master branch and commited (as a new commit) in &lt;code>master&lt;/code>. &lt;code>cherry-pick&lt;/code> behaves just like &lt;code>merge&lt;/code>. If git can&amp;rsquo;t apply the changes (e.g. you get merge conflicts), git leaves you to resolve the conflicts manually and make the commit yourself.&lt;/p>
&lt;h2 id="cherry-picking-a-range-of-commits">Cherry picking a range of commits&lt;/h2>
&lt;p>In some cases picking one single commit is not enough. You need, let&amp;rsquo;s say three consecutive commits. &lt;code>cherry-pick&lt;/code> is not the right tool for this. &lt;code>rebase&lt;/code> is. From the previous example, you&amp;rsquo;d want commit &lt;code>76cada&lt;/code> and &lt;code>62ecb3&lt;/code> in &lt;code>master&lt;/code>.&lt;/p>
&lt;p>The flow is to first create a new branch from &lt;code>feature&lt;/code> at the last commit you want, in this case &lt;code>62ecb3&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">git checkout -b newbranch 62ecb3
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Next up, you rebase the &lt;code>newbranch&lt;/code> commit &lt;code>--onto master&lt;/code>. The &lt;code>76cada^&lt;/code> indicates that you want to start from that specific commit.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">git rebase --onto master 76cada^
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The result is that commits &lt;code>76cada&lt;/code> through &lt;code>62ecb3&lt;/code> are applied to &lt;code>master&lt;/code>.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2010/06/10/cherry-picking-specific-commits-from-another-branch/</guid><pubDate>Thu, 10 Jun 2010 00:00:00 +0000</pubDate></item><item><title>Uploading files with Curl</title><link>https://www.devroom.io/2010/06/07/uploading-files-with-curl/</link><description>&lt;p>I&amp;rsquo;ve always trouble uploading files with Curl. Some how the syntax for that command won&amp;rsquo;t stick, so I post it here for future reference.&lt;/p>
&lt;p>What I want to do is perform a normal &lt;code>POST&lt;/code>, including a file and some other variables to a remote server. This is it:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">curl -i -F &lt;span class="nv">name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="nb">test&lt;/span> -F &lt;span class="nv">filedata&lt;/span>&lt;span class="o">=&lt;/span>@localfile.jpg http://example.org/upload
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>You can add as many &lt;code>-F&lt;/code> as you want. The &lt;code>-i&lt;/code> option tells curl to show the response headers as well, which I find useful most of the time.
~&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2010/06/07/uploading-files-with-curl/</guid><pubDate>Mon, 07 Jun 2010 00:00:00 +0000</pubDate></item><item><title>Firefly 0.4.3 and Firefly Client 0.4.0 released</title><link>https://www.devroom.io/2010/06/06/firefly-043-and-firefly-client-040-released/</link><description>&lt;p>Today version 0.4.3 of Firefly was released with some minor updates. To complete the package, a new gem &lt;a href="http://github.com/ariejan/firefly-client">firefly-client&lt;/a> has been released.&lt;/p>
&lt;p>The client library allows your Ruby application to easily shorten URLs with a remote Firefly server. It&amp;rsquo;s very easy to use and lightweight.&lt;/p>
&lt;h2 id="firefly-043-changelog">Firefly 0.4.3 Changelog&lt;/h2>
&lt;ul>
&lt;li>Handle invalid API keys correctly.&lt;/li>
&lt;li>Added a fix for MySQL users to update the &lt;code>code&lt;/code> column to use the correct collation. Fixes &lt;a href="github.com/ariejan/firefly/issues/9">issue #9&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="firefly-client">Firefly Client&lt;/h2>
&lt;p>Using the Firefly Client is very easy, read the following snippet from the &lt;a href="http://github.com/ariejan/firefly-client#readme">README&lt;/a>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">require&lt;/span> &lt;span class="s1">&amp;#39;rubygems&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">require&lt;/span> &lt;span class="s1">&amp;#39;firefly-client&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">firefly&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">Firefly&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="no">Client&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">new&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;http://aj.gs&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;my_api_key&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">firefly&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">shorten&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;http://google.com&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s2">&amp;#34;http://aj.gs/8ds&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Nice, huh? Get more info over at &lt;a href="http://github.com/ariejan/firefly-client">github&lt;/a>&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2010/06/06/firefly-043-and-firefly-client-040-released/</guid><pubDate>Sun, 06 Jun 2010 00:00:00 +0000</pubDate></item><item><title>Setup your own Firefly URL shortener in 2.5 minutes</title><link>https://www.devroom.io/2010/06/06/setup-your-own-firefly-url-shortener-in-25-minutes/</link><description>&lt;p>By popular demand I&amp;rsquo;ve setup a guide to setup your own personal URL shortener with &lt;a href="http://github.com/ariejan/firefly">Firefly&lt;/a> and &lt;a href="http://heroku.com">Heroku&lt;/a>. I&amp;rsquo;ve timed it an you can do it in under 2.5 minutes. How&amp;rsquo;s that for fast?&lt;/p>
&lt;p>~&lt;/p>
&lt;p>I&amp;rsquo;m going to assume that you have &lt;code>git&lt;/code> installed on your computer and that you have an already working Heroku account. If not, &lt;a href="http://api.heroku.com/signup">sign up here for Heroku first&lt;/a>.&lt;/p>
&lt;p>I&amp;rsquo;ll be referencing to a &lt;code>my_firefly_dir&lt;/code> in this guide. You can just pick a name during step 2 and it will be created for you.&lt;/p>
&lt;ol>
&lt;li>&lt;code>gem install heroku&lt;/code>&lt;/li>
&lt;li>&lt;code>git clone git://github.com/ariejan/firefly-heroku.git my_firefly_dir&lt;/code>&lt;/li>
&lt;li>&lt;code>cd my_firefly_dir&lt;/code>&lt;/li>
&lt;li>&lt;code>heroku create&lt;/code>&lt;/li>
&lt;li>Use the URL returned by &lt;code>heroku create&lt;/code> and use the FQDN (xxxx.heroku.com) to configure the &lt;code>hostname&lt;/code> in &lt;code>config.ru&lt;/code>. Like: &lt;code>set :hostname, 'xxxx.heroku.com'&lt;/code>&lt;/li>
&lt;li>Change the API key in &lt;code>config.ru&lt;/code> to something more secure.&lt;/li>
&lt;li>Commit your changes with &lt;code>git commit -am 'Updated hostname and apikey'&lt;/code>&lt;/li>
&lt;li>&lt;code>git push heroku master&lt;/code>&lt;/li>
&lt;li>Done! Open your app with &lt;code>heroku open&lt;/code>&lt;/li>
&lt;/ol>
&lt;p>So, there&amp;rsquo;s no excuse not to try Firefly now! Use the included &lt;em>bookmarklet&lt;/em> to quickly shorten URLs from your browser or use the &lt;code>firefly-client&lt;/code> gem to enable shortening in your own application!&lt;/p>
&lt;p>Please, let me know if you did a succesfull install of Firefly and what you think about it.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2010/06/06/setup-your-own-firefly-url-shortener-in-25-minutes/</guid><pubDate>Sun, 06 Jun 2010 00:00:00 +0000</pubDate></item><item><title>Upgrading to Mongoid Beta 6</title><link>https://www.devroom.io/2010/05/30/upgrading-to-mongoid-beta-6/</link><description>&lt;p>If you are working with Rails 3 and Mongoid, you&amp;rsquo;re likely to upgrade to [&lt;code>mongoid-2.0.0.beta6&lt;/code>][1]. That&amp;rsquo;s okay, but you will run into a few problems. Among others, one will be:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">Database should be a Mongo::DB, not NilClass
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>or&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">Mongoid::Errors::InvalidDatabase: Mongoid::Errors::InvalidDatabase
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Another, Mongoid-related problem is the error &lt;code>uninitialized constant OrderedHash&lt;/code>.&lt;/p>
&lt;p>Luckily, these problems can be solved quite easily.
[1]: &lt;a href="http://rubygems.org/gems/mongoid">http://rubygems.org/gems/mongoid&lt;/a>&lt;/p>
&lt;p>The first thing you need to do is make sure you use the right version of &lt;code>bson_ext&lt;/code>. Beta 6 requires you to run &lt;code>bson_ext-1.0.1&lt;/code> or you&amp;rsquo;ll get the &lt;code>OrderedHash&lt;/code> error. Okay, with that out of the way, let&amp;rsquo;s focus on the MongoDB errors.&lt;/p>
&lt;p>The problem is that Mongoid is accessed/used before it is properly initialized. To resolve this issue, add the following line to your other requires at the top of &lt;code>config/application.rb&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">require&lt;/span> &lt;span class="s1">&amp;#39;mongoid/railtie&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>With that, you initialize Mongoid correctly. Hope it helps.&lt;/p>
&lt;p>Update: I also ran into &lt;code>keys must be strings or symbols&lt;/code> errors. This is now a &lt;em>known issue&lt;/em> with &lt;code>mongoid-2.0.0.beta6&lt;/code> and has been fixed in &lt;code>master&lt;/code>. If you are using Bundler (you are, aren&amp;rsquo;t you?) then you can use the master branch instead of the gem:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># gem &amp;#39;mongoid&amp;#39;, &amp;#39;2.0.0.beta6&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">gem&lt;/span> &lt;span class="s1">&amp;#39;mongoid&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:git&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s1">&amp;#39;http://github.com/durran/mongoid.git&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2010/05/30/upgrading-to-mongoid-beta-6/</guid><pubDate>Sun, 30 May 2010 00:00:00 +0000</pubDate></item><item><title>Bundler + Passenger with Rails 2.3.5? Yes, please!</title><link>https://www.devroom.io/2010/05/17/bundler-passenger-with-rails-235-yes-please/</link><description>&lt;p>Bundler allows you to define the gems your application uses, resolve dependencies and load everything up. This is great, because you don&amp;rsquo;t have to manage all those different gem versions yourself any more.&lt;/p>
&lt;p>There is a little problem, though. When you want to use Bundler with Rails 2.3.5. you need to do a bit of extra work. You&amp;rsquo;ll need to create a file &lt;code>config/preinitializer.rb&lt;/code> that contains the following:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">require&lt;/span> &lt;span class="s2">&amp;#34;rubygems&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">require&lt;/span> &lt;span class="s2">&amp;#34;bundler&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="no">Gem&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="no">Version&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">new&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="no">Bundler&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="no">VERSION&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">&amp;lt;=&lt;/span> &lt;span class="no">Gem&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="no">Version&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">new&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;0.9.5&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">raise&lt;/span> &lt;span class="no">RuntimeError&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;Your bundler version is too old.&amp;#34;&lt;/span> &lt;span class="o">+&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;Run `gem install bundler` to upgrade.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># Set up load paths for all bundled gems&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="no">Bundler&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">setup&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">rescue&lt;/span> &lt;span class="no">Bundler&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="no">GemNotFound&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">raise&lt;/span> &lt;span class="no">RuntimeError&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;Bundler couldn&amp;#39;t find some gems.&amp;#34;&lt;/span> &lt;span class="o">+&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;Did you run `bundle install`?&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Then you deploy your app with Capistrano (as &lt;code>root&lt;/code>) and find that Passenger can&amp;rsquo;t find your gems. True, you need to install them, so you add a Capistrano task to run &lt;code>bundle install&lt;/code> after you update your code. Still, passenger can&amp;rsquo;t find the gems.&lt;/p>
&lt;p>The problem is that bundler installs the gems to your &lt;code>~/.bundle&lt;/code>. When you run bundler as &lt;code>root&lt;/code>, passenger won&amp;rsquo;t be able to find the gems in &lt;code>/root/.bundle&lt;/code>.&lt;/p>
&lt;p>A solution is easy: &lt;code>bundle install .bundle&lt;/code> will install the gems to &lt;code>./.bundle&lt;/code>, which should be your rails root directory. That solves your problem with passenger! Here&amp;rsquo;s the full Capistrano task:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="n">desc&lt;/span> &lt;span class="s2">&amp;#34;Install bundled gems into ./.bundle&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">task&lt;/span> &lt;span class="ss">:bundle&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">run&lt;/span> &lt;span class="s2">&amp;#34;cd &lt;/span>&lt;span class="si">#{&lt;/span>&lt;span class="n">release_path&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">; bundle install .bundle&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2010/05/17/bundler-passenger-with-rails-235-yes-please/</guid><pubDate>Mon, 17 May 2010 00:00:00 +0000</pubDate></item><item><title>Firefly 0.4.1 released</title><link>https://www.devroom.io/2010/04/30/firefly-041-released/</link><description>&lt;p>I just pushed &lt;a href="http://github.com/ariejan/firefly/tree/v0.4.1">Firefly 0.4.1&lt;/a> to &lt;a href="http://rubygems.org/gems/firefly">Rubygems&lt;/a>. Updating is easy:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">gem update firefly
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Don&amp;rsquo;t forget to restart your server, that&amp;rsquo;s all.&lt;/p>
&lt;p>The 0.4.1 release covers the following changes:&lt;/p>
&lt;ul>
&lt;li>Normalize URLs before shortening them. This prevents false duplicates.&lt;/li>
&lt;li>Validate URLs to be valid HTTP or HTTPS, don&amp;rsquo;t accept others. Closes #8&lt;/li>
&lt;li>Don&amp;rsquo;t ask for the API key after shortening a URL with the bookmarklet.&lt;/li>
&lt;li>Show the highlighted URL separately. Closes #7.&lt;/li>
&lt;/ul>
&lt;p>If you are interested in contributing to Firefly, please fork the project on &lt;a href="http://github.com/ariejan/firefly">github&lt;/a>. Pull requests are very welcome.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2010/04/30/firefly-041-released/</guid><pubDate>Fri, 30 Apr 2010 00:00:00 +0000</pubDate></item><item><title>Ruby version and gemset in your Bash prompt? Yes sir!</title><link>https://www.devroom.io/2010/04/25/ruby-version-and-gemset-in-your-bash-prompt-yes-sir/</link><description>&lt;p>RVM is an easy way to switch between different ruby implementations and gemsets. If you don&amp;rsquo;t know about it, &lt;a href="http://rvm.beginrescueend.com/">go check it out&lt;/a>. If you do know about, you&amp;rsquo;ll know how annoying it is never to know which ruby version and gemset you&amp;rsquo;re currently using. Here is a nice &lt;code>.profile&lt;/code> hack that shows your current ruby version and optional gemset in your prompt.&lt;/p>
&lt;p>Firstly, add the following function to your &lt;code>~/.profile&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">&lt;span class="k">function&lt;/span> rvm_version &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">local&lt;/span> &lt;span class="nv">gemset&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="k">$(&lt;/span>&lt;span class="nb">echo&lt;/span> &lt;span class="nv">$GEM_HOME&lt;/span> &lt;span class="p">|&lt;/span> awk -F&lt;span class="s1">&amp;#39;@&amp;#39;&lt;/span> &lt;span class="s1">&amp;#39;{print $2}&amp;#39;&lt;/span>&lt;span class="k">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">[&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$gemset&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> !&lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;&amp;#34;&lt;/span> &lt;span class="o">]&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="nv">gemset&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;@&lt;/span>&lt;span class="nv">$gemset&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">local&lt;/span> &lt;span class="nv">version&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="k">$(&lt;/span>&lt;span class="nb">echo&lt;/span> &lt;span class="nv">$MY_RUBY_HOME&lt;/span> &lt;span class="p">|&lt;/span> awk -F&lt;span class="s1">&amp;#39;-&amp;#39;&lt;/span> &lt;span class="s1">&amp;#39;{print $2}&amp;#39;&lt;/span>&lt;span class="k">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">[&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$version&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> !&lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;&amp;#34;&lt;/span> &lt;span class="o">]&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="nv">version&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;@&lt;/span>&lt;span class="nv">$version&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">local&lt;/span> &lt;span class="nv">full&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$version$gemset&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">[&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$full&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> !&lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;&amp;#34;&lt;/span> &lt;span class="o">]&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="nb">echo&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$full&lt;/span>&lt;span class="s2"> &amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Next, you can use this function in your prompt. Like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">export&lt;/span> &lt;span class="nv">PS1&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;\$(rvm_version) \w \$(parse_git_branch) \$ &amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The results? For standard ruby 1.8.7&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">@1.8.7 ~ $
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Or with the &lt;code>rails3&lt;/code> gemset enabled:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">@1.8.7@rails3 ~ $
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>So, now you always know which ruby you&amp;rsquo;re using! Happy coding!&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2010/04/25/ruby-version-and-gemset-in-your-bash-prompt-yes-sir/</guid><pubDate>Sun, 25 Apr 2010 00:00:00 +0000</pubDate></item><item><title>A new day, a new Firefly</title><link>https://www.devroom.io/2010/04/14/a-new-day-a-new-firefly/</link><description>&lt;p>Get ready for 0.4 (or, 0.4.0.1 to be exact)! This release has some interesting new features. Read more to find out, I&amp;rsquo;ve included a screen shot for your pleasure.&lt;/p>
&lt;ul>
&lt;li>Twitter integration&lt;/li>
&lt;li>Easier copying of short URLs&lt;/li>
&lt;li>Highlight the last shortened URL&lt;/li>
&lt;li>Several fixes, see &lt;a href="http://github.com/ariejan/firefly/blob/v0.4.0.1/HISTORY">HISTORY&lt;/a> for details&lt;/li>
&lt;/ul>
&lt;p>Coming from Digg? Check out the source and &lt;code>README&lt;/code> at &lt;a href="http://github.com/ariejan/firefly">http://github.com/ariejan/firefly&lt;/a>&lt;/p>
&lt;p>&lt;em>The 0.4.0.1 release fixes file permissions for the &lt;code>twitter.png&lt;/code> image so your server can read it properly.&lt;/em>&lt;/p>
&lt;p>~&lt;/p>
&lt;p>&lt;a href="http://ariejan.net/images/aj.gs-sample-full.jpg">&lt;img
src="http://ariejan.net/images/aj.gs-sample.jpg"
alt="aj.gs Screen shot"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/a>&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2010/04/14/a-new-day-a-new-firefly/</guid><pubDate>Wed, 14 Apr 2010 00:00:00 +0000</pubDate></item><item><title>Get ready for Firefly 0.3!</title><link>https://www.devroom.io/2010/04/13/get-ready-for-firefly-03/</link><description>&lt;p>Hot off the press is Firefly 0.3! Firefly is a simple URL shortener application intended for personal use. It&amp;rsquo;s core features are:&lt;/p>
&lt;ul>
&lt;li>Very light-weight - based on Sinatra&lt;/li>
&lt;li>Shorten URLs using 62-Base encoding&lt;/li>
&lt;li>Offers an easy to use API&lt;/li>
&lt;li>Keeps track of URL clicks&lt;/li>
&lt;li>Supports most popular database backends, including MySQL and Sqlite3&lt;/li>
&lt;li>Includes a ready-to-use Bookmarklet&lt;/li>
&lt;li>Works with any Rack capable web server&lt;/li>
&lt;/ul>
&lt;p>Interested? Give it a go! Here&amp;rsquo;s how in ht&lt;/p>
&lt;h3 id="1-install-firefly">1. Install Firefly&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">gem install firefly
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="2-ready">2. Ready?&lt;/h3>
&lt;p>Create a new directory and place the following &lt;code>config.ru&lt;/code> file in it:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">require&lt;/span> &lt;span class="s1">&amp;#39;rubygems&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">require&lt;/span> &lt;span class="s1">&amp;#39;firefly&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">disable&lt;/span> &lt;span class="ss">:run&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">app&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">Firefly&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="no">Server&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">new&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">set&lt;/span> &lt;span class="ss">:hostname&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;localhost:3000&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">set&lt;/span> &lt;span class="ss">:api_key&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;test&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">set&lt;/span> &lt;span class="ss">:database&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;sqlite3://&lt;/span>&lt;span class="si">#{&lt;/span>&lt;span class="no">Dir&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">pwd&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">/firefly.sqlite3&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">run&lt;/span> &lt;span class="n">app&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="3-go">3. Go!&lt;/h3>
&lt;p>Start your engines! In this case I use &lt;code>thin&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">thin start -R config.ru
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now, when you visit &lt;code>http://localhost:3000/&lt;/code> you&amp;rsquo;ll be asked for your API key to login. Then, start shortening!&lt;/p>
&lt;h3 id="updating">Updating&lt;/h3>
&lt;p>If you were already running Firefly 0.2, upgrading is easy. Simple update the Firefly gem to 0.3 and restart your server.&lt;/p>
&lt;p>When you update from 0.2 you&amp;rsquo;ll notice your click stats are all indicating &lt;code>0&lt;/code>. Don&amp;rsquo;t worry, no data was lost! The solution is quite simple.&lt;/p>
&lt;ol>
&lt;li>Remove the &lt;code>clicks&lt;/code> field from &lt;code>firefly_urls&lt;/code>&lt;/li>
&lt;/ol>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">ALTER&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">TABLE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">firefly_urls&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">DROP&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">clicks&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ol start="2">
&lt;li>Rename the &lt;code>visits&lt;/code> field to &lt;code>clicks&lt;/code>&lt;/li>
&lt;/ol>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">ALTER&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">TABLE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">firefly_urls&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">CHANGE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">visits&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">clicks&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nb">int&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>All done! No restart required.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2010/04/13/get-ready-for-firefly-03/</guid><pubDate>Tue, 13 Apr 2010 00:00:00 +0000</pubDate></item><item><title>Detect browser Web Sockets support</title><link>https://www.devroom.io/2010/04/05/detect-browser-web-sockets-support/</link><description>&lt;p>HTML5 Web Sockets are awesome! I&amp;rsquo;ve been toying around with it for a bit today and noticed that not every browser supports native HTML5 Web Sockets yet.&lt;/p>
&lt;p>Google Chrome 5 has native support for web sockets, FireFox 3.6 does not. This poses a problem if you&amp;rsquo;re building something awesome that does require web sockets.
~&lt;/p>
&lt;p>Luckily, it&amp;rsquo;s easy to detect web sockets support through JavaScript. All you really need to do is check if &lt;code>WebSocket&lt;/code> is defined or not.&lt;/p>
&lt;p>Here&amp;rsquo;s a simple example, note that I&amp;rsquo;m using jQuery here.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="line">&lt;span class="cl">&lt;span class="nx">$&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">document&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">ready&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kd">function&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span>&lt;span class="p">(&lt;/span> &lt;span class="k">typeof&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">WebSocket&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="s2">&amp;#34;function&amp;#34;&lt;/span> &lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">$&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;body&amp;#39;&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">html&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;&amp;lt;h1&amp;gt;Error&amp;lt;/h1&amp;gt;&amp;lt;p&amp;gt;Your browser does not support HTML5 Web Sockets. Try Google Chrome instead.&amp;lt;/p&amp;gt;&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Maybe there are better ways, but &lt;em>it works for me&lt;/em>.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2010/04/05/detect-browser-web-sockets-support/</guid><pubDate>Mon, 05 Apr 2010 00:00:00 +0000</pubDate></item><item><title>Announcing Firefly, a ruby URL shortener</title><link>https://www.devroom.io/2010/03/29/announcing-firefly-a-ruby-url-shortener/</link><description>&lt;p>Here it is! Firefly! The easiest URL shortener there is in Ruby land!&lt;/p>
&lt;p>How easy? Install the gem, copy and paste a &lt;code>config.ru&lt;/code> and you&amp;rsquo;re good to go!&lt;/p>
&lt;p>Read &lt;a href="http://github.com/ariejan/firefly#readme">the documentation&lt;/a> or &lt;a href="http://github.com/ariejan/firefly">the source&lt;/a> for more details. I&amp;rsquo;ve included the README in this post as well for your convenience.&lt;/p>
&lt;p>&lt;em>FYI: I&amp;rsquo;m currently running aj.gs on Firefly 0.1 with MySQL.&lt;/em>&lt;/p>
&lt;h3 id="firefly">FireFly&lt;/h3>
&lt;p>FireFly is a simple URL shortener for personal use.&lt;/p>
&lt;h3 id="installation">Installation&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">sudo gem install firefly
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>After you have installed the Firefly gem you should create a &lt;code>config.ru&lt;/code> file that tells your webserver what to do. Here is a sample &lt;code>config.ru&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">require&lt;/span> &lt;span class="s1">&amp;#39;rubygems&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">require&lt;/span> &lt;span class="s1">&amp;#39;firefly&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">disable&lt;/span> &lt;span class="ss">:run&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">app&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">Firefly&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="no">Server&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">new&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">set&lt;/span> &lt;span class="ss">:hostname&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;localhost:3000&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">set&lt;/span> &lt;span class="ss">:api_key&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;test&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># Use Sqlite3 by default&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">set&lt;/span> &lt;span class="ss">:database&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;sqlite3://&lt;/span>&lt;span class="si">#{&lt;/span>&lt;span class="no">Dir&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">pwd&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">/firefly.sqlite3&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># You can use MySQL as well.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># Make sure to install the do_mysql gem:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># sudo gem install do_mysql&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># set :database, &amp;#34;mysql://root@localhost/firefly&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">run&lt;/span> &lt;span class="n">app&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Next you can start your web server. You may try thin:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">thin start -R config.ru
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="configuration">Configuration&lt;/h3>
&lt;p>All configuration is done in &lt;code>config.ru&lt;/code>.&lt;/p>
&lt;ul>
&lt;li>&lt;code>:hostname&lt;/code> sets the hostname to use for shortened URLs.&lt;/li>
&lt;li>&lt;code>:api_key&lt;/code> sets the API key. This key is required when posting new URLs&lt;/li>
&lt;li>&lt;code>:database&lt;/code> a database URI that &lt;a href="http://ariejan.net/2010/03/28/really-another-sinatra-url-shortener-in-ruby/">DataMapper&lt;/a> can understand.&lt;/li>
&lt;/ul>
&lt;p>It&amp;rsquo;s possible to use all kinds of backends with DataMapper. Sqlite3 and MySQL have been tested, but others like MongoDB may work as well.&lt;/p>
&lt;h3 id="usage">Usage&lt;/h3>
&lt;p>Adding a URL is done by doing a simple POST request that includes the URL and your API key.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">curl -d &lt;span class="s2">&amp;#34;url=http://ariejan.net&amp;#34;&lt;/span> -d &lt;span class="s2">&amp;#34;api_key=test&amp;#34;&lt;/span> http://localhost:3000/api/add
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>If you&amp;rsquo;re on a MacOSX you could add the following function to your &lt;code>~/.profile&lt;/code> to automate URL shortening:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">shorten&lt;span class="o">(){&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">URL&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="nv">$1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">SHORT_URL&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="sb">`&lt;/span>curl -s -d &lt;span class="s2">&amp;#34;url=&lt;/span>&lt;span class="nv">$URL&lt;/span>&lt;span class="s2">&amp;amp;api_key=test&amp;#34;&lt;/span> http://localhost:3000/api/add&lt;span class="sb">`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">echo&lt;/span> &lt;span class="nv">$SHORT_URL&lt;/span> &lt;span class="p">|&lt;/span> pbcopy
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">echo&lt;/span> &lt;span class="s2">&amp;#34;-- &lt;/span>&lt;span class="nv">$URL&lt;/span>&lt;span class="s2"> =&amp;gt; &lt;/span>&lt;span class="nv">$SHORT_URL&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">echo&lt;/span> &lt;span class="s2">&amp;#34;Short URL copied to clipboard.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>After you restart Terminal.app (or at least reload the &lt;code>.profile&lt;/code> file) you can use it like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">$ shorten http://ariejan.net
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-- http://ariejan.net &lt;span class="o">=&lt;/span>&amp;gt; http://aj.gs/1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Short URL copied to clipboard.
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="bugs-feature-requests-etc">Bugs, Feature Requests, etc.&lt;/h3>
&lt;ul>
&lt;li>&lt;a href="http://github.com/ariejan/firefly#readme">Source&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://github.com/ariejan/firefly">Issue tracker&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>Feel free to fork Firefly and create patches for it. Here are some basic instructions:&lt;/p>
&lt;ul>
&lt;li>Fork the project.&lt;/li>
&lt;li>Make your feature addition or bug fix.&lt;/li>
&lt;li>Add tests for it. This is important so I don&amp;rsquo;t break it in a future version unintentionally.&lt;/li>
&lt;li>Commit, do not mess with Rakefile, VERSION, or HISTORY. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)&lt;/li>
&lt;li>Send me a pull request. Bonus points for topic branches.&lt;/li>
&lt;/ul>
&lt;h3 id="license">License&lt;/h3>
&lt;p>Copyright (c) 2009 Ariejan de Vroom&lt;/p>
&lt;p>Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
&amp;ldquo;Software&amp;rdquo;), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:&lt;/p>
&lt;p>The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.&lt;/p>
&lt;p>THE SOFTWARE IS PROVIDED &amp;ldquo;AS IS&amp;rdquo;, WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2010/03/29/announcing-firefly-a-ruby-url-shortener/</guid><pubDate>Mon, 29 Mar 2010 00:00:00 +0000</pubDate></item><item><title>Really? Another Sinatra URL Shortener in Ruby?</title><link>https://www.devroom.io/2010/03/28/really-another-sinatra-url-shortener-in-ruby/</link><description>&lt;p>With my recent interest in &lt;a href="http://www.sinatrarb.com/">Sinatra&lt;/a> I decided to make some us of it and write a URL shortener service for with it. This also gave me an excuse to polish up my &lt;a href="http://datamapper.org/">DataMapper&lt;/a> skills a bit.&lt;/p>
&lt;p>~&lt;/p>
&lt;p>Now, it&amp;rsquo;s finished! I put the app on &lt;a href="http://github.com/ariejan/firefly">http://github.com/ariejan/firefly&lt;/a>. &lt;a href="http://github.com/ariejan/firefly/fork">Feel free to fork&lt;/a>!&lt;/p>
&lt;p>Not sure if it works? Try this: &lt;a href="http://aj.gs/2">http://aj.gs/2&lt;/a>&lt;/p>
&lt;p>Questions? Please use the comments!&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2010/03/28/really-another-sinatra-url-shortener-in-ruby/</guid><pubDate>Sun, 28 Mar 2010 00:00:00 +0000</pubDate></item><item><title>Installing the Nokogiri ruby gem on Debian</title><link>https://www.devroom.io/2010/03/25/installing-the-nokogiri-ruby-gem-on-debian/</link><description>&lt;p>To install Nokogiri on a Debian system you need some system packages in place. This snippet will get you going quickly.
~
First, install the necessary debian packages if you don&amp;rsquo;t have them already:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">apt-get install build-essential libxml2-dev libxslt1-dev
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Then you can install nokogiri without any problem with &lt;code>gem install nokogiri&lt;/code>&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2010/03/25/installing-the-nokogiri-ruby-gem-on-debian/</guid><pubDate>Thu, 25 Mar 2010 00:00:00 +0000</pubDate></item><item><title>Ariejan.net now in valid HTML5</title><link>https://www.devroom.io/2010/03/24/ariejannet-now-in-valid-html5/</link><description>&lt;p>&lt;em>As most&lt;/em> developers already know: &lt;a href="http://dev.w3.org/html5/spec/Overview.html">HTML5&lt;/a> &lt;a href="http://www.readwriteweb.com/archives/5_exciting_things_in_html_5.php">is&lt;/a> &lt;a href="http://www.geektechnica.com/2009/06/5-amazing-html5-features-to-look-forward-to/">very&lt;/a> &lt;a href="http://www.youtube.com/html5">awesome&lt;/a>! So, with my move from WordPress to Toto I&amp;rsquo;ve made sure Ariejan.net is HTML5 compliant.&lt;/p>
&lt;p>~&lt;/p>
&lt;p>I&amp;rsquo;m not going to tell you how I did it all, there&amp;rsquo;s enough about writing HTML5 on the web already.&lt;/p>
&lt;p>Here&amp;rsquo;s the proof:&lt;/p>
&lt;p>&lt;img
src="http://ariejan.net/images/valid_html5.jpg"
alt="W3C HTML5 Validation of Ariejan.net"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;p>Or, if you don&amp;rsquo;t believe the screenshot, &lt;a href="http://validator.w3.org/check?uri=http%3A%2F%2Fariejan.net%2F&amp;charset=%28detect+automatically%29&amp;doctype=Inline&amp;group=0">validate it yourself&lt;/a>.&lt;/p>
&lt;p>&lt;em>Note: The warning is about using the expiremental HTML5 Conformance Checker&lt;/em>&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2010/03/24/ariejannet-now-in-valid-html5/</guid><pubDate>Wed, 24 Mar 2010 00:00:00 +0000</pubDate></item><item><title>How a little varnish changed my life</title><link>https://www.devroom.io/2010/03/24/how-a-little-varnish-changed-my-life/</link><description>&lt;p>Okay, it&amp;rsquo;s a bit of an exaggeration to say &lt;a href="http://varnish-cache.org/">varnish&lt;/a> changed my life, but it sure did change the speed of my site!&lt;/p>
&lt;p>I got from a rotten 6 requests per second with WordPress to a whopping 9500! If you&amp;rsquo;re on Linux and running Apache, installing varnish is a breeze! Especially if you&amp;rsquo;re hosting a well cacheable site like a blog.&lt;/p>
&lt;p>~&lt;/p>
&lt;p>I&amp;rsquo;m running on a Linux Debian virtual server (provided by &lt;a href="http://kabisa.nl">Kabisa&lt;/a>) with 1 VCPU core and 512MB of memory. Nothing fancy.&lt;/p>
&lt;p>Before I was running Apache 2 with PHP and WordPress. Doing the occasional test this gave me (uncached) a performance of about 6 request/second (using apache&amp;rsquo;s &lt;code>ab&lt;/code> with 10 concurrent connections). With some caching plugins I was able to crank that up to about 15-20 requests a second.&lt;/p>
&lt;p>After upgrading my blog to Toto, I got quite a boost to about 30 requests/second. But, my blog doesn&amp;rsquo;t contain any dynamic elements any more and Toto + Rack give you all the handles to implement caching (ETags, Cache-Control headers, etc.). FYI: I&amp;rsquo;m running Apache2 + Passenger to run Toto.&lt;/p>
&lt;p>Since I heard about Varnish a few times before I decided to give it a try and &lt;code>apt-get install varnish&lt;/code>&amp;lsquo;ed it on my Debian box (I&amp;rsquo;m running &lt;code>squeeze&lt;/code>, thank you).&lt;/p>
&lt;p>Now I have a few other sites running on my vps which I don&amp;rsquo;t want to cache just yet. The problem was how do I tell Varnish to only cache ariejan.net, and skip the rest.&lt;/p>
&lt;p>Here&amp;rsquo;s the entire configuration for Varnish to accomplish just that:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">backend default { .host = &amp;#34;127.0.0.1&amp;#34;; .port = &amp;#34;8080&amp;#34;; }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sub vcl_recv { if (req.http.host !~ &amp;#34;ariejan.net&amp;#34;) { return(pass); } }
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Yes, that is just two lines! What this does is forward everything you throw at varnish to the server at port 8080. The &lt;code>vcl_recv&lt;/code> makes sure that if the hostname does not include ariejan.net varnish passes the request forward - no caching.&lt;/p>
&lt;p>The second thing I had to do was configure Apache to listen on port 8080 instead of 80 in &lt;code>/etc/apache2/ports.conf&lt;/code>. Then also make sure to have all your virtual hosts (even those you don&amp;rsquo;t want cached) configured for port 8080 too in &lt;code>/etc/apache2/sites-available&lt;/code>&lt;/p>
&lt;p>Restart apache, restart varnish and you&amp;rsquo;re golden!&lt;/p>
&lt;p>When I first ran my &lt;code>ab&lt;/code> benchmark with 10 concurrent connections I got to about 150 requests per second. But when I really pushed it to a 1000 concurrent connections (&lt;code>ab&lt;/code> couldn&amp;rsquo;t handle more), I got to a whopping 9500 requests per seconds doing 60k requests! That is &lt;strong>epic&lt;/strong>! Oh, and my VPS didn&amp;rsquo;t even break a sweat - system load got up to 1.08 for second or so.&lt;/p>
&lt;p>For the record:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">$ ab -c &lt;span class="m">1000&lt;/span> -n &lt;span class="m">60000&lt;/span> http://ariejan.net/2010/03/22/shields-up-rrrack-alert/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Server Software: Apache/2.2.15
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Server Hostname: ariejan.net
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Server Port: &lt;span class="m">80&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Document Path: /2010/03/22/shields-up-rrrack-alert/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Document Length: &lt;span class="m">5117&lt;/span> bytes
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Concurrency Level: &lt;span class="m">1000&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Time taken &lt;span class="k">for&lt;/span> tests: 6.290 seconds
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Complete requests: &lt;span class="m">60000&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Failed requests: &lt;span class="m">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Write errors: &lt;span class="m">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Total transferred: &lt;span class="m">331434376&lt;/span> bytes
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">HTML transferred: &lt;span class="m">307460062&lt;/span> bytes
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Requests per second: 9539.34 &lt;span class="o">[&lt;/span>&lt;span class="c1">#/sec] (mean)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Time per request: 104.829 &lt;span class="o">[&lt;/span>ms&lt;span class="o">]&lt;/span> &lt;span class="o">(&lt;/span>mean&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Time per request: 0.105 &lt;span class="o">[&lt;/span>ms&lt;span class="o">]&lt;/span> &lt;span class="o">(&lt;/span>mean, across all concurrent requests&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Transfer rate: 51459.38 &lt;span class="o">[&lt;/span>Kbytes/sec&lt;span class="o">]&lt;/span> received
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Connection Times &lt;span class="o">(&lt;/span>ms&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> min mean&lt;span class="o">[&lt;/span>+/-sd&lt;span class="o">]&lt;/span> median max
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Connect: &lt;span class="m">1&lt;/span> &lt;span class="m">56&lt;/span> 364.9 &lt;span class="m">12&lt;/span> &lt;span class="m">6209&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Processing: &lt;span class="m">2&lt;/span> &lt;span class="m">22&lt;/span> 76.7 &lt;span class="m">16&lt;/span> &lt;span class="m">3073&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Waiting: &lt;span class="m">2&lt;/span> &lt;span class="m">19&lt;/span> 76.6 &lt;span class="m">13&lt;/span> &lt;span class="m">3070&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Total: &lt;span class="m">3&lt;/span> &lt;span class="m">78&lt;/span> 374.6 &lt;span class="m">29&lt;/span> &lt;span class="m">6223&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Interested? Check out &lt;a href="http://varnish-cache.org/">Varnish&lt;/a> now or ask us at &lt;a href="http://kabisa.nl">Kabisa&lt;/a> to help you out!&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2010/03/24/how-a-little-varnish-changed-my-life/</guid><pubDate>Wed, 24 Mar 2010 00:00:00 +0000</pubDate></item><item><title>Shields up! Rrrack alert!</title><link>https://www.devroom.io/2010/03/22/shields-up-rrrack-alert/</link><description>&lt;p>For a very long time I have been dissatisfied with WordPress. Although it&amp;rsquo;s the best PHP blogging engine out there, it sucked.&lt;/p>
&lt;p>Firstly, it&amp;rsquo;s slow. I got the front end a bit snappier by caching the living crap out of it, but that did not help things for me in the backend. So, what to do?
~&lt;/p>
&lt;p>I&amp;rsquo;ve been looking towards ruby-based blogs a lot. Typo and Mephisto were two of the first ones out there, but they just didn&amp;rsquo;t feel right. I did try Mephisto for a while. However, the project didn&amp;rsquo;t appear to be under active development and I had a really hard time migrating my WordPress content.&lt;/p>
&lt;p>Then came the first static site generators into my view. Jekyll being the obvious choice because of its affiliation with Github. But again, I had a hard time migrating my content (which consists of 192 blog posts, excluding this one).&lt;/p>
&lt;p>Then along came Toto. Toto is epic! Without much effort I dumped my posts into files and I had Toto up and running! It worked! All my content was here!&lt;/p>
&lt;p>There are two things that make Toto so great. Firstly all content is stored in files, so they are under version control and no database is needed. This also means that pages are served very quickly! Secondly it&amp;rsquo;s rack-based, which means I can deploy it easily to my VPS at Kabisa under passenger (and Unicorn when I migrate to that).&lt;/p>
&lt;p>Well, so here I am! Still fiddling a bit with Toto (especially the HTML) as you can see. My goal is to make everything HTML5-compliant, but that&amp;rsquo;ll have to wait a few days.&lt;/p>
&lt;p>Thinking about moving away from WordPress yourself? Feel free to comment or ask questions!&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2010/03/22/shields-up-rrrack-alert/</guid><pubDate>Mon, 22 Mar 2010 00:00:00 +0000</pubDate></item><item><title>How to order your Kindle from the Netherlands</title><link>https://www.devroom.io/2010/02/02/how-to-order-your-kindle-from-the-netherlands/</link><description>&lt;p>I get a lot of questions about how I bought my Kindle and what it cost to get it shipped to the Netherlands. So, for all those Dutchmen (and Dutchwomen) who are considering a Kindle, here&amp;rsquo;s a short &lt;em>how-to&lt;/em> in Dutch:&lt;/p>
&lt;p>Eindelijk heb je dan besloten dat je een eReader wilt en je keuze is gevallen op de Amazon Kindle. Goede keuze! Maar, hoe bestel je nu zo&amp;rsquo;n Kindle? Wat heb je nodig en waar moet je op letten.&lt;/p>
&lt;p>&lt;strong>Stap 1 - Shoppen bij Amazon&lt;/strong>&lt;/p>
&lt;p>De eerste stap is dat je &lt;a href="http://www.amazon.com/gp/product/B0015T963C?ie=UTF8&amp;tag=ariejannet-20&amp;linkCode=as2&amp;camp=1789&amp;creative=9325&amp;creativeASIN=B0015T963C">de Kindle&lt;/a> (of &lt;a href="https://images-na.ssl-images-amazon.com/images/I/51V-u8G3laL._SL160_.jpg">Kindle DX&lt;/a>) in je winkelwagentje laadt, eventueel samen met accessoires zoals &lt;a href="https://images-na.ssl-images-amazon.com/images/I/41VFwdQ3G0L._SL160_.jpg">deze cover&lt;/a>.&lt;/p>
&lt;p>De Kindle kost $259 (de DX gaat voor $489 over de virtuele toonbank). Helaas zijn er nog bijkomende kosten:&lt;/p>
&lt;ul>
&lt;li>Verzenkosten – Amazon bracht bij mijn bestelling zo'n $21 dollar aan verzendkosten in rekening. Voor dit bedrag wordt je Kindle via UPS Express bezorgt.&lt;/li>
&lt;li>Invoerrechten – Omdat je de Kindle van uit de V.S. importeert moet je hier invoerrechten over betalen. In het geval van de Kindle komt dit op een bedrag van $53.&lt;/li>
&lt;/ul>
&lt;p>De totale kosten van de bestelling bedragen ±$333. Afhankelijk van de wisselkoers komt dit uit op een bedrag van &lt;strong>€240,-&lt;/strong>.&lt;/p>
&lt;p>&lt;strong>Stap 2 - Maken van een Amazon account&lt;/strong>&lt;/p>
&lt;p>Als je de bestellig wilt gaan plaatsen zal Amazon je vragen in te loggen of een account te maken. Een account maken is snel en eenvoudig.&lt;/p>
&lt;p>&lt;strong>Stap 3 - Afleveradres&lt;/strong>&lt;/p>
&lt;p>Amazon zal je vragen waar het apparaat afgelevert moet worden. Zorg er voor dat je voor &amp;lsquo;Netherlands&amp;rsquo; kiest bij land en vul ter volledigheid bij &amp;lsquo;State&amp;rsquo; je provincie in.&lt;/p>
&lt;p>&lt;strong>Stap 4 - Betaling&lt;/strong>&lt;/p>
&lt;p>Om bij Amazon te betalen heb je een VISA of MasterCard creditcard nodig. Amazon biedt geen iDeal of bankoverschrijving aan, het is immers een Amerikaans bedrijf.&lt;/p>
&lt;p>Heb je zelf geen creditcard, dan kun je misschien een deal maken met een vriend of kennis die wel een creditcard heeft. Amazon biedt je de mogelijkheid om de bestelling in dollars of euros te betalen. Het voordeel van het direct betalen in euros is dat je exact weet hoeveel je crediccard provider in rekening brengt.&lt;/p>
&lt;p>&lt;em>Let op als je de creditcard van een vriend of kennis gebruikt! Je moet dan een apart factuuradres (billing address) opgeven dat klopt met de adresgegevens van de creditcard. Het afleveradres kan gewoon die van jezelf zijn.&lt;/em>&lt;/p>
&lt;p>&lt;strong>Stap 5 - Afronden van de bestelling&lt;/strong>&lt;/p>
&lt;p>Als je de bestelling plaats krijg je hiervan per email bevestiging van Amazon. Nu is het wachten tot UPS je Kindle komt afleveren.&lt;/p>
&lt;p>&lt;strong>Stap 6 - Levering&lt;/strong>&lt;/p>
&lt;p>Zodra Amazon je Kindle verstuurt heeft krijg je hiervan een email. In deze mail staat de verwachtte lever datum (meestal ± 5 werkdagen) en een track-and-trace code van UPS. Met deze code kun je op &lt;a href="http://ups.com">ups.com&lt;/a> zien waar je bestelling zich bevindt.&lt;/p>
&lt;p>UPS bezorgt niet op zaterdag. Als je niet thuis bent zullen ze het pakketje bij de buren proberen te bezorgen, je krijgt hiervan een briefje in de brievenbus.&lt;/p>
&lt;p>&lt;strong>Stap 7 - Uitpakken en gebruiken&lt;/strong>&lt;/p>
&lt;p>Als je je pakketje hebt uitgepakt kun je direct gaan lezen! Als je een boek koopt (via de Kindle of via je computer) dan wordt dat boek direct via de draadloze 3G verbinding naar je Kindle gestuurd!&lt;/p>
&lt;p>Veel lees plezier!&lt;/p>
&lt;p>&lt;strong>Bonus tip!&lt;/strong>&lt;/p>
&lt;p>Via project Gutenberg zijn duizenden boeken gratis te download! Op alle boeken die via deze weg beschikbaar zijn rust geen copyright meer, en zijn dus helemaal legaal. Zie hier &lt;a href="http://www.gutenberg.org/browse/scores/top">de top 100 meest gedownloadde boeken&lt;/a>.&lt;/p>
&lt;p>Download altijd het &lt;em>Mobipocket&lt;/em> formaat. Zet deze file in de &lt;em>documents&lt;/em> directory op je Kindle. (Dit doe je door de Kindle met de USB kabel aan je computer aan te sluiten).&lt;/p>
&lt;p>Mocht je via deze &amp;lsquo;guide&amp;rsquo; een Kindle kopen, dan hoor ik graag (via de comments) hoe hij bevalt!&lt;/p>
&lt;table border="0" cellspacing="6" style="margin: 10px auto; width: 400px">
&lt;tr>
&lt;td valign="top" style="text-align: center">
&lt;a href="http://www.amazon.com/gp/product/B0015T963C?ie=UTF8&amp;tag=ariejannet-20&amp;linkCode=as2&amp;camp=1789&amp;creative=9325&amp;creativeASIN=B0015T963C">&lt;img border="0" src="https://images-na.ssl-images-amazon.com/images/I/41t7SWZ2vpL._SL160_.jpg">&lt;/a>&lt;img src="http://www.assoc-amazon.com/e/ir?t=ariejannet-20&amp;l=as2&amp;o=1&amp;a=B0015T963C" width="1" height="1" border="0" alt="" style="border:none !important; margin:0px !important;" />
&lt;p>
&lt;a href="http://www.amazon.com/gp/product/B0015T963C?ie=UTF8&amp;tag=ariejannet-20&amp;linkCode=as2&amp;camp=1789&amp;creative=9325&amp;creativeASIN=B0015T963C">Amazon Kindle - $259&lt;/a>
&lt;/p>
&lt;/td>
&lt;td valign="top" style="text-align: center">
&lt;a href="http://www.amazon.com/gp/product/B0015TG12Q?ie=UTF8&amp;tag=ariejannet-20&amp;linkCode=as2&amp;camp=1789&amp;creative=9325&amp;creativeASIN=B0015TG12Q">&lt;img border="0" src="https://images-na.ssl-images-amazon.com/images/I/51V-u8G3laL._SL160_.jpg">&lt;/a>&lt;img src="http://www.assoc-amazon.com/e/ir?t=ariejannet-20&amp;l=as2&amp;o=1&amp;a=B0015TG12Q" width="1" height="1" border="0" alt="" style="border:none !important; margin:0px !important;" />
&lt;p>
&lt;a href="http://www.amazon.com/gp/product/B0015TG12Q?ie=UTF8&amp;tag=ariejannet-20&amp;linkCode=as2&amp;camp=1789&amp;creative=9325&amp;creativeASIN=B0015TG12Q">Amazon Kindle DX - $489&lt;/a>
&lt;/p>
&lt;/td>
&lt;/tr>
&lt;/table></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2010/02/02/how-to-order-your-kindle-from-the-netherlands/</guid><pubDate>Tue, 02 Feb 2010 00:00:00 +0000</pubDate></item><item><title>Sign the petition: Stop EU Software Patents</title><link>https://www.devroom.io/2010/01/19/sign-the-petition-stop-eu-software-patents/</link><description>&lt;p>From &lt;a href="http://stopsoftwarepatents.eu">stopsoftwarepatents.eu&lt;/a>:&lt;/p>
&lt;p>Our petition aims to unify the voices of concerned Europeans, associations and companies, and calls on our politicians in Europe to stop patents on software with legislative clarifications.&lt;/p>
&lt;p>The patent system is misused to restrain competition for the economical benefit of a few but fails to promote innovation. A software market environment is better off with no patents on software at all. Healthy competition forces market players to innovate.&lt;/p>
&lt;p>European court decisions still accept in many cases the validity of the software patents granted by national patent offices and the European Patent Office (EPO) that is beyond democratic control. They not only continue to grant them, but also to lobby in favor of them. Despite the current deep crisis of the patent system, they are unable to reform and put at risk too many European businesses with their soft granting policy.&lt;/p>
&lt;p>On 2005 the Commission appeared to be more supportive to the interests of major international conglomerates than of small and medium sized enterprises from Europe - who are a major driving force behind European innovation. The European Parliament rejected at the end the software patent directive, but has no rights for legislative initiatives.&lt;/p>
&lt;p>&lt;strong>We urge our legislators&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>to pass national legal clarifications to substantive patent law to rule out any software patent;&lt;/li>
&lt;li>to invalidate all granted claims on patents that can be infringed by software run on programmable apparatus;&lt;/li>
&lt;li>to also strive to propagate these rules to the European level, including the European Patent Convention.&lt;/li>
&lt;/ul>
&lt;p>&lt;a href="http://petition.stopsoftwarepatents.eu/021004176917/">Sign the petition now!&lt;/a>&lt;/p>
&lt;p>&lt;a href="http://petition.stopsoftwarepatents.eu/021004176917/">&lt;img src="http://petition.stopsoftwarepatents.eu/banner/021004176917/ssp-362-60.gif" alt="stopsoftwarepatents.eu petition banner" width="362" height="60" style="border: none;" align="center" />&lt;/a>&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2010/01/19/sign-the-petition-stop-eu-software-patents/</guid><pubDate>Tue, 19 Jan 2010 00:00:00 +0000</pubDate></item><item><title>The epic e-reading experience: Amazone Kindle</title><link>https://www.devroom.io/2010/01/17/the-epic-e-reading-experience-amazone-kindle/</link><description>&lt;p>For some time I have been eyeballing Sony&amp;rsquo;s e-reader in the local bookstore. I tried it a few times, but I didn&amp;rsquo;t like it - actually I had serious doubts about e-books in general because of the experience. Sony&amp;rsquo;s e-reader was not really easy to use with only one next-page button in a not-so-easy to access place. It also had a slow e-ink screen. It took a second to a second-and-a-half to show the next page. I didn&amp;rsquo;t like it and had serious doubts about buying any e-reader at all.&lt;/p>
&lt;p>But, reading about all the &amp;ldquo;epic win&amp;rdquo;-stories of the Kindle by &lt;a href="http://twitter.com/johnnybusca">@johnnybusca&lt;/a> I just had to give it a try - I bought one.&lt;/p>
&lt;p>Since receiving the Kindle last monday, I haven&amp;rsquo;t felt any regret! The Kindle easy beautiful, fast with screen updates and of course integrates neatly with Amazon&amp;rsquo;s Kindle Store. The whole product is designed to be easy to use - and it is.&lt;/p>
&lt;iframe src="http://rcm.amazon.com/e/cm?lt1=_top&amp;bc1=FFFFFF&amp;IS2=1&amp;nou=1&amp;bg1=FFFFFF&amp;fc1=000000&amp;lc1=0000FF&amp;t=ariejannet-20&amp;o=1&amp;p=8&amp;l=as1&amp;m=amazon&amp;f=ifr&amp;asins=B0015T963C" style="width:120px;height:240px;" scrolling="no" marginwidth="0" marginheight="0" frameborder="0" align="right">&lt;/iframe>
&lt;p>I had already purchased &lt;a href="http://www.amazon.com/gp/product/1597970085?ie=UTF8&amp;tag=ariejannet-20&amp;linkCode=as2&amp;camp=1789&amp;creative=390957&amp;creativeASIN=1597970085">The One That Got Away&lt;/a>&lt;img src="http://www.assoc-amazon.com/e/ir?t=ariejannet-20&amp;l=as2&amp;o=1&amp;a=1597970085" width="1" height="1" border="0" alt="" style="border:none !important; margin:0px !important;" /> for Kindle for iPhone. Syncing it to my Kindle was done in about 30 seconds and I was ready to start reading!&lt;/p>
&lt;p>For me the Kindle has three big advantages:&lt;/p>
&lt;ul>
&lt;li>The Kindle is easier to hold with one hand that a paperback. With a "Next page" button on either side of the device, it doesn't matter if you hold it with your right or left hand!&lt;/li>
&lt;li>I don't need to lug around paperbacks. I can always have my stack of novels with me. Also, Kindle takes up a lot less space than all my paperbacks do now!&lt;/li>
&lt;li>The built-in dictionary is very easy to access while reading. This gives me, as a non-native English speaker, the great benefit of quickly looking up words I don't yet know and actually learn them while reading.&lt;/li>
&lt;/ul>
&lt;p>There is one little problem with Kindle, and that is the battery. You can use Kindle a long time on battery, even with wireless enabled, but eventually you&amp;rsquo;ll have to recharge. It&amp;rsquo;s not a huge task to hook up the Kindle to my Mac, but it might be a little troublesome on holidays where no computer is available. The US power connector is utterly useless in Europe.&lt;/p>
&lt;p>To make a long story short. If you&amp;rsquo;re considering to buy an e-ready, go for Amazon&amp;rsquo;s Kindle. Don&amp;rsquo;t buy the Sony or any other product that might be cheaper, go for quality and ease of use! Now, enough with the blog writing, back to reading on my Kindle!&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2010/01/17/the-epic-e-reading-experience-amazone-kindle/</guid><pubDate>Sun, 17 Jan 2010 00:00:00 +0000</pubDate></item><item><title>Epic TextMate Theme</title><link>https://www.devroom.io/2009/11/24/epic-textmate-theme/</link><description>&lt;p>Okay, I&amp;rsquo;ve wanted to make a custom TextMate theme since I first installed the thing on my MacBook in 2006. Today I present you with &amp;lsquo;Epic&amp;rsquo;.&lt;/p>
&lt;p>&lt;strong>Installation&lt;/strong>&lt;/p>
&lt;p>Grab the theme here: &lt;a href="http://ariejan.net/wp-content/uploads/2009/11/EpicBlue.tmTheme.zip">EpicBlue.tmTheme.zip (1.5k)&lt;/a>. Unzip it and open it with TextMate. That&amp;rsquo;s all. Enjoy!&lt;/p>
&lt;p>&lt;strong>Samples&lt;/strong>&lt;/p>
&lt;img src="http://ariejan.net/wp-content/uploads/2009/11/epic-theme.jpg" title="Epic Ruby" width="865" height="492" class="size-full wp-image-597" />
&lt;img src="http://ariejan.net/wp-content/uploads/2009/11/epic-haml.jpg" title="Epic Haml" width="528" height="241" class="size-full wp-image-599" />
&lt;img src="http://ariejan.net/wp-content/uploads/2009/11/epic-rspec.jpg" title="Epic Rspec" width="497" height="232" class="size-full wp-image-601" /></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2009/11/24/epic-textmate-theme/</guid><pubDate>Tue, 24 Nov 2009 00:00:00 +0000</pubDate></item><item><title>How to create and apply a patch with Git</title><link>https://www.devroom.io/2009/10/26/how-to-create-and-apply-a-patch-with-git/</link><description>&lt;p>Creating a patch file with git is quite easy to do, you just need to see how it&amp;rsquo;s done a few times.&lt;/p>
&lt;p>This article will show you how to create a patch from the last few commits in your repository. Next, I&amp;rsquo;ll also show you how you can correctly apply this patch to another repository.&lt;/p>
&lt;h3 id="before-you-start">Before you start&lt;/h3>
&lt;p>To make creating patches easier, there are some common git practices you should follow. It&amp;rsquo;s not necessary, but it will make your life easier.&lt;/p>
&lt;p>If you fix a bug or create a new feature – do it in a separate branch!&lt;/p>
&lt;p>Let&amp;rsquo;s say you want to create a patch for my &lt;a href="http://github.com/ariejan/imdb">imdb&lt;/a> gem. You should clone my repository and create a new branch for the fix you have in mind. In this sample we&amp;rsquo;ll do an imaginary fix for empty posters.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">git clone git://github.com/ariejan/imdb.git
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> imdb
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">git checkout -b fix_empty_poster
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now, in the new &lt;code>fix_empty_poster&lt;/code> branch you can hack whatever you need to fix. Write tests, update code etc. etc.&lt;/p>
&lt;p>When you&amp;rsquo;re satisfied with all you changes, it&amp;rsquo;s time to create your patch. FYI: I&amp;rsquo;m assuming you made a few commits in the &lt;code>fix_empty_poster&lt;/code> branch and did &lt;em>not&lt;/em> yet merge it back in to the &lt;code>master&lt;/code> branch.&lt;/p>
&lt;h3 id="creating-the-patch">Creating the patch&lt;/h3>
&lt;p>Okay, I&amp;rsquo;ve made some commits, here&amp;rsquo;s the &lt;code>git log&lt;/code> for the &lt;code>fix_empty_poster&lt;/code> branch:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">git log --pretty&lt;span class="o">=&lt;/span>oneline -3
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">* ce30d1f - &lt;span class="o">(&lt;/span>fix_empty_poster&lt;span class="o">)&lt;/span> Added poster URL as part of cli output &lt;span class="o">(&lt;/span>&lt;span class="m">7&lt;/span> minutes ago&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">* 5998b80 - Added specs to &lt;span class="nb">test&lt;/span> empty poster URL behaviour &lt;span class="o">(&lt;/span>&lt;span class="m">12&lt;/span> minutes ago&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">* aecb8cb - &lt;span class="o">(&lt;/span>REL-0.5.0, origin/master, origin/HEAD, master&lt;span class="o">)&lt;/span> Prepare release 0.5.0 &lt;span class="o">(&lt;/span>&lt;span class="m">4&lt;/span> months ago&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>In GitX it would look like this:&lt;/p>
&lt;p>&lt;img
src="https://www.devroom.io/img/imdb_fix_empty_poster_01.jpg"
alt="gitx view"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;p>Okay, now it&amp;rsquo;s time to go and make a patch! All we really want are the two latest commits, stuff them in a file and send them to someone to apply them. But, since we created a separate branch, we don&amp;rsquo;t have to worry about commits at all!&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">git format-patch master --stdout &amp;gt; fix_empty_poster.patch
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This will create a new file &lt;code>fix_empty_poster.patch&lt;/code> with all changes from the current (&lt;code>fix_empty_poster&lt;/code>) against &lt;code>master&lt;/code>. Normally, git would create a separate patch file for each commit, but that&amp;rsquo;s not what we want. All we need is a single patch file.&lt;/p>
&lt;p>Now, you have a patch for the fix you wrote. Send it to the maintainer of the project &amp;hellip;&lt;/p>
&lt;h3 id="applying-the-patch">Applying the patch&lt;/h3>
&lt;p>&amp;hellip; who will apply the patch you just sent! But, before you do that, there are some other steps you should take.&lt;/p>
&lt;p>First, take a look at what changes are in the patch. You can do this easily with &lt;code>git apply&lt;/code>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">git apply --stat fix_empty_poster.patch
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Note that this command does not apply the patch, but only shows you the stats about what it&amp;rsquo;ll do. After peeking into the patch file with your favorite editor, you can see what the actual changes are.&lt;/p>
&lt;p>Next, you&amp;rsquo;re interested in how troublesome the patch is going to be. Git allows you to test the patch before you actually apply it.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">git apply --check fix_empty_poster.patch
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>If you don&amp;rsquo;t get any errors, the patch can be applied cleanly. Otherwise you may see what trouble you&amp;rsquo;ll run into. To apply the patch, I&amp;rsquo;ll use &lt;code>git am&lt;/code> instead of &lt;code>git apply&lt;/code>. The reason for this is that &lt;code>git am&lt;/code> allows you to &lt;em>sign off&lt;/em> an applied patch. This may be useful for later reference.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">git am --signoff &amp;lt; fix_empty_poster.patch
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Applying: Added specs to &lt;span class="nb">test&lt;/span> empty poster URL behaviour
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Applying: Added poster URL as part of cli output
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Okay, patches were applied cleanly and your master branch has been updated. Of course, run your tests again to make sure nothing got borked.&lt;/p>
&lt;p>In you git log, you&amp;rsquo;ll find that the commit messages contain a &amp;ldquo;Signed-off-by&amp;rdquo; tag. This tag will be read by Github and others to provide useful info about how the commit ended up in the code.&lt;/p>
&lt;p>&lt;img
src="https://www.devroom.io/img/imdb_signed_off.jpg"
alt="Signed off commit"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;p>That&amp;rsquo;s all folks!&lt;/p>
&lt;p>&lt;em>Are there any other git topics you&amp;rsquo;d like covered here? Please let me know!&lt;/em>&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2009/10/26/how-to-create-and-apply-a-patch-with-git/</guid><pubDate>Mon, 26 Oct 2009 00:00:00 +0000</pubDate></item><item><title>They are just tools, people!</title><link>https://www.devroom.io/2009/10/25/they-are-just-tools-people/</link><description>&lt;p>Codaset is openly asking its users to comment on what pricing strategy they would like most. I&amp;rsquo;ve spotted this before, but again, there are two types of users. Those who see a great service and know that it will make their job easier, so they are willing to pay for it. There are also those who want a trillion repositories, unlimited disk space and what not for $1 a month (or less). This post is for the latter group of people.&lt;/p>
&lt;p>Some developers claim they &lt;em>need&lt;/em> to use all of 37Signals&amp;rsquo; apps, have the biggest Github plan available and buy that new shiny 17&amp;quot; MacBook Pro (or that 27&amp;quot; iMac, I know). With all those tools and hardware available, how can your brilliant plan not succeed? All the successful people you&amp;rsquo;ve heard of use them. So, with all that setup, you&amp;rsquo;re golden! Right? Then they check the price tag. It&amp;rsquo;s huge!&lt;/p>
&lt;p>In my opinion, the problem started with shared hosting. Shared hosting was &lt;em>the&lt;/em> business to be in about 5-10 years back. Everyone wanted it and everybody wanted it cheap. So, what you got (and still have) are big, over-selling hosting companies. You get 5 Terabyte of storage, unlimited bandwidth and you can host unlimited websites! The price? Only $1 a month! A bargain!&lt;/p>
&lt;p>Did you really think that there is a big server with 5 Terabyte of space is reserved for you? Or that you really have unlimited bandwidth? I can&amp;rsquo;t remember how many people I&amp;rsquo;ve heard bitching about them hosting MP3&amp;rsquo;s and then having their accounts shutdown for excessive bandwidth usage.&lt;/p>
&lt;p>Those companies were over-selling like crazy. They basically sold every megabyte of disk space they had available 50 times or more! Of course, they attracted vast numbers of customers and business was booming. With the number of customers rising, the quality of services went down the drain. Too often have I heard people complaining about their over selling shared hosting provider. Shame.&lt;/p>
&lt;p>But why do people choose for the $1 over selling package when for $7 they could have had better service and quality, albeit for a higher price? Simply because we like the feeling that we can grow big. Every one wants to be the new Google and store all his home-made (or other wise gathered) music online.&lt;/p>
&lt;p>Luckily, most &amp;ldquo;web 2.0&amp;rdquo; service providers (which 37Signals and Github really are) have come to another idea. Sell less features for a reasonable, maintainable price. Yes, Basecamp has less features than the competition, but at least the price is fair and service is good.&lt;/p>
&lt;p>So, here we are in 2009 and every small company can get good project management, code hosting, issue trackers, invoicing tools and what not for quite reasonable prices. Truly awesome!&lt;/p>
&lt;p>But, here come the people that want to &amp;ldquo;grow big&amp;rdquo; and become the next Google. These are mostly young developers who don&amp;rsquo;t have a lot of business experience, and are still in school most of the time. They read and hear the great success stories and how those people used Basecamp and Github. Those must be magic tools!&lt;/p>
&lt;p>And look, every one is using a Mac and TextMate! I&amp;rsquo;ll need those too!&lt;/p>
&lt;p>No offense to Github, Basecamp, TextMate or Apple, but that is not how it works. Businesses are not created by using a certain set of tools. The reason a successful business turns to Basecamp is because they have a problem with their current project management. When you start freelancing and you only have one or two client, you probably don&amp;rsquo;t need the biggest Basecamp plan to get along. Oh, you are developing the next iPhone killer-app? Then you probably don&amp;rsquo;t need Basecamp at all.&lt;/p>
&lt;p>The same goes for Github. When you&amp;rsquo;re working on a project that you believe in, it&amp;rsquo;s worth the $7 for the basic plan.&lt;/p>
&lt;p>At Codaset I read some user comments and the story is the same. Some serious freelancers or companies explain what&amp;rsquo;s valuable to them and what they&amp;rsquo;d like to pay for it. All reasonable, because they know the cost of doing business.&lt;/p>
&lt;p>Then, there are also some people who want it all for free. Why, because they really need Codaset and all its features.&lt;/p>
&lt;p>People, sorry to burst your bubble, but they are just tools. It&amp;rsquo;s like buying a 112-piece screw driver kit when all you want to do is switch the occasional light bulb.&lt;/p>
&lt;p>Here&amp;rsquo;s my advice. Start your project. Just on your old Mac (or Dell) and work on it for a few weeks. Use the tools available to you to do your time tracking and invoicing. Push your git repo that old Pentium 4 Dell you have gathering dust in the basement.&lt;/p>
&lt;p>Then, when you find that communicating with your clients is getting difficult by phone and email only, then give Basecamp a try. When you feel that your code is getting so valuable that you don&amp;rsquo;t want to risk putting it only on an old machine in the basement, store it at Github.&lt;/p>
&lt;p>All those web 2.0 apps are really just tools that you can use. Tools are there to solve a problem. If you don&amp;rsquo;t have a problem, you don&amp;rsquo;t need the tools.&lt;/p>
&lt;p>Only use the tools that you really need, and when you do, they&amp;rsquo;ll be more than worth the price you pay for them.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2009/10/25/they-are-just-tools-people/</guid><pubDate>Sun, 25 Oct 2009 00:00:00 +0000</pubDate></item><item><title>Git problem: error: unable to create temporary sha1 filename</title><link>https://www.devroom.io/2009/10/15/git-problem-error-unable-to-create-temporary-sha1-filename/</link><description>&lt;p>I got &lt;code>git problem: error: unable to create temporary sha1 filename&lt;/code> when pushing to a remote repository. The fix is rather easy.&lt;/p>
&lt;p>On both your local and remote repositories perform the following magic:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">git fsck
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">git prune
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">git repack
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">git fsck
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The last fsck should not report any problems.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2009/10/15/git-problem-error-unable-to-create-temporary-sha1-filename/</guid><pubDate>Thu, 15 Oct 2009 00:00:00 +0000</pubDate></item><item><title>Epic vs. Awesome</title><link>https://www.devroom.io/2009/10/13/epic-vs-awesome/</link><description>&lt;p>There&amp;rsquo;s bit of a discussion between me and &lt;a href="http://twitter.com/ludooo">@ludooo&lt;/a> about which word has the most significance when measuring the greatness of something.&lt;/p>
&lt;p>I say epic is bigger than awesome. &lt;a href="http://twitter.com/ludooo">@ludooo&lt;/a> says awesome is bigger than epic (he&amp;rsquo;s not right, of course).&lt;/p>
&lt;p>Please help us decide!&lt;/p>
&lt;script type="text/javascript" charset="utf-8" src="http://static.polldaddy.com/p/2112882.js">&lt;/script>&lt;noscript>
&lt;p>&lt;a href="http://answers.polldaddy.com/poll/2112882/">Which is bigger? Awesome or Epic?&lt;/a>&lt;span style="font-size:9px;">(&lt;a href="http://answers.polldaddy.com">polling&lt;/a>)&lt;/span>
&lt;/noscript>&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2009/10/13/epic-vs-awesome/</guid><pubDate>Tue, 13 Oct 2009 00:00:00 +0000</pubDate></item><item><title>Valerii: 32-base string encoder and decoder</title><link>https://www.devroom.io/2009/10/13/valerii-32-base-string-encoder-and-decoder/</link><description>&lt;p>You have probably seen URL shorteners that use short, seemingly random strings to identify sites. These strings are not random, they are encoded integer values. My valerii gem allows you to easily and quickly encode and decode integer values. Let me show you.&lt;/p>
&lt;p>To understand the shortening of integer values to strings you first need to know how integers work.&lt;/p>
&lt;p>We use a base-10 system. This means that for any number, every digit can hold 10 different values: 0 through 9. If you want to express more than 10 values, you need to add a digit: 26 means (6 * 1) + (2 * 10).&lt;/p>
&lt;p>You may be familiar with hexadecimal numbers. Hexadecimal has a base of 16, meaning every digit can hold 16 distinct values. 0 through 9, a through f.&lt;/p>
&lt;h2 id="converting-an-integer-to-a-32-base-string">Converting an integer to a 32-base string&lt;/h2>
&lt;p>The key to shortening a number to a string is that you need to store as many different values in a single digit. Because we want the digits to be part of an URL, we can only use valid URL characters. Characters like &amp;lsquo;/&amp;rsquo; and &amp;lsquo;?&amp;rsquo; are a no-go in this situation.&lt;/p>
&lt;p>Another reason for sticking with 32 (and not 52 (a-z, A-Z, 0-9)) is that I&amp;rsquo;m going to use bit encoding. But, first we need to define an alphabet to use to represent the 32 different values:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="no">ENCODE_CHARS&lt;/span> &lt;span class="o">=&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="sx">%w( B C D F G H J K
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sx"> M N P Q R S T V
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sx"> W Z b c d f h j
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sx"> k m n p r x t v )&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>For a value of 3 we&amp;rsquo;d use &amp;lsquo;F&amp;rsquo;. Easy, right?&lt;/p>
&lt;p>Let&amp;rsquo;s say we want to encode the number 123. What valerii does first is convert 123 to a binary string.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="mi">123&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">to_s&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># =&amp;gt; &amp;#34;1111011&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Cool, now split up this string in blocks of 5 bits. 5 bits can contain 32 different values. We need to start &amp;lsquo;chopping&amp;rsquo; from the right to the left, so first we reverse the binary string and split it up.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="s2">&amp;#34;1111011&amp;#34;&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">reverse&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">scan&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sr">/.{1,5}/&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># =&amp;gt; [&amp;#34;11011&amp;#34;, &amp;#34;11&amp;#34;]&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now we convert the binary strings to 10-base numbers and map those to the characters defined in &lt;code>ENCODE_CHARS&lt;/code>. The resulting characters are reversed again and joined to one string:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="mi">123&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">to_s&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">reverse&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">scan&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sr">/.{1,5}/&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">map&lt;/span> &lt;span class="k">do&lt;/span> &lt;span class="o">|&lt;/span>&lt;span class="n">bits&lt;/span>&lt;span class="o">|&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="no">ENCODE_CHARS&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="n">bits&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">reverse&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">to_i&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">reverse&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">join&lt;/span> &lt;span class="c1"># =&amp;gt; &amp;#34;Fp&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="converting-a-32-base-string-to-an-integer">Converting a 32-base string to an integer&lt;/h2>
&lt;p>Converting a 32-base string back to its original integer value is quite easy now. The only trick is to create another hash that maps each character to its integer value:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="no">DECODE_MAP&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">ENCODE_CHARS&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">to_enum&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="ss">:each_with_index&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">inject&lt;/span>&lt;span class="p">({})&lt;/span> &lt;span class="k">do&lt;/span> &lt;span class="o">|&lt;/span>&lt;span class="n">h&lt;/span>&lt;span class="p">,(&lt;/span>&lt;span class="n">c&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">|&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">h&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="n">c&lt;/span>&lt;span class="o">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">i&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">h&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This looks scary, but it&amp;rsquo;s actually a common way to reverse key/values in a hash.&lt;/p>
&lt;p>Next is taking each character from the string and pushing the 5 bits we read in a variable. This variable is the original integer value.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="s2">&amp;#34;Fp&amp;#34;&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">split&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sr">//&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">map&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="o">|&lt;/span>&lt;span class="n">char&lt;/span>&lt;span class="o">|&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="no">DECODE_MAP&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="n">char&lt;/span>&lt;span class="o">]&lt;/span> &lt;span class="ow">or&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="kp">nil&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">inject&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="o">|&lt;/span>&lt;span class="n">result&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">val&lt;/span>&lt;span class="o">|&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">result&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="mi">5&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">val&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="c1"># =&amp;gt; 123&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="notes">Notes&lt;/h2>
&lt;p>Once you have established your way of encoding/decoding you should not change the alphabet you&amp;rsquo;re using, since it redefines the meaning and value of the encoded strings.&lt;/p>
&lt;p>There are a lot of ways to store an integer value into a string. Another common methods is to use a 52 characters alphabet (a-z, A-Z, 0-9). You can&amp;rsquo;t use the bit encoding here, but there are quite a few algorithms out there that let you do that.&lt;/p>
&lt;p>&lt;a href="http://gemcutter.org/gems/valerii">More info on how to get the valerii gem can be found at Gemcutter.&lt;/a>&lt;/p>
&lt;p>&lt;em>&lt;a href="http://ariejan.net/gems">Also checkout my other gems&lt;/a>&lt;/em>&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2009/10/13/valerii-32-base-string-encoder-and-decoder/</guid><pubDate>Tue, 13 Oct 2009 00:00:00 +0000</pubDate></item><item><title>Codaset.com: Github, but better</title><link>https://www.devroom.io/2009/09/08/codaset-com-github-but-better/</link><description>&lt;p>Today I had the very pleasure of giving &lt;a href="http://codaset.com">Codaset.com&lt;/a> a try! Codaset is being developed by Joel Moss in Ruby on Rails and could be a real Github killer!&lt;/p>
&lt;p>When I asked Joel what Codaset was and how he come to his idea he answered this:&lt;/p>
&lt;blockquote>Codaset [...] was born out of my experiences with several tools and services.
&lt;ul>
&lt;li>I've used Trac for years, and admire its powerful issue management and milestone features.&lt;/li>
&lt;li>I've used Github from the start, and love how social it makes open source software (as it should be).&lt;/li>
&lt;li>I have fully embraced Git, and have no love for any other [...]. It makes for fast and efficient source control.&lt;/li>
&lt;li>And I have used lots of other tools that help me in my day to day coding work.&lt;/li>
&lt;p>Codaset is a tool that I created because I wanted the powerful issue and milestone management of Trac; with the social features of Github; mixed in with a heavy dose of Git [&amp;hellip;] but I have so much more to include.&lt;/blockquote>&lt;/p>
&lt;p>Sounds like a brilliant app to me? I was invited into the beta, and this is what I saw:&lt;/p>
&lt;p>First of all, Codaset looks very slick. I&amp;rsquo;m no designer, but Codaset adds just enough visual sugar to make the app more usable, without distracting you. It just feels right.&lt;/p>
&lt;p>When compared to Github.com there are similarities and differences. Let me focus on those difference, since in my view those make Codaset really cool:&lt;/p>
&lt;p>As you create a new project you have the option of forking an existing repository. Not just a Codaset repository, no &lt;em>any&lt;/em> git repository you have access to! I had no trouble creating a new project an importing my existing github project for example.&lt;/p>
&lt;p>The next thing I noticed when setting up a project is the public and private settings. No surprises there, but there was a third option: semi-private. This option will make your project closed-source, but expose the other features like the issue tracker, wiki and blog. It&amp;rsquo;s not something an open-source developer would get excited about, but it is quite nice to have this option.&lt;/p>
&lt;p>If you&amp;rsquo;re looking to add collaborators to your project, Codaset provides a nice role/permission based system for this. It allows you to give more fine-grained permissions to users, which is always a good thing.&lt;/p>
&lt;p>Codaset has, as Joel Moss tells you, great similarities with Trac when it comes to tickets. First you&amp;rsquo;ll notice that you can define milestones in your project. To these milestones you can add tickets which in turn can have customized fields and states. Ticket-spam is battled with a built-in spam filter for tickets. The only thing missing is tags, but Joel tells me those are in the making.&lt;/p>
&lt;p>Just like github.com, codaset provides web space for you and your project. Setting this up is quite easy. Besides web space, every project has a blog as well. Hopefully this blog can be used to automatically post news about new tags in your git repository etc.&lt;/p>
&lt;p>Another nice touch is your personal dashboard. This dashboard show you your own and bookmarked projects and recent activity, but also an overview of open and assigned tickets for you. Really handy!&lt;/p>
&lt;p>The best thing ever is Twitter integration (which I have to try out), but it promises to push project activity to twitter, which would be a great way to keep people informed about your project.&lt;/p>
&lt;p>The only things I&amp;rsquo;m missing are tags for tickets and rubygem building/publishing. But, Joel Moss is still very hard at work, so I&amp;rsquo;m looking forward all the new great things he has to offer.&lt;/p>
&lt;p>For now, check out &lt;a href="http://codaset.com">Codaset.com&lt;/a> (&lt;a href="http://codaset.com/blog">and blog&lt;/a>) or sign-up for a beta invite. Also make sure to checkout &lt;a href="http://developingwithstyle.com/">Joel Moss&amp;rsquo; blog&lt;/a>.&lt;/p>
&lt;p>&lt;em>I haven&amp;rsquo;t published any images or screenshots from Codaset in this post, because I&amp;rsquo;ve not yet asked Joel Moss for permission that.&lt;/em>&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2009/09/08/codaset-com-github-but-better/</guid><pubDate>Tue, 08 Sep 2009 00:00:00 +0000</pubDate></item><item><title>Git Tag Mini Cheat Sheet Revisited</title><link>https://www.devroom.io/2009/09/05/git-tag-mini-cheat-sheet-revisited/</link><description>&lt;p>Just as a kind of mini cheat sheet for using git tags. &lt;a href="http://ariejan.net/2009/09/04/git-tag-mini-cheat-sheet/comment-page-1/#comment-10876">Jörg Mittag&lt;/a> had some great additions that weren&amp;rsquo;t in the original post which warrant a new post.&lt;/p>
&lt;p>Git has three different type of tags:&lt;/p>
&lt;ul>
&lt;li>Lightweight tags&lt;/li>
&lt;li>Annotated tags&lt;/li>
&lt;li>Signed tags&lt;/li>
&lt;/ul>
&lt;p>Let&amp;rsquo;s start with lightweight tags.&lt;/p>
&lt;h2 id="lightweight-tags">Lightweight tags&lt;/h2>
&lt;p>In the previous cheat sheet only the lightweight local tags were discussed. A lightweight tag is nothing more than a reference to a particular revision or SHA1 object name in the repository. This kind of tag is quick and easy and very usable for local development to mark places in your commit history.&lt;/p>
&lt;p>Creating a lightweight tag is easy:&lt;/p>
&lt;pre tabindex="0">&lt;code>git tag tag_name
&lt;/code>&lt;/pre>&lt;p>Viewing available tags is done with &lt;code>-l&lt;/code>:&lt;/p>
&lt;pre tabindex="0">&lt;code>git tag -l
&lt;/code>&lt;/pre>&lt;h2 id="annotated-tags">Annotated tags&lt;/h2>
&lt;p>Annotated tags are almost like lightweight tags, the big difference is that they contain a message. Normally this message indicated why this tag is interesting. Use the &lt;code>-a&lt;/code> option to create an annotated tag.&lt;/p>
&lt;pre tabindex="0">&lt;code>git tag -a tag_name
&lt;/code>&lt;/pre>&lt;p>Since a message is required for annotated tags, you will be prompted with an editor to enter a message, or you can use the &lt;code>-m&lt;/code> option to specify one directly.&lt;/p>
&lt;pre tabindex="0">&lt;code>git tag -a -m &amp;#34;Tagging release 1.0&amp;#34; v1.0
&lt;/code>&lt;/pre>&lt;p>To view annotated tags you can use the same &lt;code>-l&lt;/code> option as before, but you have to instruct git to show the annotation messages as well:&lt;/p>
&lt;pre tabindex="0">&lt;code>git tag -l -n1
&lt;/code>&lt;/pre>&lt;p>This will not only show the messages for the annotated tags, it will also show the commit message of the revisions tagged with lightweight tags as well. Quite useful!&lt;/p>
&lt;h2 id="signed-tags">Signed tags&lt;/h2>
&lt;p>Signed tags take annotated tags a step further, they include an OpenPG signature to provide trust. While gits SHA1 tags provide integrity for the repository, the OpenPG signature makes sure that a trustworthy person created the tag.&lt;/p>
&lt;p>To create a signed tag you&amp;rsquo;ll need to have GPG or some other OpenPG tool setup and use the &lt;code>-s&lt;/code> option to sign the tag:&lt;/p>
&lt;pre tabindex="0">&lt;code>git tag -s -m &amp;#34;Tagging release 2.0&amp;#34; v2.0
&lt;/code>&lt;/pre>&lt;p>The &lt;code>-s&lt;/code> options implies the &lt;code>-a&lt;/code> option, so here too a message is required.&lt;/p>
&lt;p>To verify a signed tag you can run the following:&lt;/p>
&lt;pre tabindex="0">&lt;code>git tag -v v2.0
&lt;/code>&lt;/pre>&lt;h2 id="deleting-tags">Deleting tags&lt;/h2>
&lt;p>There are times when you want to remove tags as well. This quite easy:&lt;/p>
&lt;pre tabindex="0">&lt;code>git tag -d tag_name
&lt;/code>&lt;/pre>&lt;p>To remove a tag on a remote repository, you should do a special push:&lt;/p>
&lt;pre tabindex="0">&lt;code>git push origin :refs/tags/tag_name
&lt;/code>&lt;/pre>&lt;h2 id="pushing-tags">Pushing tags&lt;/h2>
&lt;p>To push your tags to a remote repository, use the following command to push all tags:&lt;/p>
&lt;pre tabindex="0">&lt;code>git push origin --tags
&lt;/code>&lt;/pre>&lt;p>Happy tagging!&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2009/09/05/git-tag-mini-cheat-sheet-revisited/</guid><pubDate>Sat, 05 Sep 2009 00:00:00 +0000</pubDate></item><item><title>Git Tag Mini Cheat Sheet</title><link>https://www.devroom.io/2009/09/04/git-tag-mini-cheat-sheet/</link><description>&lt;p>Just as a kind of mini cheat sheet for using git tags:&lt;/p>
&lt;p>Adding a tag:&lt;/p>
&lt;ul>
&lt;li>&lt;code>git tag tag_name&lt;/code>&lt;/li>
&lt;li>&lt;code>git tag&lt;/code>&lt;br />
&lt;em>Should show your new tag.&lt;/em>&lt;/li>
&lt;li>&lt;code>git push origin --tags&lt;/code> or &lt;code>git push origin :tag_name&lt;/code>&lt;br />
&lt;em>Because &lt;code>git push&lt;/code> doesn't push tags.&lt;/em>&lt;/li>
&lt;/ul>
&lt;p>Removing a tag:&lt;/p>
&lt;ul>
&lt;li>&lt;code>git tag -d tag_name&lt;/code>&lt;/li>
&lt;li>&lt;code>git tag&lt;/code>&lt;br />
&lt;em>Should no longer show your tag.&lt;/em>&lt;/li>
&lt;li>&lt;code>git push origin :refs/tags/tag_name&lt;/code>&lt;br />
&lt;em>Because &lt;code>git push --tags&lt;/code> doesn't push deleted tags.&lt;/em>&lt;/li>
&lt;/ul>
&lt;p>Hope that helps.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2009/09/04/git-tag-mini-cheat-sheet/</guid><pubDate>Fri, 04 Sep 2009 00:00:00 +0000</pubDate></item><item><title>Rails + MySQL: Case-Sensitive strings in your database</title><link>https://www.devroom.io/2009/09/03/rails-mysql-case-sensitive-strings-in-your-database/</link><description>&lt;p>When using Rails + MySQL, you&amp;rsquo;ll find that normal string (or varchar(255)) fields are case insensitive. This can be quite a nuisance, but it&amp;rsquo;s easy to resolve. You need to set your table to the utf8_bin collation. By using the binary variant, you&amp;rsquo;re basically enabling case sensitivity.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="n">create_table&lt;/span> &lt;span class="ss">:posts&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:options&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s1">&amp;#39;ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin&amp;#39;&lt;/span> &lt;span class="k">do&lt;/span> &lt;span class="o">|&lt;/span>&lt;span class="n">t&lt;/span>&lt;span class="o">|&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">t&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">string&lt;/span> &lt;span class="ss">:title&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:limit&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="mi">100&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>That&amp;rsquo;s all. The &lt;code>title&lt;/code> field is now case sensitive.&lt;/p>
&lt;p>Another question I get a lot is how to change the collation and charset for an existing column. That&amp;rsquo;s easy with the following query. Just make sure to pick the right column and data type:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">ALTER&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">TABLE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">posts&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">MODIFY&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="n">title&lt;/span>&lt;span class="o">`&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nb">varchar&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">100&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nb">CHARACTER&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">SET&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">utf8&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">COLLATE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">utf8_bin&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2009/09/03/rails-mysql-case-sensitive-strings-in-your-database/</guid><pubDate>Thu, 03 Sep 2009 00:00:00 +0000</pubDate></item><item><title>JInput Mac OS X 64 bit natives</title><link>https://www.devroom.io/2009/09/01/jinput-mac-os-x-64-bit-natives/</link><description>&lt;p>Yesterday I ran into a little problem running &lt;a href="http://slick.cokeandcode.com/index.php">Slick 2D&lt;/a> on Java 6 64bit on my Mac. It&amp;rsquo;s a MacPro, which has a 64 bit processor and is running Leopard. The problem I encountered was related to the native libraries provided by &lt;a href="http://www.lwjgl.org">LWJGL&lt;/a>.&lt;/p>
&lt;p>If you&amp;rsquo;re a user of LWJGL, you&amp;rsquo;ll be using JInput as well. Unfortunately, JInput does not currently have any 64 bit native libraries as it is only providing for PPC and i386 (32bit). This is a problem because Mac OS X users are somewhat bound to Java 6 64bit. There is no 32 bit version of Java 6 for Mac OS X.&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-none" data-lang="none">
$ file libjinput-osx.jnilib
libjinput-osx.jnilib: Mach-O universal binary with 2 architectures
libjinput-osx.jnilib (for architecture ppc): Mach-O dynamically linked shared library ppc
libjinput-osx.jnilib (for architecture i386): Mach-O dynamically linked shared library i386
&lt;/code>&lt;/pre>&lt;p>Thus, the default JInput natives provided by LWJGL, and subsequently by projects like &lt;a href="http://slick.cokeandcode.com/index.php">Slick 2D&lt;/a> and &lt;a href="http://www.jmonkeyengine.com/">jME&lt;/a> are of no use on a system running Java 6 64 bit. Also games and other apps based on those libraries won&amp;rsquo;t run on Java 6 64 bit!&lt;/p>
&lt;p>To solve this, I recompiled the JInput natives from CVS with 64 bit support:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-none" data-lang="none">
$ file libjinput-osx.jnilib
libjinput-osx.jnilib: Mach-O universal binary with 3 architectures
libjinput-osx.jnilib (for architecture ppc): Mach-O dynamically linked shared library ppc
libjinput-osx.jnilib (for architecture i386): Mach-O dynamically linked shared library i386
libjinput-osx.jnilib (for architecture x86_64): Mach-O 64-bit dynamically linked shared library x86_64
&lt;/code>&lt;/pre>&lt;p>I packaged the Mac OS X native (&lt;code>libjinput-osx.jnilib&lt;/code>) and the resulting &lt;code>jinput.jar&lt;/code> and &lt;code>jinput-test.jar&lt;/code> up and you can download them here:&lt;/p>
&lt;p>&lt;a href="http://ariejan.net/wp-content/uploads/2009/09/jinput-20090901-osx+ppc+i386+x86_64.tar1.gz">jinput-20090901-osx+ppc+i386+x86_64.tar&lt;/a>&lt;/p>
&lt;p>&lt;em>Note: I&amp;rsquo;m not running Linux or Windows at the moment, so I can&amp;rsquo;t provide any recompiled binaries for those platforms.&lt;/em>&lt;/p>
&lt;p>&lt;em>Question: now, how do I package this up nicely so I can use it in my &lt;a href="http://slick.cokeandcode.com/index.php">Slice 2D&lt;/a> based app?&lt;/em>&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2009/09/01/jinput-mac-os-x-64-bit-natives/</guid><pubDate>Tue, 01 Sep 2009 00:00:00 +0000</pubDate></item><item><title>Once and for all: Rails migrations integer :limit option</title><link>https://www.devroom.io/2009/08/20/once-and-for-all-rails-migrations-integer-limit-option/</link><description>&lt;p>I literally always have to look up the meaning of :limit in migrations when it comes to integer values. Here&amp;rsquo;s an overview. Now let&amp;rsquo;s memorise it (oh, this works for MySQL, other databases may work differently):&lt;/p>
&lt;table>
&lt;tr>
&lt;th>:limit&lt;/th>
&lt;th>Numeric Type&lt;/th>
&lt;th>Column Size&lt;/th>
&lt;th>Max value&lt;/th>
&lt;tr>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>tinyint&lt;/td>
&lt;td>1 byte&lt;/td>
&lt;td>127&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>2&lt;/td>
&lt;td>smallint&lt;/td>
&lt;td>2 bytes&lt;/td>
&lt;td>32767&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>3&lt;/td>
&lt;td>mediumint&lt;/td>
&lt;td>3 byte&lt;/td>
&lt;td>8388607&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>nil, 4, 11&lt;/td>
&lt;td>int(11)&lt;/td>
&lt;td>4 byte&lt;/td>
&lt;td>2147483647&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>5..8&lt;/td>
&lt;td>bigint&lt;/td>
&lt;td>8 byte&lt;/td>
&lt;td>9223372036854775807&lt;/td>
&lt;/tr>
&lt;/table>
&lt;p>&lt;em>Note: by default MySQL uses signed integers and Rails has no way (that I know of) to change this behaviour. Subsequently, the max. values noted are for signed integers.&lt;/em>&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2009/08/20/once-and-for-all-rails-migrations-integer-limit-option/</guid><pubDate>Thu, 20 Aug 2009 00:00:00 +0000</pubDate></item><item><title>IMDB Ruby Gem 0.4.0 Now available at RubyForge!</title><link>https://www.devroom.io/2009/06/14/imdb-ruby-gem-0-4-0-now-available-at-rubyforge/</link><description>&lt;p>I just released version 0.4.0 of the IMDB Ruby Gem into the wild. There are only a few minor updates:&lt;/p>
&lt;p>&lt;strong>Changes in 0.4.0&lt;/strong>&lt;/p>
&lt;ul>&lt;li>Updates to the console 'imdb' utility&lt;/li>
&lt;ul>
&lt;li>Show the IMDB ID&lt;/li>
&lt;li>Show the full IMDB URL&lt;/li>
&lt;/ul>
&lt;/ul>
&lt;p>&lt;strong>Installation or upgrade&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">$ sudo gem install imdb
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">$ sudo gem update imdb
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Issues, source or contributions&lt;/strong>&lt;/p>
&lt;p>You can find the code at &lt;a href="http://github.com/ariejan/imdb">github&lt;/a>, as well as the &lt;a href="http://github.com/ariejan/imdb/issues">issue tracker&lt;/a>. Feel free to fork and contribute!&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2009/06/14/imdb-ruby-gem-0-4-0-now-available-at-rubyforge/</guid><pubDate>Sun, 14 Jun 2009 00:00:00 +0000</pubDate></item><item><title>Speaking at Rails Underground</title><link>https://www.devroom.io/2009/06/14/speaking-at-rails-underground/</link><description>&lt;p>&lt;a href="http://rails-underground.com">&lt;img src="http://ariejan.net/wp-content/uploads/2009/06/speaker_badge.png" alt="Speaking at Rails Underground" title="Speaking at Rails Underground" width="170" height="212" class="alignright size-full wp-image-463" />&lt;/a>I haven&amp;rsquo;t seen a schedule yet, but I&amp;rsquo;ve been told by &lt;a href="http://www.rails-underground.com/about-me.html">Mark&lt;/a> that I&amp;rsquo;ll be speaking at &lt;a href="http://www.rails-underground.com/">Rails Underground&lt;/a> this year.&lt;/p>
&lt;p>My talk will be on the topic of Git. In about 45 minutes time I&amp;rsquo;ll show you all the basic git features you&amp;rsquo;ll need on a daily basis. Not only that, but I&amp;rsquo;ll also explain how git manages all those commits and branches so you can be on your way to become a git power user.&lt;/p>
&lt;p>Are you attending or speaking at Rails Underground? Please let leave a comment or find me on Twitter (&lt;a href="http://twitter.com/ariejan">@ariejan&lt;/a>).&lt;/p>
&lt;p>Oh, and does anyone have a clue if Mark has the schedule ready yet? ;-)&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2009/06/14/speaking-at-rails-underground/</guid><pubDate>Sun, 14 Jun 2009 00:00:00 +0000</pubDate></item><item><title>Best Practice - The Git Development Cycle</title><link>https://www.devroom.io/2009/06/08/best-practice-the-git-development-cycle/</link><description>&lt;p>Git is quite an awesome version control system. Why? Because it&amp;rsquo;s lightning fast, even for large projects (among other reasons).&lt;/p>
&lt;p>But, how do you use Git effectively for development on a daily basis? Let me explain to you.&lt;/p>
&lt;p>&lt;strong>Branches&lt;/strong>&lt;/p>
&lt;p>With git you normally have a &amp;lsquo;master&amp;rsquo; branch. This is also the branch you use to sync your code with other repositories. That is also the reason why you should never code in the &amp;lsquo;master&amp;rsquo; branch. Always create a new branch and develop your code there.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">$ git checkout -b new_feature
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># add, commit, repeat&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Rebase&lt;/strong>&lt;/p>
&lt;p>Now, while you are working hard on your new feature, other developers complete theirs and push their changes to the remote master branch. When you&amp;rsquo;re done with your project, you need to first get the most recent version of the project&amp;rsquo;s code.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">$ git checkout master
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ git pull
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now, to make merging your new feature easy, you should rebase your new_feature_branch. What this does is add all the commits you just pulled in to your new_feature branch. Any conflicts that arise will happen in your new_feature branch as well, leaving your master branch clean and in order.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">$ git checkout new_feature
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ git rebase master
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Merge&lt;/strong>&lt;/p>
&lt;p>Now, you have resolved all (if any) conflicts with the current code and your new_feature, you can now merge your new_feature into the project&amp;rsquo;s master branch without any problems.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">$ git checkout master
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ git merge new_feature
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This will create a new commit, containing your new_feature. Now is also the time to push your changes to the remote repository.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">$ git push origin master
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>What&amp;rsquo;s next?&lt;/strong>&lt;/p>
&lt;p>More often than not, you&amp;rsquo;ll encounter conflicts when running rebase. That&amp;rsquo;s okay, but you&amp;rsquo;ll need to know how to approach a situation like that. I&amp;rsquo;ll spend another blog post on that topic later.&lt;/p>
&lt;p>&lt;strong>Need Git training?&lt;/strong>&lt;/p>
&lt;p>&lt;em>I&amp;rsquo;m currently available to provide on-location Git training. Please &lt;a href="https://www.devroom.io/contact">contact me&lt;/a> for more info.&lt;/em>&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2009/06/08/best-practice-the-git-development-cycle/</guid><pubDate>Mon, 08 Jun 2009 00:00:00 +0000</pubDate></item><item><title>ActiveRecord: Skipping callbacks like after_save or after_update</title><link>https://www.devroom.io/2009/06/07/activerecord-skipping-callbacks-like-after_save-or-after_update/</link><description>&lt;p>Active Records provides callbacks, which is great is you want to perform extra business logic after (or before) saving, creating or destroying an instance of that model.&lt;/p>
&lt;p>However, there are situations where you can easily fall into the trap of creating an infinite loop.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">Beer&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="no">ActiveRecord&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="no">Base&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">after_save&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">x&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">some_magic_method&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">self&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">update_attribute&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="ss">:my_attribute&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">x&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The above will give you a nice infinite loop (which doesn&amp;rsquo;t scale). It&amp;rsquo;s possible to update your model, without calling the callbacks and without resorting to SQL.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">Beer&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="no">ActiveRecord&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="no">Base&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">after_save&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">x&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">some_magic_method&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">self&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="no">Beer&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">update_all&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;my_attribute = &lt;/span>&lt;span class="si">#{&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="ss">:id&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="nb">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">id&lt;/span> &lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This is a bit unconventional, but it works nicely. You can use all the following ActiveRecord methods to update your model without calling callbacks:&lt;/p>
&lt;ul>
&lt;li>decrement&lt;/li>
&lt;li>decrement_counter&lt;/li>
&lt;li>delete&lt;/li>
&lt;li>delete_all&lt;/li>
&lt;li>find_by_sql&lt;/li>
&lt;li>increment&lt;/li>
&lt;li>increment_counter&lt;/li>
&lt;li>toggle&lt;/li>
&lt;li>update_all&lt;/li>
&lt;li>update_counters&lt;/li>
&lt;/ul>
&lt;p>&lt;em>An important warning: These methods don&amp;rsquo;t do all the nice SQL injection protection stuff you&amp;rsquo;re used to. In the example, the value of &lt;code>x&lt;/code> will be inserted straight into the SQL. I recommend you only use these methods if you&amp;rsquo;re absolutely sure you&amp;rsquo;ve cleaned the values you&amp;rsquo;re inserting.&lt;/em>&lt;/p>
&lt;p>Check out the rails documentation on how to use these methods.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2009/06/07/activerecord-skipping-callbacks-like-after_save-or-after_update/</guid><pubDate>Sun, 07 Jun 2009 00:00:00 +0000</pubDate></item><item><title>has_one - find all that have no associated object</title><link>https://www.devroom.io/2009/06/07/has_one-find-all-that-have-no-associated-object/</link><description>&lt;p>Let me pose a typical Rails situation:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">Person&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="no">ActiveRecord&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="no">Base&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">has_one&lt;/span> &lt;span class="ss">:fancy_hat&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">FancyHat&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="no">ActiveRecord&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="no">Base&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">belongs_to&lt;/span> &lt;span class="ss">:person&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now, how can you get all the people that don&amp;rsquo;t have a fancy hat?&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">Person&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="no">ActiveRecord&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="no">Base&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">has_one&lt;/span> &lt;span class="ss">:fancy_hat&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">named_scope&lt;/span> &lt;span class="ss">:hatless&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:joins&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s1">&amp;#39;LEFT JOIN fancy_hats ON fancy_hats.person_id = people.id&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:conditions&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s1">&amp;#39;fancy_hats.person_id IS NULL&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now you can find all the hatless people you want.&lt;/p>
&lt;p>FYI: Finding fancy hats that have no one to wear them is a lot easier, because the foreign key is stored in the &lt;code>fancy_hats&lt;/code> table.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">FancyHat&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="no">ActiveRecord&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="no">Base&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">belongs_to&lt;/span> &lt;span class="ss">:person&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">named_scope&lt;/span> &lt;span class="ss">:wearerless&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:conditions&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="ss">:person_id&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="kp">nil&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2009/06/07/has_one-find-all-that-have-no-associated-object/</guid><pubDate>Sun, 07 Jun 2009 00:00:00 +0000</pubDate></item><item><title>IMDB 0.3.0 now including console utility - query IMDB from your console</title><link>https://www.devroom.io/2009/06/07/imdb-0-3-0-now-including-console-utility-query-imdb-from-your-console/</link><description>&lt;p>With the release of &lt;a href="http://rubyforge.org/frs/?group_id=8457&amp;release_id=35280">IMDB 0.3.0&lt;/a>, a command-line utility is included!&lt;/p>
&lt;p>Why is this awesome for you? Basically, because you can now query IMDB and process the results with any of the great GNU tools available to you like &lt;code>grep&lt;/code>.&lt;/p>
&lt;p>Let me show you:&lt;/p>
&lt;p>&lt;strong>Search IMDB&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">$ imdb Star Trek
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;gt;&amp;gt; Searching &lt;span class="k">for&lt;/span> &lt;span class="s2">&amp;#34;Star Trek&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;gt; &lt;span class="m">0060028&lt;/span> &lt;span class="p">|&lt;/span> Star Trek &lt;span class="o">(&lt;/span>1966&lt;span class="o">)&lt;/span> &lt;span class="o">(&lt;/span>TV series&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;gt; &lt;span class="m">0796366&lt;/span> &lt;span class="p">|&lt;/span> Star Trek &lt;span class="o">(&lt;/span>2009&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;gt; &lt;span class="m">0092455&lt;/span> &lt;span class="p">|&lt;/span> Star Trek: The Next Generation &lt;span class="o">(&lt;/span>1987&lt;span class="o">)&lt;/span> &lt;span class="o">(&lt;/span>TV series&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;gt; &lt;span class="m">0112178&lt;/span> &lt;span class="p">|&lt;/span> Star Trek: Voyager &lt;span class="o">(&lt;/span>1995&lt;span class="o">)&lt;/span> &lt;span class="o">(&lt;/span>TV series&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;gt; &lt;span class="m">0106145&lt;/span> &lt;span class="p">|&lt;/span> Star Trek: Deep Space Nine &lt;span class="o">(&lt;/span>1993&lt;span class="o">)&lt;/span> &lt;span class="o">(&lt;/span>TV series&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;gt; &lt;span class="m">0117731&lt;/span> &lt;span class="p">|&lt;/span> Star Trek: First Contact &lt;span class="o">(&lt;/span>1996&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;gt; &lt;span class="m">0084726&lt;/span> &lt;span class="p">|&lt;/span> Star Trek: The Wrath of Khan &lt;span class="o">(&lt;/span>1982&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;gt; &lt;span class="m">0092007&lt;/span> &lt;span class="p">|&lt;/span> Star Trek IV: The Voyage Home &lt;span class="o">(&lt;/span>1986&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;gt; &lt;span class="m">0079945&lt;/span> &lt;span class="p">|&lt;/span> Star Trek: The Motion Picture &lt;span class="o">(&lt;/span>1979&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;gt; &lt;span class="m">0244365&lt;/span> &lt;span class="p">|&lt;/span> Enterprise &lt;span class="o">(&lt;/span>2001&lt;span class="o">)&lt;/span> &lt;span class="o">(&lt;/span>TV series&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>For clarity, only the ten first search results are shown. I&amp;rsquo;m thinking of including an option to set the number of returned titles. Let me know what you think about that.&lt;/p>
&lt;p>&lt;strong>Getting movie details&lt;/strong>&lt;/p>
&lt;p>So, let&amp;rsquo;s pick a movie we want to get details about.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">$ imdb &lt;span class="m">0796366&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;gt;&amp;gt; Fetching movie &lt;span class="m">0796366&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Star Trek &lt;span class="o">(&lt;/span>2009&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">========================================================================&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Rating: 8.4
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Duration: &lt;span class="m">127&lt;/span> minutes
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Directed by: J.J. Abrams
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Cast: Chris Pine, Zachary Quinto, Leonard Nimoy, Eric Bana, Bruce Greenwood
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Genre: Action, Adventure, Sci-Fi
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">A chronicle of the early days of James T. Kirk and his fellow USS Enterprise crew members. &lt;span class="p">|&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">========================================================================&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Combine with GNU commands&lt;/strong>&lt;/p>
&lt;p>Or, as mentioned earlier, you can combine it with other console commands. So to get the rating of a movie, do this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">$ imdb &lt;span class="m">0796366&lt;/span> &lt;span class="p">|&lt;/span> grep &lt;span class="s2">&amp;#34;^Rating&amp;#34;&lt;/span> &lt;span class="p">|&lt;/span> cut -d&lt;span class="s2">&amp;#34; &amp;#34;&lt;/span> -f &lt;span class="m">2&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">8.4
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Installation, source and issues&lt;/strong>&lt;/p>
&lt;p>I&amp;rsquo;m still working on perfecting the console output, so if you have any tips, please &lt;a href="http://github.com/ariejan/imdb/issues">report it in the form of an issue&lt;/a>.&lt;/p>
&lt;p>You can get this as a Ruby Gem with&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">sudo gem install imdb
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The source is also available at &lt;a href="http://github.com/ariejan/imdb">&lt;a href="http://github.com/ariejan/imdb">http://github.com/ariejan/imdb&lt;/a>&lt;/a>.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2009/06/07/imdb-0-3-0-now-including-console-utility-query-imdb-from-your-console/</guid><pubDate>Sun, 07 Jun 2009 00:00:00 +0000</pubDate></item><item><title>Install Hpricot on Ubuntu</title><link>https://www.devroom.io/2009/06/05/install-hpricot-on-ubuntu/</link><description>&lt;p>It&amp;rsquo;s quite easy. Make sure you have RubyGems and Ruby installed first, of course.&lt;/p>
&lt;p>The problem:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">$ sudo gem install hpricot
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Building native extensions. This could take a &lt;span class="k">while&lt;/span>...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ERROR: Error installing hpricot:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ERROR: Failed to build gem native extension.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">/usr/bin/ruby1.8 extconf.rb
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">extconf.rb:1:in &lt;span class="sb">`&lt;/span>require&lt;span class="err">&amp;#39;&lt;/span>: no such file to load -- mkmf &lt;span class="o">(&lt;/span>LoadError&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> from extconf.rb:1
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The solution:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">sudo apt-get install ruby1.8-dev build-essential
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo gem install hpricot
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2009/06/05/install-hpricot-on-ubuntu/</guid><pubDate>Fri, 05 Jun 2009 00:00:00 +0000</pubDate></item><item><title>Ruby Gem: IMDB</title><link>https://www.devroom.io/2009/06/03/ruby-gem-imdb/</link><description>&lt;p>I just released version 0.1.0 of my IMDB gem which allows your app to search IMDB for IMDB movie ID&amp;rsquo;s and access most data that&amp;rsquo;s publicly available.&lt;/p>
&lt;h2 id="installation">Installation&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">sudo gem install imdb
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This will also install the dependencies Hpricot and HTTParty.&lt;/p>
&lt;h2 id="usage">Usage&lt;/h2>
&lt;p>In your project, include the gem (and possibly rubygems as well).&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">require&lt;/span> &lt;span class="s1">&amp;#39;rubygems&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">require&lt;/span> &lt;span class="s1">&amp;#39;imdb&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">search&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">Imdb&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="no">Search&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">new&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;Star Trek&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="c1">#&amp;lt;Imdb::Search:0x18289e8 @query=&amp;#34;Star Trek&amp;#34;&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">puts&lt;/span> &lt;span class="n">search&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">movies&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="o">..&lt;/span>&lt;span class="mi">3&lt;/span>&lt;span class="o">].&lt;/span>&lt;span class="n">collect&lt;/span>&lt;span class="p">{&lt;/span> &lt;span class="o">|&lt;/span>&lt;span class="n">m&lt;/span>&lt;span class="o">|&lt;/span> &lt;span class="o">[&lt;/span>&lt;span class="n">m&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">m&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">title&lt;/span>&lt;span class="o">].&lt;/span>&lt;span class="n">join&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34; - &amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">}&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">join&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="mo">006002&lt;/span>&lt;span class="mi">8&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="s2">&amp;#34;Star Trek&amp;#34;&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="mi">1966&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="no">TV&lt;/span> &lt;span class="n">series&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="mo">07&lt;/span>&lt;span class="mi">96366&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="no">Star&lt;/span> &lt;span class="no">Trek&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="mi">2009&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="mo">00&lt;/span>&lt;span class="mi">92455&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="s2">&amp;#34;Star Trek: The Next Generation&amp;#34;&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="mi">1987&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="no">TV&lt;/span> &lt;span class="n">series&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="mo">011217&lt;/span>&lt;span class="mi">8&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="s2">&amp;#34;Star Trek: Voyager&amp;#34;&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="mi">1995&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="no">TV&lt;/span> &lt;span class="n">series&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">st&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">Imdb&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="no">Movie&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">new&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;0796366&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="c1">#&amp;lt;Imdb::Movie:0x16ff904 @url=&amp;#34;http://www.imdb.com/title/tt0796366/&amp;#34;, @id=&amp;#34;0796366&amp;#34;, @title=nil&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">st&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">title&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s2">&amp;#34;Star Trek&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">st&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">year&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="mi">2009&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">st&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">rating&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="mi">8&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="mi">4&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">st&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">cast_members&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="o">..&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="o">].&lt;/span>&lt;span class="n">join&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;, &amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s2">&amp;#34;Chris Pine, Zachary Quinto, Leonard Nimoy&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>As you can see, both &lt;code>Imdb::Search&lt;/code> and &lt;code>Imdb::Movie&lt;/code> are lazy loading, only doing a HTTP request when you actually request data. Also, the remote HTTP data is cached trhough-out the life span of your Imdb::Movie object.&lt;/p>
&lt;h2 id="documentation">Documentation&lt;/h2>
&lt;p>Generated RDoc documentation can be found at &lt;a href="http://ariejan.github.com/imdb/">http://ariejan.github.com/imdb/&lt;/a>.&lt;/p>
&lt;h2 id="issues-feature-requests-patches-the-works">Issues, feature requests, patches, the works&lt;/h2>
&lt;p>Please use &lt;a href="https://github.com/ariejan/imdb">&lt;a href="https://github.com/ariejan/imdb">https://github.com/ariejan/imdb&lt;/a>&lt;/a> to supply patches (preferably through a pull-request) and to report issues.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2009/06/03/ruby-gem-imdb/</guid><pubDate>Wed, 03 Jun 2009 00:00:00 +0000</pubDate></item><item><title>Speak louder! I can't hear you over the sound of how awesome I am!</title><link>https://www.devroom.io/2009/05/22/speak-louder-i-cant-hear-you-over-the-sound-of-how-awesome-i-am/</link><description>&lt;p>&lt;a href="http://bit.ly/iBGj6">Get the t-shirt!&lt;/a>&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2009/05/22/speak-louder-i-cant-hear-you-over-the-sound-of-how-awesome-i-am/</guid><pubDate>Fri, 22 May 2009 00:00:00 +0000</pubDate></item><item><title>Second RubyFest Speaker: Geoffrey Grosenbach</title><link>https://www.devroom.io/2009/05/06/second-rubyfest-speaker-geoffrey-grosenbach/</link><description>&lt;p>Geoffrey Grosenbach is going to deliver a talk about MacRuby at RubyFest on may 14th! He&amp;rsquo;ll be joining us over a live video feed. Afterward Geoffrey will be available for a short Q&amp;amp;A session.&lt;/p>
&lt;p>More details about Geoffrey&amp;rsquo;s talk are expected shortly.&lt;/p>
&lt;p>If you didn&amp;rsquo;t get your tickets for RubyFest yet, be sure to head over to &lt;a href="http://rubyfest.nl">&lt;a href="http://rubyfest.nl">http://rubyfest.nl&lt;/a>&lt;/a> and get them right now. We&amp;rsquo;ve got limited seating for this first edition of RubyFest. (Oh, drinks are included)&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2009/05/06/second-rubyfest-speaker-geoffrey-grosenbach/</guid><pubDate>Wed, 06 May 2009 00:00:00 +0000</pubDate></item><item><title>Available for iPhone Development</title><link>https://www.devroom.io/2009/04/28/available-for-iphone-development/</link><description>&lt;p>Just to put it out there: I&amp;rsquo;m available for iPhone development (preferably in the Netherlands).&lt;/p>
&lt;p>If you&amp;rsquo;re interested in having an iPhone app developed, feel free to &lt;a href="https://www.devroom.io/contact">contact me&lt;/a> to discuss your options.&lt;/p>
&lt;p>&lt;small>I&amp;rsquo;m developing iPhone apps a an employee at &lt;a href="http://kabisa.nl">Kabisa&lt;/a>.&lt;/small>&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2009/04/28/available-for-iphone-development/</guid><pubDate>Tue, 28 Apr 2009 00:00:00 +0000</pubDate></item><item><title>Compacting a SQLite3 DB file</title><link>https://www.devroom.io/2009/04/23/compacting-a-sqlite3-db-file/</link><description>&lt;p>If you have a lot of mutations in your SQLite3 database the file size of the db file will increase a lot over time.&lt;/p>
&lt;p>This can be annoying, so you&amp;rsquo;ll need to clean up old indices and other cruft that&amp;rsquo;s making your db heavy.&lt;/p>
&lt;p>The solution is über-easy:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">$ sqlite3 mystuff.db
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">SQLite version 3.6.6.2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sqlite&amp;gt; VACUUM&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sqlite&amp;gt; .quit
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Note that you need to use capitals here. This will clean your db file and reduce its file size dramatically (depending on the amount of cruft you have, of course).&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2009/04/23/compacting-a-sqlite3-db-file/</guid><pubDate>Thu, 23 Apr 2009 00:00:00 +0000</pubDate></item><item><title>How to create a DSA OpenSSL certificate</title><link>https://www.devroom.io/2009/04/19/how-to-create-a-dsa-openssl-certificate/</link><description>&lt;p>I just needed an OpenSSL DSA public key. This is not really difficult, you just need to know the right commands. On my Mac I ran the following commands to obtain both private &lt;code>dsa_priv.pem&lt;/code> and public &lt;code>dsa_pub.pem&lt;/code> keys.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">openssl dsaparam &lt;span class="m">1024&lt;/span> &amp;lt; /dev/random &amp;gt; dsaparam.pem
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">openssl gendsa dsaparam.pem -out dsa_priv.pem
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">openssl dsa -in dsa_priv.pem -pubout -out dsa_pub.pem
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Needless to say, keep your private key in secure location and make sure you have a backup of it!&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2009/04/19/how-to-create-a-dsa-openssl-certificate/</guid><pubDate>Sun, 19 Apr 2009 00:00:00 +0000</pubDate></item><item><title>MacBook Pro: Black Screen of Death (or is it just faking?)</title><link>https://www.devroom.io/2009/04/05/macbook-pro-black-screen-of-death-or-is-it-just-faking/</link><description>&lt;p>&lt;em>Disclaimer: If you are reading this, chances are there is a hardware problem with your Mac. In my case it was a faulty logic board, which had to be replaced.&lt;/p>
&lt;p>Use this guide to get your Mac up and running again and create a full backup of you system as soon as possible. If the problem repeats itself, I recommend you take your Mac back to Apple for a check-up.&lt;/em>&lt;/p>
&lt;p>Today I was happily working on some Java code, when I decided to relocate to a sunny spot in the backyard. I closed my MacBook Pro, walked outside and opened my MBP again to continue work: a black screen!&lt;/p>
&lt;p>The screen of my Mac stayed black, although it did not indicate to be in a sleeping state. WTF? I restarted the Mac, removed the battery, reset PRAM/NVRAM and the PMU, but nothing worked! ARGH! Then, I found a solution that worked.&lt;/p>
&lt;p>If you find yourself in a similar situation, do the following:&lt;/p>
&lt;ol>
&lt;li>Shutdown your Mac entirely by holding the power button for 5 seconds.&lt;/li>
&lt;li>Hook up your Mac to another one with a FireWire cable. (You have one, right?)&lt;/li>
&lt;li>Hold the T key on your mac's internal keyboard and press the power button. When your MBP's disk will shows up as an external firewire disk on your HelperMac, release the T button.&lt;/li>
&lt;li>Open finder and go to the &lt;code>/private/var/vm&lt;/code> folder on your MacBook's hard drive. You may need to use Command-Shift-G in Finder, because this folder is hidden by default. Once in &lt;code>/private/var/vm&lt;/code> remove the &lt;code>sleepfile&lt;/code> you find there.&lt;/li>
&lt;li>Next, go to &lt;code>/System/Library&lt;/code> on you MBP's disk and remove &lt;code>Extensions.mkext&lt;/code>.&lt;/li>
&lt;li>Now, eject your MBP drive and shutdown your MBP by hitting its power button.&lt;/li>
&lt;/ol>
&lt;p>After these steps, you should be able to boot your MBP as normal again.&lt;/p>
&lt;p>Although I&amp;rsquo;m not sure of the problem at hand, it appears that the &lt;code>sleepfile&lt;/code> gets corrupted in some way.&lt;/p>
&lt;p>Oh, didn&amp;rsquo;t have that FireWire cable ready? &lt;a href="http://www.alternate.nl/html/product/Kabels_FireWire/Diversen/1394_FireWire/-1071413/?">Here&amp;rsquo;s the one I used&lt;/a>.&lt;/p>
&lt;p>&lt;em>&lt;strong>Update:&lt;/strong> There are some common questions in the comments I&amp;rsquo;d like to answer here. You cannot use an USB cable to connect your Mac to another one, only Firewire 400 and 800 are supported.&lt;/p>
&lt;p>In my case, the MacBook Pro was returned to a Apple Dealer and repaired under warranty, because Apple apparently has acknowledged the problems with the video card on these machines. Your mileage may vary with your local Apple store.&lt;/em>&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2009/04/05/macbook-pro-black-screen-of-death-or-is-it-just-faking/</guid><pubDate>Sun, 05 Apr 2009 00:00:00 +0000</pubDate></item><item><title>May 14th: RubyFest!</title><link>https://www.devroom.io/2009/03/31/may-14th-rubyfest/</link><description>&lt;p>Well, the time has come to announce &lt;a href="http://rubyfest.nl">RubyFest&lt;/a>! RubyFest is an official meetup of Ruby developers and enthusiast which will be held on May 14th in Eindhoven, the Netherlands.&lt;/p>
&lt;p>Besides organising RubyFest, I&amp;rsquo;ll also be one of two speakers at RubyFest. I&amp;rsquo;ll be giving a talk about using git with your (open source) project effectively. The other speaker is yet to be announced, and we&amp;rsquo;re still accepting proposals!&lt;/p>
&lt;p>So, head over to &lt;a href="http://rubyfest.nl">RubyFest&lt;/a> and register today, because we only have limited seating!&lt;/p>
&lt;p>Follow &lt;a href="http://twitter.com/ariejan">me&lt;/a> or &lt;a href="http://twitter.com/rubyfest">RubyFest&lt;/a> on Twitter to get the latest and greatest quickly.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2009/03/31/may-14th-rubyfest/</guid><pubDate>Tue, 31 Mar 2009 00:00:00 +0000</pubDate></item><item><title>warcraft-armory 0.1.0 Released</title><link>https://www.devroom.io/2009/02/07/warcraft-armory-010-released/</link><description>&lt;p>Yay! &lt;a href="http://ariejan.net/tags/warcraft-armory">warcraft-armory&lt;/a> version 0.1.0 has been released!&lt;/p>
&lt;p>The warcraft-armory gem allows your application to easily access information from the World of Warcraft Armory site.&lt;/p>
&lt;p>This is an early version that allows the retrieval of character information from EU and US armories. But, more is in the making!&lt;/p>
&lt;p>Feel free to check-out the &lt;a href="http://github.com/ariejan/warcraft-armory">code&lt;/a>, read the &lt;a href="http://warcraft-armory.rubyforge.org/rdoc/">docs&lt;/a> or just install the gem:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">sudo gem install warcraft-armory
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">require&lt;/span> &lt;span class="s1">&amp;#39;warcraft-armory&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">adries&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">WarcraftArmory&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="no">Character&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">find&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="ss">:eu&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:aszune&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:adries&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">adries&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">description&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># =&amp;gt; &amp;#34;Level 48 Human Warrior&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>It&amp;rsquo;s my first gem, so useful comments are appreciated! &lt;a href="http://ariejan.net/tags/warcraft-armory">Keep an eye out for updates&lt;/a>!&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2009/02/07/warcraft-armory-010-released/</guid><pubDate>Sat, 07 Feb 2009 00:00:00 +0000</pubDate></item><item><title>Pagerank 3, 19k Hits/Month</title><link>https://www.devroom.io/2009/01/30/pagerank-3-19k-hitsmonth/</link><description>&lt;p>I just found out Ariejan.net has a PageRank™ of 3! If you don&amp;rsquo;t know what PageRank™ means, let Google explain:&lt;/p>
&lt;blockquote>PageRank relies on the uniquely democratic nature of the web by using its vast link structure as an indicator of an individual page's value. In essence, Google interprets a link from page A to page B as a vote, by page A, for page B. But, Google looks at more than the sheer volume of votes, or links a page receives; it also analyzes the page that casts the vote. Votes cast by pages that are themselves "important" weigh more heavily and help to make other pages "important". - &lt;small>&lt;a href="http://en.wikipedia.org/wiki/Pagerank">source&lt;/a>&lt;/small>&lt;/blockquote>
&lt;p>To make a long story short, Ariejan.net is getting a lot of attention from around the web. Personally I think of it as a sign of appreciation for my articles. I&amp;rsquo;m also quite impressed with the number of page views my site is getting. Page views average out to 19k a month over 2008. Thanks all!&lt;/p>
&lt;p>I found out about all this because I updated my listing at &lt;a href="http://is.gd/hMT7">BuySellAds.com&lt;/a>. I suggest you check them out some time.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2009/01/30/pagerank-3-19k-hitsmonth/</guid><pubDate>Fri, 30 Jan 2009 00:00:00 +0000</pubDate></item><item><title>How To Start A Rails Edge App The Easy Way</title><link>https://www.devroom.io/2009/01/04/how-to-start-a-rails-edge-app-the-easy-way/</link><description>&lt;p>There&amp;rsquo;s a lot of &lt;a href="http://blog.rubyonrails.com/2009/1/2/this-week-in-edge-rails">cool&lt;/a> &lt;a href="http://blog.rubyonrails.com/2008/12/26/this-week-in-edge-rails">stuff&lt;/a> &lt;a href="http://blog.rubyonrails.com/2008/12/23/merb-gets-merged-into-rails-3">pooring in&lt;/a> about what&amp;rsquo;s new in Rails Edge (which will become Rails 2.3 and/or Rails 3).&lt;/p>
&lt;p>Most likely you can&amp;rsquo;t wait to get started with these new features, especially when you&amp;rsquo;re about to start a new project, which doesn&amp;rsquo;t have to be stable yet, but will be by the time 2.3/3.0 come out. This post shows you the way to create a new Rails app based on the most current Rails code, also called Edge Rails.&lt;/p>
&lt;p>Let&amp;rsquo;s go&amp;hellip;&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mkdir -p myapp/vendor
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> myapp
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">git init
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">git submodule add git://github.com/rails/rails.git vendor/rails
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">git commit -m &lt;span class="s2">&amp;#34;Frozen Rails Edge as submodule&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ruby vendor/rails/railties/bin/rails .
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Add generated files to git, and code on...&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>First, you create a new directory for your app, including the &lt;code>vendor&lt;/code> directory. Easy, right?&lt;/p>
&lt;p>Next, you initialize a Git repository for your empty project. We&amp;rsquo;ll be using Git to track the remote Rails Edge code. Stay with me.&lt;/p>
&lt;p>By adding a Git submodule we tell git to clone the code from &lt;code>git://github.com/rails/rails.git&lt;/code> into the &lt;code>vendor/rails&lt;/code> directory. Nice! If you check the current git status with &lt;code>git status&lt;/code> you see git has already staged two files for you, &lt;code>.gitmodules&lt;/code> and &lt;code>vendor/rails&lt;/code>. Commit them now to attach the submodule to your local git repository.&lt;/p>
&lt;p>Git will not automatically update your submodule, you&amp;rsquo;ll have to do that by hand. I&amp;rsquo;ll show you this in a minute.&lt;/p>
&lt;p>With &lt;code>vendor/rails&lt;/code> containing Rails Edge, you can now generate your Rails Edge application. In you project directory (&lt;code>myapp/&lt;/code>), you call &lt;code>ruby vendor/rails/railties/bin/rails .&lt;/code>. This will generate a new Rails Edge application in the current directory.&lt;/p>
&lt;p>Now it&amp;rsquo;s up to you to create a fitting &lt;code>.gitignore&lt;/code> file and commit the files to your repository.&lt;/p>
&lt;p>That&amp;rsquo;s all, you now have a new Rails Edge application. Try &lt;code>ruby script/server&lt;/code> to see it all in action. Enjoy!&lt;/p>
&lt;p>&lt;strong>Cloning your project&lt;/strong>&lt;/p>
&lt;p>At some point you&amp;rsquo;ll push your &lt;code>myapp&lt;/code> project to a remote git server. When you clone a fresh copy, you&amp;rsquo;ll have to initialize the git submodules. This is quite easy:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">git submodule init
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">git submodule update
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Updating Rails Edge&lt;/strong>&lt;/p>
&lt;p>As I said earlier, Git will not keep your submodules up-to-date for you, but will stick with the revision you added. To keep track of Rails Edge&amp;rsquo;s progress, you&amp;rsquo;ll need to update the submodule. This is done like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> myapp/vendor/rails
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">git remote update
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">git merge origin/master
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This will update your Rails Edge code. Make a commit, stating you updated the code!&lt;/p>
&lt;p>After updating Rails Edge, you may want to update your rails application (like javascript files, config files etc).&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">rake rails:update
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Good luck! And happy coding!&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2009/01/04/how-to-start-a-rails-edge-app-the-easy-way/</guid><pubDate>Sun, 04 Jan 2009 00:00:00 +0000</pubDate></item><item><title>Twitterlicious!</title><link>https://www.devroom.io/2008/12/27/twitterlicious/</link><description>&lt;p>A bit late, but &amp;ldquo;Merry Christmas&amp;rdquo; everyone!&lt;/p>
&lt;p>I&amp;rsquo;m on Twitter lately, so &lt;a href="http://twitter.com/ariejan">feel free to check and follow&lt;/a>. It&amp;rsquo;s a also a nice way to keep up-to-date about new articles on Ariejan.net.&lt;/p>
&lt;p>Hey, that&amp;rsquo;s all for now. And if you don&amp;rsquo;t come back here this year: &amp;ldquo;Happy New Year!&amp;rdquo; - Cheers!&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2008/12/27/twitterlicious/</guid><pubDate>Sat, 27 Dec 2008 00:00:00 +0000</pubDate></item><item><title>Google FriendConnect now on Ariejan.net</title><link>https://www.devroom.io/2008/12/03/google-friendconnect-now-on-ariejannet/</link><description>&lt;p>Well, I just got invited for &lt;a href="http://www.google.com/friendconnect">Google FriendConnect&lt;/a>! Let me tell you a bit about FriendConnect.&lt;/p>
&lt;p>With FriendConnect you can easily join Ariejan.net. See of it as a pool of Ariejan.net visitors or fans if you like.&lt;/p>
&lt;p>Once you&amp;rsquo;ve joined up (with your google account, for example) you&amp;rsquo;ll be able to interact more closely with Ariejan.net. In the case of Ariejan.net you can share your thoughts with other Ariejan.net visitors. Just vent your frustration about Linux or let others know what Rails project you&amp;rsquo;re working on.&lt;/p>
&lt;p>Another benefit for you is that you can see what sites other people have signed up for as well. This way you&amp;rsquo;ll be able to get a list of sites that Ariejan.net visitors like as well. You might compare it with Amazon&amp;rsquo;s &amp;ldquo;other people also bought..&amp;rdquo; feature.&lt;/p>
&lt;p>The big benefit for me is that&amp;rsquo;s it&amp;rsquo;s nice to see the list of fans/visitors growing. After all, that&amp;rsquo;s what I&amp;rsquo;m keeping a blog for after all.&lt;/p>
&lt;p>In the future I&amp;rsquo;ll be able to add other widgets to Ariejan.net. This allows for other features to appear on Ariejan.net and a better over-all user experience.&lt;/p>
&lt;p>You may want to &lt;a href="http://www.google.com/friendconnect">read more&lt;/a> about Google FriendConnect and OpenSocial.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2008/12/03/google-friendconnect-now-on-ariejannet/</guid><pubDate>Wed, 03 Dec 2008 00:00:00 +0000</pubDate></item><item><title>Export CSV directly from MySQL</title><link>https://www.devroom.io/2008/11/27/export-csv-directly-from-mysql/</link><description>&lt;p>How ofter were you asked by a client for a CSV (or excel) file with data from their app? I get asked that question quite often, so I wanted make the process as easy as possible. And guess what? You can create CSV files directly from MySQL with just one query!&lt;/p>
&lt;p>Let&amp;rsquo;s say you want to export the id, name and email fields from your users table to a CSV file. Here is your code:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">SELECT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">email&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">INTO&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">OUTFILE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;/tmp/result.csv&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="n">FIELDS&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">TERMINATED&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">BY&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;,&amp;#39;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">OPTIONALLY&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">ENCLOSED&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">BY&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;&amp;#34;&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="n">ESCAPED&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">BY&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">‘\\’&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="n">LINES&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">TERMINATED&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">BY&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;\n&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">FROM&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">users&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">WHERE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Well, if you know MySQL, you&amp;rsquo;ll know how to customize this query to spit out the the right data. Your csv file can be found in /tmp/result.csv&lt;/p>
&lt;p>Make sure your MySQL server has write permissions to the location where you want to store your results file.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2008/11/27/export-csv-directly-from-mysql/</guid><pubDate>Thu, 27 Nov 2008 00:00:00 +0000</pubDate></item><item><title>SQL: Ordering with NULL values</title><link>https://www.devroom.io/2008/11/14/sql-ordering-with-null-values/</link><description>&lt;p>First seen at &lt;a href="http://blog.kabisa.nl">Kabisa Blog&lt;/a>: &lt;a href="http://blog.kabisa.nl/2008/11/14/sql-ordering-with-null-values/">SQL: Ordering with NULL values&lt;/a>&lt;/p>
&lt;p>This post tells you how to sort &lt;code>NULL&lt;/code> values in a column to the bottom and sort the remaining non-&lt;code>NULL&lt;/code> values.&lt;/p>
&lt;p>This is really great in combination with LEFT JOIN queries that may yield NULL values.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2008/11/14/sql-ordering-with-null-values/</guid><pubDate>Fri, 14 Nov 2008 00:00:00 +0000</pubDate></item><item><title>RSpec'ing with Time.now</title><link>https://www.devroom.io/2008/11/05/rspecing-with-timenow/</link><description>&lt;p>I&amp;rsquo;m currently writing some RSpec tests that use Time.now.&lt;/p>
&lt;p>I want my model to calculate a duration and store the future time in the database. I&amp;rsquo;ve already specced the calculation of the duration, but I also want to spec that everything gets saved correctly. Here&amp;rsquo;s my first spec:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="n">it&lt;/span> &lt;span class="s2">&amp;#34;should do stuff&amp;#34;&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">m&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">Model&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">create&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">m&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">expires_at&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">should&lt;/span> &lt;span class="n">eql&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="no">Time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">now&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">some_value&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This fails.&lt;/p>
&lt;p>It fails because Time.now is quite accurate and some milliseconds have passed between the two calls.&lt;/p>
&lt;p>So how do you test this kind of behaviour? I was not going to let this one beat me. Get out your gloves, because we&amp;rsquo;re going to start stubbing!&lt;/p>
&lt;p>What you need to do is stub out Time#now to return a constant value within this test. This way, both calls will use the same Time.now value and thus yield the same result. This in turn makes your test pass (if the saving goes well, of course).&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="n">it&lt;/span> &lt;span class="s2">&amp;#34;should do stuff&amp;#34;&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="vi">@time_now&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">Time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">parse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Feb 24 1981&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="no">Time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">stub!&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="ss">:now&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">and_return&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="vi">@time_now&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">m&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">Model&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">create&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">m&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">expires_at&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">should&lt;/span> &lt;span class="n">eql&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="no">Time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">now&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">some_value&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2008/11/05/rspecing-with-timenow/</guid><pubDate>Wed, 05 Nov 2008 00:00:00 +0000</pubDate></item><item><title>BaseApp: a quick start for your Rails App</title><link>https://www.devroom.io/2008/09/28/baseapp-a-quick-start-for-your-rails-app/</link><description>&lt;p>&lt;em>&lt;strong>BaseApp is no longer maintained. There is a very good alternative called &lt;a href="http://github.com/fudgestudios/bort">bort&lt;/a>&lt;/strong>.&lt;/em>&lt;/p>
&lt;p>&lt;em>For the impatient: &lt;a href="http://github.com/ariejan/baseapp">&lt;a href="http://github.com/ariejan/baseapp">http://github.com/ariejan/baseapp&lt;/a>&lt;/a>&lt;/em>&lt;/p>
&lt;p>&lt;em>Got issues? Feature requests or patches? &lt;a href="http://baseapp.lighthouseapp.com/">&lt;a href="http://baseapp.lighthouseapp.com/">http://baseapp.lighthouseapp.com/&lt;/a>&lt;/a>&lt;/em>&lt;/p>
&lt;p>Every Rails developer has at least once developed an application that needed user authentication and some basic UI features like tabs and a sidebar. Ask yourself now: &amp;ldquo;how often have you installed and extended the restful_authentication plugin?&amp;rdquo;.&lt;/p>
&lt;p>Yes, I have done it quite a few times and everytime I find myself writing the same code over and over again. User login, password reset, &amp;lsquo;forgot password&amp;rsquo; functionality. I&amp;rsquo;ve build the same basic UI over and over again. Added administrator users and roles.&lt;/p>
&lt;p>Are you tired of doing the same old things over and over again? I was! So, I created BaseApp.&lt;/p>
&lt;p>BaseApp is a Ruby on Rails application which contains a lot of code you want in your project by default. To give you an idea of what is does out of the box:&lt;/p>
&lt;ul>
&lt;li>User Authentication including password recovery, account activation and account suspensio.&lt;/li>
&lt;li>Admin interface where the admin user can easily manage users and tweak app settings&lt;/li>
&lt;li>Default CSS-based UI with tabs and a sidebar. Very acceptable by default and easy to customise.&lt;/li>
&lt;/ul>
&lt;p>BaseApp is currently based on Rails 2.1.1. And although it&amp;rsquo;s a pretty complete package and ready to be used for your next project, it still needs a bit of work. Check out the &lt;a href="http://github.com/ariejan/baseapp/tree/master/README">README&lt;/a> for features that should be in BaseApp.&lt;/p>
&lt;p>Of course, BaseApp is open source so fork a copy at &lt;a href="http://github.com/ariejan/baseapp">the GitHub&lt;/a> and send me those patches (of pull requests)!&lt;/p>
&lt;p>There are tons of feaures that can be included into BaseApp, so the next big thing is to include some sort of configuration that allows you to disable/enable certain BaseApp features.&lt;/p>
&lt;p>So, go right ahead! Use it! Fork it! Send me those pull requests!&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2008/09/28/baseapp-a-quick-start-for-your-rails-app/</guid><pubDate>Sun, 28 Sep 2008 00:00:00 +0000</pubDate></item><item><title>JRuby with Thomas Enebo</title><link>https://www.devroom.io/2008/09/03/jruby-with-thomas-enebo/</link><description>&lt;p>This morning I attended another JRuby talk, this time with Thomas Enebo. It turned out to be, almost default, Sun JRuby talk.&lt;/p>
&lt;p>There was one interesting difference, though. Normally we are shown how to run Ruby on Java and how to use Java components in our Ruby apps. Thomas took JMonkeyEngine (a java 3d scenegraph/game engine) and showed that he coded a simple game in Ruby, steering clear of the rather complex Java code.&lt;/p>
&lt;p>This is, of course, another great feature of JRuby, you can code a &amp;ldquo;Java&amp;rdquo; app, and in certain places actually use Ruby to write cleaner or &amp;ldquo;easier&amp;rdquo; code.&lt;/p>
&lt;p>Well worth the time!&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2008/09/03/jruby-with-thomas-enebo/</guid><pubDate>Wed, 03 Sep 2008 00:00:00 +0000</pubDate></item><item><title>Panel Discussion with DHH and Rails Core Members</title><link>https://www.devroom.io/2008/09/03/panel-discussion-with-dhh-and-rails-core-members/</link><description>&lt;p>Yesterday evening we attended a panel discussion with DHH and Rails Core Members Jeremy Kemper and Michael Koziarski.&lt;/p>
&lt;p>DHH elaborated on default choices (like database, templating system and test suite) after being asked if Rails would switch over to RSpec instead of TestUnit. The answer was that Rails offers several defaults, which should suffice for new and basic users, who don&amp;rsquo;t know about all options and just want to get started. More experienced users will generate a taste for different components and Rails should provide for easy integration of these components if needed.&lt;/p>
&lt;p>All in all there wasn&amp;rsquo;t any really new stuff to be heard, but a nice gathering non the less.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2008/09/03/panel-discussion-with-dhh-and-rails-core-members/</guid><pubDate>Wed, 03 Sep 2008 00:00:00 +0000</pubDate></item><item><title>JRuby with Nick Sieger</title><link>https://www.devroom.io/2008/09/02/jruby-with-nick-sieger/</link><description>&lt;p>&lt;a href="http://www.flickr.com/photos/arun-gupta/2820263933/" title="IMG_3052 by ArunGupta, on Flickr">&lt;img src="http://farm4.static.flickr.com/3136/2820263933_df70723a01_m.jpg" alt="Welcome to RailsConf" align="right" />&lt;/a>A tutorial on JRuby with Nick Sieger holding your hand is just great. This guy knows JRuby inside-out and he has an answer to even the most difficult of questions.&lt;/p>
&lt;p>After a short 15 minute introduction, Nick left us with our Rails app to start it with JRuby on a mongrel. I&amp;rsquo;ve been toying around with JRuby a bit before, and this was rather easy. Some people, however, encounter some issues with their apps because of incompatible gems, like OpenSSL or RMagic which are not available for the Java platform. Luckily, there are some nice Java alternatives like JRuby OpenSSL and ImageVooDoo. My app didn&amp;rsquo;t suffer from these issues, but from what I&amp;rsquo;ve heard, these alternatives work great.&lt;/p>
&lt;p>Next for the big work: creating a WAR file and deploying it. I&amp;rsquo;ve done this before, and it&amp;rsquo;s dead easy. In stall the Warble gem, warble your Rails app and deploy the resulting WAR file.&lt;/p>
&lt;p>Nick also talked about the differences between using the JVM and Unix. The biggest differences are memory management and the creation of new threads/processes.&lt;/p>
&lt;p>There are quite a few things that Rails and a Java app server have in common, like logging and session handling. In his sheets (I&amp;rsquo;ll link to those later), you can see how you can tune your Rails app to make use of these features and improve your apps performance (by not logging and keeping sessions in two different places).&lt;/p>
&lt;p>Continuing on the performance of Glassfish/JRuby, Nick shows several graphs on how well JRuby performs. A lot of attention goes to Rails 2.2, which will be thread-safe and what it means for JRuby.&lt;/p>
&lt;p>JRuby can run with 1 runtime and fill your CPU with threads to handle requests using native Java threads. To handle database connections, Nick Sieger has written a connection pooling component for ActiveRecord (to prevent every thread having it&amp;rsquo;s own connection to your database). An unreleased version of warbler was distributed that will allow you to set max_instances to 1. This will create 1 Rails instance and use threading in Glassfish. Of course, this is only useful with Rails 2.2.&lt;/p>
&lt;p>This was altogether a great tutorial afternoon. Besides the basic &lt;i>warble-your-app-and-deploy-it&lt;/i>, Nick provided a lot of interesting facts and tips about deploying to Glassfish. I already was enthusiastic about JRuby, but now I really have to get my hands on a decent server that will run Glassfish comfortably. This means at least at least 1Gb of RAM, so I think I have found at least one drawback of using JRuby.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2008/09/02/jruby-with-nick-sieger/</guid><pubDate>Tue, 02 Sep 2008 00:00:00 +0000</pubDate></item><item><title>RailsConfEurope: The first tutorial</title><link>https://www.devroom.io/2008/09/02/railsconfeurope-the-first-tutorial/</link><description>&lt;p>Today, RailsConfEurope 2008 started! Well, at least if you signed up for Tutorial Day.&lt;/p>
&lt;p>The first tutorial I&amp;rsquo;m attending is about Hacking Rails Internals. At first I thought about this as a great way to enhance my own apps and be able to easily integrate other apps like Radiant or Mephisto. However, the more I saw of the demo (it really is more a demo than a tutorial really), the more I disliked hacking Rails Internals.&lt;/p>
&lt;p>Basically, you hook your own application code directly into the Rails code to make it jump through hoops. But, this comes at a big price. First of all, your app code is very dependent on a specific version of Rails, so upgrading Rails will most likely break your app and cost a lot of time (and money) to upgrade.&lt;/p>
&lt;p>Next, you &lt;em>are&lt;/em> overriding internal Rails code. You just don&amp;rsquo;t know if any other code (either yours, Rails or a plugin) depends on the behaviour of that code. So you should probably expect strange stack traces.&lt;/p>
&lt;p>So, although hacking Rails to include your own or others code directly is great, it has some major drawbacks you should consider. To quote the presenter &amp;ldquo;It&amp;rsquo;s brutish, but it&amp;rsquo;s how to do it&amp;rdquo;.&lt;/p>
&lt;p>I&amp;rsquo;m really looking forward to Nick Sieger and his JRuby tutorial this afternoon. I&amp;rsquo;m all set with Glassfish installed and my Rails app ready.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2008/09/02/railsconfeurope-the-first-tutorial/</guid><pubDate>Tue, 02 Sep 2008 00:00:00 +0000</pubDate></item><item><title>Leaving for RailsConf Europe 2008</title><link>https://www.devroom.io/2008/09/01/leaving-for-railsconf-europe-2008/</link><description>&lt;p>Today a delegation of &lt;a href="http://kabisa.nl">Kabisa&lt;/a> is leaving for &lt;a href="http://www.railsconfeurope.com">RailsConf Europe 2008&lt;/a>! We&amp;rsquo;re going by train this year and we hope to arrive this afternoon around 5.&lt;/p>
&lt;p>I&amp;rsquo;ll keep regular updates here on Ariejan.net about RailsConf and all the things I&amp;rsquo;ve seen and done.&lt;/p>
&lt;p>If you&amp;rsquo;re going to visit RailsConf Europe yourself, feel free to seek us out and have a chat!&lt;/p>
&lt;p>See you at RailsConf!&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2008/09/01/leaving-for-railsconf-europe-2008/</guid><pubDate>Mon, 01 Sep 2008 00:00:00 +0000</pubDate></item><item><title>ActiveRecord Read Only Model</title><link>https://www.devroom.io/2008/08/17/activerecord-read-only-models/</link><description>&lt;p>ActiveRecord is great in providing CRUD for your data models. In some cases, however, it&amp;rsquo;s necessary to prevent write access to these models. The data may be provided by an external source and should only be used as a reference in your application, for example.&lt;/p>
&lt;p>I&amp;rsquo;m going to show you how you can easily mark a Model as read only all the time. In this example I have a Item model like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">Item&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="no">ActiveRecord&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="no">Base&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>ActiveRecord::Base provides two methods that may be of interest here:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">readonly!&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="vi">@readonly&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kp">true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">readonly?&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">defined?&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="vi">@readonly&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="vi">@readonly&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="kp">true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The first method sets the record to read only. This is great, but we don&amp;rsquo;t want to set the read only property every time we load a model. The second, readonly?, return true if the object is read only or false if it isn&amp;rsquo;t.&lt;/p>
&lt;p>So, if we return true on the readonly? method, our object is marked as read only. Great!&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">Item&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="no">ActiveRecord&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="no">Base&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">readonly?&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kp">true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>That is all! All Item objects are now marked as read only all the time. If you try to write to the model, you&amp;rsquo;ll receive an error.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="n">item&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">Item&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">find&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="ss">:first&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">item&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">update_attributes&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="ss">:name&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s1">&amp;#39;Some item name&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="no">ActiveRecord&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="no">RecordReadOnly&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2008/08/17/activerecord-read-only-models/</guid><pubDate>Sun, 17 Aug 2008 00:00:00 +0000</pubDate></item><item><title>Skinny Controllers and Overweight Models</title><link>https://www.devroom.io/2008/08/17/skinny-controllers-and-overweight-models/</link><description>&lt;p>All Rails developers know the slogan &amp;ldquo;Skinny Controllers, Fat Models&amp;rdquo; and I heartily agree with it. Every conference you go to, you hear it. But there&amp;rsquo;s a problem! My Fat models got overweight!&lt;/p>
&lt;p>What happened? By stuffing all applications logic in the Models, they become fat, very fat. Although this is supposed to be a good thing, I don&amp;rsquo;t like it. My models get so fat that it takes me forever to scroll through it and find the method I&amp;rsquo;m working on. There must be a better way!&lt;/p>
&lt;p>Well, yes there is: create modules! Normally you&amp;rsquo;d write a module to reuse your code in different places, but there&amp;rsquo;s no rule that says you may not use a module only once.&lt;/p>
&lt;p>So, I package all related code (e.g. Authentication, state management, managing associated objects, etc) into different modules and place them in the /lib directory. Let&amp;rsquo;s say you have a a bunch of methods to handle keep a counter on your User model&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">User&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="no">ActiveRecord&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="no">Base&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kp">attr_accessor&lt;/span> &lt;span class="ss">:counter&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">up&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">counter&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="mi">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">down&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">counter&lt;/span> &lt;span class="o">-=&lt;/span> &lt;span class="mi">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">reset&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">counter&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>You could create a new file lib/counter.rb and include that module in your User model.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">User&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="no">ActiveRecord&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="no">Base&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kp">attr_accessor&lt;/span> &lt;span class="ss">:counter&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kp">include&lt;/span> &lt;span class="no">Counter&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">module&lt;/span> &lt;span class="nn">Counter&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">up&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">counter&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="mi">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">down&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">counter&lt;/span> &lt;span class="o">-=&lt;/span> &lt;span class="mi">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">reset&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">counter&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>As you can see, this keeps your fat User model clean and makes it easier for you to find code that applies to a certain function.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2008/08/17/skinny-controllers-and-overweight-models/</guid><pubDate>Sun, 17 Aug 2008 00:00:00 +0000</pubDate></item><item><title>Useless Ruby Gems for your pleasure</title><link>https://www.devroom.io/2008/08/14/useless-ruby-gems-for-your-pleasure/</link><description>&lt;p>The past few days I&amp;rsquo;v taken some time to find out how to create a Ruby Gem. This has been on my to-do list for quite a while, but now I&amp;rsquo;m able to tick it off.&lt;/p>
&lt;p>Well, what did I make?&lt;/p>
&lt;p>The first Gem can also be used as a Ruby on Rails plugin and is called ActsAsGold. If you&amp;rsquo;ve ever played World of Warcraft, you&amp;rsquo;ll know how the money system works. You have Copper. A 100 Copper is worth 1 Silver. And 100 Silver is worth 1 Gold.&lt;/p>
&lt;p>The ActsAsGold Gem allows you to extend your ActiveRecord model with this money system. All you need on your model is an attribute that stores a single integer value.&lt;/p>
&lt;p>Let me give you a small tour.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">Player&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="no">ActiveRecord&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="no">Base&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">acts_as_gold&lt;/span> &lt;span class="ss">:column&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="ss">:money&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># This will be store like @player.money =&amp;gt; 2003652&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="vi">@player&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">gold&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="mi">200&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="vi">@player&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">silver&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="mi">36&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="vi">@player&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">copper&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="mi">52&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># You can also easily earn or spend money&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="vi">@player&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">earn&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">10&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">gold&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="mi">75&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">silver&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="vi">@player&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">spend&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">gold&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="mi">10&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">silver&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="mi">95&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">copper&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;a href="http://github.com/ariejan/acts_as_gold/tree/master/README.textile">Read more about the Gem&lt;/a>, or install the gem right now:&lt;/p>
&lt;pre>sudo gem install ariejan-acts_as_gold --source http://gems.github.com&lt;/pre>
&lt;p>The other gem is WarcraftArmory, which is still in early development, so new stuff can and will be added in the future.&lt;/p>
&lt;p>WA (WarcraftArmory) allows you to easily retrieve character information from the World of Warcraft Armory. Currently it can retrieve:&lt;/p>
&lt;ul>
&lt;li>Name of the character&lt;/li>
&lt;li>Name of the characters guild&lt;/li>
&lt;li>Level&lt;/li>
&lt;li>Race&lt;/li>
&lt;li>Class&lt;/li>
&lt;/ul>
&lt;p>It works for both EU and US warcraft servers.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">require&lt;/span> &lt;span class="s1">&amp;#39;warcraft_armory&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">character&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">WarcraftArmory&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">find&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="ss">:eu&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;Aszune&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;Nosius&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">character&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">race&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s2">&amp;#34;Human&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">character&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">level&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="mi">15&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Again, simply install the plugin and use it like any other gem or &lt;a href="http://github.com/ariejan/warcraft_armory/tree/master/README.textile">read the README&lt;/a> first.&lt;/p>
&lt;pre>sudo gem install ariejan-warcraft_armory --source http://gems.github.com&lt;/pre>
&lt;p>Of course, these gems are released under the MIT license. The code is on Gitub (&lt;a href="https://github.com/ariejan/acts_as_gold/tree">acts_as_gold&lt;/a>, &lt;a href="https://github.com/ariejan/warcraft_armory/tree">warcraft_armory&lt;/a>) and patches with fixes and new features are gladly accepted.&lt;/p>
&lt;p>Please let me know if you find these gems useful or if you use them in one of your projects.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2008/08/14/useless-ruby-gems-for-your-pleasure/</guid><pubDate>Thu, 14 Aug 2008 00:00:00 +0000</pubDate></item><item><title>Ruby on Rails: UUID as your ActiveRecord primary key</title><link>https://www.devroom.io/2008/08/12/ruby-on-rails-uuid-as-your-activerecord-primary-key/</link><description>&lt;p>Sometimes, using the good old &amp;lsquo;auto increment&amp;rsquo; from your database just isn&amp;rsquo;t good enough. If you really require that all your objects have unique ID, even across systems and different databases there&amp;rsquo;s only one way go: UUID or Universally Unique IDentifier.&lt;/p>
&lt;p>A UUID is generated in such a way that every generated UUID in the world is unique. For example: 12f186e6-687e-11ad-843e-001b632783f1. This string is randomly generated based on several factors that guarantee it&amp;rsquo;s uniqueness.&lt;/p>
&lt;p>Anyway, you want to replace the default integer-based primary keys in your model with a UUID. This is quite easy, but there are some caveats.&lt;/p>
&lt;p>First off, you should have a column in your database table that holds the UUID. You may be tempted to just change the column definition for id from integer to string and be done with it. But this won&amp;rsquo;t work as expected. For your development, and maybe even your production system, this may work fine, but you might be in for some unexpected surprises.&lt;/p>
&lt;p>The best example of such a surprise is RSpec. RSpec uses &amp;lsquo;rake db:schema:dump&amp;rsquo; to create a sql dump to quickly load the database with. However, the &amp;lsquo;schema:dump&amp;rsquo; does not look at the id column in your database, but instead adds the default primary key definition from the ActiveRecord adapter.&lt;/p>
&lt;p>The solution is to disable the id column and create a primary key column named uuid instead.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="n">create_table&lt;/span> &lt;span class="ss">:posts&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:id&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="kp">false&lt;/span> &lt;span class="k">do&lt;/span> &lt;span class="o">|&lt;/span>&lt;span class="n">t&lt;/span>&lt;span class="o">|&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">t&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">string&lt;/span> &lt;span class="ss">:uuid&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:limit&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="mi">36&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:primary&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="kp">true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>In your Post model you should then set the name of this new primary key column.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">Post&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="no">ActiveRecord&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="no">Base&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">set_primary_key&lt;/span> &lt;span class="s2">&amp;#34;uuid&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The next step is to create the UUID itself. We&amp;rsquo;ll have to do this the Rails app, because most databases don&amp;rsquo;t support UUID out of the box.&lt;/p>
&lt;p>First install the uuidtools gem&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">sudo gem install uuidtools
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Create a file like lib/uuid_helper.rb and add the following content.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">require&lt;/span> &lt;span class="s1">&amp;#39;rubygems&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">require&lt;/span> &lt;span class="s1">&amp;#39;uuidtools&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">module&lt;/span> &lt;span class="nn">UUIDHelper&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">before_create&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">uuid&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">UUID&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">timestamp_create&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">to_s&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Then, include this module in all UUID-enabled models, like Post in this example.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">Post&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="no">ActiveRecord&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="no">Base&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">set_primary_key&lt;/span> &lt;span class="s2">&amp;#34;uuid&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kp">include&lt;/span> &lt;span class="no">UUIDHelper&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now, when you save a new Post object, the uuid field is automatically filled with a Universally Unique Identifier. What else could you wish for?&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2008/08/12/ruby-on-rails-uuid-as-your-activerecord-primary-key/</guid><pubDate>Tue, 12 Aug 2008 00:00:00 +0000</pubDate></item><item><title>Ariejan.net Link Party 07/21/2008</title><link>https://www.devroom.io/2008/07/21/ariejannet-link-party-07212008/</link><description>&lt;ul class='diigo-linkroll'>&lt;li>&lt;p class='diigo-link'>&lt;a href="http://www.igvita.com/2007/06/05/creating-javascript-widgets-in-rails">Creating JavaScript widgets in Rails - igvita.com&lt;/a>&lt;/p>&lt;p class='diigo-description'>How to offer your users a way to 'widgetize' their content and use it elsewhere as well. &lt;/p>&lt;p class='diigo-tags'>tags: &lt;a href='http://www.diigo.com/user/ariejan/rails'>rails&lt;/a>, &lt;a href='http://www.diigo.com/user/ariejan/ruby'>ruby&lt;/a>, &lt;a href='http://www.diigo.com/user/ariejan/ror'>ror&lt;/a>, &lt;a href='http://www.diigo.com/user/ariejan/widget'>widget&lt;/a>, &lt;a href='http://www.diigo.com/user/ariejan/widgets'>widgets&lt;/a>, &lt;a href='http://www.diigo.com/user/ariejan/share'>share&lt;/a>&lt;/p>&lt;li>&lt;p class='diigo-link'>&lt;a href="http://www.brusheezy.com">Free Photoshop Brushes at Brusheezy!&lt;/a>&lt;/p>&lt;p class='diigo-description'>A great resource for (photoshop) brushes.&lt;/p>&lt;p class='diigo-tags'>tags: &lt;a href='http://www.diigo.com/user/ariejan/photoshop'>photoshop&lt;/a>, &lt;a href='http://www.diigo.com/user/ariejan/brushes'>brushes&lt;/a>, &lt;a href='http://www.diigo.com/user/ariejan/design'>design&lt;/a>, &lt;a href='http://www.diigo.com/user/ariejan/resources'>resources&lt;/a>, &lt;a href='http://www.diigo.com/user/ariejan/graphics'>graphics&lt;/a>, &lt;a href='http://www.diigo.com/user/ariejan/patterns'>patterns&lt;/a>, &lt;a href='http://www.diigo.com/user/ariejan/webdesign'>webdesign&lt;/a>, &lt;a href='http://www.diigo.com/user/ariejan/share'>share&lt;/a>&lt;/p>&lt;/ul></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2008/07/21/ariejannet-link-party-07212008/</guid><pubDate>Mon, 21 Jul 2008 00:00:00 +0000</pubDate></item><item><title>Ariejan.net Link Party 07/17/2008</title><link>https://www.devroom.io/2008/07/17/ariejannet-link-party-07172008/</link><description>&lt;ul class='diigo-linkroll'>&lt;li>&lt;p class='diigo-link'>&lt;a href="http://code.google.com/p/gearsonrails">gearsonrails - Google Code&lt;/a>&lt;/p>&lt;p class='diigo-description'>Gears on Rails helps developers to write fully offline functional web applications based on Gears without learning a bit of Gears.&lt;/p>&lt;p class='diigo-tags'>tags: &lt;a href='http://www.diigo.com/user/ariejan/gearsonrails'>gearsonrails&lt;/a>, &lt;a href='http://www.diigo.com/user/ariejan/ruby'>ruby&lt;/a>, &lt;a href='http://www.diigo.com/user/ariejan/rails'>rails&lt;/a>, &lt;a href='http://www.diigo.com/user/ariejan/ror'>ror&lt;/a>, &lt;a href='http://www.diigo.com/user/ariejan/share'>share&lt;/a>&lt;/p>&lt;li>&lt;p class='diigo-link'>&lt;a href="http://rubypond.com/articles/2008/07/16/sexy-forms-in-rails">Sexy Forms in Rails&lt;/a>&lt;/p>&lt;p class='diigo-description'>Easily create very sexy form in Rails with minimum effort. &lt;/p>&lt;p class='diigo-tags'>tags: &lt;a href='http://www.diigo.com/user/ariejan/rails'>rails&lt;/a>, &lt;a href='http://www.diigo.com/user/ariejan/forms'>forms&lt;/a>, &lt;a href='http://www.diigo.com/user/ariejan/sexy'>sexy&lt;/a>, &lt;a href='http://www.diigo.com/user/ariejan/share'>share&lt;/a>&lt;/p>&lt;/ul></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2008/07/17/ariejannet-link-party-07172008/</guid><pubDate>Thu, 17 Jul 2008 00:00:00 +0000</pubDate></item><item><title>Photography Heaven</title><link>https://www.devroom.io/2008/07/11/photography-heaven/</link><description>&lt;p>&lt;img align="right" src='http://farm3.static.flickr.com/2194/2541506824_21b5755d65_m.jpg' alt='Photography Heaven'/>For those who didn&amp;rsquo;t know yet, I&amp;rsquo;m on &lt;a href="http://flickr.com/photos/ariejan">Flickr&lt;/a> - and I just reorganized my entire photo collection.&lt;/p>
&lt;p>Back in the day when I signed up with Flickr, my idea was to store all my photo&amp;rsquo;s there and be done with it. Now, I realize that I only want to upload the good stuff, and keep the rest for myself.&lt;/p>
&lt;p>Please, hop by at &lt;a href="http://flickr.com/photos/ariejan">&lt;a href="http://flickr.com/photos/ariejan">http://flickr.com/photos/ariejan&lt;/a>&lt;/a>. Feel free to add me to your contacts or to leave a comment.&lt;/p>
&lt;p>For those interested, I&amp;rsquo;m currently shooting with a Canon EOS 400D Digital in combination with the EF-S 18-55mm 1:3.5-5.6 II kit lens, an old and battered EF 75-300mm 1:4-5.6 and very new and shiny EF 50mm 1:1.8 II Prime lens.&lt;/p>
&lt;p>I am looking to buy an EF 17-40mm L or EF 16-35mm L lens, so if you have one that&amp;rsquo;s just sitting there, please let me know.&lt;/p>
&lt;p>That&amp;rsquo;s all. Hope to see from you on Flicrk!&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2008/07/11/photography-heaven/</guid><pubDate>Fri, 11 Jul 2008 00:00:00 +0000</pubDate></item><item><title>Ariejan.net Link Party 07/10/2008</title><link>https://www.devroom.io/2008/07/10/ariejannet-link-party-07102008/</link><description>&lt;ul class='diigo-linkroll'>&lt;li>&lt;p class='diigo-link'>&lt;a href="http://newteevee.com/2008/07/09/the-pirate-bay-wants-to-encrypt-the-entire-internet">The Pirate Bay Wants to Encrypt the Entire Internet « NewTeeVee&lt;/a>&lt;/p>&lt;p class='diigo-tags'>tags: &lt;a href='http://www.diigo.com/user/ariejan/"pirate bay"'>pirate bay&lt;/a>, &lt;a href='http://www.diigo.com/user/ariejan/privacy'>privacy&lt;/a>, &lt;a href='http://www.diigo.com/user/ariejan/encryption'>encryption&lt;/a>, &lt;a href='http://www.diigo.com/user/ariejan/internets'>internets&lt;/a>, &lt;a href='http://www.diigo.com/user/ariejan/share'>share&lt;/a>&lt;/p>&lt;li>&lt;p class='diigo-link'>&lt;a href="http://antifavicon.com">Favicon Generator&lt;/a>&lt;/p>&lt;p class='diigo-description'>Create a favicon based on text and colours selected by you. Very nice.&lt;/p>&lt;p class='diigo-tags'>tags: &lt;a href='http://www.diigo.com/user/ariejan/favicon'>favicon&lt;/a>, &lt;a href='http://www.diigo.com/user/ariejan/design'>design&lt;/a>, &lt;a href='http://www.diigo.com/user/ariejan/generator'>generator&lt;/a>, &lt;a href='http://www.diigo.com/user/ariejan/web'>web&lt;/a>, &lt;a href='http://www.diigo.com/user/ariejan/webdesign'>webdesign&lt;/a>, &lt;a href='http://www.diigo.com/user/ariejan/icons'>icons&lt;/a>, &lt;a href='http://www.diigo.com/user/ariejan/tool'>tool&lt;/a>, &lt;a href='http://www.diigo.com/user/ariejan/graphics'>graphics&lt;/a>, &lt;a href='http://www.diigo.com/user/ariejan/share'>share&lt;/a>&lt;/p>&lt;li>&lt;p class='diigo-link'>&lt;a href="http://www.spheredev.org/wiki/Git_for_the_lazy">Git for the lazy - Spheriki&lt;/a>&lt;/p>&lt;p class='diigo-tags'>tags: &lt;a href='http://www.diigo.com/user/ariejan/spheriki'>spheriki&lt;/a>, &lt;a href='http://www.diigo.com/user/ariejan/git'>git&lt;/a>, &lt;a href='http://www.diigo.com/user/ariejan/usage'>usage&lt;/a>, &lt;a href='http://www.diigo.com/user/ariejan/tutorial'>tutorial&lt;/a>, &lt;a href='http://www.diigo.com/user/ariejan/tricks'>tricks&lt;/a>, &lt;a href='http://www.diigo.com/user/ariejan/tips'>tips&lt;/a>, &lt;a href='http://www.diigo.com/user/ariejan/share'>share&lt;/a>&lt;/p>&lt;li>&lt;p class='diigo-link'>&lt;a href="http://digital-photography-school.com/blog/10-ways-to-take-stunning-portraits">10 Ways to Take Stunning Portraits&lt;/a>&lt;/p>&lt;p class='diigo-tags'>tags: &lt;a href='http://www.diigo.com/user/ariejan/portraits'>portraits&lt;/a>, &lt;a href='http://www.diigo.com/user/ariejan/photography'>photography&lt;/a>, &lt;a href='http://www.diigo.com/user/ariejan/share'>share&lt;/a>&lt;/p>&lt;/ul></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2008/07/10/ariejannet-link-party-07102008/</guid><pubDate>Thu, 10 Jul 2008 00:00:00 +0000</pubDate></item><item><title>How to digg-proof your WordPress blog</title><link>https://www.devroom.io/2008/07/09/how-to-digg-proof-your-wordpress-blog/</link><description>&lt;p>Every blogger hopes to be able to say some day to his friends: &amp;ldquo;I got Dugg!&amp;rdquo;.&lt;/p>
&lt;p>It means you&amp;rsquo;ve written something special that a lot of other people are interested in and it gives you a big ego boost. But there won&amp;rsquo;t be much to enjoy when your server can&amp;rsquo;t handle the extra work.&lt;/p>
&lt;p>In this post I&amp;rsquo;ll try to explain some measures you can take to be ready for when &amp;ldquo;The Big Digg&amp;rdquo; arrives at your blog&amp;rsquo;s door step. I&amp;rsquo;ll focus on WordPress in particular here, because that&amp;rsquo;s what I&amp;rsquo;m using. However, the concepts I&amp;rsquo;ll show you can be applied to any web site.&lt;/p>
&lt;p>There are a few things you can do to survive when you get Dugg. The key to my tips here is that you can implement them at (almost) no cost in advance of getting dugg.&lt;/p>
&lt;p>It&amp;rsquo;s important to understand that once you&amp;rsquo;re blog get&amp;rsquo;s dugg, you don&amp;rsquo;t have time to install a plugin or make any other sort of change. The only thing you can do when you&amp;rsquo;re not prepared is take down your web server (including any other apps than your blog).&lt;/p>
&lt;p>Trust me, you don&amp;rsquo;t want to take down your blog when you got dugg.&lt;/p>
&lt;p>Another obvious constraint is upgrading hardware. You simply can&amp;rsquo;t move to a new server or upgrade your RAM when you&amp;rsquo;re getting dugg.&lt;/p>
&lt;p>The key is to be prepared. Here are some tips that will help you get ready for when the day comes&amp;hellip;&lt;/p>
&lt;p>&lt;strong>Caching&lt;/strong>&lt;/p>
&lt;p>WordPress, by default, is most likely not able to survive the Digg-effect. This is mainly because every page served requires WP to query your database a few dozen times and compile it all together in that nice looking template of yours.&lt;/p>
&lt;p>So, the most straight forward measure you can take to improve performance is caching. By caching your blog, you basically create static HTML files that will be served to visitors instead of the generated page mentioned earlier.&lt;/p>
&lt;p>Web servers are very good at serving static files. It&amp;rsquo;s ultra-fast. Also, it reduces the load on your server because you&amp;rsquo;re database doesn&amp;rsquo;t have to be woken on every request and there&amp;rsquo;s no PHP code that needs to be executed.&lt;/p>
&lt;p>Of course, your blog is dynamic (at least if you post new things occasionally). This means that you&amp;rsquo;ll need to update those static HTML files regularly to keep them up-to-date.&lt;/p>
&lt;p>To implement caching in your WordPress blog you don&amp;rsquo;t have to do all that much work, really. All you need is the &lt;a href="http://wordpress.org/extend/plugins/wp-super-cache/">WP Super Cache&lt;/a> plugin.&lt;/p>
&lt;p>The plugin takes care of generating static HTML files for all your blog pages and make sure Apache (which is the most commonly used webserver for WordPress) serves these pages &lt;em>very fast&lt;/em>.&lt;/p>
&lt;p>&lt;strong>Compression&lt;/strong>&lt;/p>
&lt;p>Another measure you can take, in combination with caching, is compressing the static HTML files. Because compressing is a one-time action when the page gets cached, you get a very small file that can be served by your web server even faster with minimal bandwidth usage.&lt;/p>
&lt;p>As you already suspect, WP Super Cache has this feature included. Nice ;-)&lt;/p>
&lt;p>As an added bonus, WP Super Cache will not perform caching for logged in users, users who&amp;rsquo;ve left a comment or users who&amp;rsquo;ve accessed password protected pages. This means that if a user leaves a comment, he will not see the &amp;lsquo;static&amp;rsquo; cached file (which does not include the comment yet), but the generated version of your blog with the comment.&lt;/p>
&lt;p>&lt;strong>Hardware and platform&lt;/strong>&lt;/p>
&lt;p>Another, very obvious, factor to take into account is your hardware and platform.&lt;/p>
&lt;p>If you&amp;rsquo;re on a shared hosting account you won&amp;rsquo;t survive a Digg. Your provider will shut down your site quite quickly. They do this because there are quite a few users (think hundreds) on the same server as your blog. When dugg, your site will take up a very large portion of the available server capacity, leaving all other users with an usable blog as well.&lt;/p>
&lt;p>My advice is, if you&amp;rsquo;re serious about your blog, to host it on a VPS or dedicated server. These forms of hosting guarantee you the resources you paid for and will only cause trouble to your own web sites.&lt;/p>
&lt;p>For most people a dedicated server is not an option, simply because it&amp;rsquo;s too expensive for running a sole blog.&lt;/p>
&lt;p>A VPS (Virtual Private Server) can be a good compromise between shared hosting and a dedicated server. You are guaranteed a certain amount of memory, disk space and processing power and your VPS won&amp;rsquo;t interfere with other VPS&amp;rsquo;es on the same server (unless your hosting provider uses crappy VPS software. Always look for Xen VPS hosting).&lt;/p>
&lt;p>The most important factor in choosing a VPS is the available, or guaranteed, memory you get. You can get &amp;lsquo;slices&amp;rsquo; ranging from 128Mb up to 4Gb of RAM. For an average blog I think 256 - 512Mb should be sufficient for running Apache smoothly and handling large amounts of traffic on the static files.&lt;/p>
&lt;p>I can&amp;rsquo;t help to plug &lt;a href="https://manage.slicehost.com/customers/new?referrer=1424291610">SliceHost&lt;/a> here. I recently moved Ariejan.net to a slice (a 512Mb one, I also run some Ruby on Rails apps) and I&amp;rsquo;ve been very happy with them ever since. &lt;a href="https://manage.slicehost.com/customers/new?referrer=1424291610">So go buy a slice now! I&amp;rsquo;d make me happy ;-)&lt;/a>&lt;/p>
&lt;p>Anyway, it&amp;rsquo;s important to make sure you run your blog on a platform that can handle a peak in traffic and run your blog smoothly all the time.&lt;/p>
&lt;p>&lt;strong>What else? Remove all the cruft&lt;/strong>&lt;/p>
&lt;p>What else can you do to improve performance on your WordPress blog. Well, it would be to remove everything that&amp;rsquo;s not really needed there. Dynamic JavaScripts that retrieve and parse a remote RSS Feed with every request are a classic example of cruft.&lt;/p>
&lt;p>There are a lot of nice plugins available for WordPress and there are even more &amp;lsquo;widgets&amp;rsquo;, &amp;lsquo;badges&amp;rsquo; and &amp;lsquo;buttons&amp;rsquo; you can put on your blog to show off. But every one of these will slow down the loading off your site. Especially the ones that come in the form of WordPress plugins.&lt;/p>
&lt;p>Almost every plugin that adds stuff to your sidebar will include its own stylesheets, javascript files and images that need to be downloaded from your server. Even with a cached, static HTML file these need to be downloaded.&lt;/p>
&lt;p>The point is, add a few of these nice plugins, and you&amp;rsquo;re web server has to handle up to 15x the amount of files per page view.&lt;/p>
&lt;p>My advise is to keep plugins and usage of javascript, stylesheet and most importantly images to an absolute minimum. I don&amp;rsquo;t want to encourage you to write a plain text file as a blog, but keep things functional.&lt;/p>
&lt;p>&lt;strong>Do you have any other tips?&lt;/strong>&lt;/p>
&lt;p>Sure, lots.&lt;/p>
&lt;p>Besides keeping your site up-and-running while getting dugg, it&amp;rsquo;s important to keep records. I personally like &lt;a href="http://www.google.com/analytics">Goole Analytics&lt;/a> a lot, because it gives a lot of information I haven&amp;rsquo;t seen from any other log or traffic analyzer. By gathering these statistics you can analyze later what page(s) on your blog got dugg, where all those people came from and what else they did on your site.&lt;/p>
&lt;p>Another tip is: make backups! This may sound as a stupid tip to most, but you won&amp;rsquo;t believe how many people run a blog without ever making a backup. This hasn&amp;rsquo;t much to do with getting dugg, but when terror strikes, you&amp;rsquo;ll be happy to have a spare copy of all your posts and comments.&lt;/p>
&lt;p>&lt;strong>Famous last words&amp;hellip;&lt;/strong>&lt;/p>
&lt;p>Well, to test out all my tips, &lt;a href="http://digg.com/software/How_to_digg_proof_your_WordPress_blog">please digg this story&lt;/a> to see if we can get Ariejan.net dugg for once ;-)&lt;/p>
&lt;p>Thank you for reading. Cheers.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2008/07/09/how-to-digg-proof-your-wordpress-blog/</guid><pubDate>Wed, 09 Jul 2008 00:00:00 +0000</pubDate></item><item><title>Ariejan.net Link Party 07/08/2008</title><link>https://www.devroom.io/2008/07/08/ariejannet-link-party-07082008/</link><description>&lt;ul class='diigo-linkroll'>&lt;li>&lt;p class='diigo-link'>&lt;a href="http://wiki.wegame.com/StarCraft_2_Gameplay">StarCraft 2 Gameplay&lt;/a>&lt;/p>&lt;p class='diigo-tags'>tags: &lt;a href='http://www.diigo.com/user/ariejan/starcraft'>starcraft&lt;/a>, &lt;a href='http://www.diigo.com/user/ariejan/blizzard'>blizzard&lt;/a>, &lt;a href='http://www.diigo.com/user/ariejan/gaming'>gaming&lt;/a>, &lt;a href='http://www.diigo.com/user/ariejan/share'>share&lt;/a>&lt;/p>&lt;li>&lt;p class='diigo-link'>&lt;a href="http://sixrevisions.com/resources/cheat_sheets_web_developer">Cheat Sheets for Front-end Web Developers&lt;/a>&lt;/p>&lt;p class='diigo-tags'>tags: &lt;a href='http://www.diigo.com/user/ariejan/frontend'>frontend&lt;/a>, &lt;a href='http://www.diigo.com/user/ariejan/cheat'>cheat&lt;/a>, &lt;a href='http://www.diigo.com/user/ariejan/cheatsheet'>cheatsheet&lt;/a>, &lt;a href='http://www.diigo.com/user/ariejan/html'>html&lt;/a>, &lt;a href='http://www.diigo.com/user/ariejan/css'>css&lt;/a>, &lt;a href='http://www.diigo.com/user/ariejan/share'>share&lt;/a>&lt;/p>&lt;li>&lt;p class='diigo-link'>&lt;a href="http://vandelaydesign.com/blog/design/photoshop-lighting-effects">40 Photoshop Tutorials for Lighting and Abstract Effects | Vandelay Website Design&lt;/a>&lt;/p>&lt;p class='diigo-tags'>tags: &lt;a href='http://www.diigo.com/user/ariejan/vandelay'>vandelay&lt;/a>, &lt;a href='http://www.diigo.com/user/ariejan/photoshop'>photoshop&lt;/a>, &lt;a href='http://www.diigo.com/user/ariejan/tutorials'>tutorials&lt;/a>, &lt;a href='http://www.diigo.com/user/ariejan/effects'>effects&lt;/a>, &lt;a href='http://www.diigo.com/user/ariejan/share'>share&lt;/a>&lt;/p>&lt;li>&lt;p class='diigo-link'>&lt;a href="http://www.rubyinside.com/trollop-command-line-option-parser-for-ruby-944.html">Trollop: Simple Yet Powerful Command Line Option Processor&lt;/a>&lt;/p>&lt;p class='diigo-tags'>tags: &lt;a href='http://www.diigo.com/user/ariejan/trollop'>trollop&lt;/a>, &lt;a href='http://www.diigo.com/user/ariejan/ruby'>ruby&lt;/a>, &lt;a href='http://www.diigo.com/user/ariejan/cli'>cli&lt;/a>, &lt;a href='http://www.diigo.com/user/ariejan/"option processor"'>option processor&lt;/a>, &lt;a href='http://www.diigo.com/user/ariejan/share'>share&lt;/a>&lt;/p>&lt;li>&lt;p class='diigo-link'>&lt;a href="http://typetester.maratz.com">Typetester – Compare fonts for the screen&lt;/a>&lt;/p>&lt;p class='diigo-tags'>tags: &lt;a href='http://www.diigo.com/user/ariejan/fonts'>fonts&lt;/a>, &lt;a href='http://www.diigo.com/user/ariejan/design'>design&lt;/a>, &lt;a href='http://www.diigo.com/user/ariejan/typography'>typography&lt;/a>, &lt;a href='http://www.diigo.com/user/ariejan/webdesign'>webdesign&lt;/a>, &lt;a href='http://www.diigo.com/user/ariejan/css'>css&lt;/a>, &lt;a href='http://www.diigo.com/user/ariejan/tools'>tools&lt;/a>, &lt;a href='http://www.diigo.com/user/ariejan/web'>web&lt;/a>, &lt;a href='http://www.diigo.com/user/ariejan/font'>font&lt;/a>, &lt;a href='http://www.diigo.com/user/ariejan/share'>share&lt;/a>&lt;/p>&lt;li>&lt;p class='diigo-link'>&lt;a href="http://www.longren.org/wordpress/unwakeable">Unwakeable Theme at T. Longren&lt;/a>&lt;/p>&lt;p class='diigo-description'>lso added some more functionality to this theme. For example&lt;/p>&lt;p class='diigo-tags'>tags: &lt;a href='http://www.diigo.com/user/ariejan/unwakeable'>unwakeable&lt;/a>, &lt;a href='http://www.diigo.com/user/ariejan/longren'>longren&lt;/a>, &lt;a href='http://www.diigo.com/user/ariejan/wordpress'>wordpress&lt;/a>, &lt;a href='http://www.diigo.com/user/ariejan/theme'>theme&lt;/a>, &lt;a href='http://www.diigo.com/user/ariejan/share'>share&lt;/a>&lt;/p>&lt;/ul></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2008/07/08/ariejannet-link-party-07082008/</guid><pubDate>Tue, 08 Jul 2008 00:00:00 +0000</pubDate></item><item><title>Zoek jij 'n uitdagende baan??</title><link>https://www.devroom.io/2008/06/06/zoek-jij-n-uitdagende-baan/</link><description>&lt;p>&lt;em>First off, sorry to all the English-reading people, but this post is intended for my Dutch audience.&lt;/em>&lt;/p>
&lt;p>Even wat updates voor mijn Nederlandstalige publiek. Ik heb wat nieuwtjes voor jullie, dus lees snel verder!&lt;/p>
&lt;p>Allereerst wil ik even melden dat ik a.s. dinsdag (10 juni) te vinden zal zijn op &lt;a href="http://2008.rubyenrails.nl/">RubyEnRails 2008&lt;/a>. Dus, ben jij er ook, laat &amp;rsquo;t me even weten! Laat even &amp;rsquo;n commentaartje achter, of nog beter: bel/sms me even dinsdag op 06-17103624, dan spreken we elkaar zeker!&lt;/p>
&lt;p>Dan nog even een oproep voor alle Nederlandse Ruby, Java en PHP developers. Als jij echt coole webapps kan (of wilt gaan) maken, dan moeten we praten! Kabisa zoekt namelijk op korte termijn Ruby en Java developers. Ik kan er zelf over meepraten: bij Kabisa werken is leuk, afwisselend en het biedt je alles wat je van een goede ICT job mag verwachten.&lt;/p>
&lt;p>Spreek me dinsdag even aan (zie m&amp;rsquo;n nummer hierboven) als je meer wilt weten over werken bij Kabisa! Of maak gewoon voor de lol even &amp;rsquo;n praatje!&lt;/p>
&lt;p>&lt;strong>Ben jij &amp;rsquo;n Ruby, Java of PHP developer - en ben jij toe aan een leuke ICT baan? &lt;a href="mailto:ariejan@ariejan.net">Neem dan &amp;rsquo;ns contact met me op&lt;/a>, want ik ben op zoek naar collega&amp;rsquo;s! &lt;/strong>&lt;/p>
&lt;p>Tot dinsdag bij Ruby en Rails 2008!!!&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2008/06/06/zoek-jij-n-uitdagende-baan/</guid><pubDate>Fri, 06 Jun 2008 00:00:00 +0000</pubDate></item><item><title>The best IT books hand-picked for you!</title><link>https://www.devroom.io/2008/05/30/the-best-it-books-hand-picked-for-you/</link><description>&lt;p>Are you ready to dive into Rails? Want to familiarize yourself with the deeper dungeons of Ruby? Are you an aspiring game developer? Or maybe you just want to learn how to use Git or Subversion effectively?&lt;/p>
&lt;p>In any case, I&amp;rsquo;ve opened up a &lt;a href="http://astore.amazon.com/ariejannet-20">little book shop&lt;/a> with a hand-picked selection of books on a variety of subjects, included &lt;a href="http://astore.amazon.com/ariejannet-20/102-6938134-0590555?%5Fencoding=UTF8&amp;node=1">Ruby and Rails&lt;/a>, &lt;a href="http://astore.amazon.com/ariejannet-20/102-6938134-0590555?%5Fencoding=UTF8&amp;node=4">Game Development (with Java)&lt;/a> and &lt;a href="http://astore.amazon.com/ariejannet-20/102-6938134-0590555?%5Fencoding=UTF8&amp;node=2">Version Control&lt;/a>.&lt;/p>
&lt;p>Please, feel free to take a look around. All books are priced the same as Amazon.com, and you&amp;rsquo;d be supporting Ariejan.net in the process.&lt;/p>
&lt;p>I&amp;rsquo;m also open to suggestions for other categories with some hand-picked books. I&amp;rsquo;m already planning on Java and Databases categories. Feel free to post a comment with your tips.&lt;/p>
&lt;p>&lt;strong>Thanks for shopping a &lt;a href="http://astore.amazon.com/ariejannet-20">Ariejan&amp;rsquo;s Book Shop!&lt;/a>&lt;/strong>&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2008/05/30/the-best-it-books-hand-picked-for-you/</guid><pubDate>Fri, 30 May 2008 00:00:00 +0000</pubDate></item><item><title>The migration that cannot be undone: Irreversible Migration</title><link>https://www.devroom.io/2008/05/06/the-migration-that-cannot-be-undone-irreversible-migration/</link><description>&lt;p>Migrations have up and down methods, as we all know. But in some cases, your up method does things you can&amp;rsquo;t undo in your down method.&lt;/p>
&lt;p>For example:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nc">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="nf">up&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># Change the zipcode from the current :integer to a :string type.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">change_column&lt;/span> &lt;span class="ss">:address&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:zipcode&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now, converting integers to strings will always work. But, you feel it coming, converting a string into an integer will not always be possible. In other words, we can&amp;rsquo;t reverse this migration.&lt;/p>
&lt;p>That&amp;rsquo;s why we should raise ActiveRecord::IrreversibleMigration in the down method.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nc">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="nf">down&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">raise&lt;/span> &lt;span class="no">ActiveRecord&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="no">IrreversibleMigration&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now, if you run your migration (upwards), you&amp;rsquo;ll see it being applied like it shoud. However, if you try to go back, you&amp;rsquo;ll see rake aborting with ActiveRecord::IrreversibleMigration.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">$ rake db:migrate &lt;span class="nv">VERSION&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">4&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-- Database is migrated
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ rake db:migrate &lt;span class="nv">VERSION&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">3&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-- Rake aborted!
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-- ActiveRecord::IrreversibleMigration
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>So, if you have things you can&amp;rsquo;t undo, raise ActiveRecord::IrreversibleMigration in your migration&amp;rsquo;s down method.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2008/05/06/the-migration-that-cannot-be-undone-irreversible-migration/</guid><pubDate>Tue, 06 May 2008 00:00:00 +0000</pubDate></item><item><title>How to: Compile packages on Debian/Ubuntu by hand</title><link>https://www.devroom.io/2008/05/04/how-to-compile-packages-on-debianubuntu-by-hand/</link><description>&lt;p>In some very rare situations you may find yourself in the need to recompile a Debian (or Ubuntu) package. Luckily for all of use, the great Debian packaging system makes this a piece of cake.&lt;/p>
&lt;p>Let&amp;rsquo;s say we want to recompile mod_python for apache 2 to hook in to python 2.5, instead of the default 2.4.&lt;/p>
&lt;p>First, get every thing installed you may need to build the libapache2-mod-python package.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">$ apt-get build-dep libapache2-mod-python
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Okay, next let&amp;rsquo;s grab the source for the package. The source will be unpacked to your current working directory, so it may be a good idea to create a seperate directory for this.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">$ mkdir src
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ &lt;span class="nb">cd&lt;/span> src
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ apt-get &lt;span class="nb">source&lt;/span> libapache2-mod-python
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>In the case of this example, you don&amp;rsquo;t need to do anything special to get python 2.5 linked. Just install the python2.5 and python2.5-dev packages.&lt;/p>
&lt;p>&lt;em>If you need to apply patches to the source or need to make any other modifications, this is the time!&lt;/em>&lt;/p>
&lt;p>Okay, now go to the source directory and build the package. This is the tricky command here:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">$ &lt;span class="nb">cd&lt;/span> libapache2-mod-python
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ dpkg-buildpackage -rfakeroot -b
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This will build the package. You will get some warnings and errors about missing GPG keys. This is okay. You are not the package maintainer, so your packages should not be marked as &amp;lsquo;original&amp;rsquo;.&lt;/p>
&lt;p>You&amp;rsquo;re now ready to install your compiled package.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">dpkg -i ../libapache2-mod-python-3.3.1-3-i386.deb
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>That&amp;rsquo;s all! You compiled and installed a package from source!&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2008/05/04/how-to-compile-packages-on-debianubuntu-by-hand/</guid><pubDate>Sun, 04 May 2008 00:00:00 +0000</pubDate></item><item><title>GIT: Using the stash</title><link>https://www.devroom.io/2008/04/23/git-using-the-stash/</link><description>&lt;p>I bet the following has happened to you: you are happily working on a project
and are in the middle of something. You are not ready to commit your changes,
because you your tests don&amp;rsquo;t pass yet. Then your client calls with a bug report
that needs to be fixed &lt;strong>right now&lt;/strong>. (You know how clients can
be.)&lt;/p>
&lt;p>So, what do you do? Throw away your current changes to make the patch? Checkout
a clean copy of your project to make the changes? No! You just stash your
changes away, and make the patch! Afterward you grab your changes back and
continue work.&lt;/p>
&lt;p>Git features &lt;em>The Stash&lt;/em>, which is as much as a good place to store uncommitted
changes. When you stash you changes, the will be stored, and your working copy
will be reverted to HEAD (the last commit revision) of your code.&lt;/p>
&lt;p>When you restore your stash, you changes are reapplied and you continue working
on your code.&lt;/p>
&lt;h2 id="stash-your-current-changes">Stash your current changes&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">$ git stash save &amp;lt;optional message &lt;span class="k">for&lt;/span> later reference&amp;gt;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Saved &lt;span class="s2">&amp;#34;WIP on master: e71813e...&amp;#34;&lt;/span>&amp;lt;/pre&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="list-current-stashes">List current stashes&lt;/h2>
&lt;p>Yes, you can have more than one!! The stash works like a stack. Every time you
save a new stash, it&amp;rsquo;s put on top of the stack.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">$ git stash list
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">stash@&lt;span class="o">{&lt;/span>0&lt;span class="o">}&lt;/span>: WIP on master: e71813e...
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Note the &lt;code>stash@{0}&lt;/code> part? That&amp;rsquo;s your stash ID, you&amp;rsquo;ll need it to restore it
later on. Let&amp;rsquo;s do that right now. The stash ID changes with every stash you
make. &lt;code>stash@{0}&lt;/code> refers to the last stash you made.&lt;/p>
&lt;h2 id="apply-a-stash">Apply a stash&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">git stash apply stash@&lt;span class="o">{&lt;/span>0&lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>You may notice the stash is still there after you have applied it. You can drop
it if you don&amp;rsquo;t need it any more.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">git stash drop stash@&lt;span class="o">{&lt;/span>0&lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Or, because the stash acts like a stack, you can pop off the last stash you
saved:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">git stash pop
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>If you want to wipe all your stashes away, run the &amp;lsquo;clear&amp;rsquo; command:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">git stash clear
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>It may very well be that you don&amp;rsquo;t use stashes that often. If you just want to
quickly stash your changes to restore them later, you can leave out the stash
ID.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">$ git stash
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># ...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ git stash pop
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Feel free to experiment with the stash before using it on some really important
work.&lt;/p>
&lt;p>&lt;strong>Please leave a comment if you like this article, and would like to see more
Git related stuff here on Ariejan.net.&lt;/strong>&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2008/04/23/git-using-the-stash/</guid><pubDate>Wed, 23 Apr 2008 00:00:00 +0000</pubDate></item><item><title>Permanently redirect WordPress pages</title><link>https://www.devroom.io/2008/04/15/permanently-redirect-wordpress-pages/</link><description>&lt;p>After my trip to Mephisto some time back, I noticed that some pages were accessible from different URLs. After moving back to WordPress, these permalinks no longer work.&lt;/p>
&lt;p>I&amp;rsquo;m running WordPress with Apache2, so it shouldn&amp;rsquo;t be too hard redirect those old permalinks to their new locations. (That&amp;rsquo;s what rewriting is all about anyway).&lt;/p>
&lt;p>Here is the default .htaccess file generated by WordPress:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl"># BEGIN WordPress
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;lt;IfModule mod_rewrite.c&amp;gt;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">RewriteEngine On
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">RewriteBase /
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">RewriteCond %{REQUEST_FILENAME} !-f
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">RewriteCond %{REQUEST_FILENAME} !-d
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">RewriteRule . /index.php [L]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;lt;/IfModule&amp;gt;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># END WordPress
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>As I said, it&amp;rsquo;s &lt;strong>generated&lt;/strong> by WordPress. It would be very unwise to edit this, because our changes might get lost in the future.&lt;/p>
&lt;p>The solution is simple. Add another block above the generated one that does permanent redirects. In this example I rewrite &lt;a href="http://ariejan.net/pages/svnsheet">pages/svnsheet&lt;/a> to &lt;a href="http://ariejan.net/svncheatsheet">&lt;a href="http://ariejan.net/svncheatsheet">http://ariejan.net/svncheatsheet&lt;/a>&lt;/a>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">&amp;lt;IfModule mod_rewrite.c&amp;gt;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">RewriteEngine On
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">RewriteBase /
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">RewriteCond %{REQUEST_FILENAME} !-f
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">RewriteCond %{REQUEST_FILENAME} !-d
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">RewriteRule ^pages/svnsheet$ http://ariejan.net/svncheatsheet [R=301,L]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;lt;/IfModule&amp;gt;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># BEGIN WordPress
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;lt;IfModule mod_rewrite.c&amp;gt;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">RewriteEngine On
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">RewriteBase /
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">RewriteCond %{REQUEST_FILENAME} !-f
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">RewriteCond %{REQUEST_FILENAME} !-d
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">RewriteRule . /index.php [L]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;lt;/IfModule&amp;gt;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># END WordPress
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>FYI, the R=301 means the browser (or search bot) receives a &amp;ldquo;Moved Permanently&amp;rdquo; message. L means this is the &lt;strong>l&lt;/strong>ast rewrite rule to read.&lt;/p>
&lt;p>Of course, you can go nuts and add all sorts of funky regular expressions to rewrite URLs.&lt;/p>
&lt;p>The nice thing is that WordPress will only replaces the block between the BEGIN and END tags, keeping your rewrites in tact.&lt;/p>
&lt;p>Note: don&amp;rsquo;t forget to restart apache if your rewrites don&amp;rsquo;t seem to work.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2008/04/15/permanently-redirect-wordpress-pages/</guid><pubDate>Tue, 15 Apr 2008 00:00:00 +0000</pubDate></item><item><title>Debian Etch: RMagick LoadError</title><link>https://www.devroom.io/2008/04/10/debian-etch-rmagick-loaderror/</link><description>&lt;p>If you&amp;rsquo;re on Debian Etch, you may encounter the following error&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">libMagickWand.so.1: cannot open shared object file: No such file or directory - /usr/lib/ruby/gems/1.8/gems/rmagick-2.3.0/lib/RMagick2.so
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This basically means that the libMagickWand.so.1 file cannot be found. However, it is available on your system. All you need to do to fix it, is tell your box to look in the right place for the file.&lt;/p>
&lt;p>To fix this issue once and for all, open up /etc/ld.so.conf.d/whatever_file_is_here. The whatever_file_is_here is named after the kernel you have installed.&lt;/p>
&lt;p>In this file, add the following line at the bottom&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">/usr/local/lib
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Save the file and next run the &amp;rsquo;ldconfig&amp;rsquo; command. This will reread the configuration file you just edited. Now, restart your Rails app and you&amp;rsquo;ll notice the error is gone and all is good again.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">ldconfig
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This change will be kept after you reboot, so you won&amp;rsquo;t encounter this error any time soon again.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2008/04/10/debian-etch-rmagick-loaderror/</guid><pubDate>Thu, 10 Apr 2008 00:00:00 +0000</pubDate></item><item><title>Enabling Trac Email notifications</title><link>https://www.devroom.io/2008/04/09/enabling-trac-email-notifications/</link><description>&lt;p>If you&amp;rsquo;ve ever reported a but to Ruby on Rails, you&amp;rsquo;ll have noticed that their Trac has nice email notification feature. And I bet you want that in your Trac as well!&lt;/p>
&lt;p>Now, email notification are nothing exotic. You don&amp;rsquo;t need any plugins, just an outgoing SMTP server and access to your trac.ini file.&lt;/p>
&lt;p>In your trac directory open up conf/trac.ini and look for the [notification] header. Make sure you have at least the following settings. Of course, make sure you enter valid values for your situation.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">always_notify_owner = true
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">always_notify_reporter = true
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">always_notify_updater = true
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">smtp_always_cc = trac-updates@mailinglist # Optional if you want to archive outgoing emails
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">smtp_enabled = true
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">smtp_from = trac@ariejan.net
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">smtp_from_name = Ariejan.net Trac
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">smtp_replyto = noreply@ariejan.net
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">smtp_user =
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">smtp_password =
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">smtp_server = localhost
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">smtp_port = 25
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>There are a few more options like &amp;lsquo;ignore_domains&amp;rsquo; if you don&amp;rsquo;t want to send emails to certain domains.&lt;/p>
&lt;p>Update: good news for you GMail users! There is a &amp;lsquo;use_tls&amp;rsquo; attribute. I think that if you set it to true, you can then specify GMail&amp;rsquo;s SMTP server to send out mails. Of course, a better solution would be to setup Postfix to relay mail to GMail, and just leave Trac to it&amp;rsquo;s default &amp;rsquo;localhost&amp;rsquo; settings.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2008/04/09/enabling-trac-email-notifications/</guid><pubDate>Wed, 09 Apr 2008 00:00:00 +0000</pubDate></item><item><title>Here we go again: WordPress 2.5</title><link>https://www.devroom.io/2008/04/09/here-we-go-again-wordpress-25/</link><description>&lt;p>Okay, here we go then. I&amp;rsquo;ve managed to drop &lt;a href="http://mephistoblog.com/">Mephisto&lt;/a> after only a few weeks of service.&lt;/p>
&lt;p>As a Rails developer, I liked the idea of running my own blog on something Rails. However, &lt;a href="http://mephistoblog.com/">Mephisto&lt;/a> was a big disappointment. Especially compared to &lt;a href="http://wordpress.org/">WordPress 2.5&lt;/a>. Mephisto has been on life support for quite a while now, and it&amp;rsquo;s just too complex to be easy to hack. Now, don&amp;rsquo;t get me wrong. I love hacking Ruby, but not too much on my blog. It &amp;ldquo;should just work&amp;rdquo;.&lt;/p>
&lt;p>The reason I skipped to &lt;a href="http://mephistoblog.com/">Mephisto&lt;/a> in the first place was that my Wordpress 2.3 blog was very slow. I managed to track down the problem to the &lt;a href="http://twitter.com/">Twitter&lt;/a> plugin, which was next to useless anyway.&lt;/p>
&lt;p>Now, with all the new funky stuff in 2.5, I switched back. I haven&amp;rsquo;t managed to write anything substantial in the past weeks anyway, so it wasn&amp;rsquo;t a problem to loose one post.&lt;/p>
&lt;p>There are many reasons for my not posting substantial stuff lately. One of them being my new hobby: photography (&lt;a href="http://ariejan.net/2007/09/25/the-glorious-canon-eos-400d-digital/">link&lt;/a>). Check out &lt;a href="http://flickr.com/photos/ariejan">my Flickr page&lt;/a> and let me know what you think of my more recent photos.&lt;/p>
&lt;p>So long for now. Expect more from me soon.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2008/04/09/here-we-go-again-wordpress-25/</guid><pubDate>Wed, 09 Apr 2008 00:00:00 +0000</pubDate></item><item><title>Rails Snippet: Caching expensive calls</title><link>https://www.devroom.io/2008/04/09/rails-snippet-caching-expensive-calls/</link><description>&lt;p>In Rails, from time to time, you may encounter you have a method you call several times, but which returns always the same result. For example, have the following:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">Person&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="no">ActiveRecord&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="no">Base&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">has_many&lt;/span> &lt;span class="ss">:articles&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">get_approved_articles&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">articles&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">find&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="ss">:all&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:conditions&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="ss">:approved&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="kp">true&lt;/span>&lt;span class="p">},&lt;/span> &lt;span class="ss">:order&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s1">&amp;#39;approved_on DESC&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>A query is fired every time you call Person#get_approved_articles. To cache the result of the query during this request, just add a bit of magic&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">Person&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="no">ActiveRecord&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="no">Base&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">has_many&lt;/span> &lt;span class="ss">:articles&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">get_approved_articles&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="vi">@approved_articles&lt;/span> &lt;span class="o">||=&lt;/span> &lt;span class="nb">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">articles&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">find&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="ss">:all&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:conditions&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="ss">:approved&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="kp">true&lt;/span>&lt;span class="p">},&lt;/span> &lt;span class="ss">:order&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s1">&amp;#39;approved_on DESC&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This will return the @approved_articles value if it exists. If it doesn&amp;rsquo;t, which is the first time you access the method, the query is run and stored in @approved_articles for later use.&lt;/p>
&lt;p>Note: I know it&amp;rsquo;s much easier to define this kind of behaviour, but it&amp;rsquo;s just an illustration.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">Person&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="no">ActiveRecord&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="no">Base&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">has_many&lt;/span> &lt;span class="ss">:articles&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">has_many&lt;/span> &lt;span class="ss">:approved_articles&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:class_name&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s2">&amp;#34;Article&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:conditions&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="ss">:approved&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="kp">true&lt;/span>&lt;span class="p">},&lt;/span> &lt;span class="ss">:order&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s1">&amp;#39;approved_on DESC&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2008/04/09/rails-snippet-caching-expensive-calls/</guid><pubDate>Wed, 09 Apr 2008 00:00:00 +0000</pubDate></item><item><title>Ruby on Rails plugin: Throttler</title><link>https://www.devroom.io/2008/02/07/ruby-on-rails-plugin-throttler/</link><description>&lt;p>For those of you who have missed it: I&amp;rsquo;ve released a plugin yesterday that allows you to throttle your Rails app.&lt;/p>
&lt;p>&lt;a href="http://blog.kabisa.nl/2008/02/05/rails-plugin-throttler/">Read the original announcement and installation/usage instructions&lt;/a>
&lt;a href="http://blog.kabisa.nl/2008/02/07/how-to-put-the-throttler-plugin-to-work/">Read how you can put Throttler to good use in your app&lt;/a>&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2008/02/07/ruby-on-rails-plugin-throttler/</guid><pubDate>Thu, 07 Feb 2008 00:00:00 +0000</pubDate></item><item><title>The Presidents of the United States of America Live</title><link>https://www.devroom.io/2008/01/25/the-presidents-of-the-united-states-of-america-live/</link><description>&lt;img src='http://ariejan.net/wp-content/uploads/2008/01/top_photo_placeholder_400x106shkl.jpg' alt='PUSA (C) Presidentsrock.com' />
&lt;p>Yeah! I got myself some tickets to &lt;a href="http://presidentsrock.com/">The Presidents of the United States of America&lt;/a>, live at Effenaar, Eindhoven.&lt;/p>
&lt;p>To be honest with you, I&amp;rsquo;ve never been to a real concert before, so this is going to be &lt;em>very&lt;/em> much fun for me! Among the few albums I bought back in the year 1995 was PUSA.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2008/01/25/the-presidents-of-the-united-states-of-america-live/</guid><pubDate>Fri, 25 Jan 2008 00:00:00 +0000</pubDate></item><item><title>Your help is needed! - Railsjobs.nl</title><link>https://www.devroom.io/2008/01/22/your-help-is-needed-railsjobsnl/</link><description>&lt;p>Since I started Railsjobs.nl some time ago, I&amp;rsquo;d like to ask you a simple question. Thanks for putting in your $0.02 :)&lt;/p>
&lt;div>{democracy:2}&lt;/div></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2008/01/22/your-help-is-needed-railsjobsnl/</guid><pubDate>Tue, 22 Jan 2008 00:00:00 +0000</pubDate></item><item><title>Roles: Admins pretending to be users!</title><link>https://www.devroom.io/2008/01/19/roles-admins-pretending-to-be-users/</link><description>&lt;p>Most applications have some sort of role system. You have administrators, who can do basically everything, semi-administrators and &amp;ldquo;regular Joe&amp;rdquo; users.&lt;/p>
&lt;p>Now, sometimes you want, as an administrator, be able to login as a user to solve some problem, or manage settings for a user, or whatever.&lt;/p>
&lt;p>Harm did a very nice write-up on how we did this in a Rails project of ours. &lt;a href="http://blog.kabisa.nl/2008/01/19/login_as-how-to-login-as-a-different-user/">You can read the article here&lt;/a>.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2008/01/19/roles-admins-pretending-to-be-users/</guid><pubDate>Sat, 19 Jan 2008 00:00:00 +0000</pubDate></item><item><title>Attack of the Killer Bunnies</title><link>https://www.devroom.io/2008/01/15/attack-of-the-killer-bunnies/</link><description>&lt;div style="float: right; margin-left: 10px; margin-bottom: 10px;">
&lt;a href="http://www.flickr.com/photos/ariejan/2193127815/" title="photo sharing">&lt;img src="http://farm3.static.flickr.com/2211/2193127815_ae6c310c72_m.jpg" alt="" style="border: solid 2px #000000;" />&lt;/a>
&lt;span style="font-size: 0.9em; margin-top: 0px;">
&lt;a href="http://www.flickr.com/photos/ariejan/2193127815/">Bunny killing the garden&lt;/a>
Originally uploaded by &lt;a href="http://www.flickr.com/people/ariejan/">ariejan&lt;/a>
&lt;/span>
&lt;/div>
Well, here they go then! Just swooping in, eating all the green stuff from your garden!! Someone stop them!&lt;br />
&lt;br />
This was taken in the back-yard at Laura's parents house. The neighbours have about a dozen rabbits, but they are allowed to walk around as they wish, occasionally finding their way into other people's gardens.
&lt;br clear="all" /></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2008/01/15/attack-of-the-killer-bunnies/</guid><pubDate>Tue, 15 Jan 2008 00:00:00 +0000</pubDate></item><item><title>Kabisa Blog</title><link>https://www.devroom.io/2008/01/13/kabisa-blog/</link><description>&lt;p>Well, we&amp;rsquo;ve got our &lt;a href="http://blog.kabisa.nl">blog&lt;/a> up and running at Kabisa now! Our entire team will be posting interesting Ruby, Rails, Java and Subversion articles that may be of very good use to you.&lt;/p>
&lt;p>Check out the blog now on &lt;a href="http://blog.kabisa.nl">&lt;a href="http://blog.kabisa.nl">http://blog.kabisa.nl&lt;/a>&lt;/a> or grab the &lt;a href="http://feeds.feedburner.com/Kabisa">RSS Feed&lt;/a> to stay up-to-date.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2008/01/13/kabisa-blog/</guid><pubDate>Sun, 13 Jan 2008 00:00:00 +0000</pubDate></item><item><title>Review: Parking London</title><link>https://www.devroom.io/2008/01/03/review-parking-london/</link><description>&lt;p>&lt;em>This is a paid review&lt;/em>&lt;/p>
&lt;p>I&amp;rsquo;ve been asked to review a service named &lt;a href="http://www.parklet.co.uk/parking/London/129.aspx" target="_blank">Parking London&lt;/a>. What this service does is two things, really. Firstly, it allows owners of garages and parking spaces in London to offer their spots to others. A location can be specified, with a small description. Of course, the price for the monthly tenancy or sale is added.&lt;/p>
&lt;p>The service doesn&amp;rsquo;t look very special at a first glance. Just a listing of available parking spots in London. When I took a closer look, I noticed a link &amp;ldquo;Map view&amp;rdquo;. When I clicked it, a Google map popped up of the UK, and I was able to zoom in and see where all the available parking spaces where. Hovering over the pin points shows the monthly cost for them. Great!&lt;/p>
&lt;p>Being a good reviewer, I setup a scenario where I was a potential buyer who&amp;rsquo;s looking for garage space in London. I have a budget of 250 pounds a month, and I want the garage to be in the north of the river Thames.&lt;/p>
&lt;p>This proved very difficult indeed. With a service like this, I expect to be able to specify some search criteria. I can enter a &amp;lsquo;filter&amp;rsquo; parameter, but this didn&amp;rsquo;t seem to do anything useful.&lt;/p>
&lt;p>There is an option, however to search for locations and postcodes. I gave it a try, but again, I found the results next to useless to me.&lt;/p>
&lt;p>I left the location issues for what they were, and focussed on the pricing. I&amp;rsquo;m not sure what a reasonable price for a garage is, but I set myself an imaginary budget of 250 pounds. I was not able to find any way to select items by price, or sort the listing by price to simplify my search.&lt;/p>
&lt;p>In my opinion, this service is really basic. A lot of time went into making the Google map work correctly, and it really does. It&amp;rsquo;s easy to locate a parking space near a location you want.&lt;/p>
&lt;p>Finding items with a certain pricing, or any other form of search was pretty much impossible. With a little work, this site might be a very useful tool for garage-seeking Londoners.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2008/01/03/review-parking-london/</guid><pubDate>Thu, 03 Jan 2008 00:00:00 +0000</pubDate></item><item><title>Write a DVD-Video from the Linux console</title><link>https://www.devroom.io/2007/12/31/write-a-dvd-video-from-the-linux-console/</link><description>&lt;p>This is probably my last post for this year, and it&amp;rsquo;s not about Ruby on Rails! Or web development! It&amp;rsquo;s about how to burn a DVD-Video from the Linux console.&lt;/p>
&lt;p>I know, there are tons of fancy apps (with a decent GUI) that allow you to create DVD-Video disks from Gnome or KDE. But, my Linux machine has no monitor attached and I don&amp;rsquo;t care for hogging up resources by running X.&lt;/p>
&lt;p>All you really need to burn a DVD-Video is one packages: dvd+rw-tools. If you&amp;rsquo;re on Debian or Ubuntu, just run this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">apt-get install dvd+rw-tools
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>So, I have this VIDEO_TS folder and a blank DVD in my DVD driver (which is known to my system as /dev/sr0). To make a correct DVD, you&amp;rsquo;ll also need an AUDIO_TS folder. It doesn&amp;rsquo;t matter if it&amp;rsquo;s empty, it should be there.&lt;/p>
&lt;p>&lt;em>I&amp;rsquo;m assuming you&amp;rsquo;re all doing this as root. If not, just add &amp;lsquo;sudo&amp;rsquo; in front of every command you perform&lt;/em>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">mkdir AUDIO_TS
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Next, we need to set the correct permissions on the VIDEO_TS, AUDIO_TS and the files contained in these directories.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">chown -R root:root VIDEO_TS AUDIO_TS
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">chmod &lt;span class="m">500&lt;/span> VIDEO_TS AUDIO_TS
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">chmod &lt;span class="m">400&lt;/span> VIDEO_TS/*
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Well, you already have all your files prepped, and you&amp;rsquo;re good to go.&lt;/p>
&lt;p>The following command will burn you a nice video DVD. Make sure a blank DVD is inserted into your drive. As I said before, my drive is located at /dev/sr0. You system is probably different. Check your boot log (dmesg) for messages of a dvd driver.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">growisofs -dvd-compat -Z /dev/sr0 -dvd-video .
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>It&amp;rsquo;s very important not to forget the tailing dot, it tells growisofs that you want to use the current directory as the source for your DVD. Depending on the speed of your burner, you&amp;rsquo;ll have a new DVD to watch in a few minutes. Enjoy! And a happy 2008!&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/12/31/write-a-dvd-video-from-the-linux-console/</guid><pubDate>Mon, 31 Dec 2007 00:00:00 +0000</pubDate></item><item><title>For You: Merry Christmas and a Happy 2008!!!</title><link>https://www.devroom.io/2007/12/20/for-you-merry-christmas-and-a-happy-2008/</link><description>&lt;img src='http://ariejan.net/wp-content/uploads/2007/12/el_kaart_500x324shkl.jpg' alt='Merry Christmas - Happy 2008!!!’' />
&lt;p>We (Laura and I) wish you a very merry Christmas and a happy 2008!&lt;/p>
&lt;p>I hope to welcome you back on Ariejan.net in 2008. Happy Holidays!&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/12/20/for-you-merry-christmas-and-a-happy-2008/</guid><pubDate>Thu, 20 Dec 2007 00:00:00 +0000</pubDate></item><item><title>How to install MySQL on Ubuntu/Debian</title><link>https://www.devroom.io/2007/12/12/how-to-install-mysql-on-ubuntudebian/</link><description>&lt;p>It may seem easy for some, but for others, installing MySQL on Ubuntu or Debian Linux is not an easy task. This article explains to you how to install the MySQL Server and Client packages on a Ubuntu/Debian system.&lt;/p>
&lt;p>First of all, make sure your package management tools are up-to-date. Also make sure you install all the latest software available.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">sudo apt-get update
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo apt-get dist-upgrade
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>After a few moments (or minutes, depending on the state of your system), you&amp;rsquo;re ready to install MySQL.&lt;/p>
&lt;p>By default, recent Ubuntu/Debian systems install a MySQL Server from the 5-branch. This is a good thing, so don&amp;rsquo;t worry.&lt;/p>
&lt;p>First, install the MySQL server and client packages:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">sudo apt-get install mysql-server mysql-client
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>When done, you have a MySQL database read to rock &amp;rsquo;n roll. However, there&amp;rsquo;s more to do.&lt;/p>
&lt;p>You need to set a root password, for starters. MySQL has it&amp;rsquo;s own user accounts, which are not related to the user accounts on your Linux machine. By default, the root account of the MySQL Server is empty. You need to set it. Please replace &amp;lsquo;mypassword&amp;rsquo; with your actual password and myhostname with your actual hostname.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">sudo mysqladmin -u root -h localhost password &lt;span class="s1">&amp;#39;mypassword&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo mysqladmin -u root -h myhostname password &lt;span class="s1">&amp;#39;mypassword&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now, you probably don&amp;rsquo;t want just the MySQL Server. Most likely you have Apache+PHP already installed, and want MySQL to go with that. Here are some libraries you need to install to make MySQL available to PHP:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">sudo apt-get install php5-mysql
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Or for Ruby:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">sudo apt-get install libmysql-ruby
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>You can now access your MySQL server like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">mysql -u root -p
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Have fun using MySQL Server.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/12/12/how-to-install-mysql-on-ubuntudebian/</guid><pubDate>Wed, 12 Dec 2007 00:00:00 +0000</pubDate></item><item><title>Run Internet Explorer 5, 5.5, 6 and 7 natively on Mac OS X Leopard or Tiger</title><link>https://www.devroom.io/2007/12/12/run-internet-explorer-5-55-6-and-7-natively-on-mac-os-x-leopard-or-tiger/</link><description>&lt;p>As a web developer, you probably know all about browsers. They suck. Well, some more than others. But, if you develop apps for Windows users, you&amp;rsquo;ll have to test your app with Internet Explorer.&lt;/p>
&lt;p>&lt;img src='http://ariejan.net/wp-content/uploads/2007/12/ies4osx.thumbnail.jpg' align="right" alt='ies4osx' />Now, as a good Rails developer, I&amp;rsquo;m using a Mac. I can test apps with FireFox, Safari and Opera without problems. But Internet Explorer is always a problem. I used Parallels for a while to run an instance of Windows XP to test with IE. But this in itself presented me with a problem: you can&amp;rsquo;t, without nasty hacks and a lot of trouble, run IE6 en IE7 side-by-side.&lt;/p>
&lt;p>My only option seemed to run two Parallels sessions, one with IE6, the other with IE7. Since every images takes about 5 Gb of disk space, I just wasted 10 Gb of disk space to test web apps with Internet Explorer. This is kind of ridiculous.&lt;/p>
&lt;p>But, now there is a very, very nice solution to this problem that every Mac-oriented web developer should know about: &lt;a href="http://www.kronenberg.org/ies4osx/">ies4osx&lt;/a> This solution uses darwine (±70Mb), a wine version for OS X, using X11 and allows you to install IE 5, 5.5, 6 and even IE7. All at once, right in Mac OS X. You can run any of these browsers, from the same system, all at once in parallel!&lt;/p>
&lt;p>Although this allows me to use Internet Explorer the way I want it to, I&amp;rsquo;m still not a fan&amp;hellip;&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/12/12/run-internet-explorer-5-55-6-and-7-natively-on-mac-os-x-leopard-or-tiger/</guid><pubDate>Wed, 12 Dec 2007 00:00:00 +0000</pubDate></item><item><title>Wil jij mijn collega zijn? - Kabisa is hiring!</title><link>https://www.devroom.io/2007/12/07/wil-jij-mijn-collega-zijn-kabisa-is-hiring/</link><description>&lt;p>&lt;a href='http://jobs.kabisa.nl' title='Ken jij een ICT’er?'>&lt;img src='http://ariejan.net/wp-content/uploads/2007/12/ken_jij_een_ict_er_website.gif' width="220" align="right" alt='Ken jij een ICT’er?' />&lt;/a>&lt;/p>
&lt;p>Zoals je waarschijnlijk al wel weet, werk ik al een tijdje bij &lt;a href="http://kabisa.nl">Kabisa&lt;/a>, en ik ben op zoek naar nieuwe collega&amp;rsquo;s!&lt;/p>
&lt;p>Ben jij een een ICT&amp;rsquo;er (liefst met Java of Ruby ervaring, maar dat is niet vereist), laat dan even je gegevens achter op &lt;a href="http://jobs.kabisa.nl">jobs.kabisa.nl&lt;/a>.&lt;/p>
&lt;p>Ook als je iemand kent die ICT&amp;rsquo;er is, en toe is aan een nieuwe uitdaging, kun je deze persoon opgeven op &lt;a href="http://jobs.kabisa.nl">jobs.kabisa.nl&lt;/a>.&lt;/p>
&lt;p>De eerste 100 aanmeldingen ontvangen een &lt;strong>Nationale Bioscoopbon&lt;/strong>. Bovendien wordt tussen alle aanmeldingen op 29 februari 2008 een &lt;strong>Apple iPod Touch&lt;/strong> verloot!!&lt;/p>
&lt;p>Heb je vragen over Kabisa? &lt;a href="https://www.devroom.io/contact">Neem gerust even contact op met me op&lt;/a>.&lt;/p>
&lt;p>&lt;strong>English version, for the non-Dutch folk&lt;/strong>&lt;/p>
&lt;p>As you may already know, I&amp;rsquo;ve been working at &lt;a href="http://kabisa.nl">Kabisa&lt;/a> for quite some time. Now, I&amp;rsquo;m looking for new colleagues.&lt;/p>
&lt;p>Are you a programmer (with Java or Ruby experience, but it&amp;rsquo;s not required), please leave your credentials at &lt;a href="http://jobs.kabisa.nl">jobs.kabisa.nl&lt;/a>.&lt;/p>
&lt;p>If you know a programmer who is ready for a new challenge, drop his or her credentials (and your own) at &lt;a href="http://jobs.kabisa.nl">jobs.kabisa.nl&lt;/a>.&lt;/p>
&lt;p>The first 100 applications will be rewarded with a national cinema voucher. At February 29th, 2008 we will also pick one of the applications and reward it with an &lt;strong>Apple iPod Touch&lt;/strong>!&lt;/p>
&lt;p>Do you have any questions about working at Kabisa? &lt;a href="https://www.devroom.io/contact">Feel free to contact me now.&lt;/a>&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/12/07/wil-jij-mijn-collega-zijn-kabisa-is-hiring/</guid><pubDate>Fri, 07 Dec 2007 00:00:00 +0000</pubDate></item><item><title>Rails: calculated column caching</title><link>https://www.devroom.io/2007/12/06/rails-calculated-column-caching/</link><description>&lt;p>Sometimes you&amp;rsquo;re working on a Rails project and you think: &amp;ldquo;hey! This should be easy!&amp;rdquo;. Well, most of the time it is. I&amp;rsquo;m working on a project that allows people to rate objects (what they really are doesn&amp;rsquo;t matter at all).&lt;/p>
&lt;p>I&amp;rsquo;m using the &lt;a href="http://juixe.com/svn/acts_as_rateable">acts_as_rateable plugin&lt;/a> which creates an extra database table containing all ratings. I also have a table with my objects. Using the plugin I&amp;rsquo;m now able to do the following:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="n">obj&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">Object&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">find&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="ss">:first&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">obj&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">add_rating&lt;/span> &lt;span class="no">Rating&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">new&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="ss">:rating&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="mi">4&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">obj&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">add_rating&lt;/span> &lt;span class="no">Rating&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">new&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="ss">:rating&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="mi">5&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">obj&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">rating&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="mi">4&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="mi">5&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This works all perfectly, until you want to sort objects by rating. You could construct a huge SQL query to join the two tables, but that&amp;rsquo;s not really efficient, especially when your database gets bigger.&lt;/p>
&lt;p>The solution is very easy and even more elegant. Use a cache! For this, you&amp;rsquo;ll first have to add a new field to the objects table. Do this in a migration:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="n">add_column&lt;/span> &lt;span class="ss">:objects&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:rating_cache&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:float&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now, in the Object model, add the following method:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">rate_with&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">rating&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">add_rating&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">rating&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">update_attribute&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;rating_cache&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">rating&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>You&amp;rsquo;ll need to change your controller from using #add_rating to #rate_with. The syntax is exactly the same. Now, when you add a rating, we also store the average rating in the rating_cache column.&lt;/p>
&lt;p>To get back to the sorting problem, you can now use the rating_cache column to sort Objects.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="no">Object&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">find&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="ss">:all&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:order&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s1">&amp;#39;rating_cache DESC&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Of course, you can use this trick on all sorts of relations. Have fun with it.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/12/06/rails-calculated-column-caching/</guid><pubDate>Thu, 06 Dec 2007 00:00:00 +0000</pubDate></item><item><title>MySQL: (Re)set the auto-increment value of a table</title><link>https://www.devroom.io/2007/11/30/mysql-reset-the-auto-increment-value-of-a-table/</link><description>&lt;p>Sometimes it&amp;rsquo;s necessary to set the starting point of a MySQL auto-increment value.&lt;/p>
&lt;p>Normally, MySQL starts auto-incrementing at 1. But let&amp;rsquo;s say you want to start at 10.000, because you want at least a five figure number. You can use the following query to set the MySQL auto-index:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">ALTER&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">TABLE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">some_table&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">AUTO_INCREMENT&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">10000&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>If you want to delete all records from your table and restart auto-index at 1, you might be tempted to run a DELETE query, followed by the above example, setting the auto increment value to 1. There is a shortcut, however:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">TRUNCATE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">TABLE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">some_table&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This will basically reset the table, deleting all data and resetting the auto increment index. Do not that the truncate command is a hard-reset option. For instance, any triggers &amp;ldquo;ON DELETE&amp;rdquo; will not be fired when using truncate.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/11/30/mysql-reset-the-auto-increment-value-of-a-table/</guid><pubDate>Fri, 30 Nov 2007 00:00:00 +0000</pubDate></item><item><title>RailsJobs.nl - Ruby on Rails Jobs in The Netherlands</title><link>https://www.devroom.io/2007/11/27/railsjobsnl-ruby-on-rails-jobs-in-the-netherlands/</link><description>&lt;p>The time is now ripe to announce &lt;a href="http://railsjobs.nl">RailsJobs.nl&lt;/a> - A shiny new jobboard for Ruby on Rails developers who are seeking a job in the Netherlands or Belgium. I started the site a few days ago and business and job seekers have started to find RailsJobs.nl.&lt;/p>
&lt;p>If you&amp;rsquo;re a business looking for Rails developers, feel free to &lt;a href="http://railsjobs.nl/jobs/new">add your job now&lt;/a>. Use the code &amp;lsquo;RJINTRO&amp;rsquo; to get a free 30-day trial period.&lt;/p>
&lt;p>&lt;a href="http://railsjobs.nl">RailsJobs.nl&lt;/a> is a private site, which I developed during the weekend. At first I planned the site to be free for all, but I&amp;rsquo;ve decided to charge businesses for posting jobs. This will ensure no spam, and only quality job offerings. It will also help me cover hosting bills and I can invest some of the money in advertising the site on quality websites.&lt;/p>
&lt;p>Well, I&amp;rsquo;m not sure what to say more. Just hop over to &lt;a href="http://railsjobs.nl">RailsJobs.nl&lt;/a> and see for yourself.&lt;/p>
&lt;p>Oh, I have implemented a handy &lt;a href="http://railsjobs.nl/jobs/rss">RSS Feed&lt;/a> as well, so you can easily stay up to date on new job offerings.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/11/27/railsjobsnl-ruby-on-rails-jobs-in-the-netherlands/</guid><pubDate>Tue, 27 Nov 2007 00:00:00 +0000</pubDate></item><item><title>Bash it! - Number of messages in Postfix queue</title><link>https://www.devroom.io/2007/11/15/bash-it-number-of-messages-in-postfix-queue/</link><description>&lt;p>Got bash? Here&amp;rsquo;s a nice snippet that will return the number of messages currently in the postfix queue.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">postqueue -p &lt;span class="p">|&lt;/span> tail -n &lt;span class="m">1&lt;/span> &lt;span class="p">|&lt;/span> cut -d&lt;span class="s1">&amp;#39; &amp;#39;&lt;/span> -f5
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Feel free to post any updates or improvements.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/11/15/bash-it-number-of-messages-in-postfix-queue/</guid><pubDate>Thu, 15 Nov 2007 00:00:00 +0000</pubDate></item><item><title>Party time!</title><link>https://www.devroom.io/2007/10/17/party-time/</link><description>&lt;p>It&amp;rsquo;s that time of the year again&amp;hellip; my birthday. I&amp;rsquo;m turning 27 today and I&amp;rsquo;ll be celebrating this today with friends and family.&lt;/p>
&lt;p>As to let you know what I&amp;rsquo;ve been up to the past few weeks: &lt;a href="http://flickr.com/photos/ariejan">Flickr Phototset&lt;/a>, &lt;a href="http://filmbase.org">FilmBASE&lt;/a>, &lt;a href="http://www.linkedin.com/in/ariejan">Linked-in&lt;/a> and some &amp;ldquo;classified&amp;rdquo; stuff I can&amp;rsquo;t link to.&lt;/p>
&lt;p>Feel free to congratulate me with a comment, or drop me &lt;a href="mailto:ariejan@ariejan.net">an e-mail&lt;/a>.&lt;/p>
&lt;p>&lt;em>And oh, me and &lt;a href="http://kabisa.nl">Kabisa&lt;/a> are accepting Rails projects, if you have any.&lt;/em>&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/10/17/party-time/</guid><pubDate>Wed, 17 Oct 2007 00:00:00 +0000</pubDate></item><item><title>Google increases storage!</title><link>https://www.devroom.io/2007/10/08/google-increases-storage/</link><description>&lt;p>Google today increased the storage available to &lt;a href="http://www.google.com/a/">Google App&lt;/a> users.&lt;/p>
&lt;p>Before free app users (like me) has 2Gb available, and premium users could store up to 10Gb of mail and documents.&lt;/p>
&lt;p>Today, Google upgraded the premium accounts to 25Gb! Store for the free version of Google Apps has been leveled with the current Gmail service (2910Mb at the moment).&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/10/08/google-increases-storage/</guid><pubDate>Mon, 08 Oct 2007 00:00:00 +0000</pubDate></item><item><title>Flash not clearing after a request?</title><link>https://www.devroom.io/2007/09/26/flash-not-clearing-after-a-request/</link><description>&lt;p>We all know &amp;ldquo;The Flash&amp;rdquo; to be a very useful tool in almost every application we write. What does &amp;ldquo;The Flash&amp;rdquo; actually do?&lt;/p>
&lt;blockquote> The flash provides a way to pass temporary objects between actions. Anything you place in the flash will be exposed to the very next action and then cleared out.&lt;/blockquote>
Well, that's all nice, but what if you notice that your flash is not cleared, and is shown in one or more subsequent requests as well?
&lt;p>The reason your flash is not cleared is that it will only be cleared if you render the current action, or if you redirect to another action. If you render another action or template, the flash will not be cleared out.&lt;/p>
&lt;p>Solving this is quite easy, actually. You only need to know how to do it. Use the following code in combination with render to show it only for the current action:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="n">flash&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">now&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="ss">:notice&lt;/span>&lt;span class="o">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;Render, render! This will only show up once.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>That&amp;rsquo;s all there is to it really.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/09/26/flash-not-clearing-after-a-request/</guid><pubDate>Wed, 26 Sep 2007 00:00:00 +0000</pubDate></item><item><title>The Glorious Canon EOS 400D Digital</title><link>https://www.devroom.io/2007/09/25/the-glorious-canon-eos-400d-digital/</link><description>&lt;p>Well, I finally made the big leap into (semi-)professional photography. I bought a Canon EOS 400D Digital SLR Camera. (That&amp;rsquo;s the XTi Rebel for you American folk) This nifty machine allows me to take very beautiful pictures, and I already had a little practice yesterday.&lt;/p>
&lt;p>With it&amp;rsquo;s 18-55mm lens, I can make quite nice close-up photo&amp;rsquo;s, as you can see in the example below.&lt;/p>
&lt;p style="text-align: center">&lt;a href="http://flickr.com/photo_zoom.gne?id=1434050281&amp;size=l">&lt;img src="http://ariejan.net/wp-content/uploads/2007/09/1434050281_08e9ebc898_b_475x317shkl.jpg" alt="Close-up Flower" />&lt;/a>&lt;/p>
You can check out my personal &lt;a href="http://flickr.com/photos/ariejan/">Flickr page&lt;/a> to see what new pictures I've taken lately. (No, I'm not currently available for photo shoots, thank you very much.)</description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/09/25/the-glorious-canon-eos-400d-digital/</guid><pubDate>Tue, 25 Sep 2007 00:00:00 +0000</pubDate></item><item><title>Wordpress 2.3 released!</title><link>https://www.devroom.io/2007/09/25/wordpress-23-released/</link><description>&lt;p>Today Wordpress 2.3 was released (see the &lt;a href="http://codex.wordpress.org/Version_2.3">release notes&lt;/a>). I&amp;rsquo;ve already updated Ariejan.net, of course, and I&amp;rsquo;m now able to use all of the following neat new features:&lt;/p>
&lt;ul>
&lt;li>Tagging (you can convert your categories to tags if you like)&lt;/li>
&lt;li>Update Notifications when new versions of your plugins or WordPress are released&lt;/li>
&lt;li>Improved post and draft management&lt;/li>
&lt;li>Mark articles as 'pending review'&lt;/li>
&lt;li>A new and improved visual editor (which doesn't suck! Wow!)&lt;/li>
&lt;/ul>
I highly recommend you check out the the current WordPress release from SVN. This will make future updates very easy. Read more on that &lt;a href="http://codex.wordpress.org/Installing/Updating_WordPress_with_Subversion#Tracking_Stable_Versions">here&lt;/a>.
&lt;p>So, go ahead, grab that new WordPress!&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/09/25/wordpress-23-released/</guid><pubDate>Tue, 25 Sep 2007 00:00:00 +0000</pubDate></item><item><title>Rails 2.0 New Features</title><link>https://www.devroom.io/2007/09/24/rails-20-new-features/</link><description>&lt;p>As &lt;a href="http://www.loudthinking.com">David Heinemeier Hansson&lt;/a> already told us all during his RailsConfEurope 2007 keynote, it&amp;rsquo;s time to take off the party hats. It&amp;rsquo;s no longer at time to celebrate all the new stuff we get. It&amp;rsquo;s time to celebrate what we have already.&lt;/p>
&lt;p>With this statement DHH ends the revolution of Rails. During the past three years a lot of new and exiting features were added to Rails. However, now the time has come to evolve Rails further. No more new and exiting stuff, but fine tuning. Making things even better than they already are.&lt;/p>
&lt;p>So, Rails 2.0 will not contain any major new features. But don&amp;rsquo;t despair, there are quite a few nifty changes that you&amp;rsquo;ll like to know about.&lt;/p>
&lt;p>&lt;strong>1. HTTP Authentication&lt;/strong>&lt;/p>
&lt;p>HTTP Authentication is a great way to limit access to specific areas of your application that regular users won&amp;rsquo;t access. It might even be a good second layer of security for your administrator interface. Rails 2.0 will include easy methods to get HTTP authentication working for you.&lt;/p>
&lt;p>&lt;strong>2. HTTP Performance&lt;/strong>&lt;/p>
&lt;p>The performance of a web application goes down the drain when you add too much JavaScript and stylesheets. Each and every file must be downloaded separately. Rails 2.0 will be able to take all the javascript files, stuff &amp;rsquo;em together, compress that one file and sent that to the client, where it will be cached.&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-erb" data-lang="erb">&amp;lt;%= javascript_include_tag :all, :cache =&amp;gt; true %&amp;gt;
&amp;lt;%= stylesheet_link_tag :all, :cache =&amp;gt; true %&amp;gt;
&lt;/code>&lt;/pre>&lt;p>&lt;strong>3. Asset Server&lt;/strong>&lt;/p>
&lt;p>If you have a large (and busy) site, serving static files can be quite a performance issue. Rails 2.0 adds the notion of an &amp;ldquo;asset server&amp;rdquo;. An asset server (in combination with item #2) will serve static content quickly, allowing your app to respond even faster to a user&amp;rsquo;s request. To enable asset hosts, add the following line to your configuration:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="n">config&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">action_controller&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">asset_host&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;assets%d.example.com&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>You can even cycle through multiple asset servers by simply creating the appropriate CNAME records in DNS. Neat, eh?&lt;/p>
&lt;p>&lt;strong>3. The Debugger&lt;/strong>&lt;/p>
&lt;p>&amp;ldquo;The Debugger&amp;rdquo; is here! Simple add &amp;lsquo;debugger&amp;rsquo; to your app, and it will stop just there for you to step in and debug your application. This is a very powerful way debugging your apps quickly and with ease.&lt;/p>
&lt;p>&lt;strong>4. Query Cache&lt;/strong>&lt;/p>
&lt;p>Query Caching is something that normally happens in a database server, not now. ActiveRecord keeps a cache of queries and uses it when the same query is made again. You don&amp;rsquo;t have to worry about anything, not even expiring your cache, because this is done automatically when you insert, update or delete any data that&amp;rsquo;s related to the cache.&lt;/p>
&lt;p>&lt;strong>5. Environment Configuration&lt;/strong>&lt;/p>
&lt;p>If you have ever developed a serious Rails application, you know that your environment.rb can become a mess very quickly. Rails 2.0 allows you to split up your configuration into multiple files to keep things clean. (And you can also reuse your common configuration files for other apps as well.)&lt;/p>
&lt;p>&lt;strong>6. ActionTemplate Renderer&lt;/strong>&lt;/p>
&lt;p>Say good bye to the trusty old rhtml and rxml files! There are new conventions for templates that will tell you more about what type of file the generate and which parser is used to render the templates.&lt;/p>
&lt;p>Normal HTML views will get the .html.erb extension from now on (formerly .rhtml). If you want to build an XML file with &amp;ldquo;Builder&amp;rdquo;, you would use: template.xml.builder.&lt;/p>
&lt;p>&lt;strong>7. Sexy Migrations&lt;/strong>&lt;/p>
&lt;p>Sexy Migrations have been around for some time as a plugin. Some people love them, others don&amp;rsquo;t. I still have my doubts about it, but at least it&amp;rsquo;s less code to write, and it keeps things more readable, which is good, I guess.&lt;/p>
&lt;p>Let&amp;rsquo;s take an old migration:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="n">create_table&lt;/span> &lt;span class="ss">:people&lt;/span> &lt;span class="k">do&lt;/span> &lt;span class="o">|&lt;/span>&lt;span class="n">t&lt;/span>&lt;span class="o">|&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">t&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">column&lt;/span> &lt;span class="ss">:first_name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:string&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:null&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="kp">false&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">t&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">column&lt;/span> &lt;span class="ss">:last_name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:string&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:null&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="kp">false&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">t&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">column&lt;/span> &lt;span class="ss">:group_id&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:integer&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">t&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">column&lt;/span> &lt;span class="ss">:description&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:text&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">t&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">column&lt;/span> &lt;span class="ss">:created_at&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:datetime&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">t&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">column&lt;/span> &lt;span class="ss">:updated_at&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:datetime&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>We now have this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="n">create_table&lt;/span> &lt;span class="ss">:people&lt;/span> &lt;span class="k">do&lt;/span> &lt;span class="o">|&lt;/span>&lt;span class="n">t&lt;/span>&lt;span class="o">|&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">t&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">integer&lt;/span> &lt;span class="ss">:group_id&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">t&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">string&lt;/span> &lt;span class="ss">:first_name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:last_name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:null&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="kp">false&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">t&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">text&lt;/span> &lt;span class="ss">:description&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">t&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">timestamps&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>8. Plugin Mania&lt;/strong>&lt;/p>
&lt;p>A lot of methods/features have been deprecated in Rails 2.0. Some non-core components like in-place editing have been moved to separate plugins. You will also find plugins like resource_feeder and open_id_authentication. Let&amp;rsquo;s hope that OpenID finally takes off now.&lt;/p>
&lt;p>&lt;strong>9. Thou shalt use MIT&lt;/strong>&lt;/p>
&lt;p>This one I hadn&amp;rsquo;t heard at the keynote, but had to read it on the internet.&lt;/p>
&lt;p>&lt;em>&amp;ldquo;Due to licensing confusion for people using plugins, the new plugin generator now creates a license file by default. The assumption is that you are distributing under the MIT license otherwise you have to modify the file to meet your needs.&amp;quot;&lt;/em>&lt;/p>
&lt;p>I think Rails 2.0 will be quite a nice. Apps will be a bit more manageable and performance will be better also. Now, it&amp;rsquo;s just waiting for DHH to release the &amp;ldquo;2.0 Preview Release&amp;rdquo; he promised.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/09/24/rails-20-new-features/</guid><pubDate>Mon, 24 Sep 2007 00:00:00 +0000</pubDate></item><item><title>Whooop - here it is! The new Ariejan.net!</title><link>https://www.devroom.io/2007/09/24/whooop-here-it-is-the-new-ariejannet/</link><description>&lt;p>Wow! Here it is then! The fully restyled, revamped and repimped Ariejan.net! (I hope you like it). So, &amp;ldquo;what&amp;rsquo;s changed?&amp;rdquo;, you may ask.&lt;/p>
&lt;ul>
&lt;li>I've upgraded to &lt;a href="http://www.wordpress.org">WordPress 2.3&lt;/a>, which has very sexy tagging support.&lt;/li>
&lt;li>A new template with a bit more of my personal taste included (blue!)&lt;/li>
&lt;li>A major division of articles: &lt;a href="http://beta.ariejan.net/category/blog/">Blog posts&lt;/a> (which are regular messages from me to the world) and &lt;a href="http://beta.ariejan.net/category/features/">featured articles&lt;/a> (which are the good stuff you come for). &lt;/li>
&lt;li>I've finally written the &lt;a href="https://www.devroom.io/about">about page&lt;/a> (although a photo is still missing)&lt;/li>
&lt;li>I've got &lt;a href="http://flickr.com">Flickr&lt;/a> support lined up - ready to go when I can afford myself a &lt;a href="http://www.usa.canon.com/consumer/controller?act=ModelInfoAct&amp;fcategoryid=139&amp;modelid=14256">Canon 400D SLR Camera&lt;/a> (or Rebel XTi for you American folk). (&lt;a href='http://www.pledgie.com/campaigns/337' target='_new'>help me out?&lt;/a>)&lt;/li>
&lt;/ul>
&lt;p>My goal is to publish a bit more on the advanced Rails topics I encounter these days at work. Also, I&amp;rsquo;m still working on my Subversion Kickstart e-book, which will be published before the end of this year. (Thanks to &lt;a href="http://joshblair.blogspot.com/">Josh Blair&lt;/a> for pointing out I was still working on it.)&lt;/p>
&lt;p>If you have any Rails related questions (especially if you live in Europe) please let me know. We at Kabisa are always happy to free up some of our time to help you out.&lt;/p>
&lt;p>Thanks for reading! Please grab my RSS Feed to stay up to date auotma&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/09/24/whooop-here-it-is-the-new-ariejannet/</guid><pubDate>Mon, 24 Sep 2007 00:00:00 +0000</pubDate></item><item><title>RailsConf Europe 2007!</title><link>https://www.devroom.io/2007/09/17/railsconf-europe-2007/</link><description>&lt;p>Well, after a long trip, I arrived at my hotel yesterday in Berlin. Today I, and my mates from &lt;a href="http://www.kabisa.nl">Kabisa&lt;/a>, have joined RailsConf Europe 2007. I don&amp;rsquo;t think I&amp;rsquo;ll be giving a full, in-detail report of everything that happens, but I will let you know anything worth your (and my) time.&lt;/p>
&lt;p>If you happen to be in Berlin these days, feel free to drop me an e-mail at ariejan at ariejan.net and maybe we can have chat or something. I hope to hear from you!&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/09/17/railsconf-europe-2007/</guid><pubDate>Mon, 17 Sep 2007 00:00:00 +0000</pubDate></item><item><title>10 reasons why Microsoft's 10 reasons not to use Google Apps suck</title><link>https://www.devroom.io/2007/09/11/10-reasons-why-microsofts-10-reasons-not-to-use-google-apps-suck/</link><description>&lt;p>You may have already read the &lt;a href="http://www.readwriteweb.com/archives/microsoft_10_reasons_against_google_apps.php">10 reasons why Microsoft thinks the enterprise should not use Google Apps&lt;/a>. Well, here&amp;rsquo;s my response:&lt;/p>
&lt;p>&lt;em>1. Google touts having enterprise level customers but how many “USERS” of their applications truly exist within the enterprise?&lt;/em>&lt;/p>
&lt;p>Does this matter? There are a lot of private users on Google Apps for sure, but there are quite a few large companies (with 10.000+ users) using Google Apps - and they are happy with it! How many enterprise users can say the same about Microsoft Windows and Office in the enterprise?&lt;/p>
&lt;p>&lt;em>2. Google has a history of releasing incomplete products, calling them beta software, and issuing updates on a “known only to Google” schedule – this flies in the face of what enterprises want and need in their technology partners – what is Google doing that indicates they are in lock step with customer needs?&lt;/em>&lt;/p>
&lt;p>This one makes me laugh. Google is being honest about their software not being finished. And they don&amp;rsquo;t get hopes up by setting a release date that they can&amp;rsquo;t meet. I don&amp;rsquo;t think I have to go into a discussion about how Windows Vista doesn&amp;rsquo;t include all the promised features and the several delays before it&amp;rsquo;s final release.&lt;/p>
&lt;p>&lt;em>3. Google touts the low cost of their apps –not only price but the absence of need for hardware, storage or maintenance for Google Apps. BUT if GAPE is indeed a complement to MSFT Office, the costs actually become greater for a company as they now have two IT systems to run and manage and maintain. Doesn’t this result in increased complexity and increased costs?&lt;/em>&lt;/p>
&lt;p>Very funny, again. There is no longer the need for Microsoft Office or the services that provide e-mail services like Exchange. There may even not be a need for Microsoft Windows anymore, because Google Apps works perfectly on Macs and Linux. And I think that managing Google Apps is a lot easier than managing a cluster of Microsoft servers.&lt;/p>
&lt;p>&lt;em>4. Google’s primary focus is on ad funded search. Their enterprise focus and now apps exist on the very fringe and in combination with other fringe services only account for 1% of the company’s revenue. What happens if Google executes poorly? Do they shut down given it will them in a minimal and short term way? Should customers trust that this won’t happen?&lt;/em>&lt;/p>
&lt;p>This reason is actually the only one that might be valid. But, isn&amp;rsquo;t this true for any company? Google does generate a lot of revenue from it&amp;rsquo;s ad system. What Google tends to do with it&amp;rsquo;s Google Apps suite is not only providing an alternative for Microsoft&amp;rsquo;s office, but also binding users to Google. A Google Apps users will be more likely to use Google Search than a non-apps users.&lt;/p>
&lt;p>&lt;em>5. Google’s apps only work if an enterprise has no power users, employees are always online, enterprises haven’t built custom Office apps – doesn’t this equal a very small % of global information workers today? –On a feature comparison basis, it’s not surprising that Microsoft has a huge lead.&lt;/em>&lt;/p>
&lt;p>Microsoft is still ignoring that &amp;ldquo;less is more&amp;rdquo;. It&amp;rsquo;s not about who has the most features anymore. It&amp;rsquo;s about ease of use and service. Most users -are- always online with broadband internet connections and WiFi. Office does have a lot of nifty features, but only a small % of power users use these features (if they even know the features exist and know how to use them properly).&lt;/p>
&lt;p>&lt;em>6. Google apps don’t have essential document creation features like support for headers, footers, tables of content, footnotes, etc. Additionally, while customers can collaborate on basic docs without the above noted features, to collaborate on detailed docs, a company must implement a two part process – work together on the basic doc, save it to Word or Excel and then send via email for final edits. Yes they have a $50 price tag, but with the inefficiencies created by just this one cycle, how much do GAPE really cost – and can you afford the fidelity loss?
&lt;/em>&lt;/p>
&lt;p>Again, Microsoft thinks we can&amp;rsquo;t do without it. Google Apps is not about creating huge reports and stuff. It&amp;rsquo;s about collaboration. Users collaborate, and when the document is cleared for release (or whenever you need the fancy stuff), you can use any tool (Pages? Adobe InDesign?) to make it all look sexy. Just a simple question: how many of the documents and spreadsheets you make are for pubishing and which are just quick doc for gathering and sharing thoughts with your co-workers? Personally, I use Google Docs for writing all my documents. When it needs to go to press, I export it and format it properly with Pages. No problem.&lt;/p>
&lt;p>&lt;em>7. Enterprise companies have to constantly think about government regulations and standards – while Google can store a lot of data for enterprises on Google servers, there is no easy to use, automated way for enterprises to regularly delete data, issue a legal hold for specific docs or bring copies into the corp. What happens if a company needs to respond to government regulations bodies? Google touts 99.9% uptime for their apps but what few people realize that promise is for Gmail only. Equally alarming is the definition Google has for “downtime” – ten consecutive minutes of downtime. What happens if throughout the day Google is down 7 minutes each hour? What does 7 minutes each hour for a full work day that cost an enterprise?&lt;/em>&lt;/p>
&lt;p>Again, this makes me laugh! How many minutes a day do you spend restarting, scanning for viruses and spyware on Windows? How often do you need to make backups because you need to restore/reinstall Windows? What is the up-time guarantee of an Exchange server (yes, including possible hardware failure)? There are government rules everywhere, but in my opinion this is a policy thing.&lt;/p>
&lt;p>&lt;em>8. In the world of business, it is always on and always connected. As such, having access to technical support 24/7 is essential. If a company deploys Google Apps and there is a technical issue at 8pm PST, Sorry. Google’s tech support is open M-F 1AM-6PM PST – are these the new hours of global business? And if a customer’s “designated administrator” is not available (a requirement) does business just stop?&lt;/em>&lt;/p>
&lt;p>&amp;ldquo;Always on and always connected&amp;rdquo;. Nice. In reason #5 Microsoft points out that users are not always online. What about that? How about the companies internet provider? Are they on call 24 hours a day? Just a note, when I want telephone support from Microsoft I can call them from 6am - 6pm (Pacific) and it&amp;rsquo;ll cost me $245. If you call after-hours, you&amp;rsquo;re set back $490. How much did you say Google&amp;rsquo;s support cost?&lt;/p>
&lt;p>By the way, Google does monitor its services/servers very well. If there&amp;rsquo;s a problem, it will be fixed rather quickly. With Microsoft, you&amp;rsquo;ll have to go and fix it yourself (even when you pay $490).&lt;/p>
&lt;p>(&lt;a href="http://support.microsoft.com/oas/default.aspx?ln=en-us&amp;x=16&amp;y=14&amp;prid=10257&amp;gprid=435540#hoursOfOperation">link to Micorsoft&amp;rsquo;s support page&lt;/a>)&lt;/p>
&lt;p>&lt;em>9. Google says that enterprise customers use only 10% of the features in today’s productivity applications which implies that EVERYONE needs the SAME 10% of the feature when in fact it is very clear that in each company there are specific roles people play that demands access to specific information – how does Google’s generic strategy address role specific needs?&lt;/em>&lt;/p>
&lt;p>Google offers a flexible service. In stead of customizing the crap out of the application for every possible role a user could have, they offer a generic application. It&amp;rsquo;s the &amp;lsquo;one size - fits all&amp;rsquo; principle.&lt;/p>
&lt;p>&lt;em>10. With Google apps in perpetual beta and Google controlling when and if they rollout specific features and functionality, customers have minimal if any control over the timing of product rollouts and features – how do 1) I know how to strategically plan and train and 2) get the features and functionality I have specifically requested? How much money does not knowing cost?&lt;/em>&lt;/p>
&lt;p>This just point 2 rephrased. To be more specific: do you have to plan? No - Google does the planning and testing for you. Training? Google keeps it&amp;rsquo;s apps simple and easy to understand. In terms of planning, you probably don&amp;rsquo;t know when something is released, but the same can be said for Microsoft. People expected WinFS in Vista, where is it? How many delays did Windows Vista see? How much did knowing when it was released (and when it got delayed, again) cost?&lt;/p>
&lt;p>I have written this post not to bash Microsoft, but to illustrate how single minded some companies can be. It&amp;rsquo;s not just 10 questions enterprises need to ask themselves, it&amp;rsquo;s indeed 10 reasons not to use Google Apps. Although for some of these reasons may be valid for some companies, the majority of enterprises and users can do their daily work with Google Apps, and in my opinion, they should.&lt;/p>
&lt;p>Currently I use Google Apps (free edition) to manage Ariejan.net. At Kabisa ICT we use Google Apps premier with great pleasure. We have not encountered any issues at all, and we really like the collaboration features included.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/09/11/10-reasons-why-microsofts-10-reasons-not-to-use-google-apps-suck/</guid><pubDate>Tue, 11 Sep 2007 00:00:00 +0000</pubDate></item><item><title>Content_for, yield and making sure something gets displayed</title><link>https://www.devroom.io/2007/09/01/content_for-yield-and-making-sure-something-gets-displayed/</link><description>&lt;p>You may have heard of a very nice Rails technique that used content_for and yield to stuff custom blocks of content into a layout. For example, in a view you could add a block like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-html" data-lang="html">&lt;span class="line">&lt;span class="cl">&lt;span class="err">&amp;lt;&lt;/span>% content_for :sidebar do %&amp;gt;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> This goes into the sidebar when viewing this action!
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&amp;lt;&lt;/span>% end %&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>It doesn&amp;rsquo;t matter where you put it in the view, because, as you may notice, the content within the content_for block is not shown in the view. The layout can pull the content for :sidebar and show it in a custom place, mostly the sidebar:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-html" data-lang="html">&lt;span class="line">&lt;span class="cl">&lt;span class="err">&amp;lt;&lt;/span>%= yield :sidebar %&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Nice, you now don&amp;rsquo;t have to hard-code the sidebar (and it&amp;rsquo;s layout elements) in your views everytime. You can even move the sidebar to another place, without breaking any of your views. How great is that?&lt;/p>
&lt;p>What does break your views, however, is not assigning anything to :sidebar. If you don&amp;rsquo;t assign anything to the :sidebar, nothing will be shown, which might break your layout. (Empty div&amp;rsquo;s tend to do that.)&lt;/p>
&lt;p>So, how can you solve this? Quite easily, actually. What you want, probably, is display a default sidebar when no custom sidebar has been assigned. you can do this with the following line of über sexy Ruby code:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-html" data-lang="html">&lt;span class="line">&lt;span class="cl">&lt;span class="err">&amp;lt;&lt;/span>%= (sidebar = yield :sidebar) ? sidebar : render(:partial =&amp;gt; &amp;#39;shared/sidebar&amp;#39;) %&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>First, you grab the :sidebar content and stuff it into a variable named, you guessed it, &amp;lsquo;sidebar&amp;rsquo;. If this sidebar containts anything, show it. If it doesn&amp;rsquo;t, you render a partial in the app/views/shared directory named _sidebar.rhtml.&lt;/p>
&lt;p>Well, there you are. Go enjoy yourself now.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/09/01/content_for-yield-and-making-sure-something-gets-displayed/</guid><pubDate>Sat, 01 Sep 2007 00:00:00 +0000</pubDate></item><item><title>Blueprint 0.5 Rails Plugin released</title><link>https://www.devroom.io/2007/08/31/blueprint-05-rails-plugin-released/</link><description>&lt;p>A few days ago &lt;a href="http://code.google.com/p/blueprintcss/">BlueprintCSS 0.5&lt;/a> was released (&lt;a href="http://bjorkoy.com/past/2007/8/28/blueprint_05_the_experiment/">read the Olav&amp;rsquo;s posts here&lt;/a>). I&amp;rsquo;ve updated the plugin accordingly. The most important change is the use of 24 (!) instead of 14 columns.&lt;/p>
&lt;p>Installation and usage of the plugin have not changed. See my &lt;a href="http://ariejan.net/2007/08/27/blueprintcss-rails-generator/">original announcement&lt;/a> for more information.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/08/31/blueprint-05-rails-plugin-released/</guid><pubDate>Fri, 31 Aug 2007 00:00:00 +0000</pubDate></item><item><title>BlueprintCSS Rails Generator</title><link>https://www.devroom.io/2007/08/27/blueprintcss-rails-generator/</link><description>&lt;p>&lt;em>&lt;strong>This plugin is no longer available.&lt;/strong> Blueprint nowadays ships with a very good &amp;lsquo;compress&amp;rsquo; script that allows you to generate all kinds of nice BluePrint layouts. Having a plugin to just copy some files seems a bit excessive. &lt;/em>&lt;/p>
&lt;p>I think that, if you&amp;rsquo;re a web developer, you&amp;rsquo;ve seen the &lt;a href="http://code.google.com/p/blueprintcss/">BlueprintCSS&lt;/a> framework. BlueprintCSS offers quite a bit of CSS code that allows you to quickly and easily build a grid-based layout, using pure CSS.&lt;/p>
&lt;p>That&amp;rsquo;s, of course, all very nice, but you should be able to plug it in into your Rails app. And now you can!&lt;/p>
&lt;p>Install my plugin:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">./script/plugin install http://svn.ariejan.net/plugins/blueprint
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>And then generate as many BlueprintCSS layouts as you&amp;rsquo;d like. To create a layout for your posts controller, simply run the following command:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">./script/generate blueprint posts
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This will create a posts.rhtml template in app/views/layouts, and add the proper CSS and images to your application. That&amp;rsquo;s all!&lt;/p>
&lt;p>Note 1: You may remove a few lines of inline CSS from your layout to remove the supporting background images.&lt;/p>
&lt;p>Note 2: Bugs and feature requests go into &lt;a href="http://trac.ariejan.net/report/1">Trac&lt;/a>.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/08/27/blueprintcss-rails-generator/</guid><pubDate>Mon, 27 Aug 2007 00:00:00 +0000</pubDate></item><item><title>Super Simple Authentication Plugin and Generator</title><link>https://www.devroom.io/2007/08/24/super-simple-authentication-plugin-and-generator/</link><description>&lt;p>I hereby proudly announce my &lt;em>Super Simple Authentication&lt;/em> plugin and generator.&lt;/p>
&lt;p>All right, what does it do? Sometimes you need to protect your actions and controllers, but you don&amp;rsquo;t want to go about installing restful_authentication or anything like that. Adding a simple password for certain actions would suffice. So, I wrote a little plugin that can generate some code for you that allows you to easily protect your app with a simple password.&lt;/p>
&lt;p>To get started, you must first install the plugin in your rails application:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">script/plugin install http://svn.ariejan.net/plugins/super_simple_authentication
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>When the plugin is installed, you may generate your SSA controller. This controller verifies your password and makes sure you stay authenticated for the duration of your visit.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">script/generate super_simple_authentication sessions
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Your password is located in config/super_simple_authentication.yml. Change it.&lt;/p>
&lt;p>In the SessionsController, you&amp;rsquo;ll find an include statement. Move this include to your application controller:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="kp">include&lt;/span> &lt;span class="no">SuperSimpleAuthenticationSystem&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The generator automatically added routes to your config/routes.rb file. If you want easy access to login and logout functionality, add these two lines to your config/routes.rb file as well:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="n">map&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">login&lt;/span> &lt;span class="s1">&amp;#39;/login&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:controller&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s1">&amp;#39;sessions&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:action&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s1">&amp;#39;new&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">map&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">logout&lt;/span> &lt;span class="s1">&amp;#39;/logout&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:controller&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s1">&amp;#39;sessions&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:action&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s1">&amp;#39;destroy&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:method&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="ss">:delete&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>You can now protect you actions and controllers with a before_filter:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Protect all actions in the controller&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">before_filter&lt;/span> &lt;span class="ss">:authorization_required&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Protect all actions, except :index and :recent&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">before_filter&lt;/span> &lt;span class="ss">:authorization_required&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:except&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="o">[&lt;/span>&lt;span class="ss">:index&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:recent&lt;/span>&lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Protect only :destroy&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">before_filter&lt;/span> &lt;span class="ss">:authorization_required&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:only&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="ss">:destroy&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>In your views, you can check if you are authorized or not with authorized? E.g.&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-erb" data-lang="erb">&amp;lt;% if authorized? %&amp;gt;
&amp;lt;!-- do secret admin stuff --&amp;gt;
&amp;lt;% end %&amp;gt;
&lt;/code>&lt;/pre>&lt;p>Please visit &lt;a href="http://trac.ariejan.net">&lt;a href="http://trac.ariejan.net">http://trac.ariejan.net&lt;/a>&lt;/a> to report bugs. Ariejan.net will keep you updated on new major version. &lt;a href="http://feeds.feedburner.com/Ariejan">Please subscribe to the RSS Feed&lt;/a>.&lt;/p>
&lt;p>I hope you enjoy this plugin. Please post a comment if you use it in your project, or if you just like it. Bugs, feature requests and support requests should go into &lt;a href="http://trac.ariejan.net/newticket">Trac&lt;/a>&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/08/24/super-simple-authentication-plugin-and-generator/</guid><pubDate>Fri, 24 Aug 2007 00:00:00 +0000</pubDate></item><item><title>Using Iconv to convert UTF-8 to ASCII (on Linux)</title><link>https://www.devroom.io/2007/08/21/using-iconv-to-convert-utf-8-to-ascii-on-linux/</link><description>&lt;p>There are situations where you want to remove all the UTF-8 goodness from a string
(mostly because of legacy systems you&amp;rsquo;re working with). Now, this is rather easy to do.
I&amp;rsquo;ll give you an example: &lt;code>çéß&lt;/code>&lt;/p>
&lt;p>Should be converted to &lt;code>cess&lt;/code>. On my mac, I can simply use the following snippet to convert
the string:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="n">s&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;çéß&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">s&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">Iconv&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">iconv&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;ascii//translit&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;utf-8&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">s&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">to_s&lt;/span> &lt;span class="c1"># returns &amp;#34;c&amp;#39;ess&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">s&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">gsub&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sr">/\W/&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># return &amp;#34;cess&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Very nice and all, but when I deploy to my Debian 4.0 linux system, the I get an error that
tells me that invalid characters were present. Why? Because the Mac has unicode goodness built-in.
Linux does not (in most cases).&lt;/p>
&lt;p>So, how do you go about solving this? Easy! Get unicode support!&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">sudo apt-get install unicode
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now, try again.&lt;/p>
&lt;h2 id="bonus">Bonus&lt;/h2>
&lt;p>If you want to convert a sentence (or anything else with spaces in it), you&amp;rsquo;ll notice that spaces are removed by the gsub command. I solve this by splitting up the string first into words. Convert the words and then joining the words together again.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="n">words&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">s&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">split&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34; &amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">words&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">words&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">collect&lt;/span> &lt;span class="k">do&lt;/span> &lt;span class="o">|&lt;/span>&lt;span class="n">word&lt;/span>&lt;span class="o">|&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">word&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">Iconv&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">iconv&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;ascii//translit&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;utf-8&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">word&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">to_s&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">word&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">word&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">gsub&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sr">/\W/&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s1">&amp;#39;&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">words&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">join&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34; &amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Like this? Why not write a mix-in for String?&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/08/21/using-iconv-to-convert-utf-8-to-ascii-on-linux/</guid><pubDate>Tue, 21 Aug 2007 00:00:00 +0000</pubDate></item><item><title>I’m back in business!</title><link>https://www.devroom.io/2007/08/20/im-back-in-business/</link><description>&lt;p>Ariejan.net is (almost) back in business. With some minor cosmetic improvements to the theme, I&amp;rsquo;m ready for today.&lt;/p>
&lt;p>Tomorrow I will reorganise my tags/categories a bit and add some new fancy plugins that will help searching visitors a hand in finding what they need. (More about that later, I promise).&lt;/p>
&lt;p>I hope you like the improved style of Ariejan.net. (If you don&amp;rsquo;t, feel free to make a redesign and mail it to me.)&lt;/p>
&lt;p>That&amp;rsquo;s all for now. Bye bye.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/08/20/im-back-in-business/</guid><pubDate>Mon, 20 Aug 2007 00:00:00 +0000</pubDate></item><item><title>Kabisa, RailsConf Europe and Ariejan.net</title><link>https://www.devroom.io/2007/08/20/kabisa-railsconf-europe-and-ariejannet/</link><description>&lt;p>Hello there! Just a quick post to let you know I&amp;rsquo;m still alive and kicking! Two weeks ago I started my job at &lt;a href="http://www.kabisa.nl">Kabisa&lt;/a> and I&amp;rsquo;ve been very busy with that. I really like working at Kabisa and we have some great things planned for the near future. (We&amp;rsquo;re currently working on a few client projects that need our attention.)&lt;/p>
&lt;p>&lt;img src="http://ariejan.net/wp-content/uploads/2007/08/railsconfeurope.gif" alt="Rails Conf Europe 2007" align="right" />I&amp;rsquo;d also like to let you know that I&amp;rsquo;ll be attending RailsConf Europe 2007 this year! I&amp;rsquo;m really looking forward to the conference and the tutorial day on monday. If you&amp;rsquo;re attending, please let me know, so we can meet in person.&lt;/p>
&lt;p>Then I have a small announcement regarding Ariejan.net I&amp;rsquo;m going to reorganise my VPS tonight which may cause Ariejan.net to be off the air for a few moments. I&amp;rsquo;ve been considering moving Ariejan.net to Mephisto, but Wordpress is just a way better solution at the moment. (I do have to migrate 125+ posts, comments and reformat an reorganise everything when moving to Mephisto.)&lt;/p>
&lt;p>Suffice it to say that I&amp;rsquo;ve been a very busy bee the past few weeks. I hope to show you some of my Rails work in the near future when we&amp;rsquo;ve rounded up some of the client projects were working on. For now – stay tuned!&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/08/20/kabisa-railsconf-europe-and-ariejannet/</guid><pubDate>Mon, 20 Aug 2007 00:00:00 +0000</pubDate></item><item><title>I’m Back!</title><link>https://www.devroom.io/2007/08/01/im-back/</link><description>&lt;p>All right, I&amp;rsquo;m back!&lt;/p>
&lt;p>Yes, my holiday was nice. Thanks for asking. Now, I&amp;rsquo;ve been looking at the poll in my previous post and I&amp;rsquo;ve made up my mind.&lt;/p>
&lt;ol>
&lt;li>I&amp;rsquo;m going to look into writing a blogging system in Ruby on Rails. I don&amp;rsquo;t want to copy typo or Mephisto or anything, but just put my own thoughts together, add a bit of spice you guys (and girls?) have to offer and release it some time.&lt;/li>
&lt;/ol>
&lt;p>Just to be clear (and prevent myself from getting a lot of mail about this): I&amp;rsquo;m going to see if I can make time to work on a project like this. That&amp;rsquo;s all. If I can, I&amp;rsquo;ll let you know. If I can&amp;rsquo;t.. well too bad, I guess :)&lt;/p>
&lt;ol start="2">
&lt;li>I&amp;rsquo;m going to, as I did before, write a lot of articles about Subversion and Ruby on Rails. Maybe I&amp;rsquo;ll add some new topics too, but those will pop up when they do. Just keep an eye out for news here at Ariejan.net (tip: &lt;a href="http://feeds.feedburner.com/Ariejan">grab my RSS Feed&lt;/a>).&lt;/li>
&lt;/ol>
&lt;p>&lt;em>Also, I&amp;rsquo;m now available to do Ruby on Rails projects for real! Just &lt;a href="https://www.devroom.io/contact">contact me&lt;/a> and we can discuss details at your leisure.&lt;/em>&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/08/01/im-back/</guid><pubDate>Wed, 01 Aug 2007 00:00:00 +0000</pubDate></item><item><title>Ariejan.net - What’s next?</title><link>https://www.devroom.io/2007/07/16/ariejannet-whats-next/</link><description>&lt;p>&lt;strong>&lt;a href="#whatsnext">Vote now! What&amp;rsquo;s Next for Ariejan.net? Your opinion counts!&lt;/a>&lt;/strong>&lt;/p>
&lt;p>After a few days (or were it weeks?) of silence, an update on Ariejan.net. What&amp;rsquo;s been happening?&lt;/p>
&lt;p>First of all, I &lt;a href="http://ariejan.net/2007/06/19/geslaagd-passed-my-final-exams/">recently graduated&lt;/a> and am now officially a Bachelor of ICT. Since my current employer didn&amp;rsquo;t have the same views regarding my future as I did, I decided to go &amp;ldquo;shopping&amp;rdquo; for a new job.&lt;/p>
&lt;p>I found that new job with a small business named &lt;a href="http://www.kabisa.nl">Kabisa ICT&lt;/a>. The people at Kabisa and I found common ground very quickly with Ruby on Rails and Agile Development. This, to my surprise, resulted in a job offer very quickly&amp;hellip; and I accepted.&lt;/p>
&lt;p>The next two weeks I&amp;rsquo;ve got a holiday planned to the French Alps with Laura. We hope to get some rest there while enjoying the beautiful scenery.&lt;/p>
&lt;p>When I get back from France, I start my new job at Kabisa. I hope to learn a lot of new stuff while working there. Hopefully I&amp;rsquo;ll be able to share that experience with your here, at Ariejan.net, which leads me to the following question.&lt;/p>
&lt;p>&lt;strong>What&amp;rsquo;s Next?&lt;/strong>&lt;/p>
&lt;p>What&amp;rsquo;s next for Ariejan.net? Since all the Web 2.0 buzz is all about communities and all that, I want your opinion on what to do with Ariejan.net.&lt;/p>
&lt;p>&lt;em>Please vote in the poll below and let me know what your thoughts are. When I get back from my (well earned) vacation, I&amp;rsquo;ll check back here and see what you, my guests, really want.&lt;/em>&lt;/p>
&lt;p>&lt;em>You have the option of adding new stuff. Use it wisely, please!&lt;/em>
&lt;a title="whatsnext" name="whatsnext">&lt;/a>&lt;/p>
&lt;div>{democracy:1}&lt;/div>
&lt;p>&lt;strong>Thanks for voting! It&amp;rsquo;s greatly appreciated!&lt;/strong>&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/07/16/ariejannet-whats-next/</guid><pubDate>Mon, 16 Jul 2007 00:00:00 +0000</pubDate></item><item><title>Clear DNS Cache on your router</title><link>https://www.devroom.io/2007/07/07/clear-dns-cache-on-your-router/</link><description>&lt;p>I currently have a Linksys router at home that has the &lt;a href="http://www.dd-wrt.com/dd-wrtv2/index.php">DD-WRT Firmware&lt;/a> on it. I&amp;rsquo;ve been using it for quite some time now, and I&amp;rsquo;m very happy with it.&lt;/p>
&lt;p>In my previous post I mentioned there was some trouble with DNS for Ariejan.net. I&amp;rsquo;ve changed nameservers and there&amp;rsquo;s always something that goes wrong.&lt;/p>
&lt;p>Anyway, my router runs DNSMasq, a caching nameserver for my local network. (What this does is, it stores DNS queries and when the same request is made later on, the response is already here (on my network), and my ISP&amp;rsquo;s nameservers don&amp;rsquo;t have to be queried. This makes for a great speed optimalization!)&lt;/p>
&lt;p>The problem was, that my router was caching parts from the old and parts from the new nameservers for Ariejan.net. I could have waited 24-48 hours to let DNSMasq figure it all out by itself, but I&amp;rsquo;m not that patient. What I needed to was reset the cache DNSMasq had built.&lt;/p>
&lt;p>What you need to do is login to the admin panel of your router and enable SSH access (for your own network, not for the outside world!).&lt;/p>
&lt;p>You SSH to your route, probably as the &amp;lsquo;root&amp;rsquo; or &amp;lsquo;admin&amp;rsquo; user. In this case the IP is your routers IP address.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ ssh -l admin 10.0.0.1
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Once you&amp;rsquo;re in, you can issue the killall command to issue the &amp;lsquo;HUP&amp;rsquo; signal to DNSMasq. This does not kill your DNSMasq, but tells it to flush the cache an reread its configuration, thus starting over with a clean slate.&lt;/p>
&lt;p>The &amp;lsquo;HUP&amp;rsquo; signal has the number &amp;lsquo;1&amp;rsquo; so you can run the following command once logged in:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ killall -1 dnsmasq
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now you can logout, and notice that your cache was indeed cleared.&lt;/p>
&lt;p>This method should work on every Linux based router that uses DNSMasq and support SSH logins.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/07/07/clear-dns-cache-on-your-router/</guid><pubDate>Sat, 07 Jul 2007 00:00:00 +0000</pubDate></item><item><title>How to write a Rails Plugin (for controllers)</title><link>https://www.devroom.io/2007/07/06/how-to-write-a-rails-plugin-for-controllers/</link><description>&lt;p>A few days back I posted my very first Rails plugin, &lt;a href="http://ariejan.net/2007/07/03/rails-plugin-acts-as-exportable/">Acts As Exportable&lt;/a>. Although writing a plugin is rather easy, you must know a few tricks on how to get things going.&lt;/p>
&lt;p>This article will show you how to develop a plugin that adds functionality to a &lt;em>controller&lt;/em> (other plugins, e.g. for models) will follow later. In fact, I&amp;rsquo;ll explain to you how I developed my Acts As Exportable plugin.&lt;/p>
&lt;p>Let&amp;rsquo;s take a basic Rails application for starters. You have setup a model with some attributes and a scaffolded controller that allows you to CRUD your items. In this tutorial I&amp;rsquo;ll be working with books. The model is named &amp;lsquo;Book&amp;rsquo; and the controller &amp;lsquo;BooksController&amp;rsquo;. Start your web server now and add some random data to play with.&lt;/p>
&lt;p>Before you dive into writing a plugin for the controller to export data to XML you should have some basic functionality in your controller first. I&amp;rsquo;ve found it easier to develop my code in the controller first, and then port it to a plugin.&lt;/p>
&lt;p>So, add a new method to your BooksController that&amp;rsquo;ll export books to XML. This looks quite easy:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">export_to_xml&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">books&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">Book&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">find&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="ss">:all&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:order&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s1">&amp;#39;title&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">send_data&lt;/span> &lt;span class="n">books&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">to_xml&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="ss">:type&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s1">&amp;#39;text/xml; charset=UTF-8;&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="ss">:disposition&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s2">&amp;#34;attachment; filename=books.xml&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now, call /books/export_to_xml and you download a real XML file containing all your books! To make things a bit more complicated, we want to be able to feed this method some conditions to select books. A nice solution is to add a special method for this that defines these conditions. (You could also use them in listing books, for example.) I add a new method to the BooksController:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">conditions_for_collection&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">[&lt;/span>&lt;span class="s1">&amp;#39;title = ?&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;some title!&amp;#39;&lt;/span>&lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The condition is of the same format you can feed to &lt;em>find&lt;/em>. Here you could, for example, select only the books belonging to the currently logged in user.&lt;/p>
&lt;p>Next, update the export_to_xml method to use these conditions&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">export_to_xml&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">books&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">Book&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">find&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="ss">:all&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:order&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s1">&amp;#39;title&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:conditions&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="n">conditions_for_collection&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">send_data&lt;/span> &lt;span class="n">books&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">to_xml&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="ss">:type&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s1">&amp;#39;text/xml; charset=UTF-8;&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="ss">:disposition&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s2">&amp;#34;attachment; filename=books.xml&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Nice that&amp;rsquo;s it. Now, you like what you&amp;rsquo;ve made so far, and want to stuff it into a plugin and put it on your weblog. Here&amp;rsquo;s how to go about that.&lt;/p>
&lt;h2 id="creating-the-plugin">Creating the plugin&lt;/h2>
&lt;p>First, generate the basic code for a plugin:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">./script/generate plugin acts_as_exportable
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This will create a new directory in vendor/plugins containing all the basic files you need. First, we&amp;rsquo;ll take a look at vendor/plugins/acts_as_exportable/lib/acts_as_exportable.rb. This is where all the magic happens.&lt;/p>
&lt;p>What we want is to is add a method to ActionControllerBase that allows you to easily enable the plugin in a certain controller. So, how do you want to activate the plugin? Right, you just call &amp;lsquo;acts_as_exportable&amp;rsquo; from the controller, or optionally, you add the name of the model you want to use.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="n">acts_as_exportable&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">acts_as_exportable&lt;/span> &lt;span class="ss">:book&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The vendor/plugins/acts_as_exportable/lib/acts_as_exportable.rb contains a module that&amp;rsquo;s named after our plugin:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="k">module&lt;/span> &lt;span class="nn">ActsAsExportable&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Next, we add a module named &amp;lsquo;ClassMethods&amp;rsquo;. These class methods will be added to ActionController::Base when the plugin is loaded (we&amp;rsquo;ll take care of that in a moment), and enable the functionality described above.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="k">module&lt;/span> &lt;span class="nn">ActsAsExportable&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nc">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="nf">included&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">base&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">base&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">extend&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="no">ClassMethods&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">class&lt;/span> &lt;span class="nc">Config&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kp">attr_reader&lt;/span> &lt;span class="ss">:model&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kp">attr_reader&lt;/span> &lt;span class="ss">:model_id&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">initialize&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">model_id&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="vi">@model_id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">model_id&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="vi">@model&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">model_id&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">to_s&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">camelize&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">constantize&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">model_name&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="vi">@model_id&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">to_s&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">module&lt;/span> &lt;span class="nn">ClassMethods&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">acts_as_exportable&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">model_id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kp">nil&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># converts Foo::BarController to &amp;#39;bar&amp;#39; and FooBarsController&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># to &amp;#39;foo_bar&amp;#39; and AddressController to &amp;#39;address&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">model_id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">to_s&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">split&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;::&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">last&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">sub&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sr">/Controller$/&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="p">\&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">pluralize&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">singularize&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">underscore&lt;/span> &lt;span class="k">unless&lt;/span> &lt;span class="n">model_id&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="vi">@acts_as_exportable_config&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">ActsAsExportable&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="no">Config&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="p">\&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kp">new&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">model_id&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kp">include&lt;/span> &lt;span class="no">ActsAsExportable&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="no">InstanceMethods&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># Make the @acts_as_exportable_config class variable easily&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># accessable from the instance methods.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">acts_as_exportable_config&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="vi">@acts_as_exportable_config&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="nb">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">superclass&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="p">\&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">instance_variable_get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;@acts_as_exportable_config&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>So, what happened? The first method you see extends the current class (that&amp;rsquo;s every one of your controllers with the methods from the ClassMethods module).&lt;/p>
&lt;p>Every class now has the &amp;lsquo;acts_as_exportable&amp;rsquo; method available. What does it do? The plugin automatically grabs the name of the model associated (by convention) with the controller you use, unless you specify something else.&lt;/p>
&lt;p>Next, we create a new configuration object that contains information about the model we&amp;rsquo;re working with. Later on this can contain more detailed information like what attributes to include or exclude from the export.&lt;/p>
&lt;p>Finally we include the module InstanceMethods, which we still have to define. The instance methods are only included when we enable the plugin. In our case, the instance methods include the &amp;rsquo;export_to_xml&amp;rsquo; and &amp;lsquo;conditions_for_collection&amp;rsquo; methods. We can simply copy/paste them into your plugin.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="k">module&lt;/span> &lt;span class="nn">InstanceMethods&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">export_to_xml&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">Book&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">find&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="ss">:all&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:order&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s1">&amp;#39;title&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:conditions&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="n">conditions_for_collection&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">send_data&lt;/span> &lt;span class="n">data&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">to_xml&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="ss">:type&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s1">&amp;#39;text/xml; charset=UTF-8;&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="ss">:disposition&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s2">&amp;#34;attachment; filename=books.xml&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># Empty conditions. You can override this in your controller&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">conditions_for_collection&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Take note that we don&amp;rsquo;t want to define any default conditions, because we don&amp;rsquo;t know what model we&amp;rsquo;re using here. By adding an empty method, the method is available and no conditions are used. Another developer can define &amp;lsquo;conditions_for_collection&amp;rsquo; in his controller to override the one we write here.&lt;/p>
&lt;p>In the &amp;rsquo;export_to_xml&amp;rsquo; there are a few changes as well. First of all, I generalized &amp;lsquo;books&amp;rsquo; to &amp;lsquo;data&amp;rsquo;.&lt;/p>
&lt;p>The most important step is yet to come. We have still application specific code in your plugin, namely the Book model. This is where the Config class and @acts_as_exportable_config come in.&lt;/p>
&lt;p>We have added a class variable to the controller named @acts_as_exportable_config. By default, this variable is not accessable by instance methods, so we need a little work around:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">class&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">acts_as_exportable_config&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This will call the class method &amp;lsquo;acts_as_exportable_config&amp;rsquo; we defined in ClassMethods and return the value of @acts_as_exportable_config.&lt;/p>
&lt;p>Note that we store the configuration in each seperate controller. This allows acts_as_exportable to be used with more than one controller at the same time.&lt;/p>
&lt;p>With the model name made application independent, the whole plugin code looks like:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="k">module&lt;/span> &lt;span class="nn">ActsAsExportable&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nc">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="nf">included&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">base&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">base&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">extend&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="no">ClassMethods&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">class&lt;/span> &lt;span class="nc">Config&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kp">attr_reader&lt;/span> &lt;span class="ss">:model&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kp">attr_reader&lt;/span> &lt;span class="ss">:model_id&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">initialize&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">model_id&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="vi">@model_id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">model_id&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="vi">@model&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">model_id&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">to_s&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">camelize&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">constantize&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">model_name&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="vi">@model_id&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">to_s&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">module&lt;/span> &lt;span class="nn">ClassMethods&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">acts_as_exportable&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">model_id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kp">nil&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># converts Foo::BarController to &amp;#39;bar&amp;#39; and FooBarsController to &amp;#39;foo_bar&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># and AddressController to &amp;#39;address&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">model_id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">to_s&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">split&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;::&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">last&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">sub&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sr">/Controller$/&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="p">\&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">pluralize&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">singularize&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">underscore&lt;/span> &lt;span class="k">unless&lt;/span> &lt;span class="n">model_id&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="vi">@acts_as_exportable_config&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">ActsAsExportable&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="no">Config&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">new&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">model_id&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kp">include&lt;/span> &lt;span class="no">ActsAsExportable&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="no">InstanceMethods&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># Make the @acts_as_exportable_config class variable easily&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># accessable from the instance methods.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">acts_as_exportable_config&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="vi">@acts_as_exportable_config&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="nb">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">superclass&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="p">\&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">instance_variable_get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;@acts_as_exportable_config&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">module&lt;/span> &lt;span class="nn">InstanceMethods&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">export_to_xml&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">class&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">acts_as_exportable_config&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">find&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="ss">:all&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="ss">:order&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s1">&amp;#39;title&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="ss">:conditions&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="n">conditions_for_collection&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">send_data&lt;/span> &lt;span class="n">data&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">to_xml&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="ss">:type&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s1">&amp;#39;text/xml; charset=UTF-8;&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="ss">:disposition&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s2">&amp;#34;attachment; filename=\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> &lt;/span>&lt;span class="si">#{&lt;/span>&lt;span class="nb">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">class&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">acts_as_exportable_config&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">model_name&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">pluralize&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">.xml&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># Empty conditions. You can override this in your controller&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">conditions_for_collection&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Add the following line to your BooksController and restart your web server. (Oh, and make sure to remove the export_to_xml method from the controller as well)&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="n">acts_as_exportable&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Done! – Or not?&lt;/p>
&lt;h2 id="enabling-the-plugin-by-default">Enabling the plugin by default&lt;/h2>
&lt;p>We have a very nice plugin now, but it is not loaded by default! If you take a look at your plugin directory, you&amp;rsquo;ll find a file named &amp;lsquo;init.rb&amp;rsquo;. This file is executed when you (re)start your web server. This is the perfect place to add our class methods to the ActionController::Base. Just add the following three lines of code to &amp;lsquo;init.rb&amp;rsquo;:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="no">ActionController&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="no">Base&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">class_eval&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kp">include&lt;/span> &lt;span class="no">ActsAsExportable&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>When we include our module, the &amp;lsquo;self.included&amp;rsquo; method is called, and the ClassMethods module is added, thus enabling the acts_as_exportable method.&lt;/p>
&lt;p>That&amp;rsquo;s all! Happy plugin writing!&lt;/p>
&lt;p>Feel free to comment on this post and write about any of your own plugin (for controllers) experiences.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/07/06/how-to-write-a-rails-plugin-for-controllers/</guid><pubDate>Fri, 06 Jul 2007 00:00:00 +0000</pubDate></item><item><title>Some DNS problems with Ariejan.net</title><link>https://www.devroom.io/2007/07/06/some-dns-problems-with-ariejannet/</link><description>&lt;p>This is just a quick note to let you know there are currently some issues with DNS for Ariejan.net. This basically means that Trac and SVN are currently not available. I&amp;rsquo;ve made the appropriate changes and things should be working again in a few hours.&lt;/p>
&lt;p>I&amp;rsquo;m very sorry for the inconvenience.&lt;/p>
&lt;p>&lt;strong>Update: It appears that things are working again. If you have any issues with &lt;a href="http://trac.ariejan.net/rails-plugins">Trac&lt;/a>, please let me know.&lt;/strong>&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/07/06/some-dns-problems-with-ariejannet/</guid><pubDate>Fri, 06 Jul 2007 00:00:00 +0000</pubDate></item><item><title>How to resolve Subversion Conflicts</title><link>https://www.devroom.io/2007/07/04/how-to-resolve-subversion-conflicts/</link><description>&lt;p>If there&amp;rsquo;s more than one person working on a project, chances are (although slim) that at some point two developers work on the same piece of code and check it in. To clarify, let me give you an example.&lt;/p>
&lt;p>The repository is currently at revision 5 and contains a file named &amp;lsquo;README&amp;rsquo;. Revision 5 of that file contains a single line: &amp;lsquo;This is a README file&amp;rsquo;.&lt;/p>
&lt;p>Now, both you and your colleague check out r5 and edit README. Your colleague changes the line to &amp;lsquo;This is a documentation file&amp;rsquo; and commits it back to the repository, which is bumped to revision 6.&lt;/p>
&lt;p>You&amp;rsquo;re an island, and have no clue about the new revision being created. You just happily write away and change the README file to: &amp;lsquo;This is fun stuff!&amp;rsquo;.&lt;/p>
&lt;p>When you commit your changes, you&amp;rsquo;ll get an error message:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">$ svn commit -m &lt;span class="s2">&amp;#34;Updated README&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Sending README
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Transmitting file data .svn: Commit failed &lt;span class="o">(&lt;/span>details follow&lt;span class="o">)&lt;/span>:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">svn: Out of date: &lt;span class="s1">&amp;#39;/myproject/README&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This is good. Subversion has detected that the file you want to commit has changed since you last updated it. Update the file to get it up-to-date again.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">$ svn update
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">C README
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Updated to revision 6.
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The &amp;lsquo;C&amp;rsquo; indicates there is a conflict with the README file, and Subversion does not know how to solve this. You are called in to help.&lt;/p>
&lt;p>If you now take a look at README, you&amp;rsquo;ll notice that there are several markers that indicate what parts of the code are conflicting. You can easily see what you changed, and what has changed in the repository:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&lt;/span>&amp;lt; .mine
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">This is fun stuff!
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">=======&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">This is a documentation file
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt; .r6
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="what-are-your-options">What are your options?&lt;/h2>
&lt;p>You have three options for resolving the conflict. Whatever you choose, make sure you confer with your colleague on the matter.&lt;/p>
&lt;p>&lt;em>1. Scrap your changes, and go with the current work from your colleague.&lt;/em>&lt;/p>
&lt;p>This is the easiest solution. All you have to do is revert the changes you made, and update your working copy:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">$ svn revert README
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Reverted &lt;span class="s1">&amp;#39;README&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ svn update README
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">At revision 6.
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;em>2. Keep your changes, and dump whatever your colleague did.&lt;/em>&lt;/p>
&lt;p>Performing a simple &amp;rsquo;ls&amp;rsquo; will show you that there are four files related to this conflict:&lt;/p>
&lt;ul>
&lt;li>README – the original with markers&lt;/li>
&lt;li>README.mine – your version&lt;/li>
&lt;li>README.r5 – the original your worked with&lt;/li>
&lt;li>README.r6 – the most update version from your colleague&lt;/li>
&lt;/ul>
&lt;p>To check in your changes, copy your version over the original and tell Subversion you have resolved the conflict.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">$ cp README.mine README
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ svn resolved README
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Resolved conflicted state of &lt;span class="s1">&amp;#39;README&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The &amp;lsquo;resolved&amp;rsquo; command will clean up all the special files that were generated.&lt;/p>
&lt;p>&lt;em>3. Merge both version to a new version&lt;/em>&lt;/p>
&lt;p>If you choose this option, you will have to manually edit README. Remove the markers and add whatever you need to add here.&lt;/p>
&lt;p>Subversion won&amp;rsquo;t let you commit this file, so you&amp;rsquo;ll have to mark it as &amp;lsquo;resolved&amp;rsquo; as we saw during option 2:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">$ svn resolved README
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Resolved conflicted state of &lt;span class="s1">&amp;#39;README&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;em>Before you rush ahead&lt;/em>&lt;/p>
&lt;p>With option 1, you&amp;rsquo;re done. With options 2 and 3 there is some more work to do, because you didn&amp;rsquo;t commit your changes yet. Because we&amp;rsquo;re dealing with conflicts here, I recommend you don&amp;rsquo;t just commit to your repository, but follow a slightly different route.&lt;/p>
&lt;p>First, update your working copy (again) to make sure you have all the latest, and are not trying to check in any more conflicting code. If any conflicts pop-up, fix these first.&lt;/p>
&lt;p>Now, run your tests to make sure everything is working as it should.&lt;/p>
&lt;p>When all is clear, commit your changes to your repository as you normally would.&lt;/p>
&lt;p>Done. Problem solved.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/07/04/how-to-resolve-subversion-conflicts/</guid><pubDate>Wed, 04 Jul 2007 00:00:00 +0000</pubDate></item><item><title>Got updates?</title><link>https://www.devroom.io/2007/07/03/got-updates/</link><description>&lt;p>Do you want to stay up-to-date about what&amp;rsquo;s happening at Ariejan.net? That&amp;rsquo;s real easy!&lt;/p>
&lt;p>You can subscribe to my &lt;a href="http://feeds.feedburner.com/Ariejan">RSS Feed&lt;/a> and receive updates as they happen in your favourite RSS reader.&lt;/p>
&lt;p>If you don&amp;rsquo;t have an RSS reader (or don&amp;rsquo;t want one), you can subscribe to my e-mail service. This service will gather up all the posts of the previous day (if any) and send them to you between 7am and 9am (CET).&lt;/p>
&lt;p>Interested? Sign-up now!&lt;/p>
&lt;form style="border: 1px solid #cccccc; padding: 3px; text-align: center" action="http://www.feedburner.com/fb/a/emailverify" method="post" target="popupwindow" onsubmit="window.open('http://www.feedburner.com', 'popupwindow', 'scrollbars=yes,width=550,height=520');return true">Enter your email address:
&lt;p>&lt;input style="width: 340px" name="email" type="text" /> &lt;input value="http://feeds.feedburner.com/~e?ffid=411105" name="url" type="hidden" /> &lt;input value="Ariejan.net" name="title" type="hidden" /> &lt;input name="loc" value="en_US" type="hidden" /> &lt;input value="Subscribe" type="submit" />Delivered by &lt;a href="http://www.feedburner.com" target="_blank">FeedBurner&lt;/a>&lt;/p>
&lt;/form>Or grab the &lt;a href="http://feeds.feedburner.com/Ariejan">RSS Feed&lt;/a>.</description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/07/03/got-updates/</guid><pubDate>Tue, 03 Jul 2007 00:00:00 +0000</pubDate></item><item><title>How to create and apply a patch with Subversion</title><link>https://www.devroom.io/2007/07/03/how-to-create-and-apply-a-patch-with-subversion/</link><description>&lt;p>It&amp;rsquo;s been a while since I posted something new on the use of Subversion. I&amp;rsquo;ve been working with the tool a lot, and I&amp;rsquo;ve found that patches are a great way to communicate code changes.&lt;/p>
&lt;p>For those of you who are still learning, let me first explain what a patch is. A patch is a text file that contains the alteration that were made to a specific file. It includes the lines that have been removed and the lines that have been added. In short, if you have a ruby script and edited it, you could create a patch file, containing the changes you&amp;rsquo;ve made.&lt;/p>
&lt;p>Why is this useful? You could check in your changes to your repository directly. True, but there are cases that you don&amp;rsquo;t have write access to the repository. For example, if you wanted to contribute code changes to &lt;a href="http://trac.ariejan.net/rails-plugins/wiki/ActsAsExportable">Acts As Exportable&lt;/a>, you should create a new ticket and attach a patch file. I will then review your changes before I apply them to the code and commit them to the repository.&lt;/p>
&lt;p>So, how do you go about creating a patch file and how do you later apply it to your source?
~&lt;/p>
&lt;h3 id="creating-a-patch-file">Creating a patch file&lt;/h3>
&lt;p>Creating a patch file is really easy. First, check out the most recent version of the code from Subversion using the &amp;lsquo;checkout&amp;rsquo; command.&lt;/p>
&lt;p>Make your changes.&lt;/p>
&lt;p>Then, in the root the project run the following command. It will store the patch file in your home directory. Make sure to give it meaningful filename.&lt;/p>
&lt;pre>&lt;code>svn diff &amp;gt; ~/fix_ugly_bug.diff
&lt;/code>&lt;/pre>
&lt;p>The file has the .diff extention, which stands for &lt;em>diff&lt;/em>erences. This extension is recognized by many text editors and enables &amp;lsquo;syntax highlighting&amp;rsquo; automatically. (Give it a try with TextMate and you&amp;rsquo;ll know what I mean.)&lt;/p>
&lt;p>You can send the diff-file to the author of the project by email, or you can create a ticket in Trac and add it as an attachment. The author will review the changes you made and possibly apply them to the source.&lt;/p>
&lt;h3 id="applying-a-patch">Applying a patch&lt;/h3>
&lt;p>You should never apply patches from any person other than your development team without first reading through the changes, apply them locally and test your application and then commit them. Patches can not only include bug fixes, but also alterations to create back doors or add other exploits to your code.&lt;/p>
&lt;p>&lt;strong>Always read through a patch before applying it!&lt;/strong>&lt;/p>
&lt;p>When you are sure the patch will bring no harm to you, your application or your customers, go ahead an apply it to your working copy. Here, I assume that you downloaded the patch file we previously generated, and placed it in your home directory. In the root of your application now run:&lt;/p>
&lt;pre>&lt;code>patch -p0 -i ~/fix_ugly_bug.diff
&lt;/code>&lt;/pre>
&lt;p>This will apply all the changes in the patch to your source. The -p0 option makes sure that all files can be found correctly (this has to do with something called &amp;lsquo;zero directories&amp;rsquo;, I won&amp;rsquo;t get into that right now). The -i option tells &amp;lsquo;patch&amp;rsquo; what to use as input, in this case the &amp;lsquo;fix_ugly_bug.diff&amp;rsquo; file in your home directory.&lt;/p>
&lt;p>With the code changes in place, run your tests and make sure everything works as expected. If it does, commit your changes and celebrate with a cup of coffee.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/07/03/how-to-create-and-apply-a-patch-with-subversion/</guid><pubDate>Tue, 03 Jul 2007 00:00:00 +0000</pubDate></item><item><title>How to force data to be downloaded as a file from your Rails app</title><link>https://www.devroom.io/2007/07/02/how-to-force-data-to-be-downloaded-as-a-file-from-your-rails-app/</link><description>&lt;p>In the essence of every application is data. One way or another your application manages data and at some point, you need to get that data out. Either you want to synchronize the data with another application or device. Or you want to move your data to another system all together. Either way, you&amp;rsquo;ll need to gather your data and send it from your application to the client&amp;hellip; as a file.&lt;/p>
&lt;p>Downloading files is not the hardest thing around. But the problem is that some formats, like XML, are automatically parsed by the browser and this makes it harder for users to download files like that.&lt;/p>
&lt;p>So, what you want to do is, ignore the browser and offer your data (in XML or whatever format you want) as a file that can be downloaded directly. The solution is rather easy, as always with Rails.&lt;/p>
&lt;p>Okay, in this example I have an action that renders current &amp;rsquo;entries&amp;rsquo; as XML and offers this to the user:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">export_to_xml&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="vi">@entries&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">Entry&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">find&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="ss">:all&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">render&lt;/span> &lt;span class="ss">:xml&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="vi">@entries&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">to_xml&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This works as you&amp;rsquo;d expect it to. When calling this action, the user receives a XML file containing all entries. But really, do you want that in your browser? Especially when the XML file is rather large, this can be very annoying, because your browser will want to load it all in!&lt;/p>
&lt;p>What you want here is offer the users a file named &amp;rsquo;entries.xml&amp;rsquo; for download. In this case we use Rails&amp;rsquo; send_data method. The previous action now looks like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">export_to_xml&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="vi">@entries&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">Entry&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">find&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="ss">:all&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">send_data&lt;/span> &lt;span class="vi">@entries&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">to_xml&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="ss">:type&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s1">&amp;#39;text/xml; charset=UTF-8;&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="ss">:disposition&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s2">&amp;#34;attachment; filename=entries.xml&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>It&amp;rsquo;s clear that we send the XML data to the client. I specify the type and charset of the data with the &amp;rsquo;type&amp;rsquo; paramater. This way the browser knows what is being send and allows the user to choose an application that can use the data. In this case an XML reader, for example.&lt;/p>
&lt;p>The disposition parameter tells the browser this should be downloaded as a file (or attachment). It also specifies what the name of the attachment is, &amp;rsquo;entries.xml'.&lt;/p>
&lt;p>Now, link to this method and the user will be presented with a downloadable XML file.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/07/02/how-to-force-data-to-be-downloaded-as-a-file-from-your-rails-app/</guid><pubDate>Mon, 02 Jul 2007 00:00:00 +0000</pubDate></item><item><title>ActiveScaffold + acts_as_taggable + Auto Complete</title><link>https://www.devroom.io/2007/07/01/activescaffold-acts_as_taggable-auto-complete/</link><description>&lt;p>I&amp;rsquo;ve talked before on how to use &lt;a href="http://ariejan.net/2007/06/11/activescaffold-acts_as_taggable_on_steroids/">ActiveScaffold with acts_as_taggable_on_steroids&lt;/a>.&lt;/p>
&lt;p>The problem with that solution was that, although the checkboxes for every tag are very nice, you couldn&amp;rsquo;t easily add new tags. For some people, this may be fine, for others, it is not.&lt;/p>
&lt;p>Together with a colleague (who wishes not to be named), I found a solution that is quite elegant. Instead of using check boxes, and creating all kinds of subforms in ActiveScaffold, we opted for an auto_completing, comma-separated list of tags.&lt;/p>
&lt;p>This article descripes the solution we found. I think you&amp;rsquo;ll like it very much!&lt;/p>
&lt;p>When you try to use acts_as_taggable with ActiveScaffold, you might use something like this in your BooksController.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="n">active_scaffold&lt;/span> &lt;span class="ss">:books&lt;/span> &lt;span class="k">do&lt;/span> &lt;span class="o">|&lt;/span>&lt;span class="n">config&lt;/span>&lt;span class="o">|&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">config&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">columns&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">[&lt;/span>&lt;span class="ss">:title&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:body&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:tags&lt;/span>&lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">config&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">list&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">columns&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">[&lt;/span>&lt;span class="ss">:title&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:tag_list&lt;/span>&lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">config&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">columns&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="ss">:tags&lt;/span>&lt;span class="o">].&lt;/span>&lt;span class="n">ui_type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="ss">:select&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># ...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This is not so useful when you want the flexibility of creating new tags instantly. Therefore, it&amp;rsquo;s better to use the tag_list:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="n">active_scaffold&lt;/span> &lt;span class="ss">:books&lt;/span> &lt;span class="k">do&lt;/span> &lt;span class="o">|&lt;/span>&lt;span class="n">config&lt;/span>&lt;span class="o">|&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">config&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">columns&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">[&lt;/span>&lt;span class="ss">:title&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:body&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:tag_list&lt;/span>&lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">config&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">list&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">columns&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">[&lt;/span>&lt;span class="ss">:title&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:tag_list&lt;/span>&lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># ...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>You get a text_field for writing down the tags (comma-separated). The problem of this is that the user has to keep all the tags in mind and is not allowed to make any typos in the tag. To help our users out, I use Rails&amp;rsquo; auto_complete feature.&lt;/p>
&lt;p>In your BooksController:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="n">auto_complete_for&lt;/span> &lt;span class="ss">:book&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:tag_list&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">autocomplete_tag_list&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="vi">@all_tags&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">Tag&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">find&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="ss">:all&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:order&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s1">&amp;#39;name ASC&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">re&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">Regexp&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">new&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;^&lt;/span>&lt;span class="si">#{&lt;/span>&lt;span class="n">params&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="ss">:record&lt;/span>&lt;span class="o">][&lt;/span>&lt;span class="ss">:tag_list&lt;/span>&lt;span class="o">]&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;i&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="vi">@tags&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="vi">@all_tags&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">find_all&lt;/span> &lt;span class="k">do&lt;/span> &lt;span class="o">|&lt;/span>&lt;span class="n">t&lt;/span>&lt;span class="o">|&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">t&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">match&lt;/span> &lt;span class="n">re&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">render&lt;/span> &lt;span class="ss">:layout&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="kp">false&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>We can now create the template for the results which are found.
In app/views/books/autocomplete_tag_list.rhtml:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-erb" data-lang="erb">&amp;lt;ul class=&amp;#34;autocomplete_list&amp;#34;&amp;gt;
&amp;lt;% @tags.each do |t| %&amp;gt;
&amp;lt;li class=&amp;#34;autocomplete_item&amp;#34;&amp;gt;&amp;lt;%= t %&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;% end %&amp;gt;&amp;lt;/ul&amp;gt;
&lt;/code>&lt;/pre>&lt;p>Now comes the difficult part, integration of the auto_complete widget within ActiveScaffold.&lt;/p>
&lt;p>ActiveScaffold has the possibility to change the way every attribute is displayed on the create and edit page. I want to change the form for the attribute &amp;rsquo;tag_list&amp;rsquo;. To do this, I create create a file named app/views/books/_tag_list_form_column.rhtml:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-erb" data-lang="erb">&amp;lt;dl&amp;gt;
&amp;lt;dt&amp;gt;
&amp;lt;label for=&amp;#34;record_tag_list&amp;#34;&amp;gt;AutoCompleted Tag List&amp;lt;/label&amp;gt;
&amp;lt;/dt&amp;gt;
&amp;lt;dd&amp;gt;
&amp;lt;%= text_field_tag &amp;#39;record[tag_list]&amp;#39;, @record.tag_list %&amp;gt;
&amp;lt;p class=&amp;#34;auto_complete&amp;#34; id=&amp;#34;record_tag_list_&amp;lt;%=@record[:id]%&amp;gt;_auto_complete&amp;#34;&amp;gt;
style=&amp;#34;{height: 80px;}&amp;#34;&amp;gt;
&amp;lt;script type=&amp;#34;text/javascript&amp;#34;&amp;gt;
//&amp;lt;![CDATA[
var record_tag_list_&amp;lt;%= @record[:id].to_s %&amp;gt;_auto_completer =
new Ajax.Autocompleter(
\&amp;#39;record[tag_list]\&amp;#39;,
\&amp;#39;record_tag_list_&amp;lt;%=@record[:id]%&amp;gt;_auto_complete\&amp;#39;,
\&amp;#39;/articles/autocomplete_tag_list\&amp;#39;, {tokens: \&amp;#39;,\&amp;#39;});
//]]&amp;gt;
&amp;lt;/script&amp;gt;
&amp;lt;/dd&amp;gt;
&amp;lt;/dl&amp;gt;
&lt;/code>&lt;/pre>&lt;p>This shows a text field and generates a div that contains the available tags that we can show to the user. To populate the list of tags we use Ajax.Autocompler, which requires three arguments: the id of the text_field; the id of the div where you want to show possible tags to the user; and third, the URL of the action we created before, that returns the proper tags.&lt;/p>
&lt;p>The &amp;rsquo;tokens&amp;rsquo; part of the last argument indicates that the user can seperate multiple tags with a comma. So, if you&amp;rsquo;ve entered one tag, added a comma and start typing a new tag, the auto complete feature will only lookup that second tag you&amp;rsquo;re typing!&lt;/p>
&lt;p>That&amp;rsquo;s it. Just spice things up a bit with some Style, and you&amp;rsquo;re done. Enjoy!&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/07/01/activescaffold-acts_as_taggable-auto-complete/</guid><pubDate>Sun, 01 Jul 2007 00:00:00 +0000</pubDate></item><item><title>AJAX Rules! 80 JavaScript Solutions for professional coding</title><link>https://www.devroom.io/2007/06/21/ajax-rules-80-javascript-solutions-for-professional-coding/</link><description>&lt;p>Smashing Magazine has put together a very comprehensive list of &lt;a href="http://www.smashingmagazine.com/2007/06/20/ajax-javascript-solutions-for-professional-coding/">80 AJAX JavaScript solutions for professional coding&lt;/a>. How great is that?!&lt;/p>
&lt;p>The list includes solutions for auto-completion, inline editing, menus, tabs, calendars, all sorts of interactive stuff and tables, charts, graphs, forms, grids, lightboxes, galleries, showcases, visual effects and also, some basic JavaScripts that everybody should know about.&lt;/p>
&lt;p>Check out &lt;a href="http://www.smashingmagazine.com/2007/06/20/ajax-javascript-solutions-for-professional-coding/">the list&lt;/a> now!&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/06/21/ajax-rules-80-javascript-solutions-for-professional-coding/</guid><pubDate>Thu, 21 Jun 2007 00:00:00 +0000</pubDate></item><item><title>Action Mailer: All mail comes from MAILER DAEMON</title><link>https://www.devroom.io/2007/06/20/action-mailer-all-mail-comes-from-mailer-daemon/</link><description>&lt;p>Today I was trying to send mail from my Rails application through Action Mailer. This is quite simple, but I
wanted to use a custom from-address. So, I create a setup_email method in my UserNotifier class that sets
some defaults for every email sent out:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">UserNotifier&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="no">ActionMailer&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="no">Base&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kp">protected&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">setup_email&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">user&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="vi">@recipients&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">#{&lt;/span>&lt;span class="n">user&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">email&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="vi">@from&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;My Application &amp;lt;no-reply@example.com&amp;gt;&amp;#34;&lt;/span>&lt;span class="o">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>May you spotted the problem already, but I didn&amp;rsquo;t. All the mail sent came from &amp;ldquo;MAILER DAEMON&amp;rdquo;.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">From: MAILER DAEMON
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The problem was that @from didn&amp;rsquo;t contain a properly formated from-address. It is missing the closing &amp;gt;, and so my email server ignores it.&lt;/p>
&lt;p>If you have this issue, double check the from address, and make sure it&amp;rsquo;s valid! Cheers.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/06/20/action-mailer-all-mail-comes-from-mailer-daemon/</guid><pubDate>Wed, 20 Jun 2007 00:00:00 +0000</pubDate></item><item><title>Rails production server setup and deployment on Ubuntu/Debian</title><link>https://www.devroom.io/2007/06/20/rails-production-server-setup-and-deployment-on-ubuntudebian/</link><description>&lt;p>&lt;em>Please digg this story to spread the word! Thanks!&lt;/em>&lt;/p>
&lt;p>Okay, this is a big one! This article will show you (and explain to you) how to setup a Ruby on Rails production server with Ubuntu 7.04 or Debian 4.0 and how to deploy your Rails application there.&lt;/p>
&lt;p>First, what&amp;rsquo;s getting installed:&lt;/p>
&lt;ul>
&lt;li>Ruby 1.8.5&lt;/li>
&lt;li>Ruby on Rails 1.2.3&lt;/li>
&lt;li>Subversion 1.4&lt;/li>
&lt;li>MySQL 5.x Server&lt;/li>
&lt;li>Apache 2.2.x&lt;/li>
&lt;li>Mongrel Cluster&lt;/li>
&lt;/ul>
I assume that you have just installed a fresh system with Ubuntu Linux 7.04 or Debian 4.0. If you haven't, do so now! You don't need to install the "DNS" or "LAMP" server in Ubuntu. Just a minimal system is enough for this tutorial.
&lt;p>I&amp;rsquo;ll be deploy an imaginary Rails application named &amp;ldquo;myapp&amp;rdquo; which uses MySQL and is stored in Subversion. More on that later on.&lt;/p>
&lt;p>Well, let&amp;rsquo;s get going and get that Ruby on Rails server ready.&lt;/p>
&lt;h3>Update your system&lt;/h3>
&lt;p>Before you do anything, use apt-get to update your system to the latest possible version.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">sudo apt-get update
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo apt-get dist-upgrade
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This probably installs a new kernel (linux-kernel) so a reboot may be in order for optimal performance.&lt;/p>
&lt;h3>Enable SSH&lt;/h3>
&lt;p>Most people will want to have SSH on their server to login remotely. In this case I install both the server and client so you can SSH out if you need to:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">sudo apt-get install openssh-server openssh-client
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>You will now be able to login remotely wiht SSH.&lt;/p>
&lt;p>&lt;em>You&amp;rsquo;ll need SSH later if you want to use Capistrano to deploy your Ruby on Rails application. In any case, SSH is a good thing to have around.&lt;/em>&lt;/p>
&lt;h3>Subversion&lt;/h3>
&lt;p>If you are serious about your Ruby on Rails server, you want to have Subversion around. Most deployment scripts pull the latest revision of your code from subversion. No configuration needed here.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">sudo apt-get install subversion
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>We only need the client on the production server. We&amp;rsquo;re not going to host Subversion repositories here.&lt;/p>
&lt;h3>Install MySQL Server&lt;/h3>
&lt;p>This is the first serious step you&amp;rsquo;ll have to take. Both Ubuntu 7.04 and Debian 4.0 come with MySQL 5.0.x.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">sudo apt-get install mysql-server mysql-client libmysqlclient15-dev
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Be sure to set a password for the root MySQL user. Failing to do so will leave your database open for anyone who wishes to see.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">mysqladmin -u root -h localhost password &lt;span class="s1">&amp;#39;secret&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mysqladmin -u root -h myhostname password &lt;span class="s1">&amp;#39;secret&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Make sure to replace &lt;em>secret&lt;/em> with your actual password.&lt;/p>
&lt;p>Try logging in to MySQL with your new password to make sure everything works okay.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">mysql -u root -p
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Enter password:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mysql&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>MySQL is in place now, so let&amp;rsquo;s get cracking at Ruby and Rails now.&lt;/p>
&lt;h3>Ruby, Gems, Rails&lt;/h3>
&lt;p>Installing Ruby is quite easy:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">sudo apt-get install ruby
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>You&amp;rsquo;ll now have Ruby 1.8.5. You will also need to install some other develoment package to help you build native Ruby Gems.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">sudo apt-get install make autoconf gcc ruby1.8-dev build-essentials
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>I&amp;rsquo;ll install ruby gems the conventional way (so I&amp;rsquo;m not going to use Ubuntu&amp;rsquo;s packages here). Download the &lt;a href="http://rubyforge.org/frs/?group_id=126">latest Gems .tgz here&lt;/a>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">wget http://rubyforge.org/frs/download.php/17190/rubygems-0.9.2.tgz
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ tar xvf rubygems-0.9.2.tgz
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ &lt;span class="nb">cd&lt;/span> rubygems-0.9.2/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ sudo ruby setup.rb
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ gem -v
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>That&amp;rsquo;s it for the gems. Now, install Rails an all it&amp;rsquo;s dependencies:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">sudo gem install rails --include-dependencies
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>If you get an error message complaining that the &amp;lsquo;rails&amp;rsquo; gem cannot be found include the &amp;ndash;remote option.&lt;/p>
&lt;h3>Oh no! More gems!&lt;/h3>
&lt;p>Next, let&amp;rsquo;s install some essential Ruby Gems that will make your life quite a bit easier. Here we&amp;rsquo;ll install the following gems:&lt;/p>
&lt;ul>
&lt;li>mysql - For good MySQL connectivity&lt;/li>
&lt;li>capistrano - Just to have it handy when needed&lt;/li>
&lt;li>mongrel - Rails server&lt;/li>
&lt;li>mongrel-cluster - To operate mongrel clusters&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">sudo gem install mysql capistrano mongrel mongrel-cluster --include-dependencies
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>You&amp;rsquo;ll be asked several times to choose a version for different gems. Always choose the latest available version for &amp;lsquo;ruby&amp;rsquo;. (Don&amp;rsquo;t choose win32. I don&amp;rsquo;t need to explain why.)&lt;/p>
&lt;h3>Test Rails and MySQL operability&lt;/h3>
&lt;p>Before you continue you may want to take some time to test Rails and MySQL. It&amp;rsquo;s not essential, but I recommend it because it will save you a lot of trouble later on.&lt;/p>
&lt;p>Create a new Rails application in your homedir and a create the corresponding MySQL database. Also edit config/database.yml to reflect your root password!&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">mysqladmin -u root -p create testapp_development
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ mkdir testapp
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ rails testapp
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ &lt;span class="nb">cd&lt;/span> testapp
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ vi config/database.yml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ rake db:migrate
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>If you get any errors from that last command, check the previous steps. Normally, all should be fine and you can continue safely.&lt;/p>
&lt;p>Most people configure a &amp;lsquo;socket&amp;rsquo; in their config/database.yml for MySQL. Note that this socket is in different places for different distribution. Ubuntu and Debian keep it in: /var/run/mysqld/mysqld.sock. You may need to update your configuration in order to connect to the database.&lt;/p>
&lt;h3>Apache 2.2&lt;/h3>
&lt;p>The good thing about Ubuntu/Debian is that they both include Apache 2.2.x now. This branch of Apache includes the a balancing proxy, which allows you to distribute your workload over several Mongrel servers (in your mongrel cluster). I&amp;rsquo;ll come back to that later.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">sudo apt-get install apache2
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Before you continue, enable several modules which we&amp;rsquo;ll be using later on.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">sudo a2enmod proxy_balancer
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo a2enmod proxy_ftp
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo a2enmod proxy_http
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo a2enmod proxy_connect
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo a2enmod rewrite
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>That&amp;rsquo;s it for now on Apache. Let&amp;rsquo;s move along.&lt;/p>
&lt;h3>Prepping the Rails application&lt;/h3>
&lt;p>Okay, let&amp;rsquo;s prepare the Rails application &amp;lsquo;myapp&amp;rsquo; for deployment now, shall we?&lt;/p>
&lt;p>First, create a production database on your server, and configure it in config/database.yml:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">mysql -u root -p
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">create&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">database&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">myapp_production&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">grant&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">all&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">privileges&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">on&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">myapp_production&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">to&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;myapp&amp;#39;&lt;/span>&lt;span class="o">@&lt;/span>&lt;span class="s1">&amp;#39;localhost&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="n">identified&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">by&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;secret_password&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The next step is to install capistrano. You can install it on your development machine as a gem, as demonstrated above. Next, apply capistrano to your application:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">sudo gem install capistrano
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cap --apply-to myapp
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Take a look at myapp/config/deploy.rb. This file contains the configuration for the deployment of your application. Take special care of the following, here&amp;rsquo;s an example for &amp;lsquo;myapp&amp;rsquo;:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">require&lt;/span> &lt;span class="s1">&amp;#39;mongrel_cluster/recipes&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">set&lt;/span> &lt;span class="ss">:application&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;myapp&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">set&lt;/span> &lt;span class="ss">:repository&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;http://svn.myhost.com/svn/&lt;/span>&lt;span class="si">#{&lt;/span>&lt;span class="n">application&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">/trunk&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># We only have one host&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">role&lt;/span> &lt;span class="ss">:web&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;myapp.com&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">role&lt;/span> &lt;span class="ss">:app&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;myapp.com&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">role&lt;/span> &lt;span class="ss">:db&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;myapp.com&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:primary&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="kp">true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Don&amp;#39;t forget to change this&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">set&lt;/span> &lt;span class="ss">:deploy_to&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;/home/ariejan/apps/&lt;/span>&lt;span class="si">#{&lt;/span>&lt;span class="n">application&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">set&lt;/span> &lt;span class="ss">:mongrel_conf&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">#{&lt;/span>&lt;span class="n">current_path&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">/config/mongrel_cluster.yml&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>As you can see, I&amp;rsquo;ve already included several lines that enable the use of mongrel, we&amp;rsquo;ll get to that next.&lt;/p>
&lt;p>Make sure you adapt this file to your own needs. myapp.com is the address of the server you&amp;rsquo;re going to deploy your application to. the mongrel_cluster.yml file will be created in a moment.&lt;/p>
&lt;p>On the server, make sure you create the &amp;lsquo;apps&amp;rsquo; directory. You can now setup a basic file structure for the deployment:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> myapp
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cap setup
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>On your server, you&amp;rsquo;ll notice that the /home/ariejan/apps/myapp directory was created, including some subdirectories.&lt;/p>
&lt;p>If you are annoyed with entering your SSH password every time, create and upload your public SSH key to automate this. (I&amp;rsquo;ll write something up about that later on.)&lt;/p>
&lt;p>Now, configure mongrel. For a normal setup, with moderate traffic, you can handle all traffic with two mongrel instances. The mongrel servers will only be accessable through &amp;rsquo;localhost&amp;rsquo; on the server on non-default ports. Apache will do the rest later.&lt;/p>
&lt;p>In your rails app, run the following command:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">mongrel_rails cluster::configure -e production -p &lt;span class="m">9000&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span>-a 127.0.0.1 -N &lt;span class="m">2&lt;/span> -c /home/ariejan/apps/myapp/current
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The configuration file we saw earlier has been created. Check-in all new files in to subversion now, and cold deloy your application!&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">cap cold_deploy
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The deployment will checkout the most recent code from Subversion and start the mongrel servers.&lt;/p>
&lt;p>After the deployment, migrate your production database and restart the mongrel cluster:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">cap migrate
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cap restart
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>To check that your application is running, issue the following command on your server. It should return you the HTML code from your app:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">curl -L http://127.0.0.1:9000
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3>Configure Apache and the Balacing Proxy&lt;/h3>
&lt;p>You have two mongrel servers running, ready to handle incoming requests. But, you want your visitors to use &amp;lsquo;myapp.com&amp;rsquo; and not an IP address with different port numbers. This is where apache comes in.&lt;/p>
&lt;p>Create a new file in /etc/apache2/sites-availbale named &amp;lsquo;myapp&amp;rsquo; and add the following:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-apache" data-lang="apache">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;proxy&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">BalancerMember&lt;/span> http://127.0.0.1:9000
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">BalancerMember&lt;/span> http://127.0.0.1:9001
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/proxy&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;virtualhost&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">ServerName&lt;/span> myapp.com
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">ServerAlias&lt;/span> www.myapp.com
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">DocumentRoot&lt;/span> &lt;span class="sx">/home/ariejan/apps/myapp/current/public&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;directory&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">Options&lt;/span> FollowSymLinks
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">AllowOverride&lt;/span> &lt;span class="k">None&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">Order&lt;/span> allow,deny
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">Allow&lt;/span> from &lt;span class="k">all&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/directory&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">RewriteEngine&lt;/span> &lt;span class="k">On&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">RewriteCond&lt;/span> %{DOCUMENT_ROOT}/system/maintenance.html -f
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">RewriteCond&lt;/span> %{SCRIPT_FILENAME} !maintenance.html
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">RewriteRule&lt;/span> ^.*$ &lt;span class="sx">/system/maintenance.html&lt;/span> [L]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">RewriteRule&lt;/span> ^/$ &lt;span class="sx">/index.html&lt;/span> [QSA]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">RewriteRule&lt;/span> ^([^.]+)$ $1.html [QSA]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">RewriteCond&lt;/span> %{DOCUMENT_ROOT}/%{REQUEST_FILENAME} !-f
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">RewriteRule&lt;/span> ^/(.*)$ balancer://myapps_mongrel_cluster%{REQUEST_URI} [P,QSA,L]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">ErrorLog&lt;/span> &lt;span class="sx">/home/ariejan/apps/myapps/shared/log/tankfactions_errors_log&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">CustomLog&lt;/span> &lt;span class="sx">/home/ariejan/apps/myapps/shared/log/tankfactions_log&lt;/span> combined
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/virtualhost&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now enable this new site in apache:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">sudo a2ensite myapp
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">/etc/init.d/apache2 force-reload
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>In some cases you may need to make a small change to /etc/apache2/mods-enabled/proxy.conf and swap&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-apache" data-lang="apache">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">Order&lt;/span> deny,allow
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">Deny&lt;/span> from &lt;span class="k">all&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>for&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-apache" data-lang="apache">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">Order&lt;/span> allow,deny
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">Allow&lt;/span> from &lt;span class="k">all&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>That&amp;rsquo;s all, you can now access your app on myapp.com!&lt;/p>
&lt;h3>Maintaining your application&lt;/h3>
&lt;p>Now, happily develop your application and make update (you check them in to Subversion). To update your web server run:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">cap deploy
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>If you made changes in the database, you may want to run a long_deploy:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">cap long_deploy
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>And if for some reason, your mongrel cluster dies, just restart it.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">cap restart
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>That&amp;rsquo;s it! Happy hacking :)&lt;/p>
&lt;p>&lt;strong>&lt;a href="http://digg.com/programming/Rails_production_server_setup_and_deployment_on_Ubuntu_Debian">Please digg this story&lt;/a> to help spread the word! Thanks a lot!&lt;/strong>&lt;/p>
&lt;p>&lt;em>Comments can be posted here or in my new and shiny &lt;a href="http://groups.google.com/group/ariejannet">Google Group&lt;/a>!&lt;/em>&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/06/20/rails-production-server-setup-and-deployment-on-ubuntudebian/</guid><pubDate>Wed, 20 Jun 2007 00:00:00 +0000</pubDate></item><item><title>Geslaagd! / Passed my final exams!</title><link>https://www.devroom.io/2007/06/19/geslaagd-passed-my-final-exams/</link><description>&lt;p>&lt;em>Please scroll down for the English version.&lt;/em>&lt;/p>
&lt;p>Bij wijze van hoge uitzondering, een post in het Nederlands op mijn weblog! Ik ben vandaag geslaagd voor mijn opleiding Hogere Informatica (aan Fontys Hogeschool ICT, Eindhoven) en mag mezelf nu gediplomeerd Software Engineer noemen!&lt;/p>
&lt;p>Ik ben op drie punten beoordeeld:&lt;/p>
&lt;ul>
&lt;li>Eindrapport: 7,5 – Ik ben er gewoon niet goed in, maar het is toch nog wat redelijks geworden.&lt;/li>
&lt;li>Eindpresentatie: 8 – Het ging gewoon lekker&lt;/li>
&lt;li>Resultaat: 9 – Dit slaat op het werk dat ik heb afgeleverd, mijn inzet, zelfstandigheid etc.&lt;/li>
&lt;/ul>
&lt;p>Samen komt dit uit op een eindcijfer &lt;strong>8&lt;/strong>. Natuurlijk ben ik hier heel trots op, en ga het in ieder geval de rest van de dag vieren!&lt;/p>
&lt;p>Feliciteren kan in &amp;rsquo;n commentaartje :P&lt;/p>
&lt;p>&lt;em>English version below:&lt;/em>&lt;/p>
&lt;hr />
&lt;p>Today I passed my final exams for my degree in Software Engineering. I&amp;rsquo;ve been working on a Ruby on Rails project called &amp;ldquo;Course Builder&amp;rdquo; for the past few months. Today, I had to defend my work for a jury.In the Netherlands students are judged on 1 to 10 scale, where 10 is &amp;rsquo;total perfection&amp;rsquo;, 6 is &amp;lsquo;seems okay&amp;rsquo; and 1 is total nonsense.&lt;/p>
&lt;p>I&amp;rsquo;ve been judged on three subjects:&lt;/p>
&lt;ul>
&lt;li>Final report: 7.5 – I'm not very good at writing reports, but it's above average, still.&lt;/li>
&lt;li>Presentation: 8 – It went very well. I'm so happy with my MacBook and KeyNote&lt;/li>
&lt;li>Results: 9 – The results of my work and how I approached the project as a whole&lt;/li>
&lt;/ul>
&lt;p>The final grade is an &lt;strong>8&lt;/strong>. I&amp;rsquo;m very happy with the over-all result and I&amp;rsquo;ll be celebrating the rest of the day.&lt;/p>
&lt;p>You may congratulate me in a comment :P&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/06/19/geslaagd-passed-my-final-exams/</guid><pubDate>Tue, 19 Jun 2007 00:00:00 +0000</pubDate></item><item><title>Buy it now! Mac OS X 10.5 Leopard</title><link>https://www.devroom.io/2007/06/14/buy-it-now-mac-os-x-105-leopard/</link><description>&lt;p>It appears that Amazon is accepting pre-orders for &lt;a href="http://www.amazon.com/gp/product/B000FK88JK?ie=UTF8&amp;tag=ariejannet-20&amp;linkCode=as2&amp;camp=1789&amp;creative=9325&amp;creativeASIN=B000FK88JK">Apple Mac OS X Version 10.5 Leopard&lt;/a>&lt;img src="http://www.assoc-amazon.com/e/ir?t=ariejannet-20&amp;l=as2&amp;o=1&amp;a=B000FK88JK" style="border: medium none ! important; margin: 0px ! important" border="0" height="1" width="1" />, which is scheduled for release in October 2007.&lt;/p>
&lt;p>There are, as usual two version available, &lt;a href="http://www.amazon.com/gp/product/B000FK88JK?ie=UTF8&amp;tag=ariejannet-20&amp;linkCode=as2&amp;camp=1789&amp;creative=9325&amp;creativeASIN=B000FK88JK">Apple Mac OS X Version 10.5 Leopard&lt;/a>&lt;img src="http://www.assoc-amazon.com/e/ir?t=ariejannet-20&amp;l=as2&amp;o=1&amp;a=B000FK88JK" style="border: medium none ! important; margin: 0px ! important" border="0" height="1" width="1" /> at $129 for a single license and the &lt;a href="http://www.amazon.com/gp/product/B000BR0NPO?ie=UTF8&amp;tag=ariejannet-20&amp;linkCode=as2&amp;camp=1789&amp;creative=9325&amp;creativeASIN=B000BR0NPO">Apple Mac OS X Version 10.5 Leopard Family Pack&lt;/a>&lt;img src="http://www.assoc-amazon.com/e/ir?t=ariejannet-20&amp;l=as2&amp;o=1&amp;a=B000BR0NPO" style="border: medium none ! important; margin: 0px ! important" border="0" height="1" width="1" /> for $199, which includes licenses for up to five Macs in your home.&lt;/p>
&lt;p>If you want to be sure you have the latest version of &lt;a href="http://www.amazon.com/gp/product/B000FK88JK?ie=UTF8&amp;tag=ariejannet-20&amp;linkCode=as2&amp;camp=1789&amp;creative=9325&amp;creativeASIN=B000FK88JK">&lt;/a>&lt;img src="http://www.assoc-amazon.com/e/ir?t=ariejannet-20&amp;l=as2&amp;o=1&amp;a=B000FK88JK" style="border: medium none ! important; margin: 0px ! important" border="0" height="1" width="1" /> when it arrives: &lt;a href="http://www.amazon.com/gp/product/B000FK88JK?ie=UTF8&amp;tag=ariejannet-20&amp;linkCode=as2&amp;camp=1789&amp;creative=9325&amp;creativeASIN=B000FK88JK">pre-order now!&lt;/a>&lt;img src="http://www.assoc-amazon.com/e/ir?t=ariejannet-20&amp;l=as2&amp;o=1&amp;a=B000FK88JK" style="border: medium none ! important; margin: 0px ! important" border="0" height="1" width="1" />&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/06/14/buy-it-now-mac-os-x-105-leopard/</guid><pubDate>Thu, 14 Jun 2007 00:00:00 +0000</pubDate></item><item><title>BASH your SVN and Trac installation!</title><link>https://www.devroom.io/2007/06/12/bash-your-svn-and-trac-installation/</link><description>&lt;p>I&amp;rsquo;ve already discussed how to install &lt;a href="http://ariejan.net/?s=subversion">Subversion&lt;/a> and &lt;a href="http://ariejan.net/?s=trac">Trac&lt;/a> on your &lt;a href="http://ariejan.net/?s=ubuntu">Ubuntu server&lt;/a>. In my case I have a server that manages different SVN and Trac installations for a group of developers.&lt;/p>
&lt;p>Creating a new SVN repository and Trac installation every time is quite boring and &lt;em>&amp;ldquo;if you need to do it more than once, you should automate it&amp;rdquo;&lt;/em>. So, that&amp;rsquo;s what I did.&lt;/p>
&lt;p>The result is the following BASH script. It takes one argument, the name of the project you want to create. E.g if you wanted to create a SVN repository and trac installation for &amp;ldquo;My Project&amp;rdquo; you would run the following command:&lt;/p>
&lt;pre>$ ./create_dev_env my_project&lt;/pre>
&lt;p>The script it self looks like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#!/bin/sh
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&lt;/span>&lt;span class="nb">echo&lt;/span> &lt;span class="o">==&lt;/span> Creating Subversion and Trac installation &lt;span class="k">for&lt;/span> &lt;span class="nv">$1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">echo&lt;/span> &lt;span class="o">=&lt;/span> Creating SVN Repository...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Subversion&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> /var/lib/svn
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mkdir -p /var/lib/svn/&lt;span class="nv">$1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">svnadmin create /var/lib/svn/&lt;span class="nv">$1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sed s/EXAMPLE/&lt;span class="nv">$1&lt;/span>/g /usr/share/trac/contrib/post-commit &amp;gt; /var/lib/svn/&lt;span class="nv">$1&lt;/span>/hooks/post-commit
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">chmod +x /var/lib/svn/&lt;span class="nv">$1&lt;/span>/hooks/post-commit
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">chown -R www-data:www-data /var/lib/svn/&lt;span class="nv">$1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Trac&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">echo&lt;/span> &lt;span class="o">=&lt;/span> Creating Trac install...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> /var/lib/trac
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mkdir -p /var/lib/trac/&lt;span class="nv">$1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">echo&lt;/span> - Creating files
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">trac-admin /var/lib/trac/&lt;span class="nv">$1&lt;/span> initenv &lt;span class="nv">$1&lt;/span> sqlite:db/trac.db svn &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span>/var/lib/svn/&lt;span class="nv">$1&lt;/span> /usr/share/trac/templates
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">echo&lt;/span> - Removing anonymous permissions
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">trac-admin /var/lib/trac/&lt;span class="nv">$1&lt;/span> permission remove anonymous BROWSER_VIEW
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">trac-admin /var/lib/trac/&lt;span class="nv">$1&lt;/span> permission remove anonymous CHANGESET_VIEW
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">trac-admin /var/lib/trac/&lt;span class="nv">$1&lt;/span> permission remove anonymous FILE_VIEW
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">trac-admin /var/lib/trac/&lt;span class="nv">$1&lt;/span> permission remove anonymous LOG_VIEW
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">trac-admin /var/lib/trac/&lt;span class="nv">$1&lt;/span> permission remove anonymous MILESTONE_VIEW
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">trac-admin /var/lib/trac/&lt;span class="nv">$1&lt;/span> permission remove anonymous REPORT_SQL_VIEW
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">trac-admin /var/lib/trac/&lt;span class="nv">$1&lt;/span> permission remove anonymous REPORT_VIEW
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">trac-admin /var/lib/trac/&lt;span class="nv">$1&lt;/span> permission remove anonymous ROADMAP_VIEW
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">trac-admin /var/lib/trac/&lt;span class="nv">$1&lt;/span> permission remove anonymous SEARCH_VIEW
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">trac-admin /var/lib/trac/&lt;span class="nv">$1&lt;/span> permission remove anonymous TICKET_CREATE
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">trac-admin /var/lib/trac/&lt;span class="nv">$1&lt;/span> permission remove anonymous TICKET_MODIFY
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">trac-admin /var/lib/trac/&lt;span class="nv">$1&lt;/span> permission remove anonymous TICKET_VIEW
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">trac-admin /var/lib/trac/&lt;span class="nv">$1&lt;/span> permission remove anonymous TIMELINE_VIEW
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">trac-admin /var/lib/trac/&lt;span class="nv">$1&lt;/span> permission remove anonymous WIKI_CREATE
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">trac-admin /var/lib/trac/&lt;span class="nv">$1&lt;/span> permission remove anonymous WIKI_MODIFY
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">trac-admin /var/lib/trac/&lt;span class="nv">$1&lt;/span> permission remove anonymous WIKI_VIEW
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">echo&lt;/span> - Creating Trac admins
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">trac-admin /var/lib/trac/&lt;span class="nv">$1&lt;/span> permission add ariejan TRAC_ADMIN
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">chown -R www-data:www-data /var/lib/trac/&lt;span class="nv">$1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">echo&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">echo&lt;/span> &lt;span class="o">==&lt;/span> Done.
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>First it creates the SVN directory in /var/lib/svn/my_project and creates repository and adds the trac post-commit hook for trac integration.&lt;/p>
&lt;p>Next, it creates the trac installation in /var/lib/trac/my_project and removes all the persmission the anonymous users has. (You may want to remove these lines for open source or public projects.)&lt;/p>
&lt;p>And, finally, I&amp;rsquo;m added as an administrator to the project. Make sure to replace this with you own username.&lt;/p>
&lt;p>Hope you find this script useful. Any improvements are welcome, please let me know.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/06/12/bash-your-svn-and-trac-installation/</guid><pubDate>Tue, 12 Jun 2007 00:00:00 +0000</pubDate></item><item><title>ActiveScaffold, Acts_as_taggable_on_steroids</title><link>https://www.devroom.io/2007/06/11/activescaffold-acts_as_taggable_on_steroids/</link><description>&lt;p>&lt;em>Update: also read &lt;a href="http://ariejan.net/2007/07/01/activescaffold-acts_as_taggable-auto-complete/">Active Scaffold + Acts_as_taggable + Auto Completion&lt;/a>.&lt;/em>&lt;/p>
&lt;p>This is kind of an advanced topic, but I think it may be useful to a lot of people.&lt;/p>
&lt;p>&lt;a href="http://www.activescaffold.com/">ActiveScaffold&lt;/a> is a great plugin to start building a user interface. The great thing about AS is, that is automatically recognizes associated models. When editing a model, you can easily add or select another model that you want to associate with is.&lt;/p>
&lt;p>The best example of this is an Article, where you can select the author (the associated User model) with a drop down box.&lt;/p>
&lt;p>There is only one point where I ran into trouble with ActiveScaffold: acts_as_taggable_on_steroids.&lt;/p>
&lt;p>Acts_as_taggable_on_steroids allows you to easily attach tags to models and do all kinds of crazy stuff with them. But, if you want to integrate in into AcitveScaffold, you&amp;rsquo;re in for a tough ride.&lt;/p>
&lt;p>ActiveScaffold supports has_many :through associations, but not in a way that is compatible with acts_as_taggable_on_steroids. Let me show you.&lt;/p>
&lt;p>In your ArticlesController you specify which columns to show. &amp;ldquo;tag_list&amp;rdquo; is a stringified version of the tags associated with the Article, which is great for showing to a user.&lt;/p>
&lt;p>However, if you want to edit it an article (or create one), I don&amp;rsquo;t want a text field where I have to enter tags manually, all I want are a bunch of check boxes, so I can check which tags apply to this article.&lt;/p>
&lt;p>Showing the check boxes is easy with AS. By default I show &amp;rsquo;tags&amp;rsquo;, only in the list view do I use &amp;rsquo;tag_list&amp;rsquo; instead. Also, make sure to set the ui_type for the tags column to :select. This will show you check boxes, instead of a sub form that allows you to create tags manually.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="n">active_scaffold&lt;/span> &lt;span class="ss">:article&lt;/span> &lt;span class="k">do&lt;/span> &lt;span class="o">|&lt;/span>&lt;span class="n">config&lt;/span>&lt;span class="o">|&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">config&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">columns&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">[&lt;/span>&lt;span class="ss">:title&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:body&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:tags&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:author&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:created_at&lt;/span>&lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">config&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">list&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">columns&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">[&lt;/span>&lt;span class="ss">:title&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:author&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:tag_list&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:created_at&lt;/span>&lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">config&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">columns&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="ss">:tags&lt;/span>&lt;span class="o">].&lt;/span>&lt;span class="n">ui_type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="ss">:select&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Well, very nice, right. You can now happily select the tags you want, and save your article. Not.&lt;/p>
&lt;p>As you may have noticed, the tags are not saved. Why? Acts_as_taggable adds a &amp;rsquo;tags&amp;rsquo; attribute to the model, however, when the Article model is saved, the tags attribute is overwritten by the tags specified in the &amp;ldquo;tags_list&amp;rdquo; attribute.&lt;/p>
&lt;p>The only way to solve this is to convert the tags selected in AS and store them as the tags_list attribute for the Article.&lt;/p>
&lt;p>First, let&amp;rsquo;s add a private method in the ArticleController class:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="kp">private&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">new_tag_list&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">tag_ids&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">tag_ids&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">map&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="n">k&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">h&lt;/span>&lt;span class="o">|&lt;/span> &lt;span class="n">h&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="s1">&amp;#39;id&amp;#39;&lt;/span>&lt;span class="o">]&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">collect&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="o">|&lt;/span> &lt;span class="no">Tag&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">find&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="p">)}&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">map&lt;/span> &lt;span class="k">do&lt;/span> &lt;span class="o">|&lt;/span>&lt;span class="n">tag&lt;/span>&lt;span class="o">|&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">tag&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">include?&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="no">Tag&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">delimiter&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">?&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="se">\&amp;#34;&lt;/span>&lt;span class="si">#{&lt;/span>&lt;span class="n">tag&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="se">\&amp;#34;&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="n">tag&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">name&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">join&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="no">Tag&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">delimiter&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">ends_with?&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34; &amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">?&lt;/span> &lt;span class="no">Tag&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">delimiter&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">#{&lt;/span>&lt;span class="no">Tag&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">delimiter&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> &amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>And add two protected methods that extend the functionality of ActiveScaffold:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="kp">protected&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">before_create_save&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">record&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">record&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">tag_list&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">new_tag_list&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">params&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="ss">:record&lt;/span>&lt;span class="o">][&lt;/span>&lt;span class="ss">:tags&lt;/span>&lt;span class="o">]&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">before_update_save&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">record&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">record&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">tag_list&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">new_tag_list&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">params&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="ss">:record&lt;/span>&lt;span class="o">][&lt;/span>&lt;span class="ss">:tags&lt;/span>&lt;span class="o">]&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This will take the actual form values from AS and create a tags_list. This new tags_list is then assigned to the article (named &amp;lsquo;record&amp;rsquo; here). The two protected methods process the tags every time an Article is created or updated.&lt;/p>
&lt;p>With this in place, you can happily assign tags to your articles! Please let me know if it worked for you, or if you have made any improvements to this solution.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/06/11/activescaffold-acts_as_taggable_on_steroids/</guid><pubDate>Mon, 11 Jun 2007 00:00:00 +0000</pubDate></item><item><title>Find and Replace with a MySQL Query</title><link>https://www.devroom.io/2007/06/10/find-and-replace-with-a-mysql-query/</link><description>&lt;p>There are times when you have a lot of data in a database (let&amp;rsquo;s say wp_posts for a Wordpress blog like Ariejan.net). When you need to find and replace certain strings, this can be a very tedious task. Find all posts containing the &amp;ldquo;needle&amp;rdquo; string and manually replace all these occurrences with &amp;ldquo;chocolate&amp;rdquo;. With about 200 posts, you can imagine how long this would take to do manually.&lt;/p>
&lt;p>But, as I always say: &lt;em>&amp;ldquo;You&amp;rsquo;re a programmer! You should script the hell out of everything!&amp;quot;&lt;/em>&lt;/p>
&lt;p>So, I found this: MySQL has built-in support to find and replace! Just a simple query will do:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">UPDATE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">wp_posts&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">set&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">post_body&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">replace&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">post_body&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;needle&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;chocolate&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>That&amp;rsquo;s it. The entire table &amp;lsquo;wp_posts&amp;rsquo; is searched and all occurrences of &amp;ldquo;needle&amp;rdquo; are replaced with &amp;ldquo;chocolate&amp;rdquo;. The query only took about a split second.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/06/10/find-and-replace-with-a-mysql-query/</guid><pubDate>Sun, 10 Jun 2007 00:00:00 +0000</pubDate></item><item><title>Send mail with a BASH Shell Script</title><link>https://www.devroom.io/2007/06/10/send-mail-with-a-bash-shell-script/</link><description>&lt;p>Like any good programmer, I try to automate the crap out of everything. If you have to do it more than once, I try to write a script for it.&lt;/p>
&lt;p>This time I want to show you how you can easily send an e-mail from a BASH script. The idea is that you want the script to send out an email to notify a user that something has happened.&lt;/p>
&lt;p>We&amp;rsquo;re going to use the GNU Mail utility here. The basic syntax to send an email is like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">/usr/bin/mail -s &lt;span class="s2">&amp;#34;Subject&amp;#34;&lt;/span> someone@example.org &amp;lt; message.txt
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The trick when using this in a shell script is creating and using the message.txt file correctly.&lt;/p>
&lt;p>Let&amp;rsquo;s setup the basis first:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#!/bin/bash
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&lt;/span>&lt;span class="nv">SUBJECT&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;Automated Security Alert&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">TO&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;alarms@ariejan.net&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">MESSAGE&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;/tmp/message.txt&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">/usr/bin/mail -s &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$SUBJECT&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$TO&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> &amp;lt; &lt;span class="nv">$MESSAGE&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>All we need to do now is create the message. In this example we&amp;rsquo;re going to notify the receiver that something happened at a certain time. We can use the append (&amp;raquo;) operator to add text to the message file. Afterwards, we must remove the temporary message file, of course. The complete script now becomes:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#!/bin/bash
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&lt;/span>&lt;span class="nv">SUBJECT&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;Automated Security Alert&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">TO&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;alarms@ariejan.net&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">MESSAGE&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;/tmp/message.txt&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">echo&lt;/span> &lt;span class="s2">&amp;#34;Security breached!&amp;#34;&lt;/span> &amp;gt;&amp;gt; &lt;span class="nv">$MESSAGE&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">echo&lt;/span> &lt;span class="s2">&amp;#34;Time: `date`&amp;#34;&lt;/span> &amp;gt;&amp;gt; &lt;span class="nv">$MESSAGE&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">/usr/bin/mail -s &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$SUBJECT&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$TO&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> &amp;lt; &lt;span class="nv">$MESSAGE&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">rm &lt;span class="nv">$MESSAGE&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The email will contain the a timestamp from when the mail was sent.&lt;/p>
&lt;p>This method is great for letting an administrator now if something happened. Maybe you need to check if your webserver is up and running. This script can an administrator about the issue.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/06/10/send-mail-with-a-bash-shell-script/</guid><pubDate>Sun, 10 Jun 2007 00:00:00 +0000</pubDate></item><item><title>Ultimate List of Ruby Resources</title><link>https://www.devroom.io/2007/06/10/ultimate-list-of-ruby-resources/</link><description>&lt;p>This is the first post, named &amp;ldquo;Ruby&amp;rdquo;, in a series of &amp;ldquo;Ultimate List of &amp;hellip; Resources&amp;rdquo;. I&amp;rsquo;m going to compose several lists for different topics I encounter during my development work. To start, I begin with Ruby. Later, I will add &amp;ldquo;Ultimate Lists&amp;rdquo; about Ruby on Rails, Subversion, AJAX and some other topics.&lt;/p>
&lt;p>Feel free to &lt;a href="https://www.devroom.io/contact">let me know&lt;/a> if I missed an important resource. I&amp;rsquo;m also open to suggestions about other &amp;ldquo;Ultimate Lists&amp;rdquo;.&lt;/p>
&lt;p>For now, you&amp;rsquo;ll have to settle for the &amp;ldquo;Ultimate List of Ruby Resources&amp;rdquo;.&lt;/p>
&lt;p>&lt;strong>Books&lt;/strong>&lt;/p>
&lt;ol>
&lt;li>&lt;a href="http://www.amazon.com/gp/redirect.html%3FASIN=0974514055%26tag=ariejannet-20%26lcode=xm2%26cID=2025%26ccmID=165953%26location=/o/ASIN/0974514055%253FSubscriptionId=02ZH6J1W0649DTNS6002">"Programming Ruby: The Pragmatic Programmers' Guide, Second Edition"&lt;/a> by Dave Thomas, Chad Fowler and Andy Hunt&lt;/li>
&lt;li>&lt;a href="http://www.amazon.com/gp/redirect.html%3FASIN=0672328844%26tag=ariejannet-20%26lcode=xm2%26cID=2025%26ccmID=165953%26location=/o/ASIN/0672328844%253FSubscriptionId=02ZH6J1W0649DTNS6002">"The Ruby Way, Second Edition: Solutions and Techniques in Ruby Programming"&lt;/a> by Hal Fulton&lt;/li>
&lt;li>&lt;a href="http://www.amazon.com/gp/redirect.html%3FASIN=0596523696%26tag=ariejannet-20%26lcode=xm2%26cID=2025%26ccmID=165953%26location=/o/ASIN/0596523696%253FSubscriptionId=02ZH6J1W0649DTNS6002">"Ruby Cookbook"&lt;/a> by Lucas Carlson, Leonard Richardson&lt;/li>
&lt;li>&lt;a href="http://www.amazon.com/gp/redirect.html%3FASIN=0977616614%26tag=ariejannet-20%26lcode=xm2%26cID=2025%26ccmID=165953%26location=/o/ASIN/0977616614%253FSubscriptionId=02ZH6J1W0649DTNS6002">"Everyday Scripting with Ruby: For Teams, Testers, and You" by Brian Marick&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://www.amazon.com/gp/redirect.html%3FASIN=1590597664%26tag=ariejannet-20%26lcode=xm2%26cID=2025%26ccmID=165953%26location=/o/ASIN/1590597664%253FSubscriptionId=02ZH6J1W0649DTNS6002">"Beginning Ruby: From Novice to Professional"&lt;/a> by Peter Cooper&lt;/li>
&lt;li>&lt;a href="http://www.amazon.com/gp/redirect.html%3FASIN=0596002149%26tag=ariejannet-20%26lcode=xm2%26cID=2025%26ccmID=165953%26location=/o/ASIN/0596002149%253FSubscriptionId=02ZH6J1W0649DTNS6002">"Ruby In A Nutshell"&lt;/a> by Yukihiro Matsumoto&lt;/li>
&lt;li>&lt;a href="http://www.amazon.com/gp/redirect.html%3FASIN=0976694069%26tag=ariejannet-20%26lcode=xm2%26cID=2025%26ccmID=165953%26location=/o/ASIN/0976694069%253FSubscriptionId=02ZH6J1W0649DTNS6002">"Enterprise Integration with Ruby"&lt;/a> by Maik Schmidt&lt;/li>
&lt;/ol>
&lt;strong>Online manuals and API's&lt;/strong>
&lt;ol start="8">
&lt;li>&lt;a href="http://www.noobkit.com/">Noobkit&lt;/a> — Easily searchable Ruby API&lt;/li>
&lt;li>&lt;a href="http://www.ruby-doc.org/core">Core API documentation&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://www.ruby-doc.org/stdlib/">Ruby Standard Library&lt;/a>&lt;/li>
&lt;/ol>
&lt;strong>Groups, chat, forums, wiki and other community websites&lt;/strong>
&lt;ol start="11">
&lt;li>&lt;a href="http://www.ruby-forum.com/forum/4">Ruby Forum: Ruby&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://www.ruby-forum.com/forum/14">Ruby Forum: Ruby Core&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://groups.google.com/group/comp.lang.ruby/topics?lnk=srg">comp.lang.ruby&lt;/a> — Newsgroup&lt;/li>
&lt;li>&lt;a href="http://groups.google.com/group/ruby-talk-google">Ruby-talk-google&lt;/a> — Google Group&lt;/li>
&lt;/ol>
&lt;strong>Ruby Blogs&lt;/strong> (is yours listed yet? — &lt;a href="https://www.devroom.io/contact">Let me know!&lt;/a>)
&lt;ol start="15">
&lt;li>&lt;a href="http://www.oreillynet.com/ruby/">O'Reilly Ruby Blog&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://www.rubyinside.com/">Ruby Inside&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://www.rubycorner.com/">RubyCorner&lt;/a>&lt;/li>
&lt;/ol></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/06/10/ultimate-list-of-ruby-resources/</guid><pubDate>Sun, 10 Jun 2007 00:00:00 +0000</pubDate></item><item><title>Coming up: Ubuntu Development Server Guide</title><link>https://www.devroom.io/2007/06/07/coming-up-ubuntu-development-server-guide/</link><description>&lt;p>My articles about setting up a Ubuntu Development Server (&lt;a href="http://ariejan.net/2006/12/01/how-to-setup-a-ubuntu-development-server-part-1/">part 1&lt;/a> and part 2) have been very successful.&lt;/p>
&lt;p>I&amp;rsquo;m considering writing a new guide with more up-to-date information on how to setup a development server that allows you (and your team) to develop software, manage source code, track tickets and all that stuff.&lt;/p>
&lt;p>What should, according to you, be included in this guide? Please let me know! Just leave a comment and I&amp;rsquo;ll pick it up.&lt;/p>
&lt;p>&lt;strong>Thank you for your input!&lt;/strong>&lt;/p>
&lt;p>&lt;em>Update: Estimated release of the &amp;ldquo;Ubuntu Develpment Server Guide is mid july&lt;/em>&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/06/07/coming-up-ubuntu-development-server-guide/</guid><pubDate>Thu, 07 Jun 2007 00:00:00 +0000</pubDate></item><item><title>Trac, WebAdmin plugin and global configuration</title><link>https://www.devroom.io/2007/05/30/trac-webadmin-plugin-and-global-configuration/</link><description>&lt;p>As you may know I manage quite a few &lt;a href="http://trac.edgewall.org/">trac&lt;/a> installations. A few days ago I upgrade my server from Ubuntu &amp;ldquo;Dapper Drake&amp;rdquo; 6.06 to Ubuntu &amp;ldquo;Feisty Fawn&amp;rdquo; 7.04. This also upgrade trac 0.9.x to 0.10.3.&lt;/p>
&lt;p>I was happy, since trac 0.10.3 has many improvements over 0.9.x, but there was one thing I was not so happy about. After the upgrade, I upgraded all my trac installations and everything seemed to be okay, except for the &lt;a href="http://trac.edgewall.org/wiki/WebAdmin">WebAdmin plugin&lt;/a>. Apparently it was not installed anymore.&lt;/p>
&lt;p>What happened? After upgrading the trac package, the plugins directory was emptied. Well, just re-install the WebAdmin plugin for 0.10.x.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> /usr/share/trac/plugins
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo svn co http://svn.edgewall.org/repos/trac/sandbox/webadmin/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> webadmin
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo python setup.py bdist_egg
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> dist
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo easy_install-2.4 TracWebAdmin-0.1.2dev_r4429-py2.4.egg
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>That was easy, next I wanted to enable the plugin for all my trac installations by adding the proper configuration to /usr/share/trac/conf/trac.ini, the global trac configuration file that is used by all trac installs.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">[components]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">webadmin.* = enabled
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>After restarting Apache (this is needed for some reason to get trac to read the new configuration file), no admin button showed up in any of the projects.&lt;/p>
&lt;p>What went wrong is that Ubuntu (or Debian?) maintainers have changed the location of the global configuration file for trac. There are three solutions to this, all of them work fine, although I recommend you use the first one.&lt;/p>
&lt;p>&lt;strong>1. Move your global configuration&lt;/strong>&lt;/p>
&lt;p>The best way to tackle this problem is to move your global configuration file to the new location: /etc/trac/trac.ini&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">sudo mv /usr/share/trac/conf/trac.ini /etc/trac/trac.ini
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This way you&amp;rsquo;re configuration is safe from new upgrades and confirms to the defaults the package maintainer has set.&lt;/p>
&lt;p>&lt;strong>2. Symlink the configuration&lt;/strong>&lt;/p>
&lt;p>If for some reason you don&amp;rsquo;t want to actually move your /usr/share/trac/conf/trac.ini file, you can create a symlink to the new location:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">sudo ln -sf /usr/share/trac/conf/trac.ini /etc/trac/trac.ini
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;pre>
This leaves your original configuration file in tact, but it may be removed by new upgrades.
&lt;strong>3. Change Trac&lt;/strong>
You may also change the location where trac looks for the configuration file. Open up /var/lib/python-support/python2.5/trac/siteconfig.py and change the following:&lt;/pre>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;lt;&lt;/span> &lt;span class="n">__default_conf_dir__&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;/etc/trac&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;gt;&lt;/span> &lt;span class="n">__default_conf_dir__&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;/usr/share/trac/conf&amp;#39;&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">pre&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">(&lt;/span>&lt;span class="n">Note&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">the&lt;/span> &lt;span class="o">&amp;gt;&lt;/span> &lt;span class="ow">and&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="n">symbols&lt;/span> &lt;span class="n">mark&lt;/span> &lt;span class="n">what&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="n">removed&lt;/span> &lt;span class="ow">and&lt;/span> &lt;span class="n">what&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="n">added&lt;/span> &lt;span class="n">to&lt;/span> &lt;span class="n">the&lt;/span> &lt;span class="n">file&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">In&lt;/span> &lt;span class="nb">any&lt;/span> &lt;span class="k">case&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">reboot&lt;/span> &lt;span class="n">your&lt;/span> &lt;span class="n">web&lt;/span> &lt;span class="n">server&lt;/span> &lt;span class="ow">and&lt;/span> &lt;span class="n">you&lt;/span> &lt;span class="n">should&lt;/span> &lt;span class="n">be&lt;/span> &lt;span class="n">good&lt;/span> &lt;span class="n">to&lt;/span> &lt;span class="n">go&lt;/span> &lt;span class="n">again&lt;/span>&lt;span class="o">.&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/05/30/trac-webadmin-plugin-and-global-configuration/</guid><pubDate>Wed, 30 May 2007 00:00:00 +0000</pubDate></item><item><title>Installing RMagick Ruby Gem on Mac OS X 10.4.9</title><link>https://www.devroom.io/2007/05/29/installing-rmagick-ruby-gem-on-mac-os-x-1049/</link><description>&lt;p>When you want to manipulate images with Ruby (or your Rails application) you&amp;rsquo;ll probably want RMagick installed. This is no easy feat on Mac OS X.&lt;/p>
&lt;p>The official guide suggests installing X11 and using darwinports to install everything. This guide shows you how to easily install RMagick on you Mac OS X system. In this case I use Mac OS X 10.4.9.&lt;/p>
&lt;p>Before you jump in, make sure you have &lt;a href="http://developer.apple.com/tools/xcode/">Xcode&lt;/a> installed. You can get it for free from Apple.&lt;/p>
&lt;p>I&amp;rsquo;ll also assume you have Ruby and rubygems installed and working already.&lt;/p>
&lt;p>You will need to download, compile and install several graphics libraries that RMagick needs. Let&amp;rsquo;s do this now.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">curl -O http://download.savannah.gnu.org/releases/freetype/freetype-2.1.10.tar.gz
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">tar xzvf freetype-2.1.10.tar.gz
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> freetype-2.1.10
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">./configure --prefix&lt;span class="o">=&lt;/span>/usr/local
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">make
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo make install
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> ..
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">curl -O http://superb-west.dl.sourceforge.net/sourceforge/libpng/libpng-1.2.10.tar.bz2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">bzip2 -dc libpng-1.2.10.tar.bz2 &lt;span class="p">|&lt;/span> tar xv
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> libpng-1.2.10
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">./configure --prefix&lt;span class="o">=&lt;/span>/usr/local
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">make
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo make install
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> ..
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">curl -O ftp://ftp.uu.net/graphics/jpeg/jpegsrc.v6b.tar.gz
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">tar xzvf jpegsrc.v6b.tar.gz
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> jpeg-6b
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ln -s &lt;span class="sb">`&lt;/span>which glibtool&lt;span class="sb">`&lt;/span> ./libtool
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">export&lt;/span> &lt;span class="nv">MACOSX_DEPLOYMENT_TARGET&lt;/span>&lt;span class="o">=&lt;/span>10.4
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">./configure --enable-shared --prefix&lt;span class="o">=&lt;/span>/usr/local
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">make
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo make install
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> ..
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">curl -O ftp://ftp.remotesensing.org/libtiff/tiff-3.8.2.tar.gz
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">tar xzvf tiff-3.8.2.tar.gz
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> tiff-3.8.2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">./configure --prefix&lt;span class="o">=&lt;/span>/usr/local
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">make
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo make install
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> ..
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Next we install ImageMagick:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">curl -O http://easynews.dl.sourceforge.net/sourceforge/imagemagick/ImageMagick-6.3.0-0.tar.gz
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">tar xzvf ImageMagick-6.3.0-0.tar.gz
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> ImageMagick-6.3.0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">./configure --prefix&lt;span class="o">=&lt;/span>/usr/local
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">make
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo make install
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> ..
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>And now, ladies and gentlemen, what you&amp;rsquo;ve all been waiting for: RMagick:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">sudo gem install --no-rdoc --no-ri RMagick
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>In my case the generation of the documentation fails, so I tell rubygems not to compile the docs.&lt;/p>
&lt;p>You now have RMagick installed on you Mac OS X 10.4.9 machine! Congratulations!&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/05/29/installing-rmagick-ruby-gem-on-mac-os-x-1049/</guid><pubDate>Tue, 29 May 2007 00:00:00 +0000</pubDate></item><item><title>Slow connections with ProFTPD</title><link>https://www.devroom.io/2007/05/29/slow-connections-with-proftpd/</link><description>&lt;p>My shiny new VPS, which is running Ubuntu Linux, uses ProFTPD for FTP access. Today I noticed that setting up the connection takes about 5 to 10 seconds. This is really annoying when editing files through FTP.&lt;/p>
&lt;p>So, I investigated and found that by default ProFTPD tries to revolve the hostname of the client in order to put that in the logs instead of a plain IP address. This lookup can take quite some time, let&amp;rsquo;s say 5 to 10 seconds, especially when the look up fails and you have to wait on a time-out.&lt;/p>
&lt;p>It&amp;rsquo;s easy to stop ProFTPD from behaving like this by adding the following line to your proftpd.conf in /etc/proftpd:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">IdentLookups off
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Restart ProFTPD and you&amp;rsquo;ll have a fast FTP connection to enjoy!&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/05/29/slow-connections-with-proftpd/</guid><pubDate>Tue, 29 May 2007 00:00:00 +0000</pubDate></item><item><title>BAT - TER - Y</title><link>https://www.devroom.io/2007/05/28/bat-ter-y/</link><description>&lt;p>You all know the word: &lt;em>&amp;ldquo;Battery&amp;rdquo;&lt;/em>. Like most words, it has several meanings. This site greatly explains the meaning of the word battery, and adds a little lyric-sugar to it!&lt;/p>
&lt;p>Come on boys and girls! Sing along to &lt;a href="http://bat-ter-y.com">BAT • TER • Y&lt;/a>.&lt;/p>
&lt;p>(Please digg the site or post a link to it. Thanks!)&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/05/28/bat-ter-y/</guid><pubDate>Mon, 28 May 2007 00:00:00 +0000</pubDate></item><item><title>MERGE request failed on ‘/path/to/file’</title><link>https://www.devroom.io/2007/05/21/merge-request-failed-on-pathtofile/</link><description>&lt;p>After upgrading my Subversion server to Ubuntu Feisty, I noticed that when committing I got the following error:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">svn: MERGE request failed on &amp;#39;/svn/repository/trunk&amp;#39;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">svn: MERGE of &amp;#39;/svn/repository/trunk&amp;#39;: 200 OK (http://svn.myserver.com)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Although the messages says that the commit failed, it has not. A simple &amp;lsquo;svn update&amp;rsquo; will merge the changes you made to the repository to your working copy again and you&amp;rsquo;re good to go.&lt;/p>
&lt;p>So, what is the problem here? Are you by any chance running Trac? Did you install the post-commit hook to integrate Subversion with Trac? Right, so did I.&lt;/p>
&lt;p>The problem here is that the trac-post-commit-hook script needs a few updates in order to include the proper modules. Take a look at the &lt;a href="http://trac.edgewall.org/browser/branches/0.10-stable/contrib/trac-post-commit-hook">most recent 0.10.x version&lt;/a>.&lt;/p>
&lt;p>After updating the trac-post-commit-hook script, commits worked fine again. Such an easy fix for such a nasty problem.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/05/21/merge-request-failed-on-pathtofile/</guid><pubDate>Mon, 21 May 2007 00:00:00 +0000</pubDate></item><item><title>FeedBurner acquired by Google!</title><link>https://www.devroom.io/2007/05/18/feedburner-acquired-by-google/</link><description>&lt;p>It looks like &lt;a href="http://google.com">Google&lt;/a> is about to acquire &lt;a href="http://feedburner.com">FeedBurner&lt;/a>. Just a note to all of you to let you know. If you have a blog or RSS enabled website, burn your feed now and get your AdSense account ready! Soon you&amp;rsquo;ll be able to put targeted ads in your RSS feed and add another source of income to your list! Great news!&lt;/p>
&lt;p>Please digg and share! Thank you!&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/05/18/feedburner-acquired-by-google/</guid><pubDate>Fri, 18 May 2007 00:00:00 +0000</pubDate></item><item><title>Rails Snippet: Write like Orwell with to_sentence</title><link>https://www.devroom.io/2007/05/09/rails-snippet-write-like-orwell-with-to_sentence/</link><description>&lt;p>A few weeks ago I posted &lt;a href="http://ariejan.net/2007/03/27/rails-tip-snippet-create-a-comma-seperate-list/">an article&lt;/a> that explained how to create a comma separated list from a hash of objects. When I was browsing the &lt;a href="http://api.rubyonrails.com">Rails API documentation&lt;/a> I came across a method named &lt;a href="http://api.rubyonrails.com/classes/ActiveSupport/CoreExtensions/Array/Conversions.html#M000372">to_sentence&lt;/a>.&lt;/p>
&lt;p>What this little bugger does is create a human readable, comma separated list of items in an array or hash. But the big difference here is that you can specify what the last separator must be. By default this is set to &amp;lsquo;and&amp;rsquo;. See the following example.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="vi">@users&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">User&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">find&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="ss">:all&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="vi">@users&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">collect&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="n">u&lt;/span>&lt;span class="o">|&lt;/span> &lt;span class="n">u&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">firstname&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">to_sentence&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s2">&amp;#34;Tom, Dick, and Harry&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Of you course, you can specify the last separator, called the connector. Also it&amp;rsquo;s possible to not show the last comma.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="vi">@users&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">collect&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="n">u&lt;/span>&lt;span class="o">|&lt;/span> &lt;span class="n">u&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">firstname&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">to_sentence&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="ss">:connector&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s2">&amp;#34;and of course,&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:skip_last_comma&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="kp">true&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s2">&amp;#34;tom, Dick and of course, Harry&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>I bet this will greatly simplify the way you list names, tags, categories or whatever else you want summed up in a comma separated list with a human touch.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/05/09/rails-snippet-write-like-orwell-with-to_sentence/</guid><pubDate>Wed, 09 May 2007 00:00:00 +0000</pubDate></item><item><title>Ariejan.net server move</title><link>https://www.devroom.io/2007/04/29/ariejannet-server-move/</link><description>&lt;p>Just a note to let you know that Ariejan.net has just been moved to a new server! Well, I&amp;rsquo;ve moved from shared hosting with &lt;a href="http://www.delta9.nl/">Delta9 Internet&lt;/a> to a VPS solution.&lt;/p>
&lt;p>Why on earth would I do such a thing? I&amp;rsquo;m very happy with the service I&amp;rsquo;ve recieved from Delta9 in the past. However, I wanted more control over my hosting. I&amp;rsquo;m a web developer and I have special needs. Such needs include Subversion and Ruby on Rails hosting, state-of-the art PHP and MySQL installations. PostgreSQL support and some other things. Concluding that for Ariejan.net, shared hosting was no longer an option.&lt;/p>
&lt;p>I could have taken a big step and buy myself a shiny new server all for my self, but that just doesn&amp;rsquo;t fit into my budget at this time. When the time comes to host a dedicated server I will come back to Delta9 Internet.&lt;/p>
&lt;p>So, Ariejan.net has moved to a VPS solution for the time being. Other sites I run are still hosted at Delta9, so I don&amp;rsquo;t abandon them all together.&lt;/p>
&lt;p>If you are looking for quality hosting with a personal touch, give &lt;a href="http://www.delta9.nl/">Delta9 Internet&lt;/a> a try! Tell &amp;rsquo;em I send you :)&lt;/p>
&lt;p>I want to thank &lt;a href="http://www.delta9.nl/">Delta9 Internet&lt;/a> for hosting Ariejan.net for such a long time, and when the time&amp;rsquo;s right&amp;hellip; I&amp;rsquo;ll be back!&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/04/29/ariejannet-server-move/</guid><pubDate>Sun, 29 Apr 2007 00:00:00 +0000</pubDate></item><item><title>Rails, Resources and Permalinks</title><link>https://www.devroom.io/2007/04/12/rails-resources-and-permalinks/</link><description>&lt;p>There has been quite a bit of discussion about creating permalinks with a rails resource. In this article I will show you how to create permalinks for a resource named &amp;lsquo;pages&amp;rsquo; without giving up on any of the resource goodness!&lt;/p>
&lt;p>Before I start I&amp;rsquo;ll presume you have a page scaffold_resource setup in your rails application. Make sure you have at least the following fields in your page model:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="n">t&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">column&lt;/span> &lt;span class="ss">:title&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">t&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">column&lt;/span> &lt;span class="ss">:permalink&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">t&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">column&lt;/span> &lt;span class="ss">:content&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:text&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Okay, what you want is the permalink_fu plugin. This plugin greatly simplifies the act of generating a permalink from a title. Install it first:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> railsapp
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">./script/plugin install http://svn.techno-weenie.net/projects/plugins/permalink_fu/
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>In your Page model you may now add the following line. This line will generate a permalink in the permalink attribute automatically, so you don&amp;rsquo;t have to show the permalink field in any forms.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="n">has_permalink&lt;/span> &lt;span class="ss">:title&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>That&amp;rsquo;s it for generating the appropriate permalink string in your database.&lt;/p>
&lt;p>Rails goodness has already provided you with the basic RESTful routes:&lt;/p>
&lt;ul>
&lt;li>/pages&lt;/li>
&lt;li>/pages/123&lt;/li>
&lt;li>/pages/new&lt;/li>
&lt;li>/pages/123;edit&lt;/li>
&lt;/ul>
&lt;p>But what you really want, is something like:&lt;/p>
&lt;ul>
&lt;li>/pages/perma-link-here&lt;/li>
&lt;/ul>
&lt;p>Notice that the permalink url is only a GET request and should not be used for editing or updating the page in question.&lt;/p>
&lt;p>Since using any other identifier than :id in a resource is madness, I create two new routes that will allow me to access permalinked pages. Not only that, but I do maintain the format option. Basically this means that you get three routes:&lt;/p>
&lt;ul>
&lt;li>/page/perma-link-here&lt;/li>
&lt;li>/page/perma-link-here.html&lt;/li>
&lt;li>/page/perma-link-here.xml&lt;/li>
&lt;/ul>
&lt;p>Notice that I removed the &amp;rsquo;s&amp;rsquo; from &amp;lsquo;pages&amp;rsquo; here. This is to avoid confusion with the resource &amp;lsquo;pages&amp;rsquo;. But more on that later.&lt;/p>
&lt;p>Now in config/routes.rb add the following two lines:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="n">map&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">permalink&lt;/span> &lt;span class="s1">&amp;#39;page/:permalink&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:controller&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s1">&amp;#39;pages&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:action&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s1">&amp;#39;permalink&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">map&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">connect&lt;/span> &lt;span class="s1">&amp;#39;page/:permalink.:format&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:controller&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s1">&amp;#39;pages&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:action&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s1">&amp;#39;permalink&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:format&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="kp">nil&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The first line adds a named route to an action named &amp;lsquo;permalink&amp;rsquo; in your PagesController. This gives you the ability to add peralink links easily:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="n">permalink_url&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="vi">@page&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">permalink&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The second link is unnamed, and allows you to specify a format like HTML or XML.&lt;/p>
&lt;p>The permalink action looks like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># GET /page/perma-link&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># GET /page/permal-link.xml&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">permalink&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="vi">@page&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">Page&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">find_by_permalink&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">params&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="ss">:permalink&lt;/span>&lt;span class="o">]&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">respond_to&lt;/span> &lt;span class="k">do&lt;/span> &lt;span class="o">|&lt;/span>&lt;span class="nb">format&lt;/span>&lt;span class="o">|&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">format&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">html&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="n">render&lt;/span> &lt;span class="ss">:action&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s1">&amp;#39;show&amp;#39;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">format&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">xml&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="n">render&lt;/span> &lt;span class="ss">:xml&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="vi">@page&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">to_xml&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This special permalink action uses the same &amp;lsquo;show&amp;rsquo; view as your resource.&lt;/p>
&lt;p>If you want to maintain the &amp;lsquo;pages&amp;rsquo; part of the URL, that&amp;rsquo;s possible. You&amp;rsquo;ll have to write a condition that makes sure that the :permalink parameter is a string an not an integer (ID). This article does not cover this.&lt;/p>
&lt;p>You may now use permalinks for your pages! Congratulations.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/04/12/rails-resources-and-permalinks/</guid><pubDate>Thu, 12 Apr 2007 00:00:00 +0000</pubDate></item><item><title>TipSnippet: Create a RSS feed</title><link>https://www.devroom.io/2007/04/03/tipsnippet-create-a-rss-feed/</link><description>&lt;p>RSS is hot! So, you want to fit your new Rails app with one too! That&amp;rsquo;s easy, of course, but you just need to know what to do.&lt;/p>
&lt;p>This snippet will show you how to create an RSS feed form your RESTful articles. I&amp;rsquo;ll assume you know how to generate a resource named &amp;lsquo;article&amp;rsquo; with a title, body and the default created_at and updated_at attributes.&lt;/p>
&lt;p>You&amp;rsquo;ll first need to add a new collection to your resource in config/routes.rb&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="n">map&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">resources&lt;/span> &lt;span class="ss">:articles&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:collections&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="ss">:rss&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="ss">:get&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This will expose your RSS feed as http://localhost:3000/articles;rss&lt;/p>
&lt;p>Create a corresponding action in the articles controller in app/controllers/articles_controller.rb. This method fetches the ten latest articles.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">rss&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="vi">@articles&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">Article&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">find&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="ss">:all&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:limit&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="mi">10&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:order&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s1">&amp;#39;created_at DESC&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">render&lt;/span> &lt;span class="ss">:layout&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="kp">false&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>I assume you render your articles in a layout. The render method here prevents your layout from rendering to create a plain XML file (which is what an RSS feed is).&lt;/p>
&lt;p>Next we create a view. This is not the regular RHTML you&amp;rsquo;re used to but RXML. This enables the XML generator which we&amp;rsquo;ll use to generate the RSS feed. Create &lt;code>app/views/articles/rss.rxml&lt;/code>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="n">xml&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">instruct!&lt;/span> &lt;span class="ss">:xml&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:version&lt;/span>&lt;span class="o">=&amp;gt;&lt;/span>&lt;span class="s2">&amp;#34;1.0&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">xml&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">rss&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="ss">:version&lt;/span>&lt;span class="o">=&amp;gt;&lt;/span>&lt;span class="s2">&amp;#34;2.0&amp;#34;&lt;/span>&lt;span class="p">){&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">xml&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">channel&lt;/span>&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">xml&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">title&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;My Great Blog&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">xml&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">link&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;http://www.example.com/&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">xml&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">description&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;My great blog about very interesting stuff!&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">xml&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">language&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;en-uk&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">article&lt;/span> &lt;span class="k">in&lt;/span> &lt;span class="vi">@articles&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">xml&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">item&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">xml&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">title&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">article&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">title&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">xml&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">description&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">article&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">body&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">xml&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">author&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;support@example.com&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">xml&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">pubDate&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">article&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">created_at&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">strftime&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;%a, %d %b %Y %H:%M:%S %z&amp;#34;&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">xml&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">link&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">article_url&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">article&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">xml&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">guid&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">article_url&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">article&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Well, that&amp;rsquo;s it. You now have a working RSS feed!&lt;/p>
&lt;p>If you want to enable auto discovery, you should add the following line to the header of your layout. (Auto discovery enables that little RSS icon in the address bar of your browser.)&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="s">%= auto_discovery_link_tag(:rss, :controller =&lt;/span>&lt;span class="o">&amp;gt;&lt;/span> &lt;span class="s1">&amp;#39;articles&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:action&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s1">&amp;#39;rss&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">%&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Share and enjoy! Thank you.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/04/03/tipsnippet-create-a-rss-feed/</guid><pubDate>Tue, 03 Apr 2007 00:00:00 +0000</pubDate></item><item><title>What do you want for AutoFlickr?</title><link>https://www.devroom.io/2007/04/02/what-do-you-want-for-autoflickr/</link><description>&lt;p>Almost a month ago I published my &lt;a href="http://www.wordpress.org">WordPress&lt;/a> plugin &lt;a href="http://ariejan.net/2007/03/07/wordpress-plugin-autoflickr-10/">AutoFlickr&lt;/a>, which shows one or more related photos from the popular Flickr site in your post.&lt;/p>
&lt;p>Now I&amp;rsquo;m wondering what new features AutoFlickr users want. What parts of the current plugin are superfluous. Also, I&amp;rsquo;d like to know how is using AutoFlickr?&lt;/p>
&lt;p>Please post a comment with your wishes for AutoFlickr 1.1. People using AutoFlickr may &lt;a href="https://www.devroom.io/contact?phpMyAdmin=umjo2KeaY3NOkOP-0%2Czc9FZ9J96">contact me&lt;/a> to let me know about their site. I will include a list of the ten most beautiful (my pick) sites that use AutoFlickr with the announcement of AutoFlickr 1.1.&lt;/p>
&lt;p>I hope for a lot of feedback to make this plugin even better. Thank you.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/04/02/what-do-you-want-for-autoflickr/</guid><pubDate>Mon, 02 Apr 2007 00:00:00 +0000</pubDate></item><item><title>5 Reasons why PC OEMs should offer Linux</title><link>https://www.devroom.io/2007/03/29/5-reasons-why-pc-oems-should-offer-linux/</link><description>&lt;p>DELL currently offers Linux as an option for certain laptop and desktop models. Talk is that this service may be expanded to all models. This is a good thing, of course. Results of a &lt;a href="http://www.dell.com/content/topics/global.aspx/ideastorm/ideasinaction?c=us&amp;l=en&amp;s=gen">recent survey by DELL&lt;/a> show that more than 70% of over 100.000 respondents want to use Linux for home and office! What has been keeping companies like DELL away from this?&lt;/p>
&lt;p>I&amp;rsquo;ve found five good reasons why OEMs should offer Linux as at least an option:&lt;/p>
&lt;p>&lt;strong>1. Freedom of Choice&lt;/strong> You pay for your hardware. Its&amp;rsquo; yours. You should be able to use that hardware any way you want. The OEM should not force you to use anything else than what you want.&lt;/p>
&lt;p>&lt;strong>2. It saves customers money.&lt;/strong> If a customer doesn&amp;rsquo;t want to use Microsoft Windows? Why charge him for a license anyway?&lt;/p>
&lt;p>&lt;strong>3. It&amp;rsquo;s an unserved market.&lt;/strong> There are no mayor OEMs out there offering Linux as a default option. You get Microsoft Windows. In some cases you don&amp;rsquo;t have to buy Windows and get a clean hard drive instead. People want to use Linux, but they don&amp;rsquo;t want to go through the download-install-configure process. This is a big oppertunity, if you ask me.&lt;/p>
&lt;p>&lt;strong>4. Your hardware gets more popular.&lt;/strong> We&amp;rsquo;ve seen this with Nvidia already. May Linux users buy Nvidia cards because nvidia offers superb Linux drivers. Plug in the cards, install the drivers and off you go. If a player like DELL would support their hardware like that, people would be willing to buy DELL instead of &lt;em>Brand X&lt;/em>. Once the word gets out that Linux &amp;lsquo;just works&amp;rsquo; on a DELL, you&amp;rsquo;ll know that more customers will come your way.&lt;/p>
&lt;p>&lt;strong>5. Current support options are sufficient.&lt;/strong> A majority of respondents to the Linux survey at DELL indicated that the current community support forums would suffice their needs. Besides that, there&amp;rsquo;s a big Linux community out there willing to help new Linux users.&lt;/p>
&lt;p>This space is reserved for linking to a list of why Linux should be the default OS for OEMs and Windows an option. (&lt;a href="https://www.devroom.io/contact?phpMyAdmin=umjo2KeaY3NOkOP-0%2Czc9FZ9J96">Contact me&lt;/a> if you want to post that list a a guest blogger to Ariejan.net).&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/03/29/5-reasons-why-pc-oems-should-offer-linux/</guid><pubDate>Thu, 29 Mar 2007 00:00:00 +0000</pubDate></item><item><title>Rails Tip Snippet: Create a comma-seperate list</title><link>https://www.devroom.io/2007/03/27/rails-tip-snippet-create-a-comma-seperate-list/</link><description>&lt;p>Do you have the need to create a list of roles a certain user belongs to? Enumerate the users attached to a company? All you want is a simple list with the names seperated by commas.&lt;/p>
&lt;p>Users: John, Dick, Harry&lt;/p>
&lt;p>With Ruby on Rails this is really easy. You probably have a collection of user objects. All you want is a list of names:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="vi">@users&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">collect&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="n">u&lt;/span>&lt;span class="o">|&lt;/span> &lt;span class="n">u&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">join&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;, &amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Read more &lt;a href="http://ariejan.net/tags/tipsnippets/">Tip Snippets&lt;/a>?&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/03/27/rails-tip-snippet-create-a-comma-seperate-list/</guid><pubDate>Tue, 27 Mar 2007 00:00:00 +0000</pubDate></item><item><title>Subversion: How to revert to a previous revision</title><link>https://www.devroom.io/2007/03/27/subversion-how-to-revert-to-a-previous-revision/</link><description>&lt;p>You&amp;rsquo;ve been there. You have been developing in your trunk for a while and at revision 127 you get the feeling you&amp;rsquo;ve done it all wrong! The production server is humming away at revision 123 and that&amp;rsquo;s where you want to start out again. But how can you start again from revision 123? Easy as this with Subversion:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">svn merge -rHEAD:123 .
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This will see what changes you&amp;rsquo;ve made since r123 up until now (r127 in your case) and &amp;lsquo;undo&amp;rsquo; them. Next you check in the code and you&amp;rsquo;ve go a sweet r128 that is exactly the same as r123. You can start over now!&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/03/27/subversion-how-to-revert-to-a-previous-revision/</guid><pubDate>Tue, 27 Mar 2007 00:00:00 +0000</pubDate></item><item><title>Speedlinking: Top 28 startup resources</title><link>https://www.devroom.io/2007/03/25/speedlinking-top-28-startup-resources/</link><description>&lt;p>I&amp;rsquo;ve been reading a lot about startups this weekend. Here is a quick post with all the articles I came across this weekend and found worth mentioning:&lt;/p>
&lt;ol>
&lt;li>&lt;a href="http://www.startupping.com/">Startupping&lt;/a> &lt;strong>TIP&lt;/strong>&lt;/li>
&lt;li>&lt;a href="http://www.paulgraham.com/start.html">How to start a startup&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://evhead.com/2005/11/ten-rules-for-web-startups.asp">Ten rules for web startups&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://money.cnn.com/magazines/business2/startups/index.html">Business 2.0: How to build a bulletproof startup&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://paulgraham.com/startupmistakes.html">The 18 Mistakes that kill Startups&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://www.paulgraham.com/startuplessons.html">The Hardest Lessons for Startups to Learn&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://www.paulgraham.com/startupfunding.html">How to fund a startup&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://particletree.com/features/5-reasons-to-create-your-first-startup-now/">5 Reasons to create your first startup&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://dottactics.blogspot.com/2006/02/9-must-reads-before-you-launch-startup.html">9 Must reads before you start a startup&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://www.avivadirectory.com/branding/?p=1">Little Known Ways to Brand on the Cheap: 99 Tips for Poor Web Startups&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://www.readwriteweb.com/archives/6_startup_lessons_2007.php">6 Startup Lessons For The Year 2007&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://philip.greenspun.com/business/startup-tips/">Tips for startup companies&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://paulgraham.com/ideas.html">Ideas for startups&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://www.scoreboard-media.com/internet-startup-traffic/">8 Simple Steps to Build Traffic For Your Internet Startup&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://www.folksonomy.org/2006/10/7_tips_for_naming_your_startup/">7 Tips for naming your Web 2.0 startup&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://www.downloadsquad.com/2006/12/01/seven-rules-for-web-2-0-startups/">7 Rules for Web 2.0 Startups&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://onstartups.com/home/tabid/3339/bid/193/Startup-Websites-That-Work.aspx">Startup Website That Work&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://onstartups.com/home/tabid/3339/bid/115/Hindsight-2-0-Lessons-From-A-Failed-Web-2-0-Startup.aspx">Hindsight 2.0: Lessons From A Failed Web 2.0 Startup&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://www.stevepavlina.com/blog/2006/04/10-stupid-mistakes-made-by-the-newly-self-employed/">10 Stupid Mistakes Made by the Newly Self-Employed&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://startupspark.com/the-5-most-common-mistakes-made-by-startups/">The 5 Most common mistakes made by startups&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://paulgraham.com/mit.html">A Student's guide to startups&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://www.thisisgoingtobebig.com/2005/08/10_steps_to_a_h.html">10 Steps to a Hugely Successful Web 2.0 Company&lt;/a> &lt;strong>TIP&lt;/strong>&lt;/li>
&lt;li>&lt;a href="http://onstartups.com/home/tabid/3339/bid/68/Startup-Success-The-Phenomenal-Force-Of-Focus.aspx">Startup Success: The Phenomenal Force Of Focus&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://www.monthlydollar.com/startup/">Web 2.0 Startup: The Business Model&lt;/a> &lt;strong>TIP&lt;/strong>&lt;/li>
&lt;li>&lt;a href="http://37signals.com/svn/archives2/small_biz_101_how_to_get_started.php">Small Biz 101: How to Get Started&lt;/a> &lt;strong>TIP&lt;/strong>&lt;/li>
&lt;li>&lt;a href="http://money.cnn.com/magazines/business2/business2_archive/2006/05/01/8375910/index.htm">5 ways to start a company (without quitting your day job)&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://onstartups.com/home/tabid/3339/bid/69/Startup-Suicide-Five-Ways-To-Kill-Your-Startup-Which-Will-You-Pick.aspx">Startup Suicide: Five Ways To Kill Your Startup, Which Will You Pick?&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://www.miketaber.net/articles/StartupsForTheRestOfUs.aspx">Startups for the rest of us&lt;/a>&lt;/li>
&lt;/ol>
&lt;p>Got any articles that are not on the list? Please add a comment to let me know!&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/03/25/speedlinking-top-28-startup-resources/</guid><pubDate>Sun, 25 Mar 2007 00:00:00 +0000</pubDate></item><item><title>Be productive! 37 things you can do on the toilet</title><link>https://www.devroom.io/2007/03/23/be-productive-37-things-you-can-do-on-the-toilet/</link><description>&lt;p>We all have to go. Most of us once a day. Others have to do it more often. However, we can be much more productive while doing or daily duty at the toilet.&lt;/p>
&lt;p>Besides the old &amp;ldquo;joke book&amp;rdquo; and &amp;ldquo;newspaper&amp;rdquo; what can you do on the toilet to kill the time? I&amp;rsquo;ve found 37 things that might be a good option for you next number 2.&lt;/p>
&lt;p>&lt;strong>1. Newspaper&lt;/strong> - The age old classic of reading the newspaper. Nothing strage about that, right?&lt;/p>
&lt;p>&lt;strong>2. Joke book&lt;/strong> - Some loos feature a book with jokes for you to read. Nice, but mostly the jokes are of a very low quality.&lt;/p>
&lt;p>&lt;strong>3. Puzzle &lt;/strong>- Bring your sudoku book (any other type of puzzle will do) and start puzzling.&lt;/p>
&lt;p>&lt;strong>4. Novels&lt;/strong> - If you find it hard make time to read that novel everyone has been talking about, read it on the toilet. It&amp;rsquo;ll take a bit longer than usual, but you&amp;rsquo;ll get there.&lt;/p>
&lt;p>&lt;strong>5. Brainstorm&lt;/strong> - It&amp;rsquo;s quiet out there. Use your time to brainstorm and get some new ideas for whatever you need new ideas for. A pen and paper are handy to write it all down. Otherwise you may flush your ideas with the rest.&lt;/p>
&lt;p>&lt;strong>6. Redo your makeup&lt;/strong> - This one is for women only, but this is a nice place to redo your makeup with that travel-makeup-kit-thingy you always carry around.&lt;/p>
&lt;p>&lt;strong>7. Poke your nose&lt;/strong> - This is also a classic one. Nobody pokes his nose in public (well, most of us don&amp;rsquo;t) but if you like to clean it out, the toilet a good place to do it. No one sees you and you&amp;rsquo;ll get to wash your hands afterwards.&lt;/p>
&lt;p>&lt;strong>8. Listen to music&lt;/strong> - iPods are portable. Carry them with you at all times and listen to some tunes while sitting there. Other music devices such as mobile phones or mp3 players can be used as well.&lt;/p>
&lt;p>&lt;strong>9. Listen to podcasts&lt;/strong> - If you subscribe to podcasts, this is the time to listen to a few of them.&lt;/p>
&lt;p>&lt;strong>10. Watch some video&lt;/strong> - Have an iPod video or another portable video device such as the Playstation Portable? Make sure to set a clock, some movies run longer than 2 hours.&lt;/p>
&lt;p>&lt;strong>11. Play a game&lt;/strong> - You can play games on mobile phones, PDAs, iPods and portable gaming devices such as the Playstation Portable or a GameBoy. This is the time to finally beat the machine or set a new high score.&lt;/p>
&lt;p>&lt;strong>12. Learn stuff&lt;/strong> - Want to learn how to play Texas Hold&amp;rsquo;em poker? How to build your own kite? How to &amp;hellip; You get it. Print out an article or two, bring a book or even your laptop and read more on these subjects.&lt;/p>
&lt;p>&lt;strong>13. Watch movie trailers&lt;/strong> - Don&amp;rsquo;t have time to watch trailers? Put &amp;rsquo;em on your iPod and watch while waiting.&lt;/p>
&lt;p>&lt;strong>14. Chat&lt;/strong> - A lot of mobile devices have chat services. A laptop is the easiest way of chatting on the loo. If you want to talk, go ahead.&lt;/p>
&lt;p>&lt;strong>15. Read your mail&lt;/strong> - Use you mobile phone, PDA or laptop to check your (personal) email.&lt;/p>
&lt;p>&lt;strong>16. Check your stock&lt;/strong> - If you have stock feel free to check how much it&amp;rsquo;s worth now.&lt;/p>
&lt;p>&lt;strong>17. Blog&lt;/strong> - Want to write a blog post? Write it on the loo. If you don&amp;rsquo;t have wireless access (you should get that right now!) you can submit the post when you come back.&lt;/p>
&lt;p>&lt;strong>18. Tune your phone&lt;/strong> - This is more of a one time thing, but you may want to change your phones theme, background or ringtone every so often.&lt;/p>
&lt;p>&lt;strong>19. Read news&lt;/strong> - If you don&amp;rsquo;t have a newspaper feel free to read the news now on your laptop or PDA.&lt;/p>
&lt;p>&lt;strong>20. Organise your schedule&lt;/strong> - While you have the time, plan all the activities you need to plan and mark any problems in your schedule so you can fix them later (or fix them right now!).&lt;/p>
&lt;p>&lt;strong>21. Check Adsense&lt;/strong> - It&amp;rsquo;s always a good time to check up on Adsense and todays earnings.&lt;/p>
&lt;p>&lt;strong>22. Check your site statistics&lt;/strong> - Login to Analytics or view your logfiles and see if anything new is developing.&lt;/p>
&lt;p>&lt;strong>23. Decide on dinner&lt;/strong> - Don&amp;rsquo;t know what to eat after work? This is a time to decide on that.&lt;/p>
&lt;p>&lt;strong>24. Install/update software&lt;/strong> - Are new updates for your favourite programs waiting, take a bit of time and install them now.&lt;/p>
&lt;p>&lt;strong>25. Organise your to-do list&lt;/strong> - Check off items you have finished and add new ones you just thought off.&lt;/p>
&lt;p>&lt;strong>26. See what movies are running&lt;/strong> - Feel like going to a movie? Check online what&amp;rsquo;s running tonight.&lt;/p>
&lt;p>&lt;strong>27. Ping your coffee machine&lt;/strong> - If you&amp;rsquo;re a real geek, ping your coffee machine and brew a fresh pot for when you&amp;rsquo;re finished here.&lt;/p>
&lt;p>&lt;strong>28. Browse YouTube&lt;/strong> - Find a few movies you like and watch them. Laptop required.&lt;/p>
&lt;p>&lt;strong>29. Uninstall software&lt;/strong> - Uninstall all applications you don&amp;rsquo;t use to free up more space for your iTunes library.&lt;/p>
&lt;p>&lt;strong>30. Organise your documents&lt;/strong> - Organise all your files. Put them in neat folders and rename them properly.&lt;/p>
&lt;p>&lt;strong>31. SEO your site&lt;/strong> - Apply some quick patches to your site to improve SEO.&lt;/p>
&lt;p>&lt;strong>32. Link!&lt;/strong> - Post a speedlinking post or add links to your blogroll for sites your recently visisted.&lt;/p>
&lt;p>&lt;strong>33. Check free sites&lt;/strong> - Check those sites that collect free offers. Maybe something interesting has come up.&lt;/p>
&lt;p>&lt;strong>34. Hack the wireless routes&lt;/strong> - Try hack the wireless router your currently using. Make sure you don&amp;rsquo;t get fired over it!&lt;/p>
&lt;p>&lt;strong>35. Plan your trip home&lt;/strong> - Use an online planner to plan your trip home.&lt;/p>
&lt;p>&lt;strong>36. Work!&lt;/strong> - Need to finish a document fast? Continue working!&lt;/p>
&lt;p>&lt;strong>37. Do nothing&lt;/strong> - Old fashioned Number 2. Just sit there, do your thing and go!&lt;/p>
&lt;p>Do you know other tasks you may perform while doing a number 2? Please continue the list in the comments. Thanksk for sharing!&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/03/23/be-productive-37-things-you-can-do-on-the-toilet/</guid><pubDate>Fri, 23 Mar 2007 00:00:00 +0000</pubDate></item><item><title>Rails Tip Snippet: Logging informational messages to your log</title><link>https://www.devroom.io/2007/03/22/rails-tip-snippet-logging-informational-messages-to-your-log/</link><description>&lt;p>This &amp;ldquo;Rails Tip Snippet&amp;rdquo; is one in a series of small blocks of code that will make your life developing Rails applications a bit easier.&lt;/p>
&lt;p>This first snippet shows you how you can log informational message to your log file so you can track any important actions that happened. As an example you may want to log all user accounts that get deleted:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">destroy&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="vi">@user&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">User&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">find&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">params&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="ss">:id&lt;/span>&lt;span class="o">]&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="vi">@user&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">destroy!&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">logger&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">info&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;User account &amp;#39;&lt;/span>&lt;span class="si">#{&lt;/span>&lt;span class="vi">@user&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">login&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#39; deleted by user &lt;/span>&lt;span class="si">#{&lt;/span>&lt;span class="n">current_user&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">login&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> ...
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">end
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Line 4 will put a string like &amp;ldquo;User account &amp;lsquo;johnlocke&amp;rdquo; delete by user ariejan&amp;quot; in your log file. Use that information any way you want.&lt;/p>
&lt;p>You may also use the logger to inspect variables:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="n">logger&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">info&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;article inspection: &lt;/span>&lt;span class="si">#{&lt;/span>&lt;span class="vi">@article&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">inspect&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Until next time! Any tips are welcome at &lt;a href="mailto:tips@ariejan.net">&lt;a href="mailto:tips@ariejan.net">tips@ariejan.net&lt;/a>&lt;/a>.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/03/22/rails-tip-snippet-logging-informational-messages-to-your-log/</guid><pubDate>Thu, 22 Mar 2007 00:00:00 +0000</pubDate></item><item><title>26 Things you can do with an old PC</title><link>https://www.devroom.io/2007/03/21/26-things-you-can-do-with-an-old-pc/</link><description>&lt;p>We&amp;rsquo;ve all been there. Your old trusty PC dies. First thing you do is buy a new one. After you&amp;rsquo;re done playing with all the new bells and whistles your old PC didn&amp;rsquo;t have, it&amp;rsquo;s time to thing what to do with the old fella. Here are some tips!&lt;/p>
&lt;p>The whole system can be used like this:&lt;/p>
&lt;ol>
&lt;li>Give it away to friends or family who can use the "upgrade"&lt;/li>
&lt;li>Take it apart and see what comes out&lt;/li>
&lt;li>Create route/firewall for your network&lt;/li>
&lt;li>Creata a file server&lt;/li>
&lt;li>Install and try Linux&lt;/li>
&lt;li>Create a jukebox / media player&lt;/li>
&lt;li>Sell it&lt;/li>
&lt;li>Trash it / Recycle it&lt;/li>
&lt;li>Ceremonially set it on fire&lt;/li>
&lt;li>See what happens when you shoot it&lt;/li>
&lt;li>Symbolically throw it out of the window&lt;/li>
&lt;/ol>
&lt;p>If you only have the casing, this can be put to great use!&lt;/p>
&lt;ol start="12">
&lt;li>Take everthing out and turn it into a barbeque&lt;/li>
&lt;li>See what happens when you don't cool your processor&lt;/li>
&lt;li>Strip it and sell the working parts&lt;/li>
&lt;li>Blow it up&lt;/li>
&lt;/ol>
&lt;p>You an also strip the insides of your case and put another device in it:&lt;/p>
&lt;ol start="16">
&lt;li>Coffee maker&lt;/li>
&lt;li>Aquarium (with plexi glass plating, of course)&lt;/li>
&lt;li>Webcam&lt;/li>
&lt;li>Paper shredder&lt;/li>
&lt;li>Use it as a trash can&lt;/li>
&lt;li>Make it a mailbox&lt;/li>
&lt;li>Build a can dispenser (hit 'reset' to obtain a beverage of your choice)&lt;/li>
&lt;/ol>
&lt;p>The monitor (CRT) can be used for various purposes as well:&lt;/p>
&lt;ol start="23">
&lt;li>This can also be converted into an aquarium&lt;/li>
&lt;li>Remove the tube and create a cage for small animals&lt;/li>
&lt;li>Ash tray&lt;/li>
&lt;li>CD / DVD Rack&lt;/li>
&lt;/ol>
&lt;p>Got your own personal favourite? Please post a comment and share it with us all!&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/03/21/26-things-you-can-do-with-an-old-pc/</guid><pubDate>Wed, 21 Mar 2007 00:00:00 +0000</pubDate></item><item><title>8 Great ways to use Google for your start-up</title><link>https://www.devroom.io/2007/03/20/8-great-ways-to-use-google-for-your-start-up/</link><description>&lt;p>&lt;a href="http://www.google.com">Google&lt;/a> is more than a search engine or an advertising company. Of course, it&amp;rsquo;s their core business, but Google has more to offer. In this article I&amp;rsquo;ll describe how you can put Google to work for your start-up (or existing business!). Oh, did I mention you can do all of this for free?&lt;/p>
&lt;p>The most commonly known service Google provides besides search and advertising is &lt;a href="http://mail.google.com">Gmail&lt;/a>. Nowadays Gmail is a common concept in the field of e-mail services. When the services started you got a 1Gb inbox. Today you can store up to around 2.8Gb of emails and Google Talk chat transcripts! Wouldn&amp;rsquo;t it be great to use this for you company for free?&lt;/p>
&lt;p>Well you can! And a lot more!&lt;/p>
&lt;p>Most people may have heard about &lt;a href="http://www.google.com/a/">Google Apps&lt;/a>.&lt;/p>
&lt;p>So, what is Google Apps and how can I put it to use for my start-up?&lt;/p>
&lt;p>When you visit Google Apps you can sign up for the &amp;ldquo;free&amp;rdquo; edition which will give you the following&lt;/p>
&lt;ol>
&lt;li>Gmail with 2Gb inbox per user - You know how it works. Just for @yourdomain.com&lt;/li>
&lt;li>Google Talk - Allow your users to chat easily with Google Talk&lt;/li>
&lt;li>Google Calendar - Shared and personal calendars for free!&lt;/li>
&lt;li>Docs &amp; Spreadsheets - Write documents and spreadsheets easily online and collaborate with co-workers.&lt;/li>
&lt;li>Page Creator - Creating a web site has never been easier!&lt;/li>
&lt;li>Start Page - Know &lt;a href="http://www.google.com/ig">Google Personal Homepage&lt;/a>? Same stuff but for yourdomain.com!&lt;/li>
&lt;li>Easy administration panel to manager services and users&lt;/li>
&lt;li>Unlimited number of users and online support&lt;/li>
&lt;/ol>
&lt;p>So, if you already own a domain name, or register one for a few bucks (or for $10 at Google), you can run email, instant messenging, calendar services, online office (docs &amp;amp; spreadsheets), page creator and a custom start page for your users for free!&lt;/p>
&lt;p>This will cost you $10 bucks a year (for the domain name when registered with Google). All you need is a computer, Internet connecton and a decent browser.&lt;/p>
&lt;p>How&amp;rsquo;s that for an initial investment in your start-up company? Oh, don&amp;rsquo;t forget that you can access all the above everywhere as long as you have an Internet connection! You can always (and easily) access your documents and e-mail or check your calendar. You can even do that with your mobile phone!&lt;/p>
&lt;p>Personally I think Google Apps are a great investment in your start-up company. I&amp;rsquo;ll give you the pro&amp;rsquo;s and cons one more time:&lt;/p>
&lt;p>&lt;strong>Pro&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>It's free (except for the domainname)&lt;/li>
&lt;li>Quality service you expect from Google&lt;/li>
&lt;li>You have it all under one roof ready to go.&lt;/li>
&lt;li>Access to your stuff everywhere in the world!&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Con&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Nothing really.&lt;/li>
&lt;/ul>
&lt;p>Let me know if you have any experience with Google Apps. Did I miss some feature? Please let me know!&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/03/20/8-great-ways-to-use-google-for-your-start-up/</guid><pubDate>Tue, 20 Mar 2007 00:00:00 +0000</pubDate></item><item><title>How I made 6 figures with Google Adsense in 10 days</title><link>https://www.devroom.io/2007/03/20/how-i-made-6-figures-with-google-adsense-in-10-days/</link><description>&lt;p>There has been a lot of talk about making money with Google Adsense. There are claims of people earning over $10.000 every month by just putting a few ads up on their site.&lt;/p>
&lt;p>I say they are amateurs! I&amp;rsquo;ll show you how to make a 6-figure earning within 10 days after you put up ads on your site! Just follow these easy steps and you can quit your day job after just ten days of work. It&amp;rsquo; what I did.&lt;/p>
&lt;h3>Decide on a topic for your site&lt;/h3>
&lt;p>First you must decide on what the topic of your site is. Try a &lt;a href="http://uv.bidtool.overture.com/d/search/tools/bidtool/?mkt=uk">keyword bid searching tool&lt;/a> to find out which keywords and topics will generate the most.&lt;/p>
&lt;p>It&amp;rsquo;s not important if you don&amp;rsquo;t have any knowledge on the topic you will be writing about. It&amp;rsquo;s just about making money.&lt;/p>
&lt;h3>Register a domain&lt;/h3>
&lt;p>Next you must register a domain and get your hosting setup. You can do this for a few bucks. This is the only investment you have to do here! For the $10 investment in hosting and domain registration you&amp;rsquo;ll get a ROI of more than 100.000%.&lt;/p>
&lt;p>When choosing a domain, make sure it&amp;rsquo;s catchy. Something like &amp;ldquo;earn-money-with-adsense-for-free.com&amp;rdquo; or &amp;ldquo;imearningsixfigureswithadsensesucker.com&amp;rdquo;. It doesn&amp;rsquo;t need to be readable it just needs to contain a lot of keywords for you site.&lt;/p>
&lt;p>Don&amp;rsquo;t bother with quality hosting. Just find the cheapest reseller you can find. Make sure you get at least 5Tb of bandwidth, though. You&amp;rsquo;ll need it.&lt;/p>
&lt;h3>Setup your site&lt;/h3>
&lt;p>Open up Frontpage and write an article. It only needs to be a few paragraphs. Make sure to include lots of keywords so Google can attach the right, high paying, ads.&lt;/p>
&lt;h3>Add the ads!&lt;/h3>
&lt;p>With your site in order now, add the maximum amount of ads allowed. For optimal results use the biggest ad format available and make sure a visitor sees the ads even before they reach the content of your site. This ensure the maximum clich-thru rate possible.&lt;/p>
&lt;p>Also include a link-block. This will lure visitors to a page with only ads from Google! This is a real gold mine!&lt;/p>
&lt;p>The third thing Google offers are referrals. Plaster some big images at the bottom. Claim how much you make with your site and have users sign-up for adsense too. This will make you $200 almost instantly!&lt;/p>
&lt;p>Don&amp;rsquo;t forget the obligatory &amp;ldquo;Use Firefox&amp;rdquo; referral!&lt;/p>
&lt;h3>Publish your site&lt;/h3>
&lt;p>Now it&amp;rsquo;s time to publish your site and start making the big bucks! It&amp;rsquo;s almost time to quit your day job.&lt;/p>
&lt;p>Surf around and post a link to your site everywhere you can. Comment on any blog you see and put your link there. Roam forums and randomly insert links to your site there too.&lt;/p>
&lt;p>If you&amp;rsquo;re feeling adventurous you may also setup an Adwords campaign yourself and pay for visitors. Since you have many high-paying ads on your site you can make money at a 1:10 ratio ($1 spend on adwords is $10 earned in Adsense)&lt;/p>
&lt;h3>And now... you wait&lt;/h3>
&lt;p>Now you wait ten days and then quit your job (you have a two weeks notice anyway). After those ten days you&amp;rsquo;ll have made a nice 6 figures in hard currency. Just wait for the check to arrive and you&amp;rsquo;re done!&lt;/p>
&lt;p>Oh, don&amp;rsquo;t forget to ***** my *** since I gave you this very valuable information and I still need to reach my 6 figures for this month. Thank you.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/03/20/how-i-made-6-figures-with-google-adsense-in-10-days/</guid><pubDate>Tue, 20 Mar 2007 00:00:00 +0000</pubDate></item><item><title>Wordpress Plugin: AutoFlickr 1.0</title><link>https://www.devroom.io/2007/03/07/wordpress-plugin-autoflickr-10/</link><description>&lt;p>This post announces my first Wordpress Plugin: AutoFlickr.&lt;/p>
&lt;h3>What does it do?&lt;/h3>
&lt;p>When enabled, AutoFlickr will automatically find and insert one ore more photos that are related to the content of your post. Photos are found and hosted on &lt;a href="http://www.flickr.com">Flickr&lt;/a>.&lt;/p>
&lt;h3>Features&lt;/h3>
&lt;ul>
&lt;li>Automatically insert one or more photos&lt;/li>
&lt;li>Customizable (photo size, number of photos and type of search).&lt;/li>
&lt;li>Option to select what pages to automatically show photos.&lt;/li>
&lt;/ul>
&lt;h3>Installation&lt;/h3>
&lt;ol>
&lt;li>Download the latest version here: &lt;a href="http://ariejan.net/upload/autoflickr/autoflickr-10.zip">AutoFlickr 1.0&lt;/a>.&lt;/li>
&lt;pre>&lt;code>&amp;lt;li&amp;gt;Unzip the downloaded file and put the entire 'autoflickr' directory in /wp-content/plugins&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;Activate the plugin under Plugins&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;Go to Options -&amp;gt; AutoFlickr to customize the settings. &amp;lt;/li&amp;gt;
&lt;/code>&lt;/pre>
&lt;/ol>
&lt;p>That&amp;rsquo;s it. Optionally you may want to adapt autoflickr.css to change the way the images are aligned and styled.&lt;/p>
&lt;p>If you want to include images in another place than the default, you may disable AutoFlickr for all pages and include the following PHP code in your template. Note that this code must be used within &amp;ldquo;The Loop&amp;rdquo;.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;lt;?&lt;/span>&lt;span class="nx">php&lt;/span> &lt;span class="k">echo&lt;/span> &lt;span class="nx">autoflickr_html&lt;/span>&lt;span class="p">();&lt;/span> &lt;span class="cp">?&amp;gt;&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3>Demo&lt;/h3>
&lt;p>This plugin was developed for &lt;a href="http://citytrippers.net">CityTrippers.net&lt;/a>. You can see the plugin in action when reading a single story.&lt;/p>
&lt;h3>Support&lt;/h3>
&lt;p>Please leave a comment or &lt;a href="http://ariejan.net/contact">contact me&lt;/a> directly if you have any questions regarding this plugin.&lt;/p>
&lt;h3>New versions?&lt;/h3>
&lt;p>Please subscribe to my &lt;a href="http://feeds.feedburner.com/Ariejan">RSS Feed&lt;/a> (or use &lt;a href="http://www.feedburner.com/fb/a/emailverifySubmit?feedId=411105">e-mail subscription&lt;/a>) to get automatic updates about Ariejan.net and this plugin.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/03/07/wordpress-plugin-autoflickr-10/</guid><pubDate>Wed, 07 Mar 2007 00:00:00 +0000</pubDate></item><item><title>Subversion Cheat Sheet Update: 1.0.1</title><link>https://www.devroom.io/2007/03/06/subversion-cheat-sheet-update-101/</link><description>&lt;p>I&amp;rsquo;ve just uploaded version 1.0.1 of the &lt;a href="http://ariejan.net/svncheatsheet">Subversion Cheat Sheet&lt;/a> to Ariejan.net.&lt;/p>
&lt;p>Please download this new version and get the following change:&lt;/p>
&lt;ul>
&lt;li>Fixed typo. Thanks to Gregory Gerard.&lt;/li>
&lt;/ul>
&lt;p>Head to the &lt;a href="http://ariejan.net/svncheatsheet">Subversion Cheat Sheet&lt;/a> page now and download the new version.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/03/06/subversion-cheat-sheet-update-101/</guid><pubDate>Tue, 06 Mar 2007 00:00:00 +0000</pubDate></item><item><title>4 Unusual uses for Subversion</title><link>https://www.devroom.io/2007/02/26/4-unusual-uses-for-subversion/</link><description>&lt;p>The most common use of Subversion is to keep source code of applications versioned and secure. However, there are quite a few other options that are not so common at all.&lt;/p>
&lt;p>Quickly read on and find out if maybe you can put Subversion to use in quite a few ways you didn&amp;rsquo;t expect.&lt;/p>
&lt;p>&lt;strong>1. CMS&lt;/strong>&lt;/p>
&lt;p>Maybe CMS is a bit strong, but it&amp;rsquo;s very easy to develop a static website locally and store it in Subversion. You just checkout your website on your production server and you&amp;rsquo;re good to go. You can easily jump back to previous content and you always have a backup around.&lt;/p>
&lt;p>&lt;strong>2. Todo list / Notes&lt;/strong>&lt;/p>
&lt;p>You can version a list of text files as todo items or notes. You can check it out anywhere you want and keep track of all your past notes and items. You will have to establish some sort of formatting convention for yourself as how you name files and all that.&lt;/p>
&lt;p>&lt;strong>3. Backups&lt;/strong>&lt;/p>
&lt;p>Put your entire home directory in Subversion. You&amp;rsquo;ll always have a backup handy and you can easily jump back in time! Maybe it&amp;rsquo;s not as sophisticated as &lt;a href="http://www.apple.com/macosx/leopard/timemachine.html">Mac OS X Leopard&amp;rsquo;s Time Machine&lt;/a>, but it serves its purpose.&lt;/p>
&lt;p>&lt;strong>4. Community Story Writing&lt;/strong>&lt;/p>
&lt;p>It&amp;rsquo;s a bit like coding. You could use Subversion to write a story (or manuscript or book or &amp;hellip;) as a community. It&amp;rsquo;s a bit like a wiki, but you can all use your favourite editors. You&amp;rsquo;ll have to agree on an ASCII based file format, though.&lt;/p>
&lt;p>Maybe you know some very cool way to use Subversion as well? Please comment below!&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/02/26/4-unusual-uses-for-subversion/</guid><pubDate>Mon, 26 Feb 2007 00:00:00 +0000</pubDate></item><item><title>Subversion Cheat Sheet 1.0!</title><link>https://www.devroom.io/2007/02/23/subversion-cheat-sheet-10/</link><description>&lt;p>I&amp;rsquo;ve noticed a huge interest in my &lt;a href="https://www.devroom.io/tags/subversion?phpMyAdmin=umjo2KeaY3NOkOP-0%2Czc9FZ9J96">Subversion articles&lt;/a> lately and I thought to create a nice cheat sheet for all you.&lt;/p>
&lt;p>The cheat sheets includes common commands that you&amp;rsquo;ll use when using Subversion on a daily basis. I didn&amp;rsquo;t include every option or command that subversion supports, nor did I include any administration stuff. This sheet is aimed at developers who use Subversion on a daily basis.&lt;/p>
&lt;p>You can find the Subversion Cheat Sheet and future updates at:&lt;/p>
&lt;blockquote>&lt;a href="http://ariejan.net/svncheatsheet">http://ariejan.net/svncheatsheet&lt;/a>&lt;/blockquote>
&lt;p>&lt;strong>Please link to the url above from your blog to spread the word! Thanks.&lt;/strong>&lt;/p>
&lt;p>Topics covered in this cheat sheet are:&lt;/p>
&lt;ul>
&lt;li>Subversion Status codes&lt;/li>
&lt;li>Daily usage commands&lt;/li>
&lt;li>Bug fixing&lt;/li>
&lt;li>Branching and tagging&lt;/li>
&lt;li>File Magic&lt;/li>
&lt;li>Locking&lt;/li>
&lt;li>Properties&lt;/li>
&lt;li>Experimenting&lt;/li>
&lt;/ul>
&lt;p>Please let me know if anything&amp;rsquo;s missing.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/02/23/subversion-cheat-sheet-10/</guid><pubDate>Fri, 23 Feb 2007 00:00:00 +0000</pubDate></item><item><title>Ruby: Sort an array of objects by an attribute</title><link>https://www.devroom.io/2007/01/28/ruby-sort-an-array-of-objects-by-an-attribute/</link><description>&lt;p>In this example I&amp;rsquo;ll show you how easy it is to sort an array of (the same kind of) objects by an attribute. Let&amp;rsquo;s say you have an array of User objects that have the attributes &amp;rsquo;name&amp;rsquo; and &amp;rsquo;login_count&amp;rsquo;. First, find all users.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="vi">@users&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">User&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">find&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="ss">:all&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now, we have to sort this array by &amp;rsquo;name&amp;rsquo;. Since we don&amp;rsquo;t know if any user used capitals in his name or not, we use &amp;lsquo;downcase&amp;rsquo; to sort without case sensitivity.&lt;/p>
&lt;p>A small not. &amp;lsquo;sort&amp;rsquo; returns a new array and leaves the original unchanged. You may want to just reorder the @users array, so use the &amp;lsquo;sort!&amp;rsquo; method. The &amp;lsquo;!&amp;rsquo; indicates it&amp;rsquo;s a destructive method. It will overwrite the current @users array with the new sorting.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="vi">@users&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">sort!&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="o">|&lt;/span>&lt;span class="n">a&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">b&lt;/span>&lt;span class="o">|&lt;/span> &lt;span class="n">a&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">downcase&lt;/span> &lt;span class="o">&amp;lt;=&amp;gt;&lt;/span> &lt;span class="n">b&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">downcase&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>That&amp;rsquo;s all! Since strings are comparable, this will sort you user objects alphabetically by name. Want to sort on login_count instead?&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="vi">@users&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">sort!&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="o">|&lt;/span>&lt;span class="n">a&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">b&lt;/span>&lt;span class="o">|&lt;/span> &lt;span class="n">a&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">login_count&lt;/span> &lt;span class="o">&amp;lt;=&amp;gt;&lt;/span> &lt;span class="n">b&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">login_count&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>So, now you can easily sort any object in an array just like you want it too!&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/01/28/ruby-sort-an-array-of-objects-by-an-attribute/</guid><pubDate>Sun, 28 Jan 2007 00:00:00 +0000</pubDate></item><item><title>New in Rails: Resource Scaffold Generator</title><link>https://www.devroom.io/2007/01/23/new-in-rails-resource-scaffold-generator/</link><description>&lt;p>Oh boy! Rails 1.2 is all about resources. A product entry in your application is not just a rendered HTML page, but it &amp;ldquo;is&amp;rdquo; data. Rails 1.2 allows you to add a .xml extension to your url to retrieve the same product information in XML format!&lt;/p>
&lt;p>Now, this all sounds hard. Two ways of rendering the same action depending on an extension. Generating XML code for every model in your database. No way you want to spend time developing all that stuff.&lt;/p>
&lt;p>Well, Rails wouldn&amp;rsquo;t be Rails if there weren&amp;rsquo;t a generator for it!&lt;/p>
&lt;p>This example is really easy. I want to create products in my database and expose them as HTML and XML to my users. Just do the following:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ ./script/generate scaffold_resource Product title:string description:text
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>A migration is generated automatically so update that if you want to before running&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ rake db:migrate
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>As you can see in config/routes.rb, Rails has automatically generated routes for this resource!&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="n">map&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">resource&lt;/span> &lt;span class="ss">:products&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now you can start up your Rails app and access http://localhost:8000/products. Add some products if you like. Now if you got to http://localhost:8000/products/1 you a nice HTML scaffold. If you add .xml: http://localhost:8000/products/1.xml you&amp;rsquo;ll get nice XML!&lt;/p>
&lt;p>Well, writing a seperate desktop application that retrieves informatin from your web application just got 100x easier!&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/01/23/new-in-rails-resource-scaffold-generator/</guid><pubDate>Tue, 23 Jan 2007 00:00:00 +0000</pubDate></item><item><title>Rails: Nested resource scaffold</title><link>https://www.devroom.io/2007/01/23/rails-nested-resource-scaffold/</link><description>&lt;p>In my &lt;a href="http://ariejan.net/2007/01/23/new-in-rails-resource-scaffold-generator/">previous post&lt;/a> I told you about the resource scaffold. What you&amp;rsquo;ll be doing a lot is nesting these resources. Ingredients in recipes, comments on posts, options for products. You name it, you nest it!&lt;/p>
&lt;p>Since Rails does not automatically nest resources for you, you should do this yourself. This is, with some minor tweaks, really easy to accomplish. In this example I&amp;rsquo;ll create recipes that have multiple ingredients.&lt;/p>
&lt;p>I assume you have Rails 1.2.1 installed for this tutorial to work properly.&lt;/p>
&lt;p>First, I create an new rails project named &amp;lsquo;cookbook&amp;rsquo;. I use an SQLite3 database because it&amp;rsquo;s easy to do so. You may use any Rails compatible database for this example.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ mkdir cookbook
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">rails --database sqlite3 cookbook
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> cookbook
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>First I create resource scaffolds for both the Recipe and Ingredient models:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ ./script/generate scaffold_resource Recipe title:string instructions:text
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">./script/generate scaffold_resource Ingredient name:string quantity:string
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>As you can see I did not add a recipe_id to the ingredient model because of the has_many relationship. Add this column to the migration file. You should now be able to migrate your database:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ rake db:migrate
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>If you add the recipe_id to the generate script the view for your ingredients will include a field for the recipe_id and that&amp;rsquo;s not what you want.&lt;/p>
&lt;p>Next, make the has_many relationship in your models.&lt;/p>
&lt;p>app/models/recipe.rb:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">Recipe&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="no">ActiveRecord&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="no">Base&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">has_many&lt;/span> &lt;span class="ss">:ingredients&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>app/models/ingredient.rb
&lt;/pre>```ruby
class Ingredient &amp;lt; ActiveRecord::Base
belongs_to :recipe
end&lt;/p>
&lt;pre tabindex="0">&lt;code>
So far, nothing new. Next we check out config/routes.rb:
```ruby
map.resources :ingredients
map.resources :recipes
&lt;/code>&lt;/pre>&lt;p>What we want is to map ingredients as a resource to recipes. Replace these two lines with:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="n">map&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">resources&lt;/span> &lt;span class="ss">:recipes&lt;/span> &lt;span class="k">do&lt;/span> &lt;span class="o">|&lt;/span>&lt;span class="n">recipes&lt;/span>&lt;span class="o">|&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">recipes&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">resources&lt;/span> &lt;span class="ss">:ingredients&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This will give you urls like /recipes/123/ingredients/321&lt;/p>
&lt;p>Now we need to make some changes to the ingredients controller. Every ingredient belongs to a recipe. First add the filter:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="n">before_filter&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="ss">:get_recipe&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kp">private&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">get_recipe&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="vi">@recipe&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">Recipe&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">find&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">params&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="ss">:recipe_id&lt;/span>&lt;span class="o">]&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This will make sure that every ingredient knows what recipe it belongs to.&lt;/p>
&lt;p>In the index method of the ingredient controller, make sure you have this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="vi">@ingredients&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="vi">@recipe&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">ingredients&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">find&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="ss">:all&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This makes sure you only show ingredients for this recipe, and not all ingredients in the database.&lt;/p>
&lt;p>Because we changed the route for ingredients, we need to update all ingredient_url() and ingredient_path() calls in our controller and views. Change all occurrences of&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="n">ingredient_url&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="vi">@ingredient&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>and&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="n">ingredient_path&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="vi">@ingredient&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>to&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="n">ingredient_url&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="vi">@recipe&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="vi">@ingredient&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>and&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="n">ingredient_path&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="vi">@recipe&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="vi">@ingredient&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;em>Note: Make sure that you don&amp;rsquo;t replace &amp;lsquo;ingredient&amp;rsquo; with &amp;lsquo;@ingredient&amp;rsquo; in your views!&lt;/p>
&lt;p>Add a link to the ingredients to your recipe&amp;rsquo;s index.rhtml view.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="n">link_to&lt;/span> &lt;span class="s1">&amp;#39;Ingredients&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">ingredients_path&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">recipe&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>And, at last, make sure the create method of your ingredients controller attaches the ingredient to the right recipe. Make sure the first two lines look like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">create&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="vi">@ingredient&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="vi">@recipe&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">ingredients&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">new&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">params&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="ss">:ingredient&lt;/span>&lt;span class="o">]&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>You may now start your webserver and check out http://localhost:8000/recipes . Create some recipes and click &amp;lsquo;ingredients&amp;rsquo;. You can now add ingredients for this recipe!&lt;/p>
&lt;p>The next step will be customizing each method to suit your own needs.&lt;/em>&lt;/pre>&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/01/23/rails-nested-resource-scaffold/</guid><pubDate>Tue, 23 Jan 2007 00:00:00 +0000</pubDate></item><item><title>Updates: Wordpress 2.1, Themes and Social</title><link>https://www.devroom.io/2007/01/23/updates-wordpress-21-themes-and-social/</link><description>&lt;p>You can&amp;rsquo;t really see it, but Ariejan.net has been upgraded to &lt;a href="http://wordpress.org">Wordpress 2.1&lt;/a>. I&amp;rsquo;ve been running 2.1 beta&amp;rsquo;s on a private server for some time now, so there weren&amp;rsquo;t any surprises during the upgrade.&lt;/p>
&lt;p>I&amp;rsquo;ve also updated the theme to something more stylish and sober. Google Ads are less annoying now and merge nicely with the content. I&amp;rsquo;ve also re-enabled the social bookmark links so you can quickly bookmark articles on Del.icio.us or Digg.&lt;/p>
&lt;p>Hope you like the new style. Some minor tweaks will be applied the following days where needed. Please let me know your thoughts on ariejan.net!&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/01/23/updates-wordpress-21-themes-and-social/</guid><pubDate>Tue, 23 Jan 2007 00:00:00 +0000</pubDate></item><item><title>“Print this page” with Ruby on Rails</title><link>https://www.devroom.io/2007/01/19/print-this-page-with-ruby-on-rails/</link><description>&lt;p>You have put a lot of effort into creating a sexy overview of whatever data your application stores and allow your users to manipulate that data through AJAX controls. But, some people just want to print their data.&lt;/p>
&lt;p>How to go about that? Just printing the page with data is generally not a good idea because it has been optimized for display on a screen. The first step we need to take is adapting our page for printing. Stylesheets are very handy tools for this. Check the following part of the header of my layout:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="n">stylesheet_link_tag&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;default&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:media&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="ss">:screen&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">stylesheet_link_tag&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;print&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:media&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="ss">:print&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This will generate HTML code that includes two stylesheets. However, only the &amp;lsquo;default&amp;rsquo; stylesheet is used on screen. When the users prints a particular page, the print stylesheet is used instead. So, what do you want to change in the print stylesheet?&lt;/p>
&lt;ul>
&lt;li>Fonts should be serif (not sans-serif) for printing&lt;/li>
&lt;li>Hide images as much as possible&lt;/li>
&lt;li>Hide ads&lt;/li>
&lt;li>Display printable ads that are hidden on screen&lt;/li>
&lt;li>Hide navigational elements&lt;/li>
&lt;li>Use a black-on-white colour scheme&lt;/li>
&lt;li>Underline links and colour them blue for easy recognition&lt;/li>
&lt;li>Add the actual URL to your links (see below)&lt;/li>
&lt;/ul>
&lt;p>To add the actual URL in the href-part of your link to the name of your link add the following to you print stylesheet:&lt;/p>
&lt;pre lang='css'>a:after { content:' [' attr(href) '] '}&lt;/pre>
&lt;p>Now, with all this in place, load your page with data. Print your page or watch a preview and be amazed at what stylesheets can do for you!&lt;/p>
&lt;p>The final touch is adding a &amp;ldquo;Print this page&amp;rdquo; link to your navigation on screen. Here you can use link_to_function:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="n">link_to_function&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Print this Page&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;javascript:print()&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>That&amp;rsquo;s it. Just hit the Print this Page button and your browsers print dialog will pop up and use your fancy new print stylesheet.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/01/19/print-this-page-with-ruby-on-rails/</guid><pubDate>Fri, 19 Jan 2007 00:00:00 +0000</pubDate></item><item><title>Why Ruby Rocks - Convince your fellow developers</title><link>https://www.devroom.io/2007/01/19/why-ruby-rocks-convince-your-fellow-developers/</link><description>&lt;p>I often hear questions from my Java and PHP oriented friends about what makes Ruby so great and easy to use. Until today I&amp;rsquo;ve shown them some of my Rails feats (AJAX Scaffold always amazes people). Now, I came across this &lt;a href="http://www.ruby-lang.org/en/documentation/quickstart/">20 minute Ruby introduction&lt;/a>. Starting with the basic &amp;ldquo;Hello World&amp;rdquo; item, this article show step by step improvements to end up with blocks, objects and all that makes Ruby really worth while.&lt;/p>
&lt;p>So, if you need to show developers what Ruby (and not so much Rails) can do, show &amp;rsquo;em this! It impresses the shit out of &amp;rsquo;em.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/01/19/why-ruby-rocks-convince-your-fellow-developers/</guid><pubDate>Fri, 19 Jan 2007 00:00:00 +0000</pubDate></item><item><title>Rails: Group results by week (using group_by)</title><link>https://www.devroom.io/2007/01/12/rails-group-results-by-week-using-group_by/</link><description>&lt;p>The Enumerable class in Rails contains a method named &amp;lsquo;group_by&amp;rsquo;. This method is pure magic for a developer&amp;rsquo;s point of view. I&amp;rsquo;ll give you a simple example that shows the power of group_by.&lt;/p>
&lt;p>Let&amp;rsquo;s say you have a table &amp;lsquo;posts&amp;rsquo; containing blog posts. Now, you normally show these chronologically a few at a time. Nothing special there. For some special overview page, you want to group your posts by week.&lt;/p>
&lt;p>With normal ActiveRecord operations this would be quite an elaborate task. But with group_by from Enumerable, it becomes child&amp;rsquo;s play.&lt;/p>
&lt;p>First of all, in the controller, just get all the posts you need. In this case, all of them:&lt;/p>
&lt;p>Controller:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">list&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="vi">@posts&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">Post&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">find&lt;/span> &lt;span class="ss">:all&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>As you can see, I perform no ordering or whatsoever here.&lt;/p>
&lt;p>Now, in your view you normally would iterate over all posts like this:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-erb" data-lang="erb">&amp;lt;%= render :partial =&amp;gt; &amp;#39;post&amp;#39;, :collection =&amp;gt; @posts %&amp;gt;
&lt;/code>&lt;/pre>&lt;p>But, as I said, we want to group the posts by week. To make life easy, I add a method to the Post class that returns the week number in which a post was written:&lt;/p>
&lt;p>Model Post:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">week&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">created_at&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">strftime&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;%W&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now, the magic will happen in our view:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-erb" data-lang="erb">&amp;lt;% @posts.group_by(&amp;amp;:week).each do |week, posts| %&amp;gt;
&amp;lt;div id=&amp;#34;week&amp;#34;&amp;gt;
&amp;lt;h2&amp;gt;Week &amp;lt; %= week %&amp;gt;&amp;lt;/h2&amp;gt;
&amp;lt; %= render :partial =&amp;gt; &amp;#39;post&amp;#39;, :collection =&amp;gt; @posts %&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;% end %&amp;gt;
&lt;/code>&lt;/pre>&lt;p>Let me explain the above. We specify that we want to call group_by for @posts. But we need to say how we want to group these posts. By specifying &amp;amp;:week we tell group_by that we want to group by the result of the week attribute of every post. This is the attribute we specified earlier in the model.&lt;/p>
&lt;p>Well, when the grouping is done we create a block that will handle every group of items. We extract &amp;lsquo;week&amp;rsquo; and &amp;lsquo;posts&amp;rsquo; here. &amp;lsquo;week&amp;rsquo; contains the week number and &amp;lsquo;posts&amp;rsquo; all the posts for that week.&lt;/p>
&lt;p>As normal, we can now show the week number and iterate over the posts.&lt;/p>
&lt;h2 id="sorting-groups">Sorting groups&lt;/h2>
&lt;p>The result of group_by is not guaranteed to be ordered in any way. Simply call &amp;lsquo;sort&amp;rsquo; before each and you&amp;rsquo;re set:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="vi">@posts&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">group_by&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">&amp;amp;&lt;/span>&lt;span class="ss">:week&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">sort&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">each&lt;/span> &lt;span class="k">do&lt;/span> &lt;span class="o">|&lt;/span>&lt;span class="n">week&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">posts&lt;/span>&lt;span class="o">|&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Mostly, you&amp;rsquo;ll find that the posts for every group are not sorted either. With the example above I think it&amp;rsquo;s easy to figure out how to do that now. (hint: .sort)&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2007/01/12/rails-group-results-by-week-using-group_by/</guid><pubDate>Fri, 12 Jan 2007 00:00:00 +0000</pubDate></item><item><title>Textmate+Rails: Easy partials for better code</title><link>https://www.devroom.io/2006/12/22/textmaterails-easy-partials-for-better-code/</link><description>&lt;p>As you may know, I use &lt;a href="http://macromates.com/">TextMate&lt;/a> for editing Rails code.&lt;/p>
&lt;p>I&amp;rsquo;ve just been browsing the Rails bundle today and I came across some very interesting things. Today I&amp;rsquo;ll tell you about partials.&lt;/p>
&lt;p>Partials are ERb templates. They are mostly HTML (or RJS or XML or whatever output format you use) and include some embedded Ruby to show actual content. Partials are not linked to a method in a controller, but instead they can be easily rendered through-out your application.&lt;/p>
&lt;p>TextMate allows you to refactor your application to use partials with almost no effort!&lt;/p>
&lt;p>First something about partials for those who don&amp;rsquo;t know about it.&lt;/p>
&lt;p>Partials are stored in files that start with an _ (underscore) and end in .rthml (or .rjs or .rxml) and you may include them in your ERb like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;lt;&lt;/span> &lt;span class="s">%= render :partial =&lt;/span>&lt;span class="o">&amp;gt;&lt;/span> &lt;span class="s1">&amp;#39;userinfo&amp;#39;&lt;/span> &lt;span class="o">%&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This will include the partial _userinfo.rhtml for you. This way you can avoid duplication by including the same ERb template (partial) over and over again.&lt;/p>
&lt;p>Partials are also useful to keep your views (templates) clean and easy to understand. In large projects this will allow you later to easily change a view without having to spit through 500 lines of ERb.&lt;/p>
&lt;p>Let&amp;rsquo;s say you have a profile page where you want to show a users&amp;rsquo;s profile and some options for when the user is viewing his own profile. Also, you want to show a simple message when the users has not yet created a profile, which is a possibility in my application. The show.rhtml template for the profile may look like this&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="n">h2&lt;/span>&lt;span class="o">&amp;gt;&lt;/span>&lt;span class="no">Profile&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="s">%=h @profile.user.login %&amp;gt;&amp;lt;/h2&amp;gt;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">&amp;lt; % if @profile.exists? -%&amp;gt;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> &amp;lt;div id=&lt;/span>&lt;span class="s2">&amp;#34;membersince&amp;#34;&lt;/span>&lt;span class="o">&amp;gt;&amp;lt;&lt;/span> &lt;span class="s">%= @profile.firstname %&amp;gt; has been a member for &amp;lt; %=&lt;/span> &lt;span class="n">distance_of_time_in_words_to_now&lt;/span> &lt;span class="vi">@profile&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">user&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">created_at&lt;/span> &lt;span class="s">%&amp;gt;&amp;lt;/div&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">&amp;lt;&lt;/span>&lt;span class="n">h3&lt;/span>&lt;span class="o">&amp;gt;&lt;/span>&lt;span class="no">General&lt;/span> &lt;span class="n">information&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="sr">/h3&amp;gt;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sr"> &amp;lt;div id=&amp;#34;name&amp;#34;&amp;gt;Name: &amp;lt; %= @profile.fullname %&amp;gt;&amp;lt;/&lt;/span>&lt;span class="n">div&lt;/span>&lt;span class="o">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">&amp;lt;&lt;/span>&lt;span class="n">div&lt;/span> &lt;span class="nb">id&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;country&amp;#34;&lt;/span>&lt;span class="o">&amp;gt;&lt;/span>&lt;span class="ss">Country&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="s">%= @profile.country %&amp;gt;&amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> &amp;lt;div id=&lt;/span>&lt;span class="s2">&amp;#34;birthday&amp;#34;&lt;/span>&lt;span class="o">&amp;gt;&lt;/span>&lt;span class="ss">Birthday&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="s">%= fmt_date @profile.birthday %&amp;gt;&amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> &amp;lt;h3&amp;gt;About &amp;lt; %=&lt;/span> &lt;span class="vi">@profile&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">firstname&lt;/span> &lt;span class="s">%&amp;gt;&amp;lt;/h3&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">&amp;lt;&lt;/span>&lt;span class="n">div&lt;/span> &lt;span class="nb">id&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;about&amp;#34;&lt;/span>&lt;span class="o">&amp;gt;&amp;lt;&lt;/span> &lt;span class="s">%= markdown @profile.about %&amp;gt;&amp;lt;/div&amp;gt;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">&amp;lt; % else -%&amp;gt;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> &amp;lt;div id=&lt;/span>&lt;span class="s2">&amp;#34;membersince&amp;#34;&lt;/span>&lt;span class="o">&amp;gt;&amp;lt;&lt;/span> &lt;span class="s">%= @profile.user.login %&amp;gt; has been a member for &amp;lt; %=&lt;/span> &lt;span class="n">distance_of_time_in_words_to_now&lt;/span> &lt;span class="vi">@profile&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">user&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">created_at&lt;/span> &lt;span class="s">%&amp;gt;&amp;lt;/div&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">&amp;lt;&lt;/span>&lt;span class="n">h3&lt;/span>&lt;span class="o">&amp;gt;&lt;/span>&lt;span class="no">Oh&lt;/span> &lt;span class="n">my!&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="sr">/h3&amp;gt;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sr"> &amp;lt;p&amp;gt;This user has not yet created a profile.&amp;lt;/&lt;/span>&lt;span class="nb">p&lt;/span>&lt;span class="o">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;lt;&lt;/span> &lt;span class="sx">% end &lt;/span> &lt;span class="o">-&lt;/span>&lt;span class="s">%&amp;gt;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">&amp;lt; % if @profile.user.id == current_user.id -%&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="n">h3&lt;/span>&lt;span class="o">&amp;gt;&lt;/span>&lt;span class="no">Options&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="sr">/h3&amp;gt;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sr">&amp;lt;p&amp;gt;This is your profile, so you have some options:&amp;lt;/&lt;/span>&lt;span class="nb">p&lt;/span>&lt;span class="o">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="n">ul&lt;/span>&lt;span class="o">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">&amp;lt;&lt;/span>&lt;span class="n">li&lt;/span>&lt;span class="o">&amp;gt;&amp;lt;&lt;/span> &lt;span class="s">%= link_to &amp;#34;Change password&amp;#34;, :controller =&lt;/span>&lt;span class="o">&amp;gt;&lt;/span> &lt;span class="s1">&amp;#39;account&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:action&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s2">&amp;#34;change_password&amp;#34;&lt;/span> &lt;span class="s">%&amp;gt;&amp;lt;/li&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">&amp;lt;&lt;/span>&lt;span class="n">li&lt;/span>&lt;span class="o">&amp;gt;&amp;lt;&lt;/span> &lt;span class="s">%= link_to &amp;#34;Edit your profile&amp;#34;, profile_edit_url(:login =&lt;/span>&lt;span class="o">&amp;gt;&lt;/span> &lt;span class="n">current_user&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">login&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="s">%&amp;gt;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">&amp;lt;/li&amp;gt;&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="sr">/ul&amp;gt;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sr">&amp;lt; % end -%&amp;gt;
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>As you may see, this is chaos. It&amp;rsquo;s not easy to read and you can&amp;rsquo;t easily change something when you need to. Wouldn&amp;rsquo;t it be great to store all three seperate blocks (profile, no profile message and options) in partials?&lt;/p>
&lt;p>This is where TextMate does it&amp;rsquo;s magic. Here&amp;rsquo;s a before snaphot:&lt;/p>
&lt;img id="image68" src="https://www.devroom.io/wp-content/uploads/2006/12/before.png" alt="before.png" />
&lt;p>Now, select the part between the if and else statements. This is where a normal profile is shown. Next, use TextMate: &lt;strong>SHIFT-CTRL-H&lt;/strong>. You&amp;rsquo;ll get a pop-up asking you how you&amp;rsquo;d like to name the partial. In this case I choose &amp;lsquo;profile&amp;rsquo;.&lt;/p>
&lt;img id="image69" src="http://ariejan.net/wp-content/uploads/2006/12/during.png" alt="during.png" />
&lt;p>After you click OK, the partial will be created containing the code you selected and an appropriate render method is placed instead. This is what the code looks like after:&lt;/p>
&lt;img id="image70" src="http://ariejan.net/wp-content/uploads/2006/12/after.png" alt="after.png" />
&lt;p>After all this the same ERb template looks like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="n">h2&lt;/span>&lt;span class="o">&amp;gt;&lt;/span>&lt;span class="no">Profile&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="s">%=h @profile.user.login %&amp;gt;&amp;lt;/h2&amp;gt;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">&amp;lt; % if @profile.exists? -%&amp;gt;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> &amp;lt; %=&lt;/span> &lt;span class="n">render&lt;/span> &lt;span class="ss">:partial&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s1">&amp;#39;profile&amp;#39;&lt;/span> &lt;span class="s">%&amp;gt;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">&amp;lt; % else -%&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="s">%= render :partial =&lt;/span>&lt;span class="o">&amp;gt;&lt;/span> &lt;span class="s1">&amp;#39;noprofile&amp;#39;&lt;/span> &lt;span class="s">%&amp;gt;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">&amp;lt; % end -%&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;lt;&lt;/span> &lt;span class="sx">% if &lt;/span> &lt;span class="vi">@profile&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">user&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">id&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="n">current_user&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">id&lt;/span> &lt;span class="o">-&lt;/span>&lt;span class="s">%&amp;gt;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> &amp;lt; %= render :partial =&amp;gt;&lt;/span> &lt;span class="s1">&amp;#39;options&amp;#39;&lt;/span> &lt;span class="s">%&amp;gt;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">&amp;lt; % end -%&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now, isn&amp;rsquo;t that more readable? Even, if you want to change a profile, you can just edit _profile.rhtml and you&amp;rsquo;re done.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2006/12/22/textmaterails-easy-partials-for-better-code/</guid><pubDate>Fri, 22 Dec 2006 00:00:00 +0000</pubDate></item><item><title>SVN: How often should you commit?</title><link>https://www.devroom.io/2006/12/20/svn-how-often-should-you-commit/</link><description>&lt;p>I often hear discussion about how often developers should commit their work to the central repository. Some say that you should only commit when you&amp;rsquo;re next &amp;lsquo;release&amp;rsquo; is ready. Others say that you should commit every change you make in your code. There are even people who say you should commit your changes only at the end of the day.&lt;/p>
&lt;p>All wrong! There is no such thing as &amp;rsquo;the way&amp;rsquo;, but there is a thing called best practice and that&amp;rsquo;s what I want to talk to you about. How often and what should you commit to your Subversion repository.&lt;/p>
&lt;p>Actually, it&amp;rsquo;s quite simple. If you use a tool like Trac or any other ticketing tracking system you have already got a great starting point (given, of course, that you add &amp;rsquo;things you need to do&amp;rsquo; to your tracker).&lt;/p>
&lt;p>I split my development activities into small atomic actions. Normally these actions take up a few minutes to an hour of your time. Besides the fact that these atomic actions are managable, they also give you a sense of satisfaction because you can complete quite a few of these smaller actions every day.&lt;/p>
&lt;p>All right. You have these atomic actions. I grab my code, perform the atomic actions and when it succeeds, I comment my changes back to Subversion. This always closes a ticket in my Trac system and most of the time refences the &amp;lsquo;big picture&amp;rsquo; ticket.&lt;/p>
&lt;p>Here&amp;rsquo;s what I mean.&lt;/p>
&lt;p>&lt;strong>Big picture ticket&lt;/strong>: &lt;em>&amp;ldquo;Create an accounting system for users&amp;rdquo;&lt;/em>&lt;/p>
&lt;p>&lt;strong>Atomic Action ticket&lt;/strong>: &lt;em>&amp;ldquo;Create and customize the user model and unit tests&amp;rdquo;&lt;/em>
&lt;strong>Atomic Action ticket&lt;/strong>: &lt;em>&amp;ldquo;Install acts_as_authenticated plugin&amp;rdquo;&lt;/em>&lt;/p>
&lt;p>So, I start with my code and first I install the acts_as_authenticated plugin. Great. Commit that. This takes a few minutes at the most.&lt;/p>
&lt;p>Next, I create and customize the user model and unit tests. When that&amp;rsquo;s done, I commit my changes again. As you can see, I&amp;rsquo;ve already closed two tickets (and got that warm satisfactory feeling).&lt;/p>
&lt;p>Okay, you get the picture. Why would you work like this? Subversion allows you to keep track of changes in your code, but it doesn&amp;rsquo;t force you on how ofter or when you track changes.&lt;/p>
&lt;p>The most logical way of keeping track of changes is atomic actions. If you decide later that you don&amp;rsquo;t want the installed version of the acts_as_authenticated plugin, but a later release. You can see what files were added when you installed the plugin.&lt;/p>
&lt;p>By using atomic actions you create atomic changesets in Subversion. This way you can easily jump between different states (revisions) of you code without breaking things. It keeps your code working and it allows you to easily track changes you made and to what purpose.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2006/12/20/svn-how-often-should-you-commit/</guid><pubDate>Wed, 20 Dec 2006 00:00:00 +0000</pubDate></item><item><title>SVN: Merge a branch with your trunk</title><link>https://www.devroom.io/2006/12/20/svn-merge-a-branch-with-your-trunk/</link><description>&lt;p>When created a TRY-branch a few days back to try some fancy new AJAX technology in my application. Not problems there, so now I want to merge the code in the branch with my trunk.&lt;/p>
&lt;p>Since I&amp;rsquo;m a lone hacker, the trunk has not been touched since I created the branch. I have a checked-out working copy of the branch available.&lt;/p>
&lt;p>Here&amp;rsquo;s a handy-dandy guide on how to merge your branch code with your trunk.&lt;/p>
&lt;p>Firstly, make sure you have a working copy of your trunk. I choose to switch my working copy back: (oh, make sure you have all your changes checked in in your branch before you switch!)&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ svn switch http://example.com/svn/myproject/trunk
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This removes, adds and updates all files you have worked on in your branch and creates a working copy of the code in the trunk.&lt;/p>
&lt;p>Now, with my trunk in place, I can call &amp;lsquo;merge&amp;rsquo; and apply the changes.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ svn merge http://example.com/svn/myproject/trunk http://example.com/svn/myproject/branches/TRY-AJAX
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Since the files from the trunk and the beginning of the TRY-branch are still exact copies, I won&amp;rsquo;t get in any trouble. If you do (and you did change your code in your trunk), make sure to resolve merging problems before checking in. When ready, check in your new code!&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ svn ci -m &lt;span class="s2">&amp;#34;Merge TRY-AJAX branch with trunk&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>That&amp;rsquo;s it. You have now merged the branch with your trunk. Enjoy!&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2006/12/20/svn-merge-a-branch-with-your-trunk/</guid><pubDate>Wed, 20 Dec 2006 00:00:00 +0000</pubDate></item><item><title>Show the current SVN revision in your Rails app</title><link>https://www.devroom.io/2006/12/13/show-the-current-svn-revision-in-your-rails-app/</link><description>&lt;p>I&amp;rsquo;m current developing a Rails application. I deploy this application to a demonstration server using &lt;a href="http://manuals.rubyonrails.com/read/book/17">capistrano&lt;/a>.&lt;/p>
&lt;p>To streamline feedback and bug reporting I want to show the current revision number of the code that&amp;rsquo;s published on the demo server to show in the footer of every page.&lt;/p>
&lt;p>First I looked into Subversion keyword expansion, but this is marked as &amp;rsquo;evil&amp;rsquo; and it doesn&amp;rsquo;t meet my requirements. I want to show the latest revision number of the entire repository and not just that of the current file.&lt;/p>
&lt;p>Luckily for me, I use capistrano. Here&amp;rsquo;s how I fixed the problem.&lt;/p>
&lt;p>First of all, I created a partial that contains the revision number and render this in my layout.&lt;/p>
&lt;p>app/views/layouts/_revision.rhtml:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">CURRENT
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This shows CURRENT always when I work with my working copy.&lt;/p>
&lt;p>app/views/layouts/mylayout.rhtml&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="s">%= render :partial =&lt;/span>&lt;span class="o">&amp;gt;&lt;/span> &lt;span class="s1">&amp;#39;layout/revision&amp;#39;&lt;/span> &lt;span class="o">%&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now, I assume you have already setup capistrano for your project and that you have a config/deploy.rb file.&lt;/p>
&lt;p>I&amp;rsquo;ve added the following helper to my config/deploy.rb file:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="n">desc&lt;/span> &lt;span class="s2">&amp;#34;Write current revision to app/layouts/_revision.rhtml&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">task&lt;/span> &lt;span class="ss">:publish_revision&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">run&lt;/span> &lt;span class="s2">&amp;#34;svn info &lt;/span>&lt;span class="si">#{&lt;/span>&lt;span class="n">release_path&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> | grep ^Revision &amp;gt; &lt;/span>&lt;span class="si">#{&lt;/span>&lt;span class="n">release_path&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">/app/views/layouts/_revision.rhtml&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Next, I added the following hook &amp;lsquo;after_update_code&amp;rsquo;. This will automatically be run after update_code which is called in &amp;lsquo;deploy&amp;rsquo;.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="n">desc&lt;/span> &lt;span class="s2">&amp;#34;Run this after update_code&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">task&lt;/span> &lt;span class="ss">:after_update_code&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">publish_revision&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>That&amp;rsquo;s it. When I deploy the application, the current code is checked out and and the layouts/_revision.rhtml file is overwritten with the current revision information.&lt;/p>
&lt;h3>Bonus&lt;/h3>
&lt;p>You could also leave the layouts/_revision.rhtml files empty and update it for your demonstration server, but not for your production box. This way there won&amp;rsquo;t be a revision added.&lt;/p>
&lt;p>Of course, you could also create a deploy_demonstration method in deploy.rb and call publish_revision manually from there.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2006/12/13/show-the-current-svn-revision-in-your-rails-app/</guid><pubDate>Wed, 13 Dec 2006 00:00:00 +0000</pubDate></item><item><title>Install ruby-mysql on Mac OS X 10.4 Tiger</title><link>https://www.devroom.io/2006/12/06/install-ruby-mysql-on-mac-os-x-104-tiger/</link><description>&lt;p>You probably know that the built-in mysql code in Rails sucks. To rephrase that, the ruby-mysql gem contains better code, so you want that. Rails automatically detects if you have ruby-mysql installed or not, and uses it if you have it.&lt;/p>
&lt;p>Most notably, you want to install this gem if you get dropped MySQL connections running your Rails application.&lt;/p>
&lt;p>So we do:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ sudo gem install mysql
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ERROR: Failed to build gem native extension.
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Normall this would install fine, but not on Mac OS X. This is because Mac OS X keeps its code, headers and libraries in odd places (compared to Linux). But don&amp;rsquo;t panic. There&amp;rsquo;s an easy solution to all this!&lt;/p>
&lt;p>Just compile the gem yourself, and add some special ingredients. Well, not even that. Just compile it again like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ &lt;span class="nb">cd&lt;/span> /usr/local/lib/ruby/gems/1.8/gems/mysql-2.7
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ sudo ruby extconf.rb --with-mysql-lib&lt;span class="o">=&lt;/span>/usr/local/mysql/lib --with-mysql-include&lt;span class="o">=&lt;/span>/usr/local/mysql/include
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ sudo make
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ sudo make install
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now, you have the ruby-mysql gem installed and ready to go! No more dropped MySQL connections for Rails!&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2006/12/06/install-ruby-mysql-on-mac-os-x-104-tiger/</guid><pubDate>Wed, 06 Dec 2006 00:00:00 +0000</pubDate></item><item><title>Installing Rails on Ubuntu Dapper / Edgy</title><link>https://www.devroom.io/2006/12/03/installing-rails-on-ubuntu-dapper-edgy/</link><description>&lt;p>&lt;em>Update 2010-03-25: Bumped to RubyGems version 1.3.6.&lt;/em>&lt;/p>
&lt;p>&lt;em>Update 2009-02-19: Bumped to RubyGems version 1.3.1 and MySQL 5 libraries. This guide now works for all recent version of Ubuntu and Debian. Enjoy!&lt;/em>&lt;/p>
&lt;p>Installing Ruby on Rails on your Ubuntu box is not always as easy as it seems. Here&amp;rsquo;s a comprehensive overview of the steps you need to take. Mostly you&amp;rsquo;ll be using apt-get and gems, so it&amp;rsquo;s not all that hard after all.&lt;/p>
&lt;p>This method was tested on both Dapper and Edgy systems. It may work on other Ubuntu releases as well. It&amp;rsquo;s also possible that it works on Debian.&lt;/p>
&lt;p>Besides Rails, I&amp;rsquo;ll also be install mysql and sqlite3 support.&lt;/p>
&lt;h3>1. Install Ruby&lt;/h3>
&lt;p>Before putting anything on Rails, install Ruby.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">sudo apt-get install irb1.8 libreadline-ruby1.8 libruby libruby1.8 rdoc1.8 ruby ruby1.8 ruby1.8-dev
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>You may now check what version of Ruby you have by running &lt;code>ruby -v&lt;/code>. It&amp;rsquo;s just for your information.&lt;/p>
&lt;h3>2. Install Ruby Gems&lt;/h3>
&lt;p>Surf to &lt;a href="http://rubyforge.org/frs/?group_id=126">&lt;a href="http://rubyforge.org/frs/?group_id=126">http://rubyforge.org/frs/?group_id=126&lt;/a>&lt;/a> and download the latest available gems pacakge in tgz format. (You may also use the zip if you feel comfortable.)&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">wget http://rubyforge.org/frs/download.php/69365/rubygems-1.3.6.tgz
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">tar zxf rubygems-1.3.6.tgz
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> rubygems-1.3.6
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo ruby setup.rb
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo ln -sf /usr/bin/gem1.8 /usr/bin/gem
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3>3. Install Rails!&lt;/h3>
&lt;p>You may now install Rails now!&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">sudo gem install rails
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>That&amp;rsquo;s it! Well, almost. You probably want some other things as well.&lt;/p>
&lt;h3>4. Install development tools&lt;/h3>
&lt;p>Before you continue, stop a moment to install some development tools. These tools are probably needed to compile and install the gems we are going to install next.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">sudo apt-get install build-essential
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3>MySQL Support&lt;/h3>
&lt;p>You probably want MySQL Ruby support. The MySQL code in Rails sucks (no offence), but the Ruby code is much better. You should install it. Rails will notice that it&amp;rsquo;s available and use it.&lt;/p>
&lt;p>First, install MySQL.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">sudo apt-get install mysql-server mysql-client
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Next, install development files for MySQL. We&amp;rsquo;ll need these in order to make Ruby understand.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">sudo apt-get install libmysqlclient15-dev
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Then, you may install the gem&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">sudo gem install mysql
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>You have to choose what version you want to install. Enter the number corresponding with the latest version that is tagged &amp;lsquo;ruby&amp;rsquo;. Installing win32 stuff on linux is generally not a good thing.&lt;/p>
&lt;h3>SQLite 3&lt;/h3>
&lt;p>For SQLite 3 you need to install some packages before installing the gem.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">sudo apt-get install sqlite3 libsqlite3-dev
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo gem install sqlite3-ruby
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3>If things go wrong&lt;/h3>
&lt;p>If things go wrong, make sure you installed all recommended packages mentioned above. Also , check any log files that the error message refers to.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2006/12/03/installing-rails-on-ubuntu-dapper-edgy/</guid><pubDate>Sun, 03 Dec 2006 00:00:00 +0000</pubDate></item><item><title>How to setup a Ubuntu development server - Part 2</title><link>https://www.devroom.io/2006/12/02/how-to-setup-a-ubuntu-development-server-part-2/</link><description>&lt;p>Also read &lt;a href="http://ariejan.net/2006/12/01/how-to-setup-a-ubuntu-development-server-part-1/">Part 1 - Subversion&lt;/a>.&lt;/p>
&lt;p>In this part I will tell you how to install &lt;a href="http://trac.edgewall.org/">Trac&lt;/a> on top of your Subversion repositories on your Ubuntu development server. Trac offers you a wiki, roadmap, tickets (tracking system) and access to your SubVersion repository. All of this is bundeled in a very sexy web interface.&lt;/p>
&lt;p>Well, let&amp;rsquo;s get to work now and get Trac installed. When you&amp;rsquo;re done you will have trac available for all your Subversion repositories.&lt;/p>
&lt;h3>Install Trac&lt;/h3>
&lt;p>First thing to do is install trac. Here I will also install mod_python for your apache webserver and python-setuptools that we&amp;rsquo;ll need later with the webadmin plugin.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">sudo apt-get install trac libapache2-mod-python python-setuptools
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now, I create a directory where all Trac information will be stored.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">sudo mkdir -p /var/lib/trac
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Common sense dictates that you use the same name here for the trac environment as for the subversion repository.&lt;/p>
&lt;p>Change to the trac directory and intitialize the project:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> /var/lib/trac
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo trac-admin colt initenv
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>You&amp;rsquo;ll need to name the project, choose a database file (default is okay), specify where the subversion repository resides ( /var/lib/svn/colt, in this case) and a template (the default is okay here too).&lt;/p>
&lt;p>I recommend you also create an administrator user right now. Make sure you add a user who&amp;rsquo;s already in your /etc/apache2/dav_svn.passwd file.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">sudo trac-admin colt permission add ariejan TRAC_ADMIN
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Well, that&amp;rsquo;s it. Trac has been installed. Now let&amp;rsquo;s make sure we can access trac through the web.&lt;/p>
&lt;h3>Configuring Apache&lt;/h3>
&lt;p>Configuring apache is rather easy when you know what to do. Add the following code to /etc/apache2/sites-available/default (at the bottom before the end of the virtualhost tag) or put it in a seperate virtual host file if you want to dedicate a special domain to this.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-apache" data-lang="apache">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;location&lt;/span> &lt;span class="s">/projects&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">SetHandler&lt;/span> mod_python
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">PythonHandler&lt;/span> trac.web.modpython_frontend
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">PythonOption&lt;/span> TracEnvParentDir &lt;span class="sx">/var/lib/trac&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">PythonOption&lt;/span> TracUriRoot &lt;span class="sx">/projects&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/location&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;locationmatch&lt;/span> &lt;span class="s">&amp;#34;/projects/[^/]+/login&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">AuthType&lt;/span> Basic
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">AuthName&lt;/span> &lt;span class="s2">&amp;#34;Trac Authentication&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">AuthUserFile&lt;/span> &lt;span class="sx">/etc/apache2/dav_svn.passwd&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">Require&lt;/span> valid-user
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/locationmatch&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Notice here, again that we use TracEnvParentDir to show we host multiple instances of Trac. You may change the TracUriRoot to something different.&lt;/p>
&lt;p>Again, make sure to chown your Trac installation to www-data:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">sudo chown -R www-data.www-data /var/lib/trac
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now, access your trac over the web: &lt;a href="http://example.com/projects">http://example.com/projects&lt;/a> for a complete listing of hosted projects or &lt;a href="http://example.com/projects/colt">http://example.com/projects/colt&lt;/a> for the COLT project.&lt;/p>
&lt;p>You may also login now! As you can see, we use the dav_svn.passwd file here so everyone with subversion access also has access to trac.&lt;/p>
&lt;h3>Webadmin&lt;/h3>
&lt;p>Normally you would administrate a Trac installation through the command-line interface we used to initialize the environment and add the administrator user. Nowadays there is a webadmin plugin for Trac, which will be included in Trac from version 0.11. Since Ubuntu ships with Trac 0.9.3 we need to add this webadmin ourselves.&lt;/p>
&lt;p>First, download the following file to your server: &lt;a href="http://trac.edgewall.org/attachment/wiki/WebAdmin/TracWebAdmin-0.1.2dev_r4240-py2.4.egg.zip?format=raw">&lt;a href="http://trac.edgewall.org/attachment/wiki/WebAdmin/TracWebAdmin-0.1.2dev_r4240-py2.4.egg.zip">http://trac.edgewall.org/attachment/wiki/WebAdmin/TracWebAdmin-0.1.2dev_r4240-py2.4.egg.zip&lt;/a>&lt;/a>.&lt;/p>
&lt;p>Don&amp;rsquo;t unzip this file, just remove the .zip extension.&lt;/p>
&lt;p>Because we installed setuptools earlier, we can now use easy_install to install this plugin system-wide, enabling it for all our trac installations.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">sudo easy_install TracWebAdmin-0.1.2dev_r4240-py2.4.egg
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Next we enable webadmin in the global configuration file of track. You may need to create the &amp;lsquo;conf&amp;rsquo; directory in this case:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> /usr/share/trac
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo mkdir conf
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo vi conf/trac.ini&lt;span class="o">[&lt;/span>/conf&lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Next enter the following in trac.ini&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ini" data-lang="ini">&lt;span class="line">&lt;span class="cl">&lt;span class="k">[components]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">webadmin.*&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s">enabled&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Save the file and off you go. Login as the administrator user you specified earlier and you can make use of the &amp;lsquo;admin&amp;rsquo; button that has appeared in the menu of Trac.&lt;/p>
&lt;p>Enjoy your trac! Next time (in Part 3) I will talk about setting up a commit-hook so Trac tickets are updated by posting subversion commit messages (throught commit-hooks).&lt;/p>
&lt;p>Also read &lt;a href="http://ariejan.net/2006/12/01/how-to-setup-a-ubuntu-development-server-part-1/">Part 1 - Subversion&lt;/a> on how to install Subversion over WebDAV.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2006/12/02/how-to-setup-a-ubuntu-development-server-part-2/</guid><pubDate>Sat, 02 Dec 2006 00:00:00 +0000</pubDate></item><item><title>How to setup a Ubuntu development server - Part 1</title><link>https://www.devroom.io/2006/12/01/how-to-setup-a-ubuntu-development-server-part-1/</link><description>&lt;p>Since I&amp;rsquo;m starting some real work on my final school project, I want to install a Ubuntu development server here at home. I have a Pentium 4 box here that will perform that task.&lt;/p>
&lt;p>In this first part I will show you how to install Subversion over WebDAV. All of this will be done in such a way that it&amp;rsquo;s easy to serve multiple projects at once.&lt;/p>
&lt;p>In future parts I will tell you more about installing Trac, FastCGI (with Apache) to host Rails applications and how to use Capistrano to deploy your app properly.&lt;/p>
&lt;p>For now, let&amp;rsquo;s get cracking at Subversion.&lt;/p>
&lt;p>First off, I installed Ubuntu 6.10 on my server. Because I don&amp;rsquo;t need a graphical user interface, I have installed Ubuntu in text-only mode.&lt;/p>
&lt;h3>Open up to the universe&lt;/h3>
&lt;p>The first thing I always do when I install a Ubuntu box is to enable the universe package repositories. This is rather easy.&lt;/p>
&lt;p>Edit /etc/apt/sources.list and uncomment all the Universe related lines. Also, comment out your install disk. Here&amp;rsquo;s what my /etc/apt/sources.list looks like:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl"># deb cdrom:[Ubuntu 6.10 _Edgy Eft_ - Release i386 (20061025)]/ edgy main restricted
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">deb http://nl.archive.ubuntu.com/ubuntu/ edgy main restricted
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">deb-src http://nl.archive.ubuntu.com/ubuntu/ edgy main restricted
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">## Major bug fix updates produced after the final release of the
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">## distribution.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">deb http://nl.archive.ubuntu.com/ubuntu/ edgy-updates main restricted
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">deb-src http://nl.archive.ubuntu.com/ubuntu/ edgy-updates main restricted
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">## Uncomment the following two lines to add software from the &amp;#39;universe&amp;#39;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">## repository.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">## N.B. software from this repository is ENTIRELY UNSUPPORTED by the Ubuntu
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">## team, and may not be under a free licence. Please satisfy yourself as to
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">## your rights to use the software. Also, please note that software in
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">## universe WILL NOT receive any review or updates from the Ubuntu security
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">## team.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">deb http://nl.archive.ubuntu.com/ubuntu/ edgy universe
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">deb-src http://nl.archive.ubuntu.com/ubuntu/ edgy universe
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">## Uncomment the following two lines to add software from the &amp;#39;backports&amp;#39;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">## repository.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">## N.B. software from this repository may not have been tested as
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">## extensively as that contained in the main release, although it includes
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">## newer versions of some applications which may provide useful features.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">## Also, please note that software in backports WILL NOT receive any review
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">## or updates from the Ubuntu security team.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># deb http://nl.archive.ubuntu.com/ubuntu/ edgy-backports main restricted universe multiverse
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># deb-src http://nl.archive.ubuntu.com/ubuntu/ edgy-backports main restricted universe multiverse
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">deb http://security.ubuntu.com/ubuntu edgy-security main restricted
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">deb-src http://security.ubuntu.com/ubuntu edgy-security main restricted
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">deb http://security.ubuntu.com/ubuntu edgy-security universe
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">deb-src http://security.ubuntu.com/ubuntu edgy-security universe
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The next step is to make sure all software present is up-to-date. There are already a few updates available so run these two commands:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">sudo apt-get update
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo apt-get dist-upgrade
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>That&amp;rsquo;s it.&lt;/p>
&lt;h3>Getting SSH up and running&lt;/h3>
&lt;p>Because I have a MacBook, I&amp;rsquo;d like to use it to do my work. To do so I need to install the OpenSSH server on my server so I can access it over the network.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">sudo apt-get install ssh
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This will install ssh and the OpenSSH server. It also generates everything you need automatically like RSA keys and all that.&lt;/p>
&lt;p>Now, try to login wiht SSH from your desktop machine.&lt;/p>
&lt;h3>Apache&lt;/h3>
&lt;p>Since I want to use Subversion of WebDAV I will need to install Apache first. I&amp;rsquo;ll grab a vanilla copy of Apache from Ubuntu here.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">sudo apt-get install apache2
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>If that&amp;rsquo;s finished you should see a placeholder when you access your server with a browser. Check this now.&lt;/p>
&lt;h3>Getting Subversion&lt;/h3>
&lt;p>Subversion is also easily installed.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">sudo apt-get install subversion subversion-tools
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Next we should setup a location for our Subversion repositories. I choose to put them in /var/lib/svn. Create this directory now:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">sudo mkdir -p /var/lib/svn
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>I also create a repository now and create a basic Subversion structure there. In my case the project is called &amp;lsquo;colt&amp;rsquo;.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">sudo mkdir -p /var/lib/svn/colt
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo svnadmin create /var/lib/svn/colt
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo svn mkdir file:///var/lib/svn/colt/trunk -m &lt;span class="s2">&amp;#34;Trunk&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo svn mkdir file:///var/lib/svn/colt/tags -m &lt;span class="s2">&amp;#34;Tags&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo svn mkdir file:///var/lib/svn/colt/branches -m &lt;span class="s2">&amp;#34;Branches&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Note that you need to sudo the svn commands because only root has write access to your repository currently.&lt;/p>
&lt;h3>WebDAV for SVN&lt;/h3>
&lt;p>Okay. You are already at revision 3 on your repository. Good work! Now let&amp;rsquo;s make sure that you repositories are accessable over the web. First, we install libapache2-svn. This packages includes WebDAV support for SVN.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">sudo apt-get install libapache2-svn
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Next I open up /etc/apache2/mods-available/dav_svn.conf. This file contains configuration for the WebDAV and SubVersion modules we just installed.&lt;/p>
&lt;p>Here I enable basic HTTP authentication, which is good enough for my local network. I also enable DAV by uncommenting &amp;ldquo;DAV svn&amp;rdquo;.&lt;/p>
&lt;p>You need to take special care of the &amp;ldquo;SVNPath&amp;rdquo; variable here. We don&amp;rsquo;t host just one repository. We host several and /var/lib/svn is the parent directory of all our repositories. E.g. &amp;lsquo;colt&amp;rsquo; is a sub directory that resides in /var/lib/svn. To make Apache understand this we need to change &amp;ldquo;SVNPath&amp;rdquo; to &amp;ldquo;SVNParentPath&amp;rdquo;. This enables all sub directories to be independent repositories.&lt;/p>
&lt;p>Note: The authentication file we use here can be recycled later when we install Trac! Handy-Dandy, isn&amp;rsquo;t it?&lt;/p>
&lt;p>I made my configuration look like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-apache" data-lang="apache">&lt;span class="line">&lt;span class="cl">&lt;span class="c"># dav_svn.conf - Example Subversion/Apache configuration&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">#&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c"># For details and further options see the Apache user manual and&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c"># the Subversion book.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c"># &amp;lt;location URL&amp;gt; ... &amp;lt;/location&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c"># URL controls how the repository appears to the outside world.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c"># In this example clients access the repository as http://hostname/svn/&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;location&lt;/span> &lt;span class="s">/svn&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c"># Uncomment this to enable the repository,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">DAV&lt;/span> svn
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c"># Set this to the path to your repository&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">SVNParentPath&lt;/span> &lt;span class="sx">/var/lib/svn&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c"># The following allows for basic http authentication. Basic authentication&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c"># should not be considered secure for any particularly rigorous definition of&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c"># secure.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c"># to create a passwd file&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c"># # rm -f /etc/apache2/dav_svn.passwd&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c"># # htpasswd2 -c /etc/apache2/dav_svn.passwd dwhedon&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c"># New password:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c"># Re-type new password:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c"># Adding password for user dwhedon&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c"># #&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c"># Uncomment the following 3 lines to enable Basic Authentication&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">AuthType&lt;/span> Basic
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">AuthName&lt;/span> &lt;span class="s2">&amp;#34;Subversion Repository Access&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">AuthUserFile&lt;/span> &lt;span class="sx">/etc/apache2/dav_svn.passwd&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c"># Uncomment the following line to enable Authz Authentication&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c"># AuthzSVNAccessFile /etc/apache2/dav_svn.authz&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c"># The following three lines allow anonymous read, but make&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c"># committers authenticate themselves.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;limitexcept&lt;/span> &lt;span class="s">GET PROPFIND OPTIONS REPORT&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">Require&lt;/span> valid-user
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/limitexcept&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/location&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now kick Apache to reload and you should be able to access your repository over the web! Try &lt;a href="http://example.com/svn/colt">http://example.com/svn/colt&lt;/a>.&lt;/p>
&lt;h3>Authentication&lt;/h3>
&lt;p>Reading of the repository is okay without authentication. But writing needs to be protected. We need to create a password file for this. This is easy and is already explained in /etc/apache2/mods-available/dav_svn.conf:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">sudo htpasswd2 -c /etc/apache2/dav_svn.passwd ariejan
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Go ahead, add as many users as you need.&lt;/p>
&lt;h3>Apache, Subversion and the world&lt;/h3>
&lt;p>Before you start using Subversion, make sure you make the repositories owned by Apache. Apache is the one who wil access the repositories physically. This is really easy:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">sudo chown -R www-data.www-data /var/lib/svn
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>When you access your repository for write actions now, you will recieve the following message:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">Authentication realm: &amp;lt;http ://example.com:80&amp;gt; Subversion Repository Access
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Password for &amp;#39;ariejan&amp;#39;:
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Alright sparky! Subversion access is ready for you now! Next time I&amp;rsquo;ll tell you how to integrate Trac with your hot new Subversion repositories.&lt;/p>
&lt;h3>Notes&lt;/h3>
&lt;p>All users in you passwd file can write to all repositories. I&amp;rsquo;ve not yet found a way to prevent this, since I don&amp;rsquo;t need that functionality right now. If you know more about this, please let me know.&lt;/p>
&lt;p>Subversion repositories are always readable by anonymous people. You should remove the LimitExcept block from dav_svn.conf to make sure all users authenticated themselves before accessing your repository.&lt;/p>
&lt;p>If you add more repositories, you always have to chown them to your apache&amp;rsquo;s user and group. After that you can use them through WebDAV.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2006/12/01/how-to-setup-a-ubuntu-development-server-part-1/</guid><pubDate>Fri, 01 Dec 2006 00:00:00 +0000</pubDate></item><item><title>Rails: Security Check-up</title><link>https://www.devroom.io/2006/11/30/rails-security-check-up/</link><description>&lt;p>Is your Rails app secure? Really? Maybe you need to perform a major check-up of your Rails application to make sure.&lt;/p>
&lt;p>Here&amp;rsquo;s a &lt;a href="http://rubythis.blogspot.com/2006/11/rails-security-checklist.html">comprehensive list&lt;/a> that will take you through the most common mistakes and forgotten security risks in your Rails application.&lt;/p>
&lt;p>It&amp;rsquo;s a great post. Print it, hang it on your wall and create secure Rails apps from now on.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2006/11/30/rails-security-check-up/</guid><pubDate>Thu, 30 Nov 2006 00:00:00 +0000</pubDate></item><item><title>The Three Corner Stones of Developerhood</title><link>https://www.devroom.io/2006/11/25/the-three-corner-stones-of-developerhood/</link><description>&lt;p>As a developer, there are just some things you can&amp;rsquo;t do without. I have found that there are three things that I need. Really need. If one of them is missing, I have trouble doing my job properly.&lt;/p>
&lt;p>I call them &lt;em>&amp;ldquo;The Three Corner stones of Developerhood&amp;rdquo;&lt;/em>:
~&lt;/p>
&lt;h3>1. Coffee&lt;/h3>
&lt;p>Most importantly coffee. I just can&amp;rsquo;t do without it. I don&amp;rsquo;t drink it to stay awake or anything, but it&amp;rsquo;s just a warm beverage that keeps me focused and gives me the energy I need to keep working. I prefer coffee over all those horrible caffeine drinks.&lt;/p>
&lt;h3>2. A good text editor&lt;/h3>
&lt;p>No matter how good Integrated Developement Environments are, I always prefer my trusty &lt;a href="http://macromates.com/">TextMate&lt;/a> to get things done for me.&lt;/p>
&lt;p>Syntax highlighting and the huge amount of macro&amp;rsquo;s is just a pleasure to work with. I support PHP, HTML, CSS and most importantly, Ruby on Rails.&lt;/p>
&lt;p>If you have a Mac. Buy this now!&lt;/p>
&lt;h3>3. Google&lt;/h3>
&lt;p>I can&amp;rsquo;t tell you how many problems I&amp;rsquo;ve solved by using &lt;a href="http://www.google.com">Google&lt;/a> to find the answer.&lt;/p>
&lt;p>Of course, you can just insert any search engine here, but Google is really my favourite. With &lt;a href="http://groups.google.com">Google Groups&lt;/a> I can find stuff in news groups easily, and as a developer, news groups are a very valuable resource of information.&lt;/p>
&lt;p>It&amp;rsquo;s very well possible that you have three other corner stones. Feel free to comment them below and let me know.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2006/11/25/the-three-corner-stones-of-developerhood/</guid><pubDate>Sat, 25 Nov 2006 00:00:00 +0000</pubDate></item><item><title>SVN: How to structure your repository</title><link>https://www.devroom.io/2006/11/24/svn-how-to-structure-your-repository/</link><description>&lt;p>&lt;em>You are reading an article about Subversion. That&amp;rsquo;s great, because it
means you&amp;rsquo;re thinking about the benefits of version control for your
project. However, I have long since moved from Subversion to Git. I
strongly recommend you read up on my &lt;a
href="http://ariejan.net/2009/06/08/best-practice-the-git-development-cycle/">Best
Practice - The Git Developmenet Cycle&lt;/a>. Git is much faster an
flexible than Subversion, go check it out now!&lt;/em>&lt;/p>
&lt;p>Most people know what Subversion is and that there&amp;rsquo;s something called &amp;ldquo;The Trunk&amp;rdquo; with code in it. Well, there&amp;rsquo;s more to your SubVersion repository than you think! This article will discuss how to structure your repository in order for you to take full advantage of Subversion&amp;rsquo;s possibilities.&lt;/p>
&lt;p>As you may have read in my previous &lt;a href="http://ariejan.net/tags/subversion">Subversion articles&lt;/a> the base of your Subversion repository are three directories: branches, tags and trunk.&lt;/p>
&lt;p>Each directory in subversion can be checked out seperately. See the examples for more information.&lt;/p>
&lt;h3 id="trunk">Trunk&lt;/h3>
&lt;p>The trunk contains the most current development code at all times. This is where you work up to your next major release of code.&lt;/p>
&lt;p>I see, almost too often, that people only use the trunk to store their code. Releases are pulled from a certain revision and then development continues. This is no good. Not for you, not for your product.&lt;/p>
&lt;p>The trunk should only be used to develop code that will be your next major release. Don&amp;rsquo;t brand the trunk with version numbers or release names. Just keep the trunk in &amp;ldquo;development mode&amp;rdquo; at all times.&lt;/p>
&lt;p>Example: &lt;code>https://svn.example.com/svnroot/project/trunk&lt;/code>&lt;/p>
&lt;h3 id="branches">Branches&lt;/h3>
&lt;p>There are different types of branches. I&amp;rsquo;ll tell you about the most common ones here.&lt;/p>
&lt;p>With the branches directory you can create paths for you code to travel to more specific goals like an upcoming release. As I discussed in my &lt;a href="http://ariejan.net/2006/11/21/svn-how-to-release-software-properly/">article on releasing software from Subversion&lt;/a> the branches directory contains copies of your trunk at various stages of development.&lt;/p>
&lt;h4 id="release-branches">Release Branches&lt;/h4>
&lt;p>We have already seen the RB-x.x release branches. When the trunk reaches the stage that it&amp;rsquo;s ready to be released (or when you want to freeze the addition of new features) you create a release branch. This release branch is just a copy of your current trunk code.&lt;/p>
&lt;p>The branch can be checked out seperately and you can start branding and versioning the project. You can also use the release branch to fix bugs that pop up during (beta) testing (&lt;a href="http://ariejan.net/2006/11/22/svn-how-to-fix-bugs-properly/">See my article on that too&lt;/a>). The idea of this is to keep development in the trunk going without being bothered by release specific stuff. So it&amp;rsquo;s perfectly fine to add new features to your trunk while you (or others) prepare the release.&lt;/p>
&lt;p>Of course, you can address a release branch directly to check it out:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">https://svn.example.com/svnroot/project/branches/RB-1.0
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="bug-fix-branches">Bug fix branches&lt;/h4>
&lt;p>Branches may also be used to address the more serious bugs found in the trunk or a release branch. The bugs are of such a magnitute that you can&amp;rsquo;t fix them by yourself in a single commit. So, in order to focus on the problem of fixing this bug you should create a new branch for this purpose. This allows development in the trunk or your release branch to continue, and you won&amp;rsquo;t disturb them with new bugs or tests that break the current code.&lt;/p>
&lt;p>Bug fix branches are named after the ID they are assigned in your bugtracking tool. Mostly this is a number: BUG-3391&lt;/p>
&lt;p>Of course, you can access your bugfix branch like any other.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">https://svn.example.com/svnroot/project/branches/BUG-3391
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Read my &lt;a href="http://ariejan.net/2006/11/22/svn-how-to-fix-bugs-properly/">how to fix bugs properly&lt;/a> article for more specific bug fixing information. Also read on in this article to the tags section.&lt;/p>
&lt;h4 id="experimental-branches">Experimental branches&lt;/h4>
&lt;p>Something that also happens a lot is the introduction of new technologies. This is fine, of course, but you don&amp;rsquo;t want to bet your entire project on it.&lt;/p>
&lt;p>Imagine that you want to change from PHP 4 to PHP 5 for your web application. How long would it take you to convert your entire project? Do you want your entire code base (trunk) to be useless until you have converted all of your code? Probably not!&lt;/p>
&lt;p>These experiments, maybe PHP 5 is a bridge too far for your app, should be given their own branch. You can hack your way to PHP 5-ism there and if you fail, you still have your current PHP 4 code in the branch.&lt;/p>
&lt;p>Experimental branches may be abandonned when the experiment fails. If they succeed you can easily merge that branch with the trunk and deliver your big new technology. These branches are named after what you&amp;rsquo;re experimenting with. I always prefix them with &amp;lsquo;TRY-&amp;rsquo;:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">https://svn.example.com/svnroot/project/branches/TRY-new-technology
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="tags">Tags&lt;/h3>
&lt;p>Tags are, like branches, copies of your code. Tags, however, are not to be used for active development. They mark (tag) a certain state your code is in.&lt;/p>
&lt;h4 id="release-tags">Release tags&lt;/h4>
&lt;p>Release tags mark the release (and state) of your code at that release point. Release tags are always copies of the corresponding release branch. Release tags are prefixed with &amp;lsquo;REL-&amp;rsquo; followed by a version number.&lt;/p>
&lt;p>You can access these tags easily:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">https://svn.example.com/svnroot/project/tags/REL-1.0.0
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>See &lt;a href="http://ariejan.net/2006/11/21/svn-how-to-release-software-properly/">my article on releasing software&lt;/a> for more information.&lt;/p>
&lt;h4 id="bug-fix-pre-and-post-tags">Bug fix PRE and POST tags&lt;/h4>
&lt;p>When you have created a bug fix branch, you want to mark (tag) the situation of your code before and after the bugfix. This allows you to easily refer to the changes you made when you want to merge them back to your trunk or release branch.&lt;/p>
&lt;p>The start-tag is called &amp;lsquo;PRE&amp;rsquo; and the end-tag called &amp;lsquo;POST&amp;rsquo;. Of course, you should add the bug ID number here too to show what bug you are tagging here.&lt;/p>
&lt;p>You probably don&amp;rsquo;t check out bug fix tags, but you want to reference them when merging bug fixes with your other code:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">https://svn.example.com/svnroot/project/tags/PRE-3391
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">https://svn.example.com/svnroot/project/tags/POST-3391
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Read more on &lt;a href="http://ariejan.net/2006/11/22/svn-how-to-fix-bugs-properly/">fixing bugs wiht subversion&lt;/a> in my other article.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2006/11/24/svn-how-to-structure-your-repository/</guid><pubDate>Fri, 24 Nov 2006 00:00:00 +0000</pubDate></item><item><title>Google Project Hosting: SourceForge Competitor</title><link>https://www.devroom.io/2006/11/22/google-project-hosting-sourceforge-competitor/</link><description>&lt;p>I just found out that &lt;a href="http://code.google.com/">Google Code&lt;/a> is now offering &lt;a href="http://code.google.com/hosting/">Project Hosting&lt;/a>! It&amp;rsquo;s in the same style as &lt;a href="http://www.sourceforge.net">SourceForge&lt;/a>, but the Google way!&lt;/p>
&lt;p>As a Google user you can create a project with a built-in issue tracker and Subversion repository. And if that&amp;rsquo;s not all, you can tie in a blog (at Blogger.com is you like) and adiscussion group (on Google Groups, of course).&lt;/p>
&lt;p>This is really a nice package Google is offering and it looks like a serious competitor for SourceForge.&lt;/p>
&lt;p>It&amp;rsquo;s just that I didn&amp;rsquo;t find any possibility to release files yet, but other than that, I&amp;rsquo;d like to move there!&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2006/11/22/google-project-hosting-sourceforge-competitor/</guid><pubDate>Wed, 22 Nov 2006 00:00:00 +0000</pubDate></item><item><title>SVN: How to fix bugs properly</title><link>https://www.devroom.io/2006/11/22/svn-how-to-fix-bugs-properly/</link><description>&lt;p>I&amp;rsquo;ve already told you about &lt;a href="http://ariejan.net/2006/11/21/svn-how-to-release-software-properly/">releasing your project with help from Subversion&lt;/a>. Now I want to talk to you about using Subversion to fix bugs in your application.&lt;/p>
&lt;p>Fixing bugs can be as easy as fixing a few lines of code or as hard as rewriting a significant portion of your application. Both situations need a different approach from us. Let&amp;rsquo;s talk about the easy stuff first.&lt;/p>
&lt;p>For this example let&amp;rsquo;s say we have a project. It has a release branch named RB-1.0 and current development is going on in the trunk.&lt;/p>
&lt;p>A user has submitted a bug report (numbered #3391) against your 1.0 release. Here&amp;rsquo;s what to do:&lt;/p>
&lt;h3 id="easy-bug-fixes">Easy bug fixes&lt;/h3>
&lt;p>Let&amp;rsquo;s say bug #3391 is an easy fix. First, check out a working copy of the release branch.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">svn co https://example.com/branches/RB-1.0 rb-1.0
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now, go in there, write tests that expose the bug and fix it. As I said, it&amp;rsquo;s an easy fix so you can commit all your changes at once to the release branch. When you do this, remember the new revision number.&lt;/p>
&lt;p>&lt;strong>Note:&lt;/strong> it&amp;rsquo;s always smart to include the number of the bug (in this case #3391) in your commit message. This will make sure other developers (and later on, yourself) know what bug was fixed here.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">$ svn commit -m &lt;span class="s2">&amp;#34;Bug fixed #3391&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Committed revision 183.
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>As I said, remember the revision number: 183.&lt;/p>
&lt;p>Now you have the bug fixed in the release branch for version 1.0, you are probably wondering if the bug exists in your current development code as well. Since branches are plain copies of your development code, the big probably is there too.&lt;/p>
&lt;p>Don&amp;rsquo;t start editing your working copy of the trunk and start fixing the bug all over again. Let Subversion do the work.&lt;/p>
&lt;p>Go into your trunk working copy and update it to the latest revision, this is revision 183. But, we only made changes to the release branch, and not to the trunk, so we need to merge those changes. We can do this by running the following command:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">svn merge -r182:183 https://example.com/branches/RB-1.0 rb-1.0
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>You&amp;rsquo;ll now see the fix you applied in the release branch getting merged with your current development code. Great, isn&amp;rsquo;t it?&lt;/p>
&lt;p>Before you leave to party, don&amp;rsquo;t forget to commit the changes to the trunk. Again, name the bug number you fixed here and also which revision you userd to merge it.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">svn commit -m &lt;span class="s2">&amp;#34;Merge r183 (bug fixed #3391)&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>You can apply this merge process with any other release branch you have if that&amp;rsquo; necessary.&lt;/p>
&lt;h3 id="difficult-bug-fixes">Difficult bug fixes&lt;/h3>
&lt;p>Another scenario is that the bug is not easy to fix. You need to rewrite a lot of tests to expose the bug in the first place, and even then you&amp;rsquo;re not sure how to go about fixing it.&lt;/p>
&lt;p>If that&amp;rsquo;s the case, you are better of creating a seperate bug fix branch. This allows you to fix the bug in the background, possibly with the help of others, while current development can still go on. Also, you want to create snapshots of before (pre) and after (post) the bug fix. These tags will help you merge the fix into other branches later on.&lt;/p>
&lt;p>First, create a bug fix branc. By convention Release Branches were called RB-1.0, Bug fix branches are called BUG-###. Of course, ### corresponds to the bug report number. In this case we create a branch named BUG-3391. We also need to create a snapshot of the code before we start fixing the bug. We call this tag PRE-3391.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">svn copy -m &lt;span class="s2">&amp;#34;Create bugfix branch&amp;#34;&lt;/span> https://example.com/branches/RB-1.0 https://example.com/branches/BUG-3391
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">svn copy -m &lt;span class="s2">&amp;#34;Tag start of bug fix&amp;#34;&lt;/span> https://example.com/branches/BUG-3391 https://example.com/tags/PRE-3391
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now, you can checkout the bug fix branch and start work. You may call in the help of others if you need to. It&amp;rsquo;s okay to make multiple commits to this branch.&lt;/p>
&lt;p>When you have reached the point were the bug is fixed, you&amp;rsquo;ll need to mark the end of it. We create a new tag named POST-3391 to mark the end of the bugfix:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">svn copy -m &lt;span class="s2">&amp;#34;Tag end of bug fix&amp;#34;&lt;/span> https://example.com/branches/BUG-3391 https://example.com/tags/POST-3391
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Well. You&amp;rsquo;re done! Your bug has been fixed! But wait a minute. The fix is not present in the release branch yet! Here, again, we need to merge the bug fix into the release branch (and possibly into the trunk also).&lt;/p>
&lt;p>First, update your current working of the release branch and merge the changes between the PRE-3391 and POST-3391 tags with the release branch. When done, run your tests to make sure everything works as expected and commit your changes.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">svn update
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">svn merge https://example.com/tags/PRE-3391 https://example.com/tags/POST-3391
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">svn commit -m &lt;span class="s2">&amp;#34;Merged bug fix for bug #3391&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="final-note">Final note&lt;/h3>
&lt;p>It&amp;rsquo;s clear that fixing bugs the easy way is the preferred method. Try to use this method as much as possible. But it&amp;rsquo;s okay to use the difficult way to solve the more difficult bugs.&lt;/p>
&lt;p>Putting the bug report number into Subversion comments is a good way to keep track of what code changes have been made based on what bug report. Most bug tracking tools can make use of this and link bug reports to the appropriate revisitons and visa-versa. If your bug tracking tool doesn&amp;rsquo;t have this ability, it&amp;rsquo;s a good idea to comment the appropriate revision number or tags on the bug so that others know where to look for the fix.&lt;/p>
&lt;p>Happy bug fixing!&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2006/11/22/svn-how-to-fix-bugs-properly/</guid><pubDate>Wed, 22 Nov 2006 00:00:00 +0000</pubDate></item><item><title>WordPress: Author comment highlighting</title><link>https://www.devroom.io/2006/11/22/wordpress-author-comment-highlighting/</link><description>&lt;p>I&amp;rsquo;ve seen it lots of times before, but I just added it to Ariejan.net (and the next release of the iAriejan theme). Sometimes there are lots of comments and it&amp;rsquo;s nice for visitors to see what the official reaction of the blog author is.&lt;/p>
&lt;p>Since I haven&amp;rsquo;t really looked into a plugin or anything, this is just a very simple theme hack.&lt;/p>
&lt;p>You can apply it to your current theme with almost no effort at all.&lt;/p>
&lt;p>Open up your comments.php file in your themes directory. And look for the following code:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-html+php" data-lang="html+php">&amp;lt;li class=&amp;#34;&amp;lt;?php echo $oddcomment; ?&amp;gt;&amp;#34; id=&amp;#34;comment-&amp;lt; ?php comment_ID() ?&amp;gt;&amp;#34; id=&amp;#34;comment-&amp;lt; ?php comment_ID() ?&amp;gt;&amp;#34;&amp;gt;
&lt;/code>&lt;/pre>&lt;p>and replace it with&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-html+php" data-lang="html+php">&amp;lt;li class=&amp;#34;&amp;lt;?php if ( $comment-&amp;gt;comment_author_email == get_the_author_email() ) echo &amp;#39;authorcomment&amp;#39;; else echo $oddcomment; ?&amp;gt;&amp;#34; id=&amp;#34;comment-&amp;lt; ?php comment_ID() ?&amp;gt;&amp;#34; id=&amp;#34;comment-&amp;lt; ?php comment_ID() ?&amp;gt;&amp;#34;&amp;gt;
&lt;/code>&lt;/pre>&lt;p>What this will do is match the e-mail address of the poster with the e-mail address of the post author. This is in some way spoofable, as users may be able to post a comment with your e-mail address on it.&lt;/p>
&lt;p>If you posted the comment an extra CSS class named &amp;lsquo;authorcomment&amp;rsquo; is added. So add the following to your style.css file. (You may change this to suit your own taste of course):&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-css" data-lang="css">&lt;span class="line">&lt;span class="cl">&lt;span class="p">.&lt;/span>&lt;span class="nc">authorcomment&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">background-color&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mh">#363636&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">border&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="kt">px&lt;/span> &lt;span class="kc">solid&lt;/span> &lt;span class="mh">#969696&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>To prevent this you can add your e-mail address (the one you use with your WP account) to Options -&amp;gt; Discussion -&amp;gt; Comment Moderation. This will keep any post that contains your email address back for moderation by you. This is the only fool-proof method I know right now to keep people from spoofing. There might be some other hacks for this, but I haven&amp;rsquo;t had time to think about that yet.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2006/11/22/wordpress-author-comment-highlighting/</guid><pubDate>Wed, 22 Nov 2006 00:00:00 +0000</pubDate></item><item><title>CSE-Tool 1.1.0 Released</title><link>https://www.devroom.io/2006/11/21/cse-tool-110-released/</link><description>&lt;p>Hot of the press! CSE-Tool 1.1.0 has been released just a few minutes ago! &lt;a href="http://sourceforge.net/project/showfiles.php?group_id=182622&amp;package_id=211849&amp;release_id=465386">Grab the code now&lt;/a> or check a &lt;a href="http://www.search-london.net/">live demonstration&lt;/a>!&lt;/p>
&lt;p>Please report any feature, support or bug requests back at the project&amp;rsquo;s &lt;a href="http://sourceforge.net/projects/cse-tool/">SourceForge page&lt;/a>.&lt;/p>
&lt;p>Also, if you like CSE-Tool (and want your feature added sooner rather than later) please consider making a small donation to the project to encourage further development and support.&lt;/p>
&lt;p>Donations are handled by &lt;a href="http://www.sourceforge.net">SourceForge&lt;/a>.&lt;/p>
&lt;p>&lt;a href="http://sourceforge.net/project/project_donations.php?group_id=182622">&lt;strong>Please consider making a donation to the project. Thank you!&lt;/strong>&lt;/a>&lt;/p>
&lt;p>To see how I manage a the creation of a release through SubVersion, check &lt;a href="http://ariejan.net/2006/11/21/svn-how-to-release-software-properly/">this post over here&lt;/a>.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2006/11/21/cse-tool-110-released/</guid><pubDate>Tue, 21 Nov 2006 00:00:00 +0000</pubDate></item><item><title>SVN: How to release software properly</title><link>https://www.devroom.io/2006/11/21/svn-how-to-release-software-properly/</link><description>&lt;p>Many projects use SubVersion nowadays to store their project code. I do this also at work, and for my personal projects like &lt;a href="http://ariejan.net/tags/cse-tool/">CSE-Tool&lt;/a>.&lt;/p>
&lt;p>The question, however, is how to release your current code properly to the public. You probably don&amp;rsquo;t want your users to check out your current development code. Either you want them to check out a certain version (release) or you want to present them with a download archive containing the code.&lt;/p>
&lt;p>I&amp;rsquo;m going to show you how to release a simple PHP application from SubVersion as an archive file to my users.&lt;/p>
&lt;p>The base layout of my svn repository is like this. I have directory named &amp;rsquo;trunk&amp;rsquo; that always contains the most recent version of the software. This is the development branch, so to say. I also have a &amp;lsquo;branches&amp;rsquo; and a &amp;rsquo;tags&amp;rsquo; directory. If you don&amp;rsquo;t have these, you&amp;rsquo;ll need to create them now:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">$ svn mkdir -m &lt;span class="s2">&amp;#34;Creating branches directory&amp;#34;&lt;/span> svn://yourrepository/branches
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Commited revision 123.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ svn mkdir -m &lt;span class="s2">&amp;#34;Creating tags directory&amp;#34;&lt;/span> svn://yourrepository/tags
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Commited revision 124.
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>In this case my current development code, in the trunk of the svn repository is at revision 10. All files in the trunk are marked to be develoment quality. This means that I don&amp;rsquo;t display version numbers, but simply show &amp;lsquo;HEAD&amp;rsquo; to indicate you&amp;rsquo;re working with a development quality product. Before I release this code to the public, I want to tweak a few things. Since this is not general development, I create a &lt;strong>Release Branch&lt;/strong>. This release branch is basically a copy of the current code in the trunk. Changes to that branch are stored seperately from the development code, so I can easily tweak it to release quality.&lt;/p>
&lt;p>Creating the release branch is really easy. Let&amp;rsquo;s say I want to release version 1.1.0 of CSE-Tool. I just copy the code in subversion. Note that I don&amp;rsquo;t download and store the code on my local machine. SubVersion is smart and duplicates the code on the server. This can save you valueable bandwidth, especially when you&amp;rsquo;re working on a large project.&lt;/p>
&lt;p>Well, create the Release Branch which is named, by convention, RB-1.1.0.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">$ svn copy -m &lt;span class="s2">&amp;#34;Creating release branch 1.1.0&amp;#34;&lt;/span> https://svn.sourceforge.net/svnroot/cse-tool/trunk https://svn.sourceforge.net/svnroot/cse-tool/branches/RB-1.1.0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Committed revision 11.
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>You can see at &lt;a href="http://cse-tool.svn.sourceforge.net/viewvc/cse-tool/branches/">&lt;a href="http://cse-tool.svn.sourceforge.net/viewvc/cse-tool/branches/">http://cse-tool.svn.sourceforge.net/viewvc/cse-tool/branches/&lt;/a>&lt;/a> that the new release branch (RB-1.1.0) was created as a directory containing a copy of the current develoment code.&lt;/p>
&lt;p>I can now do two things. Either I checkout a seperate working copy of the release branch or I change my current working copy (which is using the trunk) to use the release branch. Checking out is easy, but I&amp;rsquo;ll show it first to you anyway. Next I&amp;rsquo;ll show you how to switch your repository.&lt;/p>
&lt;p>Just check out your code as you&amp;rsquo;d normally do, but make sure you specify the release branch. I&amp;rsquo;ve also specified to store this code in a directory named cse-tool-1.1.0 so I don&amp;rsquo;t confuse it with the trunk code, which is stored in a directory named &amp;lsquo;cse-tool&amp;rsquo;.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">$ svn co https://svn.sourceforge.net/svnroot/cse-tool/branches/RB-1.0.0 cse-tool-1.1.0
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>I could also swich my current working copy to the release branch. This may be useful if your project is very huge and you don&amp;rsquo;t want to download the whole thing again. Switching between a release branch and the trunk is usually more efficient because you only need to download the differences between the two.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">$ svn switch https://svn.sourceforge.net/svnroot/cse-tool/branches/RB-1.0.0
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Okay, now I can work on the release branch. Branding it with the right version number among other things. In your case it might be a good place to two SQL files to install or update a database you are using. You might want to update the changelog and other documentation.&lt;/p>
&lt;p>When you commit changes, they will be applied to the release branch only, &lt;strong>not&lt;/strong> not to the current development code.&lt;/p>
&lt;p>When you&amp;rsquo;re done you may switch back to the current development code:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">$ svn switch https://svn.sourceforge.net/svnroot/cse-tool/trunk
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The code in the release branch is now ready to be shipped out. We want to mark this code as being Relese 1.1.0. This is called tagging. A tag is nothing more than a copy of the repository on a give moment. Technically, a branch and tag are the same. However, the conventions I use dictate that you don&amp;rsquo;t change the code in a tag because it represents a certain state of your code, in this case the state the code was in at the time of Release 1.1.0.&lt;/p>
&lt;p>Now, to actually create a release tag, named REL-1.1.0, we use the same procedure as with the creation of the release branch. Just note the differences in the source and destination repositories.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">$ svn copy -m &lt;span class="s2">&amp;#34;Tag release 1.1.0&amp;#34;&lt;/span> https://svn.sourceforge.net/svnroot/cse-tool/branches/RB-1.1.0 https://svn.sourceforge.net/svnroot/cse-tool/tags/REL-1.1.0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Committed revision 13.
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>With the REL-1.1.0 tag we can create an archive that we can distribute to our users. Because we don&amp;rsquo;t want to include svn metadata in our release we can&amp;rsquo;t use checkout for this. SubVersion allows us to export our code, which is basically a check out, but without all the svn metadata. This is ideal to ship to our customers.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">$ svn &lt;span class="nb">export&lt;/span> https://svn.sourceforge.net/svnroot/cse-tool/tags/REL-1.1.0 cse-tool-1.1.0
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Next I can tar up the cse-tool-1.1.0 directory and put the files on SourceForge. (&lt;a href="http://sourceforge.net/project/showfiles.php?group_id=182622&amp;package_id=211849">Download them here :)&lt;/a>)&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2006/11/21/svn-how-to-release-software-properly/</guid><pubDate>Tue, 21 Nov 2006 00:00:00 +0000</pubDate></item><item><title>Announcing CSE-Tool: Deploy you Google CSE with ease</title><link>https://www.devroom.io/2006/11/17/announcing-cse-tool-deploy-you-google-cse-with-ease/</link><description>&lt;p>This is just a short note to let you know that &lt;a href="http://www.sourceforge.net/projects/cse-tool/">CSE-Tool 1.0&lt;/a> has been released.&lt;/p>
&lt;p>The CSE-Tool allows you to easily (just copy-paste a few items presented to you by Google) deploy your Custom Search Engine. All you need is a CSE (of course) and optionally a Google Analytics account to track who&amp;rsquo;s coming by.&lt;/p>
&lt;p>System requirements are pretty low. You&amp;rsquo;ll only need PHP 4.3.x or better. Check out the &lt;a href="http://www.sourceforge.netl/projects/cse-tool/">SourceForge Project Page&lt;/a> or jump directly to the &lt;a href="http://sourceforge.net/project/showfiles.php?group_id=182622&amp;package_id=211849&amp;release_id=464420">downloads section&lt;/a>.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2006/11/17/announcing-cse-tool-deploy-you-google-cse-with-ease/</guid><pubDate>Fri, 17 Nov 2006 00:00:00 +0000</pubDate></item><item><title>Ubuntu 6.10 Live DVD on the Apple MacBook</title><link>https://www.devroom.io/2006/11/15/ubuntu-610-live-dvd-on-the-apple-macbook/</link><description>&lt;p>Since I teach various Linux courses at Fontys Centrum IT, I want to run a live CD or DVD on my MacBook. First off, here are my specs:&lt;/p>
&lt;ul>
&lt;li>Apple MacBook (White)&lt;/li>
&lt;li>Intel Core Duo 2.0Ghz&lt;/li>
&lt;li>1.0Gb RAM&lt;/li>
&lt;li>13.3" 1280x800 TFT&lt;/li>
&lt;li>60 Gb Internal Harddisk&lt;/li>
&lt;li>300 Gb External FireWire harddisk&lt;/li>
&lt;/ul>
&lt;p>As my Live medium I chose the Ubuntu Linux 6.10 Live DVD. This DVD has several nice options (install server, for example) that I like. It also can boot up in a live desktop environment.
~
As a matter of fact, everything I need works out of the box. Wireless works fine, my mouse (USB Logitech) works, the FireWire harddisk (which has a FAT32 filesystem, so I can hook it to my girl friend&amp;rsquo;s Windows PC) works perfectly.&lt;/p>
&lt;p>There are only two things that didn&amp;rsquo;t work.&lt;/p>
&lt;ol>
&lt;li>
&lt;p>Xvid codecs are missing, so I can&amp;rsquo;t watch my Prison Break episodes from the Live environment, but that&amp;rsquo;s perfectly fine. That&amp;rsquo;s not what I need the live environment for.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>The screen resolution can only be set to a maximum of 1024x768. As I mentioned before, the TFT has a native resolution of 1280x800. Because of the scaling, my screen doesn&amp;rsquo;t look as sharp and sexy as it should.&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>In order to solve this problem I had to take a few, rather easy, steps.&lt;/p>
&lt;p>To get started, let us assign a password to the default ubuntu user.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">sudo passwd ubuntu
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now enter something that you&amp;rsquo;ll remember easily, twice.&lt;/p>
&lt;p>In order to get Ubuntu to recognize the native screen resolution automatically, we&amp;rsquo;ll need a special package: 915resolution. Since the MacBook has an Intel 945 graphics chipset, we can use this tool. But, this tool is not in the default package respository. It&amp;rsquo;s in the Universe.&lt;/p>
&lt;p>So, we now need to change &lt;strong>/etc/apt/sources.list&lt;/strong> and add the universe repository. This is rather easy, because these repositories already exist, but are commented out. Just open up /etc/apt/sources.list and uncomment the two universe lines. Make sure your sources.list looks like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">deb http://archive.ubuntu.com/ubuntu edgy main restricted
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">deb-src http://archive.ubuntu.com/ubuntu edgy main restricted
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">## Uncomment the following two lines to add software from the &amp;#39;universe&amp;#39;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">## repository.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">## N.B. software from this repository is ENTIRELY UNSUPPORTED by the Ubuntu
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">## team, and may not be under a free licence. Please satisfy yourself as to
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">## your rights to use the software. Also, please note that software in
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">## universe WILL NOT receive any review or updates from the Ubuntu security
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">## team.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">deb http://archive.ubuntu.com/ubuntu edgy universe
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">deb-src http://archive.ubuntu.com/ubuntu edgy universe
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">deb http://security.ubuntu.com/ubuntu edgy-security main restricted
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">deb-src http://security.ubuntu.com/ubuntu edgy-security main restricted
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now, we can update our system and install the 915resolution package.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">apt-get update
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">apt-get install 915resolution
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>You&amp;rsquo;ll notice that 915resolution spits out a lot of information on your chipset and native resolution. You should check here that the 1280x800 resolution has been detected.&lt;/p>
&lt;p>Now you&amp;rsquo;re all set. Well, you&amp;rsquo;ll need to restart the X Server in order for it to recognize the newly discovered resolution. I basically pressed CTRL-ALT-BACKSPACE to kick the X Server. I&amp;rsquo;m presented with a login screen. Here I login as &amp;lsquo;ubuntu&amp;rsquo; with the password I specified earlier.&lt;/p>
&lt;p>If everything went fine, you&amp;rsquo;ll now get a clear and sharp display of your Ubuntu life desktop.&lt;/p>
&lt;p>I also turned on sub-pixel smoothing in System -&amp;gt; Preferences -&amp;gt; Fonts to get some sexier and smoother looking fonts.&lt;/p>
&lt;p>That&amp;rsquo;s all. I can now boot-up the Ubuntu Live DVD and get the native resolution for my MacBook.&lt;/p>
&lt;p>For those who want to keep the native resolution on their installed Ubuntu, just install the 915resolution package mentioned above and you&amp;rsquo;re set. Easy as that.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2006/11/15/ubuntu-610-live-dvd-on-the-apple-macbook/</guid><pubDate>Wed, 15 Nov 2006 00:00:00 +0000</pubDate></item><item><title>CUPS: 426 - Upgrade Required</title><link>https://www.devroom.io/2006/11/13/cups-426-upgrade-required/</link><description>&lt;p>As I was installing my printer on my Ubuntu 6.06 Dapper LTS server with CUPS I noticed the following error:&lt;/p>
&lt;blockquote>
&lt;p>426 Upgrade Required&lt;/p>
&lt;/blockquote>
&lt;p>After some research I came to the conclusion that CUPS, by default, tries to use SSL whenever possible. So, with this 426 error, you are redirected to the SSL domain. Chances are, you haven&amp;rsquo;t configured SSL properly, if at all.&lt;/p>
&lt;p>In my case, I didn&amp;rsquo;t want to configure SSL. To get rid of this problem, the key lies in editing your configuration files ( /etc/cups/cupsd.conf ) and adding the following line:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">DefaultEncryption Never
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>There are several options, Never, IfRequired and Required. By setting this to Never, SSL will never be enforced. Just restart your CUPS server with&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">/etc/init.d/cupsys restart
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>and you&amp;rsquo;re good to go.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2006/11/13/cups-426-upgrade-required/</guid><pubDate>Mon, 13 Nov 2006 00:00:00 +0000</pubDate></item><item><title>WordpressMu: Don’t allow new blogs</title><link>https://www.devroom.io/2006/10/31/wordpressmu-dont-allow-new-blogs/</link><description>&lt;p>If you&amp;rsquo;re using &lt;a href="http://mu.wordpress.org">WordpressMu&lt;/a>, the blog hosting tool used on &lt;a href="http://www.wordpress.com">Wordpress.com&lt;/a>, you may want to disable the creation of blogs by your visitors.&lt;/p>
&lt;p>Whatever your reasons for this are, I wanted to prevent this, because I (and my team of editors) want to maintain several blogs on different topics. Users are free to register and post comments, but creating new blogs is reserved for the administrator.&lt;/p>
&lt;p>So, how do you implement this in WordpressMu? There is no checkbox (yet) that disables this feature. So, I had to hack the WordpressMu code a bit.&lt;/p>
&lt;p>First, open up wp-signup.php. If you access a blog that does not exist, you&amp;rsquo;ll be redirected to the signup page and be presented a signup form for that particular blog.&lt;/p>
&lt;p>Open up wp-singup.php en just above the get_header(); call, place the following code:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="o">!&lt;/span>&lt;span class="nx">is_user_logged_in&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="nv">$user_identity&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="s1">&amp;#39;admin&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">header&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Location: http://example.com/gofishatthispage/&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">exit&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>What this does is make sure that only a logged in user named &amp;lsquo;admin&amp;rsquo; is allowed to proceed to the blog creation form. Others will be redirected to a location of your choice. A good idea is to send people to a page that explains why they can&amp;rsquo;t&amp;rsquo; create a blog or what they have to do to get an administrator to create one for them.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2006/10/31/wordpressmu-dont-allow-new-blogs/</guid><pubDate>Tue, 31 Oct 2006 00:00:00 +0000</pubDate></item><item><title>Adsense Resource Inventory</title><link>https://www.devroom.io/2006/10/30/adsense-resource-inventory/</link><description>&lt;p>In addition to my &lt;a href="http://ariejan.net/2006/10/29/do-your-ads-pay-your-blogging-bills/">previous post&lt;/a> I have gathered some popular resources from around the web that talk about making money off &lt;a href="http://www.google.com/adsense">Google Adsense&lt;/a>. I hope this sums up all you need to start making money yourself.&lt;/p>
&lt;p>Please, let me know if I missed something.&lt;/p>
&lt;h3>Things to know about making money with Google Adsense&lt;/h3>
&lt;p>There are three things you need in order to make money off your site or blog.&lt;/p>
&lt;ul>
&lt;li>Visitors&lt;/li>
&lt;li>Content&lt;/li>
&lt;li>Ads&lt;/li>
&lt;/ul>
&lt;p>This post is about the last part, Ads. I&amp;rsquo;ll talk about getting visitors and creating content in a later post, so stay tuned!&lt;/p>
&lt;p>So, how to get those ads setup right so you generate the most revenue possible. It&amp;rsquo;s not as easy as it sounds and mostly it&amp;rsquo;s a trial-and-error process. But, there are some general guidelines that will help you get started.&lt;/p>
&lt;p>&lt;em>I highly recommend you do &lt;strong>NOT&lt;/strong> buy any software that claims to track Google Ads or create content or sites for you! If you need to track your ads, use Google Analytics, it&amp;rsquo;s free. Content and site generators give you the same site and same content as others. Google knows all and they don&amp;rsquo;t like copy-cats whos only goal is to make money.&lt;/em>&lt;/p>
&lt;ol>
&lt;li>&lt;a href="http://www.quickonlinetips.com/archives/2006/07/google-adsense-tips-and-tools-collection/">Google Adsense Tips and Tools Collection&lt;/a>&lt;br />This should be in your bookmarks! Great tips, tools and free software that will get you started immediately!&lt;/li>
&lt;li>&lt;a href="http://www.adsensecalculator.com/">Adsense Calculator&lt;/a>&lt;br />This nifty device tries to predict how much money you can make of your site according to your current statistics. &lt;em>Let me know if this actually works or not!&lt;/em>&lt;/li>
&lt;li>&lt;a href="http://www.associateprograms.com/articles/129/1/How-to-boost-your-AdSense-revenue">How to boost your Adsense revenue&lt;/a>&lt;br />Great article that introduces new users to Adsense and gives tips on how to get started. See my remark about buying software, though.&lt;/li>
&lt;li>&lt;a href="http://adsensemaster.blogspot.com/2005/11/15-common-mistakes-by-google-adsense.html">15 Common mistakes&lt;/a>&lt;br />15 Common mistakes made by Adsense publishers. Read this, and don't make the same mistake.&lt;/li>
&lt;li>&lt;a href="http://www.seobook.com/">SEOBook&lt;/a>&lt;br />Great site. It contains many links and resources to Adsense related articles.&lt;/li>
&lt;li>&lt;a href="http://www.wolf-howl.com/22/google-adsense-tips-tricks-and-secrets/">More Adsense Tips and Secrets&lt;/a>&lt;br />Another site with great tips and Adsense secrets.&lt;/li>
&lt;li>&lt;a href="http://wisdom-of-adsense.org/?q=node/13">Adsense Wisdom&lt;/a>&lt;br />10 Easy steps in making your Adsense experience successful.&lt;/li>
&lt;li>&lt;a href="http://explorerdestroyer.com/">Explorer Destroyer&lt;/a>&lt;br />This site has some code that will kick Internet Explorer users to use Firefox instead. Oh, you use Google referrals for that.&lt;/li>
&lt;li>&lt;a href="http://adsense.blogspot.com/">Inside Adsense&lt;/a>&lt;br />The official AdSense blog from Google.&lt;/li>
&lt;/ol>
&lt;p>Please post a comment if I have missed anything important.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2006/10/30/adsense-resource-inventory/</guid><pubDate>Mon, 30 Oct 2006 00:00:00 +0000</pubDate></item><item><title>Cheat sheets? Look here!</title><link>https://www.devroom.io/2006/10/30/cheat-sheets-look-here/</link><description>&lt;p>Getting confused of all the tools you use on a daily basis? Don&amp;rsquo;t remember all the exact method names and possibilities of SubVersion, Apache, HTML, CSS, MySQL, Ruby on Rails, CVS, AJAX, JavaScript, FireFox, Google, etc. etc.? There is a solution! Cheat sheets make your daily work easier by providing you all you need to know on a single paper!&lt;/p>
&lt;p>Here&amp;rsquo;s a &lt;a href="http://www.smashingmagazine.com/2006/10/30/cheat-sheet-round-up-ajax-css-latex-ruby/">very comprehensive list&lt;/a> of cheat sheets.&lt;/p>
&lt;p>Cheat sheets are simple put a collection of all important (if not just all) functions and methods you can use for a given product, framework or language. They&amp;rsquo;re great to have on your desk if you just can&amp;rsquo;t quite remember what function to use. Check &amp;rsquo;em out, print &amp;rsquo;em out and start loving &amp;rsquo;em!&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2006/10/30/cheat-sheets-look-here/</guid><pubDate>Mon, 30 Oct 2006 00:00:00 +0000</pubDate></item><item><title>Confused about CSS Columns?</title><link>https://www.devroom.io/2006/10/30/confused-about-css-columns/</link><description>&lt;p>I&amp;rsquo;m mainly confused on how to make different layouts with pure CSS. Back in the time when tables were okay, it was rather easy. But with CSS it&amp;rsquo;s gotten rather tricky.&lt;/p>
&lt;p>&lt;a href="http://www.dynamicdrive.com/style/layouts/">Dynamic Drive&lt;/a> has some great examples (with CSS code!) of different kinds of layouts. It&amp;rsquo;s great to use as a starting point for your design!&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2006/10/30/confused-about-css-columns/</guid><pubDate>Mon, 30 Oct 2006 00:00:00 +0000</pubDate></item><item><title>How does your site look on …?</title><link>https://www.devroom.io/2006/10/30/how-does-your-site-look-on/</link><description>&lt;p>When designing a web site you always check how the site looks in different browsers, even maybe at different resolutions. But how many browsers do you really use? Firefox, Safari when on Mac, Internet Explorer when on Windows and that&amp;rsquo;s mostly it.&lt;/p>
&lt;p>&lt;a href="http://browsershots.org/">Browser Shots&lt;/a> allows you to select different browsers and set several options like colour depth, screen resolution, java etc. and make screenshots of your site (by entering the URL).&lt;/p>
&lt;p>I don&amp;rsquo;t need to tell you this is a great resource. It&amp;rsquo;s great to check if your design works and it&amp;rsquo;s even better to get a screenshot generated from your new designs. Only problem is that you&amp;rsquo;ll have to put them online first.&lt;/p>
&lt;p>Check out some screenshots &lt;a href="http://browsershots.org/screenshots/">here&lt;/a>.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2006/10/30/how-does-your-site-look-on/</guid><pubDate>Mon, 30 Oct 2006 00:00:00 +0000</pubDate></item><item><title>Do your ads pay your (blogging) bills?</title><link>https://www.devroom.io/2006/10/29/do-your-ads-pay-your-blogging-bills/</link><description>&lt;p>I&amp;rsquo;ve read many articles on earning money with &lt;a href="http://www.google.com/adsense">Google&amp;rsquo;s AdSense&lt;/a>. Some guru&amp;rsquo;s claim to recieve five figure checks from Google every month.&lt;/p>
&lt;p>The trick with these people is that they have a lot of content. More content, means more visitors, which means more clicks and thus more money. The content is written to attract certain keywords which are known earn a lot of money. Of course, professionals don&amp;rsquo;t just have one site, they have several. In order to cope with all this content they have an almost full-time job. That&amp;rsquo;s not bad if you earn a five figure amount every month.
~
But, how about us, regular bloggers who just want to share our bit of knowledge with the rest of the world. Most of us have Google Ads on our site, including me. My site has been refurbished quite a few times in the past few months and the content I had &lt;a href="http://ariejan.net/2006/10/09/welcome-to-ariejannet/">is gone&lt;/a>. So, I&amp;rsquo;m not really hot in this business.&lt;/p>
&lt;p>Most bloggers post a few items every few days. Some of us are really cool and post several items a day. What I want to know is: &lt;strong>can Google Ads pay your (blogging) bills?&lt;/strong>&lt;/p>
&lt;p>I mean, is there really anyone, except the kick-ass professional bloggers, who can make a bit of money of their blog? And if so, do they have any tips they&amp;rsquo;d like to share with the world?&lt;/p>
&lt;p>Please let me know or post any relevant articles here. I&amp;rsquo;m looking forward to your reponse!&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2006/10/29/do-your-ads-pay-your-blogging-bills/</guid><pubDate>Sun, 29 Oct 2006 00:00:00 +0000</pubDate></item><item><title>Ruby On Rails for PHP: CakePHP</title><link>https://www.devroom.io/2006/10/23/ruby-on-rails-for-php-cakephp/</link><description>&lt;p>The framework has been around for some time, but I found out about it a few days ago: &lt;a href="http://www.cakephp.org">CakePHP&lt;/a>.&lt;/p>
&lt;p>I&amp;rsquo;ve been using &lt;a href="http://www.rubyonrails.com">Ruby on Rails&lt;/a> for quite some time now. It&amp;rsquo;s a very cool framework and it&amp;rsquo;s fun to work with. However, there&amp;rsquo;s one big problem I have with it: I can&amp;rsquo;t host it anywhere!&lt;/p>
&lt;p>Well, there are several hosting companies that offer Rails hosting, but the price is pretty steep when compared to my current hosting plan. I could get a VPS or dedicated server, but that just be overkill for the applications I want to run.&lt;/p>
&lt;p>So, I started thinking. What can be done in Ruby could be done in PHP as well. Maybe not in such an elegant and intuitive way, but it should be possible. And I was right.
~
After some googling I came across &lt;a href="http://www.cakephp.org">CakePHP&lt;/a>. CakePHP aims to implement the MVC (Model-View-Controller) structure in PHP much in the same way Rails does it for Ruby.&lt;/p>
&lt;p>After giving it a try I found that CakePHP is a pretty good alternative to Rails. Most importantly, it runs with both PHP4 and PHP5 and it can handle MySQL, SQLite and other standard databases.&lt;/p>
&lt;p>If you&amp;rsquo;re an agile developer but you don&amp;rsquo;t want to nail yourself down on hosting with Ruby or if you really want to use PHP, CakePHP is a great framework to use!&lt;/p>
&lt;p>&lt;a href="http://www.cakephp.org">&lt;a href="http://www.cakephp.org">http://www.cakephp.org&lt;/a>&lt;/a>&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2006/10/23/ruby-on-rails-for-php-cakephp/</guid><pubDate>Mon, 23 Oct 2006 00:00:00 +0000</pubDate></item><item><title>Having fun with SPAM!</title><link>https://www.devroom.io/2006/10/13/having-fun-with-spam/</link><description>&lt;p>I was just wading through my SPAM at Gmail to see if it flagged anything important. Then I came across this message:&lt;/p>
&lt;p>&lt;strong>Is your website &lt;a href="https://www.gmail.com">www.gmail.com&lt;/a> offline, or why can&amp;rsquo;t I find it on Yahoo?&lt;/strong>&lt;/p>
&lt;blockquote>Your website (www.gmail.com) on top positions on Google, Yahoo and MSN search!
We will get your website (www.gmail.com) to the top positions on all major search engines.
&lt;p>Use our great value offer:
We will submit your website (&lt;a href="https://www.gmail.com">www.gmail.com&lt;/a>) to 890 Search Engines, including Google, Yahoo and MSN.&lt;/blockquote>&lt;/p>
&lt;p>Just keep an eye out for funny SPAM mails. Feel free to let me know if you ever read a funny or really stupid SPAM message.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2006/10/13/having-fun-with-spam/</guid><pubDate>Fri, 13 Oct 2006 00:00:00 +0000</pubDate></item><item><title>Migrate SQLite3 to MySQL easily</title><link>https://www.devroom.io/2006/10/13/migrate-sqlite3-to-mysql-easily/</link><description>&lt;p>I&amp;rsquo;ve been using a simple Rails application locally with a SQlite 3 database for some time. Now I want to move to another host and use MySQL instead. But guess what? You can&amp;rsquo;t just migrate your data!&lt;/p>
&lt;p>Here are some easy steps on how to migrate your data to MySQL. First of all you need to dump your SQLite3 database. This includes transaction statements and create commands. That&amp;rsquo;s fine. Since we also migrate the schema information, our RoR app will not know any difference after we change config/database.yml.&lt;/p>
&lt;p>The biggest probem I encoutered was that the SQLite3 dump places table names in double quotes, which MySQL won&amp;rsquo;t accept.&lt;/p>
&lt;p>First, make sure you create your MySQL database and create a user to access that database. Then run the following command. (It&amp;rsquo;s a long one, so where you see a , just continue on the same line.)&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">sqlite3 db/production.sqlite3 .dump &lt;span class="p">|&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span>grep -v &lt;span class="s2">&amp;#34;BEGIN TRANSACTION;&amp;#34;&lt;/span> &lt;span class="p">|&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span>grep -v &lt;span class="s2">&amp;#34;COMMIT;&amp;#34;&lt;/span> &lt;span class="p">|&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span>perl -pe &lt;span class="s1">&amp;#39;s/INSERT INTO \&amp;#34;(.*)\&amp;#34; VALUES/INSERT INTO `\1` VALUES/&amp;#39;&lt;/span> &lt;span class="p">|&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span>mysql -u YOURUSERNAME -p YOURPROJECT_production&lt;span class="o">[&lt;/span>/source&lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This will take the SQLite 3 dump, remote the transaction commands. Next I use perl to replace all INSERT commands containing double quotes with something MySQL will understand.&lt;/p>
&lt;p>That&amp;rsquo;s it. You MySQL database will be populated with your data.&lt;/p>
&lt;p>&lt;em>Don&amp;rsquo;t forget to change your config/database.yml file after this!&lt;/em>&lt;/p>
&lt;p>Note. You may also migrate your MySQL database using Rails. If you do this I recommend that you dump the SQLite3 database to a file first before you commit it directly to MySQL. You&amp;rsquo;ll have to remove the CREATE TABLE statements as well as any reference to the schema_info table.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2006/10/13/migrate-sqlite3-to-mysql-easily/</guid><pubDate>Fri, 13 Oct 2006 00:00:00 +0000</pubDate></item><item><title>Tagging in ajax_scaffold</title><link>https://www.devroom.io/2006/10/13/tagging-in-ajax_scaffold/</link><description>&lt;p>I&amp;rsquo;ve been using the &lt;a href="http://www.ajaxscaffold.com/">Ajax Scaffold&lt;/a> for quite some time now. It&amp;rsquo;s a great piece of software by &lt;a href="http://www.height1percent.com/">Mr. Richard White&lt;/a> for &lt;a href="http://www.rubyonrails.com">Ruby on Rails&lt;/a>. It seems that the plugin version of AS is getting quite a bit more attention than the generator. I started out with the generator but quickly reverted to the plugin since it&amp;rsquo;s way more flexible and easier to use.&lt;/p>
&lt;p>Since I wanted to create a quick app to inventory my CD/DVD collection (which is now in a very sexy alu DJ case) I used Ajax Scaffold to get me started. In the spirit of Web 2.0 I wanted to add tags to every CD so it would be easier to find certain kinds of disks later on. So, I added &lt;a href="http://wiki.rubyonrails.org/rails/pages/Acts+As+Taggable+Plugin">acts_as_taggable&lt;/a>.&lt;/p>
&lt;p>Acts_as_taggable basically allows you to tag any model in your app. So, I made my Disk model taggable. Great. Now I could do this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="n">d&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">Disk&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">new&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="ss">:number&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:name&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s2">&amp;#34;Mac OS X 10.4.6 Install DVD 1&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">d&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">tag_with&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;macoxs apple macbook install&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">d&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">save&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The real problem was, how to get this functionality easily integerated in Ajax Scaffold?&lt;/p>
&lt;p>First of all I had to show a column in AS that included the tags attached to a given disk. I specify all rows manually in the Manager controller. Manager is scaffolded using AS. Here&amp;rsquo;s what my Manager controller looks like:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">ManagerController&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="no">ApplicationController&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">ajax_scaffold&lt;/span> &lt;span class="ss">:disk&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="vc">@@scaffold_columns&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="no">AjaxScaffold&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="no">ScaffoldColumn&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">new&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="no">Disk&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="ss">:name&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s2">&amp;#34;number&amp;#34;&lt;/span> &lt;span class="p">}),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="no">AjaxScaffold&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="no">ScaffoldColumn&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">new&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="no">Disk&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="ss">:name&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s2">&amp;#34;name&amp;#34;&lt;/span> &lt;span class="p">}),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="no">AjaxScaffold&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="no">ScaffoldColumn&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">new&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="no">Disk&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="ss">:name&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s2">&amp;#34;tags&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="ss">:eval&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s2">&amp;#34;row.tag_list&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:sort&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s2">&amp;#34;tag_list&amp;#34;&lt;/span>&lt;span class="p">}),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This will show three columns, including a column named &amp;rsquo;tags&amp;rsquo;. Every model that acts_as_taggable has some extra methods. tag_list is a single string containing all tags seperated by spaces. So, the tags column shows the tag_list for that row. With :sort I specify that AS can just sort keywords alphabetically.&lt;/p>
&lt;p>Great! I now can see tags on disks! But, we also need to add those tags and that&amp;rsquo;s where it got tricky.&lt;/p>
&lt;h4>Adding tags&lt;/h4>
&lt;p>Adding tags is not done by assignment but by calling a method with your tags, as shown before: tag_with (string). I could create a custom create and update method for the Disks, but there&amp;rsquo;s a prettier solution available.&lt;/p>
&lt;p>&lt;code>tag_list&lt;/code> returns a string with the current tags. How about using that same name to assign tags? It&amp;rsquo;s rather easy. Here&amp;rsquo;s my &lt;code>Disk&lt;/code> model:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">Disk&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="no">ActiveRecord&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="no">Base&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">acts_as_taggable&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">validates_presence_of&lt;/span> &lt;span class="ss">:name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:number&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">tag_list&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">new_tags&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">tag_with&lt;/span> &lt;span class="n">new_tags&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now we can assign tags to tag_list as well as read the tags out. Now the only step is to add a special textfield to the form partial for AS.&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-erb" data-lang="erb">&amp;lt;label class=&amp;#34;required&amp;#34;&amp;gt;Tags&amp;lt;/label&amp;gt;
&amp;lt;%= text_field &amp;#39;disk&amp;#39;, &amp;#39;tag_list&amp;#39; %&amp;gt;
&lt;/code>&lt;/pre>&lt;p>Now when a new disk is created or when one is updated, the tag_list will automagically be updated in a correct fashion.&lt;/p>
&lt;p>Enjoy!&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2006/10/13/tagging-in-ajax_scaffold/</guid><pubDate>Fri, 13 Oct 2006 00:00:00 +0000</pubDate></item><item><title>Easily create a FavIcon online!</title><link>https://www.devroom.io/2006/10/10/easily-create-a-favicon-online/</link><description>&lt;p>You know those little icons you see in your browsers address bar? Yes, the ones in you bookmarks! Well, you want such an icon for your site?&lt;/p>
&lt;p>These icons are called FavIcons, short for Favourites Icon. Most modern browsers make use of them.&lt;/p>
&lt;p>To create such an icon you&amp;rsquo;ll need some image first. Normally the process would be rather difficult involving several graphics manipulation programs and conversions. Don&amp;rsquo;t fall for that! Use the followin &lt;a href="http://www.chami.com/html-kit/services/favicon/">FavIcon from Pics&lt;/a> site!&lt;/p>
&lt;p>Just upload a gif, jpeg or png image (with transparency if you like) and get a FavIcon for free! There are also some other goodies included!&lt;/p>
&lt;p>Of course, there&amp;rsquo;s information available on how to upload your FavIcon and how to let your browser know about it.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2006/10/10/easily-create-a-favicon-online/</guid><pubDate>Tue, 10 Oct 2006 00:00:00 +0000</pubDate></item><item><title>Generate a SQlite-based Rails app</title><link>https://www.devroom.io/2006/10/10/generate-a-sqlite-based-rails-app/</link><description>&lt;p>When you create a Rails application a database.yml files is included with some default configuration for your database. Unfortunately these are defaults for MySQL. If you want to use another database, like SQlite, you&amp;rsquo;d have to rewrite the entire configuration file. And that&amp;rsquo;s not what you want!&lt;/p>
&lt;p>Luckily, rails is very adapative and we can make it do all the work for us.&lt;/p>
&lt;p>When you create a rails application, firstr check out the help message from rails itself.&lt;/p>
&lt;p>&lt;code>$ rails &amp;ndash;help&lt;/code>&lt;/p>
&lt;p>When you look closely you&amp;rsquo;ll see that there&amp;rsquo;s an option available called &amp;lsquo;&amp;ndash;database&amp;rsquo;. It has the following message attached to it:&lt;/p>
&lt;blockquote>Preconfigure for selected database (options: mysql/oracle/postgresql/sqlite2/sqlite3).&lt;/blockquote>
&lt;p>Basically, you can specify, when create your rails project, what database type you want to use, and Rails&amp;rsquo;ll create a matching configuration file for you. When you leave this option out, it defaults to MySQL.&lt;/p>
&lt;p>As you can see there are several databases supported, including SQLite. In order to create a new rails project with SQlite3 configured by default, run this:&lt;/p>
&lt;p>&lt;code>$ rails &amp;ndash;database=sqlite3 myproject&lt;/code>&lt;/p>
&lt;p>Check out config/database.yml and you&amp;rsquo;ll see that there are sensible defaults for you there.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2006/10/10/generate-a-sqlite-based-rails-app/</guid><pubDate>Tue, 10 Oct 2006 00:00:00 +0000</pubDate></item><item><title>Hi-Res Wallpapers for your MacBook!</title><link>https://www.devroom.io/2006/10/10/hi-res-wallpapers-for-you-macbook/</link><description>&lt;p>For all of you who have a flashy Apple MacBook (or any other device with a 1280x800 resolution) here&amp;rsquo;s a nice site with lot&amp;rsquo;s of hi-res wallpapers. Of course, they&amp;rsquo;re all for free!&lt;/p>
&lt;p>Oh, they have other sizes as well.&lt;/p>
&lt;p>Go check out &lt;a href="http://interfacelift.com/wallpaper/index.php?sort=downloads&amp;w=1280&amp;h=800">InterfaceLIFT&lt;/a>.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2006/10/10/hi-res-wallpapers-for-you-macbook/</guid><pubDate>Tue, 10 Oct 2006 00:00:00 +0000</pubDate></item><item><title>Top 5 DVDs you must have</title><link>https://www.devroom.io/2006/10/10/top-5-dvds-you-must-have/</link><description>&lt;p>Everybody at this point in time at least owns one or more DVD - Digital Versatile Disk - Videos. Some people try to collect as many DVD&amp;rsquo;s as possible, others just buy the ones they like (or can afford).&lt;/p>
&lt;p>I&amp;rsquo;m not sure how many DVD&amp;rsquo;s I have at this time, but there are quite a few DVD&amp;rsquo;s that you just have to have in your collection.&lt;/p>
&lt;p>If you feel wronged because your favourite movie is not listed, feel free to add a comment stating your favourites. (Oh, let me know why it&amp;rsquo;s such a great movie too!)&lt;/p>
&lt;h3>5. Gladiator&lt;/h3>
&lt;p>&lt;img align="right" id="image11" src="http://ariejan.net/wp-content/uploads/2006/10/06m1.jpg" alt="Gladiator" />Gladiator, by Ridley Scott, is tells the story off a Roman general who became a slave and then a gladiator. It sounds pretty boring when you put it like that, but Scott really makes it into a very gripping movie.&lt;/p>
&lt;p>Gladiator is more than a few man in shorts fighting in a big bowl of sand. You should really check this one out and add it to your collection.&lt;/p>
&lt;h3>4. The Lord of the Rings Trilogy&lt;/h3>
&lt;p>&lt;img align="right" id="image10" src="http://ariejan.net/wp-content/uploads/2006/10/10m.jpg" alt="Lord of the Rings" />Well, &lt;a href="http://imdb.com/title/tt0120737/">The Lord of the Rings&lt;/a> is not really a trilogy since it is one big story. Still, this movie is great.&lt;/p>
&lt;p>LOTR, by &lt;a href="http://imdb.com/name/nm0001392/">Peter Jackson&lt;/a>, attracts old and young alike. It features humour, heroism, love, massive battles between good and evil and everything else needed to make a successful movie.&lt;/p>
&lt;p>Many complain that the Fellowship (part 1) does not have much action in it and that Return of the king (part 2) is just one big fat ending. Well, that&amp;rsquo;s correct. In my opinion you should watch the three movies in sequence all at once. That will give you the full sensation the way Jackson intended it.&lt;/p>
&lt;p>Because this movie appeals to a very broad audience, it&amp;rsquo;s placed at position 4.&lt;/p>
&lt;h3>3. Pulp Ficton&lt;/h3>
&lt;p>&lt;img id="image7" align="right" src="http://ariejan.net/wp-content/uploads/2006/10/06m.jpg" alt="Pulp Fiction" />The combining of several story lines into one movie with a lot of violence by &lt;a href="http://imdb.com/name/nm0000233/">Quentin Tarantino&lt;/a>. The result is &lt;a href="http://imdb.com/title/tt0110912/">Pulp Fiction&lt;/a>.&lt;/p>
&lt;p>Every story line has it&amp;rsquo;s own characters and Tarantino was able to get a lot of big names attached to this film including Bruce Willis, John Travolta, Samuel L. Jackson and Uma Thurman.&lt;/p>
&lt;p>This movie is not just entertainment, it&amp;rsquo;s art. If you don&amp;rsquo;t have it, you should probably get is a.s.a.p.&lt;/p>
&lt;h3>2. Star Wars (all six of 'em)&lt;/h3>
&lt;p>&lt;img id="image9" align="right" src="http://ariejan.net/wp-content/uploads/2006/10/42m.jpg" alt="Star Wars" />&lt;a href="http://imdb.com/title/tt0076759/">Star Wars&lt;/a> is the work of &lt;a href="http://imdb.com/name/nm0000184/">George Lucas&lt;/a>. George has done many innovative things during the creation of his Star Wars movies. Not only was he the first to introduce (and claim the rights to) merchandising, he has also brought special effects to a new level. Of course, I don&amp;rsquo;t have to remind you that George Lucas is also the genius behind the &lt;a href="http://en.wikipedia.org/wiki/Thx">THX&lt;/a> sound system.&lt;/p>
&lt;p>If I have to tell you what Star Wars is about, you should not be allowed to have a DVD collection at all.&lt;/p>
&lt;h3>1. The Godfather Trilogy&lt;/h3>
&lt;p>&lt;img id="image8" src="http://ariejan.net/wp-content/uploads/2006/10/20m.jpg" alt="The Godfather" align="right" />I think everybody knows the &lt;a href="http://imdb.com/title/tt0068646/">Godfather&lt;/a>. Directed by &lt;a href="http://imdb.com/name/nm0000338/">Francis Ford Coppola&lt;/a>, this epic trilogy gives us an inside look in the Corleone family.&lt;/p>
&lt;p>Part one is really the best movie of all three. It&amp;rsquo;s based on the original novel by &lt;a href="http://en.wikipedia.org/wiki/Mario_Puzo">Mario Puzo&lt;/a>. However, parts two and three really complement the whole story and that&amp;rsquo;s what makes this such a great trilogy.&lt;/p>
&lt;p>There are some very nice Trilogy DVD Boxes available. If you say you like movies, you cannot be taken seriously unless you have this trilogy in your collection.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2006/10/10/top-5-dvds-you-must-have/</guid><pubDate>Tue, 10 Oct 2006 00:00:00 +0000</pubDate></item><item><title>Welcome to Ariejan.net</title><link>https://www.devroom.io/2006/10/09/welcome-to-ariejannet/</link><description>&lt;p>Well, it has happened to me. Although I make regular backups of my site, this time I was screwed!&lt;/p>
&lt;p>Since I&amp;rsquo;m very interested in &lt;a href="http://rubyonrails.com">Ruby on Rails&lt;/a>, I gave a RoR weblog, &lt;a href="http://typosphere.org/">TypoSphere&lt;/a>, a try. I exported my entire &lt;a href="http://www.wordpress.org">Wordpress&lt;/a> blog to typo and I was a happy man. Until disaster struck&amp;hellip;&lt;/p>
&lt;p>After a while I noticed that typo&amp;rsquo;s search functionality was not working. I recieved a MySQL error messages that the table containing my artilces did not exists! To make a long story short, all database files containing data were gone. Luckily I make regular backups every day automatically.&lt;/p>
&lt;p>Well, I make regular backups and keep the seven most recent backup files at hand. But, you guessed it, apparently the disaster happened more than a week ago. So, all my backup files contained the corrupted database files. Bugger!&lt;/p>
&lt;p>So, after some flirting with &lt;a href="http://www.adobe.com/products/dreamweaver/">Dreamweaver&lt;/a>, &lt;a href="http://drupal.org">Drupal&lt;/a> and &lt;a href="http://joomla.org">Joomla&lt;/a>, I&amp;rsquo;m back at Wordpress which really is the best, easy to use and customize publishing tool I know.&lt;/p>
&lt;p>Since all my previous tutorials are gone, I&amp;rsquo;m going to start with a clean slate here. Please comment on my articles if you like and feel free to link anything you find here.&lt;/p>
&lt;p>For now, I&amp;rsquo;ll have to re-customize my site and then I&amp;rsquo;m ready to start posting useful things here.&lt;/p></description><author>map[bio:Jack of all Trades, Professional Software Craftsman email:ariejan@devroom.io headline:Welcome to my homepage. Under construction. Est. 1996 image:img/profile.jpg name:Ariejan de Vroom]</author><guid>https://www.devroom.io/2006/10/09/welcome-to-ariejannet/</guid><pubDate>Mon, 09 Oct 2006 00:00:00 +0000</pubDate></item></channel></rss>