<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/atom10full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" xml:lang="en-US">
  <id>tag:loo.no,2005:/posts</id>
  <link type="text/html" rel="alternate" href="http://loo.no" />
  
  <title>loo.no - blog posts</title>
  <updated>2010-11-21T16:06:56+00:00</updated>
  <atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/atom+xml" href="http://feeds.feedburner.com/loo" /><feedburner:info uri="loo" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/" /><entry>
    <id>tag:loo.no,2005:Post/4ce943a08155e36c1c000002</id>
    <published>2010-11-21T16:06:56+00:00</published>
    <updated>2010-11-21T17:16:29+00:00</updated>
    <link type="text/html" rel="alternate" href="http://feedproxy.google.com/~r/loo/~3/Zymbp5uYk2g/installing-a-ruby-dev-environment-on-osx" />
    <title>Installing a Ruby dev environment on OSX</title>
    <content type="html">&lt;p&gt;I recently bought a new Macbook Air 13&amp;quot; (which is übercool btw), and took my time to set up my development environment properly. Here&amp;#8217;s a recipe for setting up everything you need on a fresh &lt;span class="caps"&gt;OSX&lt;/span&gt; install, with some nice-to-have applications as well.&lt;/p&gt;
&lt;h3&gt;&lt;span class="caps"&gt;RVM&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;Although &lt;span class="caps"&gt;OSX&lt;/span&gt; comes with Ruby by default, you&amp;#8217;re probably going to need different versions of Ruby sooner or later. &lt;a href="http://rvm.beginrescueend.com/rvm/install/"&gt;&lt;span class="caps"&gt;RVM&lt;/span&gt;: Ruby Version Manager&lt;/a&gt; is a nice set of scripts, that enables you to switch between different versions of Ruby, including JRuby, &lt;span class="caps"&gt;REE&lt;/span&gt;, &lt;span class="caps"&gt;MRI&lt;/span&gt; and &lt;span class="caps"&gt;YARV&lt;/span&gt;. The basic installation is pretty straight forward, just run the following script and follow the post-installation instructions to run the required scripts upon opening a new bash shell.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;bash &amp;lt; &amp;lt;( curl http://rvm.beginrescueend.com/releases/rvm-install-head )&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After you&amp;#8217;ve installed &lt;span class="caps"&gt;RVM&lt;/span&gt;, and loaded it, I recommend installing &lt;span class="caps"&gt;YARV&lt;/span&gt; (1.9.2) and set that as your default Ruby:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rvm install 1.9.2-head &amp;amp;&amp;amp; rvm use 1.9.2 --default&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Homebrew&lt;/h3&gt;
&lt;p&gt;&lt;a href="http://mxcl.github.com/homebrew/"&gt;Homebrew&lt;/a&gt; is a simple, yet powerful, package manager for &lt;span class="caps"&gt;OSX&lt;/span&gt;. It&amp;#8217;ll allow you to easily install and manage different &lt;span class="caps"&gt;UNIX&lt;/span&gt; tools, pretty much like &lt;span class="caps"&gt;APT&lt;/span&gt; on Ubuntu. Installing Homebrew is pretty straight forward, except for the last step where you have to get the Apple developer tool, XCode.&lt;/p&gt;
&lt;p&gt;Run this script to get Homebrew:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ruby -e "$(curl -fsSL https://gist.github.com/raw/323731/install_homebrew.rb)"&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then go &lt;a href="http://developer.apple.com/technologies/xcode.html"&gt;download XCode&lt;/a&gt; from Apple. You&amp;#8217;ll have to make an account, and download quite a large installation file. Uncheck everything except the base during the installation, unless you want the libs for iOS development and such.&lt;/p&gt;
&lt;h3&gt;Git&lt;/h3&gt;
&lt;p&gt;&lt;a href="http://git-scm.com/"&gt;Git&lt;/a&gt; is a widely used &lt;span class="caps"&gt;VCS&lt;/span&gt; (Version Control System) amongst Rubyists, and you&amp;#8217;ll need that for working with code on &lt;a href="http://github.com"&gt;Github&lt;/a&gt;, the most popular host for open source Ruby projects. Since we&amp;#8217;ve installed Homebrew, getting Git up and running on your machine is pretty easy:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;brew install git&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To configure Git for all your projects, you can add rules into &lt;code&gt;~/.gitconfig&lt;/code&gt;, this is how my config file looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[user]
        name = Lars
        email = ovelar@gmail.com
[core]
        excludesfile = /Users/lars/.config/global-gitignore&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The last line references a global &lt;code&gt;gitignore&lt;/code&gt; file, where we&amp;#8217;ll add files we know that we never want to check into our repositories. A basic &lt;code&gt;gitignore&lt;/code&gt; file on &lt;span class="caps"&gt;OSX&lt;/span&gt; should atleast contain &lt;code&gt;.DS\_Store&lt;/code&gt;, and you&amp;#8217;ll probably add &lt;code&gt;*.log&lt;/code&gt; as well.&lt;/p&gt;
&lt;h3&gt;MySQL&lt;/h3&gt;
&lt;p&gt;Installing &lt;a href="http://dev.mysql.com/"&gt;MySQL&lt;/a&gt; with Homebrew is pretty straight forward as well:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;brew install mysql
mysql_install_db&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Post installation you will see a set of commands you can run to make MySQL start when you log in to your computer.&lt;/p&gt;
&lt;h3&gt;MongoDB&lt;/h3&gt;
&lt;p&gt;For my NoSQL adventures, I use &lt;a href="http://www.mongodb.org/"&gt;MongoDB&lt;/a&gt;. For my production setup, I use a hosted service from &lt;a href="http://mongohq.com"&gt;MongoHQ&lt;/a&gt;, but I would recommend setting up a local instance for development and running tests. Installing MongoDB with Homebrew is also as easy as MySQL:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;brew install mongodb&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You&amp;#8217;ll get the same output post installation, with two lines you have to run in order to have your MongoDB instance start up on login.&lt;/p&gt;
&lt;h3&gt;Editor&lt;/h3&gt;
&lt;p&gt;When it comes to editors, this is a matter of personal taste and a religious topic amongst programmers. When I got my first mac a year ago, I started using &lt;a href="http://macromates.com/"&gt;TextMate&lt;/a&gt;, which has been kind to me. Lately, I&amp;#8217;ve started using &lt;a href="http://macvim.org/"&gt;MacVim&lt;/a&gt;, mostly because I wanted to have the same environment on my mac and ubuntu machine. Others might prefer emacs or even &lt;a href="http://www.activestate.com/komodo-edit"&gt;KomodoEdit&lt;/a&gt;. Anyways, you can install MacVim easily with Homebrew: &lt;code&gt;brew install macvim&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;Nice to have utilitites&lt;/h3&gt;
&lt;ul&gt;
	&lt;li&gt;&lt;a href="http://visor.binaryage.com/"&gt;Visor&lt;/a&gt; allows you to have a roll-down terminal window on all your spaces.&lt;/li&gt;
	&lt;li&gt;&lt;a href="http://growl.info/"&gt;Growl&lt;/a&gt; gives you pop-up notifications on your desktop, that can be utilized by things like AutoTest&lt;/li&gt;
	&lt;li&gt;&lt;a href="http://www.google.com/url?sa=D&amp;amp;q=https%3A%2F%2Fgithub.com%2Fbrotherbard%2Fgitx%2Fdownloads"&gt;GitX&lt;/a&gt; is a nice &lt;span class="caps"&gt;GUI&lt;/span&gt; for basic git stuff.&lt;/li&gt;
	&lt;li&gt;&lt;a href="http://hyperdock.bahoom.de/"&gt;HyperDock&lt;/a&gt; enhances the default window manager in &lt;span class="caps"&gt;OSX&lt;/span&gt;&lt;/li&gt;
	&lt;li&gt;&lt;a href="http://www.google.com/url?sa=D&amp;amp;q=https%3A%2F%2Fgithub.com%2Fctshryock%2FGithubNotifier"&gt;GithubNotifier&lt;/a&gt; gives you notifications (via growl) on new commits on you Github projects&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Also, having a cool bash prompt is a must, this is what I use for my prompt which shows current ruby version (from &lt;span class="caps"&gt;RVM&lt;/span&gt;) and branch name if you&amp;#8217;re in a Git repo:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PS1='\[\033[0;32m\]\u@\h\[\033[1;30m\] ($(~/.rvm/bin/rvm-prompt i v g))\[\033[0m\]\[\033[0m\] \[\033[1;33m\]\w\[\033[0m\]\[\033[1;37m\]$(__git_ps1 "[%s]")$ '&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Happy hacking, and good luck with your new Mac :)&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/loo/~4/Zymbp5uYk2g" height="1" width="1"/&gt;</content>
  <feedburner:origLink>http://loo.no/posts/installing-a-ruby-dev-environment-on-osx</feedburner:origLink></entry>
  <entry>
    <id>tag:loo.no,2005:Post/4ca3ac3b8155e35482000002</id>
    <published>2010-09-29T21:14:35+00:00</published>
    <updated>2010-09-29T21:15:40+00:00</updated>
    <link type="text/html" rel="alternate" href="http://feedproxy.google.com/~r/loo/~3/EjSYhFthlkA/installing-ubuntu-10-10-on-a-sata-3-disk" />
    <title>Installing Ubuntu 10.10 on a SATA-3 disk </title>
    <content type="html">&lt;p&gt;First, I have to brag about the specs for this little monster (compared to the old Core 2 Duo rig it&amp;#8217;s replacing):&lt;/p&gt;
&lt;ul&gt;
	&lt;li&gt;GA-X58A-UD3R motherboard&lt;/li&gt;
	&lt;li&gt;24GB of Corsair DDR3 1333mhz CL9 ram&lt;/li&gt;
	&lt;li&gt;Intel i7 950&lt;/li&gt;
	&lt;li&gt;Crucial RealSSD C300 2,5&amp;quot; 128GB &lt;span class="caps"&gt;SATA&lt;/span&gt;-3 (6gb/s)&lt;/li&gt;
	&lt;li&gt;Silverstone Fortress II miditower&lt;/li&gt;
	&lt;li&gt;Corsair HX 750W &lt;span class="caps"&gt;PSU&lt;/span&gt;&lt;/li&gt;
	&lt;li&gt;2 x Western Digitial Caviar® Green 2TB SATA2&lt;/li&gt;
	&lt;li&gt;Nvidia &lt;span class="caps"&gt;GTX&lt;/span&gt; 295&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It&amp;#8217;s a bit over-specced for web-development, but it sure is fast when running the cucumber test suite on the project I&amp;#8217;m working on right now :)&lt;/p&gt;
&lt;p&gt;I always install Windows before Ubuntu on a new rig, because I&amp;#8217;m lazy enough to appreciate the automagic adding of the windows boot partition to grub. Installing windows on the &lt;span class="caps"&gt;SSD&lt;/span&gt; disk required it to be on a Sata-2 controller, and then switching to the Marvell 9128 chip once everything was installed.&lt;/p&gt;
&lt;p&gt;However, when running the Ubuntu 10.10 installer for the first time, it only came up with my two Sata-2 drives at the partitioning step. Turns out, Ubuntu needs &lt;span class="caps"&gt;AHCI&lt;/span&gt; enabled for the Sata-3 controller in order to correctly identify the disks on it. &lt;span class="caps"&gt;AHCI&lt;/span&gt;, or Advanced Host Controller Interface, is a add-on for the Sata spec, which enables hot-plugging and native command queuing, as far as I know.&lt;/p&gt;
&lt;p&gt;Enabling &lt;span class="caps"&gt;AHCI&lt;/span&gt; on the Marvell 9128 chip&amp;#8217;s port 6-7 in the bios worked, and enabled me to install Ubuntu 10.10 beta without any more hassle. All devices working smoothly, and everything felt snappy and nice. However, when trying to boot Windows 7 again, I was getting this annoying and infamous &lt;span class="caps"&gt;BSOD&lt;/span&gt; (Blue Screen of Death). I rolled up my sleeves, and got to googling.&lt;/p&gt;
&lt;p&gt;When you install Windows 7, which is the only windows version actually supporting &lt;span class="caps"&gt;AHCI&lt;/span&gt;, without any &lt;span class="caps"&gt;AHCI&lt;/span&gt; devices, Windows actually unloads the &lt;span class="caps"&gt;AHCI&lt;/span&gt; driver (which imo is a good thing). This resulted in my newly &lt;span class="caps"&gt;AHCI&lt;/span&gt; enabled system disk to be unreadable for Windows. Luckily, it&amp;#8217;s pretty easy to re-enable the &lt;span class="caps"&gt;AHCI&lt;/span&gt; driver in Windows 7:&lt;/p&gt;
&lt;ol&gt;
	&lt;li&gt;Shut down the machine, and turn off &lt;span class="caps"&gt;AHCI&lt;/span&gt;&lt;/li&gt;
	&lt;li&gt;Reboot into Windows 7 (Grub will still load)&lt;/li&gt;
	&lt;li&gt;Open regedit.exe, the equivalent of /etc/ on a *nix system&lt;/li&gt;
	&lt;li&gt;Navigate to HKEY_LOCAL_MACHINE/System/CurrentControlSet/Services/Msahci&lt;/li&gt;
	&lt;li&gt;Set it&amp;#8217;s value to 0, and close regedit.&lt;/li&gt;
	&lt;li&gt;Reboot and celebrate&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now, you should have both Windows 7 and Ubuntu working perfectly alongside each other &amp;#8211; and you can enjoy the awesomeness of having a Sata-3 system disk (Ubuntu uses 3 seconds (seriously) to boot on my rig).&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/loo/~4/EjSYhFthlkA" height="1" width="1"/&gt;</content>
  <feedburner:origLink>http://loo.no/posts/installing-ubuntu-10-10-on-a-sata-3-disk</feedburner:origLink></entry>
  <entry>
    <id>tag:loo.no,2005:Post/4c5ce6108155e369bf000002</id>
    <published>2010-08-07T04:50:24+00:00</published>
    <updated>2010-08-09T00:45:17+00:00</updated>
    <link type="text/html" rel="alternate" href="http://feedproxy.google.com/~r/loo/~3/ZMPBmBTEx9c/goodbye-google-wave" />
    <title>Goodbye Google Wave</title>
    <content type="html">&lt;p&gt;It&amp;#8217;s been over 12 months since &lt;a href="http://loo.no/2009/07/27/first-impressions-of-google-wave"&gt;I first tried Google Wave&lt;/a&gt;, and much has happened since that time. At first, it was compelling because Google promised they would &amp;#8220;revolutionize the way we communicate&amp;#8221;, and the open source nature of the protocol. In the first few months, I, as many others, struggled to find some good use-cases for this new platform, and stumbled around making &lt;a href="http://loo.no/2009/07/28/my-first-google-wave-robot-lobo-bot"&gt;silly robots&lt;/a&gt; but soon enough &lt;a href="http://loo.no/2009/10/14/practical-use-of-google-wave"&gt;we found some good use for it&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For the past 6-7 months, Google Wave has been the platform for most of our day to day needs at &lt;a href="http://skalar.no"&gt;Skalar&lt;/a&gt;. We&amp;#8217;ve used it for project management, &lt;span class="caps"&gt;CRM&lt;/span&gt;, code reviews, brainstorming, knowledge base, issue tracking and so on. The last couple of months, we&amp;#8217;ve even managed to get some of our customers and partners as active users.&lt;/p&gt;
&lt;h3&gt;What went wrong&lt;/h3&gt;
&lt;p&gt;Google Wave is brilliant in terms of communication and availability of the development team towards customers. It also provides the flexibility to be used (and abused) for almost anything you need &amp;#8211; with a little imagination and patience. However, herein lies most of the &amp;#8220;flaws&amp;#8221; being pointed out by the people who didn&amp;#8217;t embrace it.&lt;/p&gt;
&lt;p&gt;When the beta-access started to roll out, nobody quite knew what to do with it, and being busy with other things and satisfied with their current collection of tools, they didn&amp;#8217;t really give Wave a try. One of the commonly used arguments against Google Wave was that nobody of interest (customers, co-workers, average people (aka friends)) were there, and therefore it was no use to be there for you either. Instability and alpha versions of the web-client was probably the drop for those who never came back after their first visits.&lt;/p&gt;
&lt;p&gt;However, Wave has changed rapidly over the past 12 months, becoming a more stable and refined platform. People can now be invited to and participate in a Wave without signing up for it, just by adding an email adress as you would normally to when sharing information with people. The web-client has improved a lot by adding wave-templates for common tasks, richer control over participants (yes, you can even remove people and give read-only permissions now!), gadgets have become more useful, and there are people with experience you can consult if you&amp;#8217;re having trouble.&lt;/p&gt;
&lt;p&gt;Sitepoint has &lt;a href="http://www.sitepoint.com/blogs/2010/08/05/google-scraps-wave/"&gt;a poll&lt;/a&gt; running now where 45% of the votes are against the shutdown, but these are mainly geeks that see the benefits of scrapping some of the old technologies like mail, the average user probably still &lt;a href="http://twitter.com/frogandcode/status/20395080561"&gt;wonders what google wave is/was&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;No matter how much I, and 45% of sitepoint&amp;#8217;s readers disagree with their decision, Google has decided to &lt;a href="http://googleblog.blogspot.com/2010/08/update-on-google-wave.html"&gt;shut down wave&lt;/a&gt; early 2011. I still have a small hope that they will change their mind, but that&amp;#8217;s probably not going to happen.&lt;/p&gt;
&lt;h3&gt;Wave lives on&lt;/h3&gt;
&lt;p&gt;Although Google is abandoning Wave, it is fully open sourced (except for the web client), and will therefore live on to some extent. There are some server implementations like &lt;a href="http://github.com/p2k/pygowave"&gt;PygoWave&lt;/a&gt; and &lt;a href="http://github.com/danopia/ruby-on-sails"&gt;RubyOnSails&lt;/a&gt;. And a couple of client implementations like &lt;a href="http://wavelook.com"&gt;WaveLook&lt;/a&gt; and the open sourced &lt;a href="http://groups.google.com/group/wave-protocol/browse_thread/thread/fa5e530b29d9215f"&gt;simple client&lt;/a&gt; from Google.&lt;/p&gt;
&lt;p&gt;At their current state, none of these are production ready as far as I know, but hopefully they will get some momentum now that there&amp;#8217;s a actual need for them.&lt;/p&gt;
&lt;h3&gt;Alternatives to Wave&lt;/h3&gt;
&lt;p&gt;What I&amp;#8217;m looking for now, is a web-application that can cover all the stuff that Google Wave did. So far I&amp;#8217;ve been checking out a couple of candidates, but none of them quite seem to have the flexibility that Wave had. This situation is like being given a Ferrari to drive for 12 months, then having it taken away. I want a new Ferrari-like car, but I don&amp;#8217;t want to build it myself using bits and scraps from semi-good cars (lame analogy, I know, but you get the point).&lt;/p&gt;
&lt;p&gt;What&amp;#8217;s your take on the shutdown of Google Wave, and do you have some suggestions as to what application might be replacing it?&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/loo/~4/ZMPBmBTEx9c" height="1" width="1"/&gt;</content>
  <feedburner:origLink>http://loo.no/posts/goodbye-google-wave</feedburner:origLink></entry>
  <entry>
    <id>tag:loo.no,2005:Post/4c5c80fe8155e37011000002</id>
    <published>2010-08-06T21:39:10+00:00</published>
    <updated>2010-08-09T00:49:31+00:00</updated>
    <link type="text/html" rel="alternate" href="http://feedproxy.google.com/~r/loo/~3/WSDVhdKAIZY/new-design-new-backend-based-on-mongodb" />
    <title>New design, new backend based on MongoDB</title>
    <content type="html">&lt;p&gt;I&amp;#8217;ve remade the blog from scratch, again. This time I decided to not only give the front-end a overhaul, but also try out a NoSQL alternative to get some first-hand experience with it. While I&amp;#8217;m polishing it, some things might be a bit weird, but hopefully everything should be readable and working as before.&lt;/p&gt;
&lt;p&gt;I will do some articles later on some of the stuff I learned while coding this thing, but to put it short and sweet; I&amp;#8217;ve had a great experience with developing using a combination of &lt;a href="http://mongodb.org"&gt;MongoDB&lt;/a&gt; (using &lt;a href="http://mongoid.org"&gt;Mongoid&lt;/a&gt; as my &lt;span class="caps"&gt;ORM&lt;/span&gt;), &lt;a href="http://compass-style.org"&gt;Compass&lt;/a&gt; and Rails 3 (beta 1,2,3,4 and RC :p).&lt;/p&gt;
&lt;p&gt;This time I even managed to do it completely according to &lt;span class="caps"&gt;BDD&lt;/span&gt;, so every little function has green tests, and hopefully I&amp;#8217;ll manage to keep it that way if I decide to add some more features.&lt;/p&gt;
&lt;p&gt;Here&amp;#8217;s some highlights:&lt;/p&gt;
&lt;ul&gt;
	&lt;li&gt;Backend is now MongoDB running on &lt;a href="http://mongohq.com"&gt;MongoHQ&lt;/a&gt;&lt;/li&gt;
	&lt;li&gt;Frontend is valid HTML5, with some CSS3 styling&lt;/li&gt;
	&lt;li&gt;Comments system has been thrown away in favor of Disqus (sadly that removed &amp;#8220;all&amp;#8221; the old comments)&lt;/li&gt;
	&lt;li&gt;New design (with a color instead of just black &amp;amp; white)!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Thank you for taking the time to visit, and please leave some feedback in the comments if you have any!&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/loo/~4/WSDVhdKAIZY" height="1" width="1"/&gt;</content>
  <feedburner:origLink>http://loo.no/posts/new-design-new-backend-based-on-mongodb</feedburner:origLink></entry>
  <entry>
    <id>tag:loo.no,2005:Post/4c5b225dacde146522000016</id>
    <published>2010-02-05T23:00:00+00:00</published>
    <updated>2010-08-09T00:47:40+00:00</updated>
    <link type="text/html" rel="alternate" href="http://feedproxy.google.com/~r/loo/~3/Ck21Mkohlzg/mirror-images-with-paperclip" />
    <title>Mirror images with Paperclip</title>
    <content type="html">&lt;p&gt;While implementing an awesome design, I needed to dynamically create a mirror effect on some thumbnails. Since I&amp;#8217;m more or less dedicated to Paperclip, I decided to see if it could be done, without any monkeypatching. Here&amp;#8217;s what I came up with.&lt;/p&gt;
&lt;p&gt;First, I should explain my setup of Paperclip attachments in this project. Because I&amp;#8217;m a bit lazy, I didn&amp;#8217;t want to make too many migrations for every different type of attachment. Instead I created a &lt;span class="caps"&gt;STI&lt;/span&gt; class called PaperclipAttachment that is the base class of all the different types of attachments in this project&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class CreatePaperclipAttachments &amp;lt; ActiveRecord::Migration
  def self.up
    create_table(:paperclip_attachments) do |t|
      t.string   :data_file_name
      t.string   :data_content_type
      t.integer  :data_file_size
      t.datetime :data_updated_at
      t.string :type
      t.references :container, :polymorphic =&amp;gt; true
    end
  end
 
  def self.down
    drop_table :paperclip_attachments
  end
end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This class is defined as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class PaperclipAttachment &amp;lt; ActiveRecord::Base
  # Relationships
  belongs_to :container, :polymorphic =&amp;gt; true
 
  # Attributes
  attr_protected :data_file_name, :data_content_type, :data_file_size
 
  # Delegations
  delegate :path,:url, :to =&amp;gt; :data
 
  # Extensions
  has_attached_file :data, { 
    :styles =&amp;gt; lambda{|data| data.instance.class.attachment_styles},
    :processors =&amp;gt; lambda{|instance| instance.class.processors},
    :path =&amp;gt; ":rails_root/public/system/:class/:id_partition/:style/:basename.:extension",
    :url =&amp;gt; "/system/:class/:id_partition/:style/:basename.:extension",
    :default_url =&amp;gt; "/system/:class/defaults/missing_:style.png"
  }
 
  def self.attachment_styles
    {}
  end
 
  def self.processors
    [:thumbnail]
  end
end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The reason I&amp;#8217;m proc&amp;#8217;ing :styles and :processors, is that I want to be able to treat the different attachment styles differently. The file structure still stays logical, and the only &amp;#8220;downside&amp;#8221; I can see with this approach is that all attachments reside in the same table in the database.&lt;/p&gt;
&lt;p&gt;So with this as the base for my project, I created a &lt;span class="caps"&gt;PDF&lt;/span&gt; class that inherits from this PaperclipAttachment class. I defined the different &lt;span class="caps"&gt;JPG&lt;/span&gt; snapshots of the &lt;span class="caps"&gt;PDF&lt;/span&gt; that I needed like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Pdf &amp;lt; PaperclipAttachment
  def self.attachment_styles
    {
      :small_preview =&amp;gt; { :geometry =&amp;gt; '106x72&amp;gt;',  :format =&amp;gt; :jpg },
      :preview =&amp;gt;       { :geometry =&amp;gt; '297x210&amp;gt;', :format =&amp;gt; :jpg }, 
      :big_preview =&amp;gt;   { :geometry =&amp;gt; '460x300&amp;gt;', :format =&amp;gt; :jpg }
    }
  end
end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So, it was time to implement the mirror effect. After considering making a processor for a bit, I decided to go with a less time consuming solution. Based on this &lt;a href="http://roundcubeinbox.wordpress.com/2009/09/11/mirror-images-with-imagemagick/"&gt;shellscript from roundcube.net&lt;/a&gt; I started hacking on my Pdf class.&lt;/p&gt;
&lt;p&gt;First, I added a couple of methods for getting the path and url of this mirror image. I wanted it to be connected to each Paperclip style, so I decided to put it in the same directory:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def mirror_path(style=:original)
  path(style).gsub(/.\w+$/,'') + '_mirror.png'
end
 
def mirror_url(style=:original)
  url(style).gsub(/.\w+(|\?\d+)$/,'') + '_mirror.png'
end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, the method that does the dirty work turned out like this. You can probably strip out half of it, but I wanted to have the height of the mirror effect snap to my current line-height, and some other semi-bloat stuff:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def mirror(args = {})
  style        = args[:style] || :small_preview
  infile       = args[:in] || path(style)
  outfile      = args[:out] || mirror_path(style)
  alpha        = args[:alpha] || 30
  line_height  = args[:line_height] || 18
  height_f     = 0.2
  width,height = Paperclip::Geometry.from_file(infile).to_s.split('x').collect(&amp;amp;:to_i)
  height       = ( (height * height_f) * ( 1.0 / line_height ) ).round / ( 1.0/line_height )
  geometry     = [width, height].collect(&amp;amp;:to_i).join 'x'
 
  command = "convert -size #{geometry} xc:none"
  command &amp;lt;&amp;lt; " \\( \\( -flip #{infile} -crop #{geometry}+0+0 \\)"
  command &amp;lt;&amp;lt; " -size #{geometry} gradient: -evaluate Pow 1.4"
  command &amp;lt;&amp;lt; " -compose Copy_Opacity -composite \\) "
  command &amp;lt;&amp;lt; " -compose blend -set \"option:compose:args\" #{alpha} -composite #{outfile}"
  
  warn "Mirroring: " + command if RAILS_ENV == 'development'
  
  system command
end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For configuration, I wanted to use the :styles hash provided by Paperclip. So I added a key called mirror on the styles I wanted to mirror:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  :small_preview =&amp;gt; { :geometry =&amp;gt; '106x72&amp;gt;',  :format =&amp;gt; :jpg, :mirror =&amp;gt; {:alpha =&amp;gt; 30} },
  :preview =&amp;gt;       { :geometry =&amp;gt; '297x210&amp;gt;', :format =&amp;gt; :jpg, :mirror =&amp;gt; {:alpha =&amp;gt; 30} }, 
  :big_preview =&amp;gt;   { :geometry =&amp;gt; '460x300&amp;gt;', :format =&amp;gt; :jpg }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And then made a method to check which styles I should mirror, and call the mirror method:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def make_mirrors
  data.styles.each_pair do |style,style_hash|
    style_hash.select{|k,v| k == :mirror}.each{|k,v| self.mirror({:style =&amp;gt; style}.merge(v))}
  end
  true
end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Voila! That&amp;#8217;s the proof of concept that I&amp;#8217;ve come up with so far, but I will probably refactor it a bit before putting it into production. Hopefully it&amp;#8217;s of some use for someone else too :)&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/loo/~4/Ck21Mkohlzg" height="1" width="1"/&gt;</content>
  <feedburner:origLink>http://loo.no/posts/mirror-images-with-paperclip</feedburner:origLink></entry>
  <entry>
    <id>tag:loo.no,2005:Post/4c5b2128acde146522000014</id>
    <published>2010-01-09T23:00:00+00:00</published>
    <updated>2010-08-09T00:50:05+00:00</updated>
    <link type="text/html" rel="alternate" href="http://feedproxy.google.com/~r/loo/~3/yvpnOJaXQAA/bringing-my-blog-into-the-new-decade" />
    <title>Bringing my blog into the new decade</title>
    <content type="html">&lt;p&gt;Being a new decade and all, I got some inspiration to do a little work on my blog again. This time I wanted to get up to speed with things, and make my markup &lt;a href="http://html5.validator.nu/?doc=http://loo.no"&gt;valid HTML5&lt;/a&gt;. I also polished up the design a bit, adding some icons and stuff.&lt;/p&gt;
&lt;p&gt;So far I haven&amp;#8217;t really explored all the new features of HTML5 fully, but for now I&amp;#8217;ll leave a few links to some good resources on the subject. Hopefully I&amp;#8217;ll have an opportunity to play around with the more exotic features of HTML5 soon.&lt;/p&gt;
&lt;ul&gt;
	&lt;li&gt;&lt;a href="http://whatwg.org/html5"&gt;Specification of &lt;span class="caps"&gt;HTML&lt;/span&gt; 5&lt;/a&gt;&lt;/li&gt;
	&lt;li&gt;&lt;a href="http://blog.whatwg.org/"&gt;Blog of the creators of &lt;span class="caps"&gt;HTML&lt;/span&gt; 5&lt;/a&gt;&lt;/li&gt;
	&lt;li&gt;&lt;a href="http://html5doctor.com/"&gt;&lt;span class="caps"&gt;HTML&lt;/span&gt; 5 Doctor&lt;/a&gt;&lt;/li&gt;
	&lt;li&gt;&lt;a href="http://html5demos.com/"&gt;&lt;span class="caps"&gt;HTML&lt;/span&gt; 5 Demos&lt;/a&gt;&lt;/li&gt;
	&lt;li&gt;&lt;a href="http://html5gallery.com/"&gt;&lt;span class="caps"&gt;HTML&lt;/span&gt; 5 Gallery&lt;/a&gt;&lt;/li&gt;
	&lt;li&gt;&lt;a href="http://html5.validator.nu/"&gt;&lt;span class="caps"&gt;HTML&lt;/span&gt; 5 Validator&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And btw, Happy new year :)&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/loo/~4/yvpnOJaXQAA" height="1" width="1"/&gt;</content>
  <feedburner:origLink>http://loo.no/posts/bringing-my-blog-into-the-new-decade</feedburner:origLink></entry>
  <entry>
    <id>tag:loo.no,2005:Post/4c5b20c4acde146522000012</id>
    <published>2009-11-19T23:00:00+00:00</published>
    <updated>2010-08-09T00:47:53+00:00</updated>
    <link type="text/html" rel="alternate" href="http://feedproxy.google.com/~r/loo/~3/Rcl_ZaPmi5M/easy-zip-generating-with-rails-nginx-paperclip" />
    <title>Easy zip generating with Rails, Nginx &amp; Paperclip</title>
    <content type="html">&lt;p&gt;Tonight I decided to try making &lt;span class="caps"&gt;NGINX&lt;/span&gt; handle my zip generating, to save the server some work, and the user of some frustration waiting for background jobs to finish. Here&amp;#8217;s what I came up with.&lt;/p&gt;
&lt;p&gt;First of all, I re-compiled Nginx with two modules; &lt;a href="http://wiki.nginx.org/NginxNgxZip#mod_zip"&gt;mod_zip&lt;/a&gt;, and &lt;a href="http://www.modrails.com/"&gt;passenger&lt;/a&gt; module. You may drop the Passenger module if you are using some other server, this solution works with anything that can print out some text :)&lt;/p&gt;
&lt;p&gt;Roughly, these are the required steps:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gem install passenger
cd nginx-folder/
.configure --add-module=/path/to/passenger/gem/ext/nginx --add-module=/path/to/mod_zip-1.x
sudo make &amp;amp;&amp;amp; sudo make install&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you wonder where your passenger is, you can easily find the root of passenger by running&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;passenger-config --root&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So on to the fun part! I started by adding a member method to my galleries route called zip:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ActionController::Routing::Routes.draw do |map|
  map.resources :galleries, :member =&amp;gt; {:zip =&amp;gt; :get}
end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And In my controller I added these two methods (it can probably be done cleaner, but hey, it&amp;#8217;s working :) Suggestions for other ways of writing these are welcome!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class GalleriesController &amp;lt; ApplicationController
  def zip
    @gallery = Gallery.find(params[:id])
    paperzip(@gallery.images.collect(&amp;amp;:data), @gallery.name)
  end
  
  private 
    def paperzip( attachments= [], name= 'file', style= :original )
      # set headers
      response.headers["Content-Disposition"] = "attachment; filename=#{name}.zip"
      response.headers["X-Archive-Files"] = "zip"
  
      # Generate file list
      render :text =&amp;gt; attachments.collect{|a| 
        ['-', File.size(a.path(style)), a.url(style,false), a.original_filename.gsub(/ /,'_')].join(' ')
      }.join("\r\n")+"\r\n"
    end
end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Whoa! Let me break that down for you a little bit. Firstly the member method zip, which now has the named route &amp;#8220;zip_gallery_path(@gallery)&amp;#8221; in you views btw:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def zip
  @gallery = Gallery.find(params[:id])
  paperzip(@gallery.images.collect(&amp;amp;:data), @gallery.name)
end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This basically finds our gallery from the :id param, and calls on this method we&amp;#8217;re defining as private for this controller, called paperzip() (I know, catchy). paperzip() takes a collection of Images (which has_attached_file :data), a string containing a name for the zip file about to be made, and a symbol which you can use to zip down the style you want. I&amp;#8217;m running with :original here.&lt;/p&gt;
&lt;p&gt;Let&amp;#8217;s move on to the next couple of lines:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;response.headers["Content-Disposition"] = "attachment; filename=#{name}.zip"
response.headers["X-Archive-Files"] = "zip"&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What we do here is setting some custom headers for our response back to the client, and Nginx. The content-disposition tells it we want the user to be prompted to download a file, called something.zip. The X-Archive-Files header is the one that makes mod_zip trigger, and eagerly await a list of files to zip down and send. Mod_zip requires a list of files that looks something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;md5 bytes /relative/path filename&amp;lt;linebreak&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that the linebreak needs to be a \r\n, and be on every line, even the last one. So, in order to construct this list, I added the following lines to the method, returning it using &amp;#8220;render :text&amp;#8221;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;render :text =&amp;gt; attachments.collect{|a| 
    ['-', File.size(a.path(style)), a.url(style,false), a.original_filename.gsub(/ /,'_')].join(' ')
  }.join("\r\n")+"\r\n" &lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And voila! Calling this action with a get request, will now trigger mod_zip and create us a fully working zip file. The good thing about using this over some other method, is that Nginx only uses a couple of kilobytes of that precious memory of yours while doing this, and does the job way quicker than any other method I&amp;#8217;ve tried.&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/loo/~4/Rcl_ZaPmi5M" height="1" width="1"/&gt;</content>
  <feedburner:origLink>http://loo.no/posts/easy-zip-generating-with-rails-nginx-paperclip</feedburner:origLink></entry>
  <entry>
    <id>tag:loo.no,2005:Post/4c5b1fe2acde146522000010</id>
    <published>2009-10-14T22:00:00+00:00</published>
    <updated>2010-08-09T00:45:52+00:00</updated>
    <link type="text/html" rel="alternate" href="http://feedproxy.google.com/~r/loo/~3/CmAaj_k5apk/practical-use-of-google-wave" />
    <title>Practical use of Google Wave</title>
    <content type="html">&lt;p&gt;After playing around with wave in the developer preview, and giving it some thought, we tried the new beta version of Google Wave in a real-life scenario at the office today. Four of us were doing a sales effort (new experience), and found it to be a great tool for this purpose.&lt;/p&gt;
&lt;p&gt;The case was that we wanted to do an effort on a backlog of leads and ideas by taking them one step further in the process. We started out by creating a Wave for each lead, inviting all the team members that were going to work on this.&lt;/p&gt;
&lt;p&gt;Next thing was to get some organizing of these newly created waves. We solved it by tagging each of them with &amp;#8220;lead&amp;#8221; in the beginning, so we could create a saved search and clear them from our inboxes. In order to know who was responsible for the different leads, we also tagged the waves with our initials and created a saved search for our own leads.&lt;/p&gt;
&lt;p&gt;And so the fun started. While I was working on my first lead, I needed some feedback on a few details from my colleague &lt;a href="http://twitter.com/ken_guru"&gt;Guru&lt;/a&gt;, so I pinged him with a link to my lead wave and asked for some feedback. While I continued to work on another part of the same lead, I saw his comments coming in letter by letter, and responded quickly to some of them. This resulted in a very effective and quick dialogue and case solving method, and as a bonus we have a full log of how we came to the conclusions.&lt;/p&gt;
&lt;p&gt;There have been a lot of people disbanding Google Wave lately due to it not being email, twitter, facebook or some other app. In my opinion, Google Wave is not the solution to all the quirks of our current tools, but as a simple group collaboration tool it&amp;#8217;s so far the best I&amp;#8217;ve tried. It will probably not replace Email or random IM in it&amp;#8217;s current state, as many people (including myself) hope for, but it&amp;#8217;s the thing in between that I didn&amp;#8217;t know I was missing before I tried it.&lt;/p&gt;
&lt;p&gt;I would love to hear about other success stories with using Google Wave for something useful. I found a few links to a few cheatsheets during our little session today, that you also might find useful:&lt;/p&gt;
&lt;ul&gt;
	&lt;li&gt;&lt;a href="http://www.googlewaveblogger.com/reviews/another-cheat-sheet-hints-and-tips-for-google-wave/"&gt;Cheatsheet #1&lt;/a&gt;&lt;/li&gt;
	&lt;li&gt;&lt;a href="http://www.googlewaveblogger.com/reviews/new-google-wave-users-cheat-sheet/"&gt;Cheatsheet #2&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;img src="http://feeds.feedburner.com/~r/loo/~4/CmAaj_k5apk" height="1" width="1"/&gt;</content>
  <feedburner:origLink>http://loo.no/posts/practical-use-of-google-wave</feedburner:origLink></entry>
  <entry>
    <id>tag:loo.no,2005:Post/4c5b1fb4acde14652200000e</id>
    <published>2009-09-13T22:00:00+00:00</published>
    <updated>2010-08-09T00:49:47+00:00</updated>
    <link type="text/html" rel="alternate" href="http://feedproxy.google.com/~r/loo/~3/YMWhXkY2a-8/got-my-shiny-new-macbook-pro-13-today" />
    <title>Got my shiny new Macbook Pro 13" today!</title>
    <content type="html">&lt;p&gt;After some drooling over the recent macbooks that have been popping up all over the office during the summer, I finally got my own little shiny toy today. Thought I&amp;#8217;d write a few words about my experience so far.&lt;/p&gt;
&lt;p&gt;I&amp;#8217;ve been playing around with my colleagues&amp;#8217; macbooks, and even owned two iPhones (I&amp;#8217;m now a happy Android user btw), but it&amp;#8217;s still something with the way Apple has packaged their gadgets that makes you feel like you&amp;#8217;ve received some state-of-the-art space equipment. Needless to say &amp;#8211; unpacking the macbook gave me that same &amp;#8220;oooh, new gadget&amp;#8221; feeling I like so much.&lt;/p&gt;
&lt;p&gt;First thing I did after booting it up (and filling in all the boring details about me, myself, my work, hobbies and what I ate for breakfast, lunch and dinner) was to install Snow Leopard (fancy-talk for MacOS 10.6). The installation was pretty straight forward, and completed in under 1 hour.&lt;/p&gt;
&lt;p&gt;Next big thing was installing &lt;a href="http://macromates.com"&gt;TextMate&lt;/a&gt; &amp;#8211; pretty much the whole reason that I got this Macbook in the first place tbh. I&amp;#8217;ve envied all the happy ruby hackers with their Textmates up until now, so I can&amp;#8217;t wait to try it out for myself. My primary use for this little shiny toy is development, so I needed to get all my ruby versions, gems and everything installed right away. Luckily for me, &lt;a href="http://twitter.com/mikeG1"&gt;Mike Gunderloy&lt;/a&gt; wrote &lt;a href="http://afreshcup.com/2009/09/02/migrating-to-snow-leopard-for-rails-development-a-definitive-guide/"&gt;a complete guide&lt;/a&gt; to getting up and running with ruby development on Snow Leopard. After following his guide (skipping the backup &amp;amp; restore stuff since this is my first mac) I was up and running with all ruby versions, and enough gems to do a &amp;#8220;rake gems:install&amp;#8221; on my current projects.&lt;/p&gt;
&lt;p&gt;To sum it all up; my first 6 hours as a macbook owner has been great. I&amp;#8217;m having some difficulties getting used to the new way of navigating (loving the multi-touch pad btw), and installing applications. I&amp;#8217;ve done a &amp;#8220;sudo apt-get &lt;appname&gt;&amp;#8221; countless times the last couple of hours. Also, my original plan of switching some keys from this US version to norwegian letters failed brutally since the whole top of the chassis is different on the US version &amp;#8211; so I&amp;#8217;m learning a whole new keyboard layout as I go &amp;#8211; no way I&amp;#8217;m paying 4500 &lt;span class="caps"&gt;NOK&lt;/span&gt; (currently 760 &lt;span class="caps"&gt;USD&lt;/span&gt;) to get my æøå back!&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/loo/~4/YMWhXkY2a-8" height="1" width="1"/&gt;</content>
  <feedburner:origLink>http://loo.no/posts/got-my-shiny-new-macbook-pro-13-today</feedburner:origLink></entry>
  <entry>
    <id>tag:loo.no,2005:Post/4c5b1f35acde14652200000c</id>
    <published>2009-08-09T22:00:00+00:00</published>
    <updated>2010-08-09T00:48:02+00:00</updated>
    <link type="text/html" rel="alternate" href="http://feedproxy.google.com/~r/loo/~3/oJOuo5fr1YQ/rounded-corners-with-paperclip" />
    <title>Rounded corners with Paperclip</title>
    <content type="html">&lt;p&gt;When applying a new design to one of our projects, we were in a need of a Paperclip style with rounded corners. Luckily for us we had Paperclip&amp;#8217;s :convert_options and strong ImageMagick-fu.&lt;/p&gt;
&lt;p&gt;After trying out some different &lt;span class="caps"&gt;CSS&lt;/span&gt; hacks, and jQuery plugins for getting the rounded corner effects we wanted, we turned to &lt;a href="http://github.com/thoughtbot/paperclip"&gt;Paperclip&lt;/a&gt;. Since this particular style of a photo was only to be used at one place in the new design, we could apply the rounded corners directly to the image.&lt;/p&gt;
&lt;p&gt;I googled around a bit and found &lt;a href="http://github.com/shadow11/paperclip"&gt;a fork&lt;/a&gt; of Paperclip by &lt;a href="http://github.com/shadow11/"&gt;shadow11&lt;/a&gt; with some changes that never made it into the official Paperclip repository. His approach was to add the rounded corner code into Paperclip itself, and provide a argument to has_attached called rounded. &lt;span class="caps"&gt;IMO&lt;/span&gt; that&amp;#8217;s probably nice-to-have if you do a lot of rounded corners on your attachments, but probably not something most people need. So I went ahead and copy-pasted out the juicy parts from his commit.&lt;/p&gt;
&lt;p&gt;Firstly, we need to make sure that the style we are applying rounded corners to is a &lt;span class="caps"&gt;PNG&lt;/span&gt; file, since that&amp;#8217;s the only file-format we have on the web that&amp;#8217;s capable of transparancy (which we&amp;#8217;ll use for the rounded corners effect). So my initial User model (borrowed from the &lt;a href="http://wiki.github.com/thoughtbot/paperclip/usage"&gt;Paperclip example&lt;/a&gt;) looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class User &amp;lt; ActiveRecord::Base
  has_attached_file :avatar,
    :styles =&amp;gt; { :medium =&amp;gt; ["300x300&amp;gt;", :png],
                          :thumb =&amp;gt;  "100x100&amp;gt;" }
end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So, in order to get some corners stripped of this new &lt;span class="caps"&gt;PNG&lt;/span&gt; style, we need to utilize Paperclip&amp;#8217;s convert_options, which allows us to give extra parameters to the ImageMagick convert command that Paperclip uses for resizing. The extra flags that does this job is as follows (I&amp;#8217;m no ImageMagick pro-user, but a good copy-paster, and it actually works):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;\( +clone  -threshold -1 -draw 'fill black polygon 0,0 0,5 5,0 fill white circle 5,5 5,0' \( +clone -flip \) -compose Multiply -composite \( +clone -flop \) -compose Multiply -composite \) +matte -compose CopyOpacity -composite&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This adds a 5 pixel rounding to all four corners of our image, replacing the old ones with a transparent background.&lt;/p&gt;
&lt;p&gt;In order to get this into my Avatar, all I had to do was to include this code in the hash being sent to has_attached (&lt;span class="caps"&gt;THE&lt;/span&gt; Paperclip command). While testing, I actually Proc&amp;#8217;ed the convert_options and ended up with the code below, but that&amp;#8217;s really not necessary, you probably want to roll your own something on this part. But here&amp;#8217;s the full User model that&amp;#8217;s working:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
class User &amp;lt; ActiveRecord::Base
  has_attached_file :avatar,
                    :styles =&amp;gt; { :medium =&amp;gt; ["300x300&amp;gt;", :png],
                                 :thumb =&amp;gt; "100x100&amp;gt;" },
                    :convert_options =&amp;gt; {:medium =&amp;gt; Proc.new{self.convert_options}}
 
  def self.convert_options
    trans = ""
    px = 20
    trans &amp;lt;&amp;lt; " \\( +clone  -threshold -1 "
    trans &amp;lt;&amp;lt; "-draw 'fill black polygon 0,0 0,#{px} #{px},0 fill white circle #{px},#{px} #{px},0' "
    trans &amp;lt;&amp;lt; "\\( +clone -flip \\) -compose Multiply -composite "
    trans &amp;lt;&amp;lt; "\\( +clone -flop \\) -compose Multiply -composite "
    trans &amp;lt;&amp;lt; "\\) +matte -compose CopyOpacity -composite "
  end
end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And that&amp;#8217;s all. The medium style of my attached Avatar is now rounded with nice 20px corners. Looking forward to implement this (in a cleaner way) on the project tomorrow.&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/loo/~4/oJOuo5fr1YQ" height="1" width="1"/&gt;</content>
  <feedburner:origLink>http://loo.no/posts/rounded-corners-with-paperclip</feedburner:origLink></entry>
  <entry>
    <id>tag:loo.no,2005:Post/4c5b1e8aacde14652200000a</id>
    <published>2009-07-28T22:00:00+00:00</published>
    <updated>2010-08-09T00:46:13+00:00</updated>
    <link type="text/html" rel="alternate" href="http://feedproxy.google.com/~r/loo/~3/sFYDr89BTzY/my-first-google-wave-robot-lobo-bot" />
    <title>My first Google Wave Robot: lobo-bot</title>
    <content type="html">&lt;p&gt;Since I&amp;#8217;m a bit too tired to do anything useful (or useless) in either Python or Java, and these are the only two languages Google support as of now, I decided to hit &lt;a href="http://github.com"&gt;Github&lt;/a&gt; in search for a ruby wrapper to the wave api. On my first search I found &lt;a href="http://github.com/diminish7/rave/tree/master"&gt;Rave&lt;/a&gt;, a JRuby wrapper for (parts of) the python &lt;span class="caps"&gt;API&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;First I downloaded and installed &lt;a href="http://www.jruby.org/"&gt;JRuby&lt;/a&gt; and &lt;a href="http://code.google.com/appengine/downloads.html"&gt;Google App Engine &lt;span class="caps"&gt;SDK&lt;/span&gt;&lt;/a&gt;, which was surprisingly easy. I&amp;#8217;ve never really had a chance to test any of them, so it was interesting seeing what they were all about.&lt;/p&gt;
&lt;p&gt;Setting up a new Rave robot is a breeze too. After installing the gem, you use this command to set it all up:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;jruby -S rave create [robot_name] [options]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So, on to the hard part; coming up with an idea that was good enough to implement. Well, I failed at that part, and created a robot that edits all blips, and reverses words wrapped with @&amp;#8217;s. Extremely useless, but fun to watch. Here&amp;#8217;s the robot.rb for lobo-bot:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;require 'rubygems'
require 'rave'
 
module LoboBot
  class Robot &amp;lt; Rave::Models::Robot
    ME = "lobo-bot@appspot.com"
    REGEXP = Regexp.compile '@\w+@'
    
    def document_changed(event, context)
      # Don't do anything if we triggered the event ourselves
      return if event.modified_by == ME
 
      context.blips.values.each do |blip|
        blip.content.scan(REGEXP).each do |match|
          # Find which range of the match
          range = blip.content.rindex(match)..blip.content.rindex(match)+match.length
          # Reverse!!
          blip.set_text_in_range(range, match.reverse)
        end
      end
    end
    
  end  
end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So&amp;#8230; Well, it works, and I got to try out all of these new tools at once :-) For something a bit more useful, you can check out &lt;a href="http://twitter.com/senikk"&gt;@senikk&amp;#8217;s&lt;/a&gt; &lt;a href="http://senikk.com/min-f%C3%B8rste-google-wave-robot"&gt;little robot&lt;/a&gt; who links all @names to their twitter accounts. I was thinking about porting it to Ruby, but unfortunately Rave doesn&amp;#8217;t have set_annotation() method yet.&lt;/p&gt;
&lt;p&gt;Hopefully I will come up with something more useful another day, but meanwhilst I recommend reading this stuff:&lt;/p&gt;
&lt;ul&gt;
	&lt;li&gt;&lt;a href="http://code.google.com/apis/wave/extensions/robots/index.html"&gt;Google Wave Robots: Overview&lt;/a&gt;&lt;/li&gt;
	&lt;li&gt;&lt;a href="http://code.google.com/apis/wave/extensions/robots/python-tutorial.html"&gt;Google Wave Robots: Python tutorial&lt;/a&gt;&lt;/li&gt;
	&lt;li&gt;&lt;a href="http://code.google.com/apis/wave/extensions/robots/java-tutorial.html"&gt;Google Wave Robots: Java tutorial&lt;/a&gt;&lt;/li&gt;
	&lt;li&gt;And fork &lt;a href="http://github.com/diminish7/rave/tree/master"&gt;diminish7&amp;#8217;s rave&lt;/a&gt; at GitHub!&lt;/li&gt;
&lt;/ul&gt;&lt;img src="http://feeds.feedburner.com/~r/loo/~4/sFYDr89BTzY" height="1" width="1"/&gt;</content>
  <feedburner:origLink>http://loo.no/posts/my-first-google-wave-robot-lobo-bot</feedburner:origLink></entry>
  <entry>
    <id>tag:loo.no,2005:Post/4c5b1df7acde146522000008</id>
    <published>2009-07-27T22:00:00+00:00</published>
    <updated>2010-08-09T00:46:37+00:00</updated>
    <link type="text/html" rel="alternate" href="http://feedproxy.google.com/~r/loo/~3/wAC2vUwdOK0/first-impressions-of-google-wave" />
    <title>First Impressions of Google Wave</title>
    <content type="html">&lt;p&gt;So, I received an invitation to Google Wave yesterday, and I&amp;#8217;ve been playing around with it quite a lot since that. One of the practical things about this sandbox access is that you actually get &lt;em&gt;two&lt;/em&gt; users in the sandbox, one that is username@wavesandbox.com, and another called username-test@wavesandbox.com. This enables you to do some testing with yourself, and not having to bother everyone else with it.&lt;/p&gt;
&lt;p&gt;Accounts at wavesandbox.com comes with Google Apps as well, so you have access to mail, docs, calendar and the regular Google Apps settings.&lt;/p&gt;
&lt;p&gt;The Web interface looks very much like the Gmail interface, with some polishing. It&amp;#8217;s three columns instead of the two in Gmail; on the left you have a navigation pane that lets you browse your waves by saved searches, folders, and some other. There&amp;#8217;s also a settings menu under this pane, but this has not been implemented yet. In the middle you have a list over all waves in the category you&amp;#8217;re in, which looks a lot like your inbox in gmail. On the right, you have the &amp;#8220;show&amp;#8221; view. This is where you view a selected wave, and can do all the magic editing, drag&amp;#8217;n&amp;#8217;drop of images etc. One very nice feature is that you can minimize &amp;#8220;windows&amp;#8221; within this web gui, to give more room to the ones you use at the moment.&lt;/p&gt;
&lt;p&gt;Even though this is still a preview and it has some lacking features and a whole lot bugs, I can clearly see how this is going to be a liberation and replacement for most businesses that are currently using mail, forums, &lt;span class="caps"&gt;CRM&lt;/span&gt; systems, IM and other communication tools. Google Wave is all of these systems mixed together into awesomeness, plus a lot more.&lt;/p&gt;
&lt;p&gt;Today (whenever I have some spare time) I will try to write my first robot to get to know the &lt;span class="caps"&gt;API&lt;/span&gt; a bit. Playing with the idea that Google Wave would be an awesome tool for collaborating on a &lt;span class="caps"&gt;CMS&lt;/span&gt;/Blog like this, so that will probably be my first little project.&lt;/p&gt;
&lt;p&gt;Some other Google Wave articles worth reading:&lt;/p&gt;
&lt;ul&gt;
	&lt;li&gt;&lt;a href="http://www.readwriteweb.com/archives/google_wave_our_first_hands-on_impressions.php"&gt;ReadWriteWeb: First hands-on impressions&lt;/a&gt;&lt;/li&gt;
	&lt;li&gt;&lt;a href="http://blogs.zdnet.com/Hinchcliffe/?p=560"&gt;Dion Hinchcliffe: First impressions of google wave&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;img src="http://feeds.feedburner.com/~r/loo/~4/wAC2vUwdOK0" height="1" width="1"/&gt;</content>
  <feedburner:origLink>http://loo.no/posts/first-impressions-of-google-wave</feedburner:origLink></entry>
  <entry>
    <id>tag:loo.no,2005:Post/4c5b1885acde1464c4000002</id>
    <published>2009-07-26T22:00:00+00:00</published>
    <updated>2010-08-09T00:48:10+00:00</updated>
    <link type="text/html" rel="alternate" href="http://feedproxy.google.com/~r/loo/~3/vwW_cmh7VY4/background-processing-with-paperclip" />
    <title>Background processing with Paperclip</title>
    <content type="html">&lt;p&gt;I&amp;#8217;m going to assume that you are familiar with &lt;a href="http://github.com/thoughtbot/paperclip/tree/master"&gt;Paperclip&lt;/a&gt; by now. If you&amp;#8217;re not, you should download and implement immediately. The background daemon I&amp;#8217;m using is &lt;a href="http://github.com/collectiveidea/delayed_job/tree/master"&gt;delayed_job&lt;/a&gt; but you are of course free to use your weapon of choice. Ryan Bates recently did a nice &lt;a href="http://railscasts.com/episodes/171-delayed-job"&gt;screencast about delayed_job&lt;/a&gt; which I highly recommend (check out this &lt;a href="http://asciicasts.com/episodes/171-delayed-job"&gt;Ascii-cast&lt;/a&gt; if you don&amp;#8217;t like videos).&lt;/p&gt;
&lt;p&gt;Ok, so on to the cool stuff, we start by making a table and all that:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class CreateImages &amp;lt; ActiveRecord::Migration
  def self.up
    create_table(:images) do |t|
    
      # default paperclip fields
      t.string   :data_file_name
      t.string   :data_content_type
      t.integer  :data_file_size
      t.datetime :data_updated_at
      
      # processing
      t.boolean :processing, :default =&amp;gt; true
      
      # timestamps
      t.timestamps
      
    end
  end
  
  def self.down
    drop_table :images
  end
end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pretty straight forward migration. The &lt;em&gt;processing&lt;/em&gt; boolean is used to check whether a particular photo has been processed yet. It defaults to true, since we will always intercept Paperclip&amp;#8217;s processing when a new Photo is added.&lt;/p&gt;
&lt;p&gt;So let&amp;#8217;s create an ImageJob, the little delayed_job gnome that will be doing all the dirty work.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class ImageJob &amp;lt; Struct.new(:image_id)
  def perform
    Image.find(self.image_id).perform
  end
end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is a regular pattern for creating jobs in delayed_job. What it does is basically to (on perform) find the image in question, and call the instance method perform() on it which will do all the processing stuff. I normally add these jobs into lib/ since they&amp;#8217;re not models per say.&lt;/p&gt;
&lt;p&gt;In our Image model, we add the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;before_data_post_process do |image|
  false if image.processing? # do not process if just added
end
  
after_create do |image|
  Delayed::Job.enqueue ImageJob.new(image.id) # add to queue
end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This utilizes Paperclip&amp;#8217;s before_:attachment_post_process callback to halt the processing and just go through with the create action (save the record in db). After create, we add this Image to our delayed job queue.&lt;/p&gt;
&lt;p&gt;Since it might take some time processing our Image, we add this little convenience method which serves two purposes; normally when using Paperclip you have to do model.attachment.url(:style) to get the url of the attachment, this method allows you to to model.url(:style). We are also able to return a default &lt;span class="caps"&gt;URL&lt;/span&gt; while processing the Image in the background.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def url(style = :original)
  if self.data &amp;amp;&amp;amp; processing? &amp;amp;&amp;amp; style != :original
    return data.send(:interpolate, @@default_url, "#{style}")
  end
  data.url(style)
end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So now we are ready to try out the actual processing, with this insanely advanced method on our Image model:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def perform
  self.processing = false # unlock for processing
  data.reprocess! # do the processing
  save
end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And that&amp;#8217;s it. You can now start up your delayed_job queue listener, go into console and try out with a few files.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;10.times { Image.create(:data =&amp;gt; File.open('/home/lars/test.jpg')) }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Happy hacking :-)&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/loo/~4/vwW_cmh7VY4" height="1" width="1"/&gt;</content>
  <feedburner:origLink>http://loo.no/posts/background-processing-with-paperclip</feedburner:origLink></entry>
  <entry>
    <id>tag:loo.no,2005:Post/4c5b1cb6acde146522000004</id>
    <published>2009-07-26T22:00:00+00:00</published>
    <updated>2010-08-05T20:54:13+00:00</updated>
    <link type="text/html" rel="alternate" href="http://feedproxy.google.com/~r/loo/~3/crXo7E9nA38/redirect-all-mails-while-developing-your-rails-app" />
    <title>Redirect all mails while developing your rails app</title>
    <content type="html">&lt;p&gt;The plugin is called &lt;a href="http://github.com/trym/mail-bouncer/tree/master"&gt;MailBouncer&lt;/a&gt;, and is written by a colleague of mine, Trym Skaar. The installation is as easy as&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;config.gem "trym-mail_bouncer", 
  :lib =&amp;gt; "mail_bouncer", :source =&amp;gt; "http://gems.github.com"&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;in your &lt;code&gt;environment.rb&lt;/code&gt;, or you can still do&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gem install trym-mail_bouncer --source http://gems.github.com&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After installing, go into &lt;code&gt;$RAILS_ROOT/config/initializers/mail_bouncer.rb&lt;/code&gt; and do your setup&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;MailBouncer.configure do |config|
  config.enabled    = %w(development test).include? ENV["RAILS_ENV"]
  config.recipients = 'your@email.com'
end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And you&amp;#8217;re done. All mails sent while being in development- or test-mode on your project will be redirected to your own debug mail account instead of out to your customers, and it will keep all headers intact so you can use it for debugging.&lt;/p&gt;
&lt;p&gt;It has saved my day a couple of times already :-)&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/loo/~4/crXo7E9nA38" height="1" width="1"/&gt;</content>
  <feedburner:origLink>http://loo.no/posts/redirect-all-mails-while-developing-your-rails-app</feedburner:origLink></entry>
  <entry>
    <id>tag:loo.no,2005:Post/4c5b1dbbacde146522000006</id>
    <published>2009-07-26T22:00:00+00:00</published>
    <updated>2010-08-09T00:46:48+00:00</updated>
    <link type="text/html" rel="alternate" href="http://feedproxy.google.com/~r/loo/~3/KiUWLjiy52c/gone-surfin-on-google-wave" />
    <title>Gone surfin' (on Google Wave)</title>
    <content type="html">&lt;p&gt;After watching the &lt;a href="http://www.youtube.com/watch?v=v_UyVmITiYQ"&gt;video from Google I/O conference about Google Wave&lt;/a&gt;, and some hours of excited discussions and dreaming with my colleagues at the office, we all signed up for the Google Wave Sandbox program, hoping that one of us would get accepted.&lt;/p&gt;
&lt;p&gt;I was the one who got lucky, and received an invitation to the sandbox today :D I&amp;#8217;m really looking forward to many hours of exploring and coding in the weeks to come now. And the envy from my colleagues :P&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/loo/~4/KiUWLjiy52c" height="1" width="1"/&gt;</content>
  <feedburner:origLink>http://loo.no/posts/gone-surfin-on-google-wave</feedburner:origLink></entry>
  <entry>
    <id>tag:loo.no,2005:Post/4c5b1bf0acde146522000002</id>
    <published>2009-07-26T22:00:00+00:00</published>
    <updated>2010-08-09T00:48:19+00:00</updated>
    <link type="text/html" rel="alternate" href="http://feedproxy.google.com/~r/loo/~3/L3UEQZG9aFc/adding-exif-data-to-your-image-model-with-exifr" />
    <title>Adding Exif data to your Image model with exifr</title>
    <content type="html">&lt;p&gt;I&amp;#8217;m now using this great little gem called &lt;a href="http://exifr.rubyforge.org/"&gt;exifr&lt;/a&gt; in my project. It&amp;#8217;s fairly nimble, but gets the job done. To include it in your rails project, add the following line in your config/environment.rb&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;config.gem "exifr"&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You&amp;#8217;ll also want to add some fields to the model you are saving the exif data to. The exif reading is quite fast so you might want to do it on-the-fly, but then you don&amp;#8217;t get some new fields on your models for all those cool queries. Let&amp;#8217;s add it to our Image model.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class AddExifFieldsToImages &amp;lt; ActiveRecord::Migration
  def self.up
    add_column :images, :width, :integer
    add_column :images, :height, :integer
    add_column :images, :camera_brand, :string
    add_column :images, :camera_model, :string
    add_column :images, :exposure_time, :string
    add_column :images, :f_number, :float
    add_column :images, :iso_speed_rating, :integer
    add_column :images, :focal_length, :float
    add_column :images, :shot_date_time, :datetime
  end
 
  def self.down
    remove_column :images, :width
    remove_column :images, :height
    remove_column :images, :camera_brand
    remove_column :images, :camera_model
    remove_column :images, :exposure_time
    remove_column :images, :f_number
    remove_column :images, :iso_speed_rating
    remove_column :images, :focal_length
    remove_column :images, :shot_date_time
  end
end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These are the fields I normally use, but there are probably more that are worth saving.&lt;/p&gt;
&lt;p&gt;In order to get some data into these fields, we create this little method in our Image model:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def set_exif_data
  exif = EXIFR::JPEG.new( self.data.path )
  return if exif.nil? or not exif.exif?
  self.width            = exif.width
  self.height           = exif.height
  self.camera_brand     = exif.make
  self.camera_model     = exif.model
  self.exposure_time    = exif.exposure_time.to_s
  self.f_number         = exif.f_number.to_f
  self.iso_speed_rating = exif.iso_speed_ratings
  self.shot_date_time   = exif.date_time
  self.focal_length     = exif.focal_length.to_f
rescue
  false
end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this example I get the path of the file from self.data.path, where data is my paperclip attachment called :data. If you have some other setup, you&amp;#8217;ll have to change this to fit your needs.&lt;/p&gt;
&lt;p&gt;Calling this method sets the fields to the correct values, but does not save the object. Using the setup with delayed_job from the &lt;a href="http://loo.no/articles/background-processing-with-paperclip"&gt;last article&lt;/a&gt; you can a line in your perform() method to get exif data for all new Images, making the method as a whole like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def perform
  self.processing = false
  set_exif_data # this is the line we added
  data.reprocess!
  save
end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And voila, you now have added exif data to your images! Now it&amp;#8217;s time to think out something cool to do with it&amp;#8230;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/loo/~4/L3UEQZG9aFc" height="1" width="1"/&gt;</content>
  <feedburner:origLink>http://loo.no/posts/adding-exif-data-to-your-image-model-with-exifr</feedburner:origLink></entry>
</feed>

