Direct unsigned image uploads with Cloudinary and Ember.js

This is an update to my previous post on Direct image uploads from the browser to the cloud with Ember.js and Cloudinary. A few days after writing that, Cloudinary announced support for unsigned image uploads. This post is intended to supplement my original tutorial and only includes the pieces that need to change from those examples. For comparison, here is everything that can be removed from the example Rails API and everything that needs to change in the Ember application.

Note: You will need cloudinary_js >= 1.0.17 for this feature. If you used my example ember-cli project, you will already have this version.

Signed vs Unsigned Uploads

A signed upload, as described in my original post, is an upload request to Cloudinary that sends an extra "authentication signature" parameter with the image data. This signature is created using your Cloudinary secret key, and for security reasons, requires a server-side API request to generate it. Signed uploads are tricky to work with because they require you to have total control over the server-side application, which may not be the case if you're just building a client-side application. They are also hard to troubleshoot because of the order that things must be done in the JavaScript.

On the other hand, unsigned uploads do not require an authenticated signature and are easier to get started with because you completely bypass your API server. The way unsigned uploads work is that they use an upload_preset generated in the Cloudinary management UI, rather than the API key and secret that we used previously. According to the Cloudinary docs, "not all upload parameters can be specified directly when performing unsigned upload calls", but it seems like most of the functionality you would want is still provided. The only feature I found disabled with unsigned uploads was the ability to overwrite existing images with the same public ID.

When you add a new preset in the Cloudinary UI, it will automatically generate a "Preset name". While you're in there, you also specify settings like tags, a folder, or even eager transformations so they don't have to be created on-the-fly. All you need to get up and running is the preset name - everything else can be left with the default settings. Note that preset settings will take precedence over any options sent with the image upload request.

Environment Settings

In an ember-cli project, environment specific settings are defined in config/environment.js. We'll add a setting for the preset in addition to the existing cloud name, and remove CLOUDINARY_KEY because it is no longer required.

// omitted ...

if (environment === 'development') {
  ENV.CLOUDINARY_NAME = 'dev_cloud';
  ENV.CLOUDINARY_UPLOAD_PRESET = 'abc123';
}

if (environment === 'production') {
  ENV.CLOUDINARY_NAME = 'production_cloud';
  ENV.CLOUDINARY_UPLOAD_PRESET = '123abc';
}

// omitted

Cloudinary Config Initializer

Since you no longer need to specify the api_key, that option can be removed from the initializer, leaving just the cloud name.

export default {
  name: 'cloudinary',

  initialize: function(/* container, app */) {
    $.cloudinary.config({
      cloud_name: CatsUiENV.CLOUDINARY_NAME
    });
  }
};

Changes to the View

The Ember view we introduced previously can be simplified because the app no longer needs to load the authentication signature from the server. We don't need to wrap the Cloudinary setup in a callback that uses Ember.run either, which felt a little hacky to me.

export default Ember.View.extend({
  tagName: 'input',
  name: 'file',
  classNames: ['cloudinary-fileupload'],
  attributeBindings: ['name', 'type'],
  type: 'file',

  didInsertElement: function() {
    var controller = this.get('controller');

    this.$().unsigned_cloudinary_upload(
      CatsUiENV.CLOUDINARY_UPLOAD_PRESET, {
        cloud_name: CatsUiENV.CLOUDINARY_NAME
      }, {
        disableImageResize: false,
        imageMaxWidth: 1000,
        imageMaxHeight: 1000,
        acceptFileTypes: /(\.|\/)(gif|jpe?g|png|bmp|ico)$/i,
        maxFileSize: 5000000 // 5MB
      }
    );

    this.$().bind('fileuploaddone', function (e, data) {
      controller.set('newCat.cloudinaryPublicId', data.result.public_id);
    });
  }
});

The entire view is included above, but I wanted to point out a few changes in this particular block of code:

this.$().unsigned_cloudinary_upload(
  CatsUiENV.CLOUDINARY_UPLOAD_PRESET, {
    cloud_name: CatsUiENV.CLOUDINARY_NAME
  }, {
    disableImageResize: false,
    imageMaxWidth: 1000,
    imageMaxHeight: 1000,
    acceptFileTypes: /(\.|\/)(gif|jpe?g|png|bmp|ico)$/i,
    maxFileSize: 5000000 // 5MB
  }
);

You'll notice above that we're now using unsigned_cloudinary_upload() which takes three arguments, as opposed to cloudinary_fileupload() from the previous post which only required one. The first argument is the name of the upload preset that you create in the Cloudinary settings UI. The second argument is the list of Cloudinary-specific upload parameters that are not defined by the preset. The last argument is the list of jQuery File Upload options that we specified in the original implementation.

One of the things that confused me was the fact that Cloudinary's tutorial passes the cloud_name option to the unsigned_cloudinary_upload() function. It seems redundant to specify the value in both places, but I tried removing the cloud name setting from the config initializer and got an "Uncaught Unknown cloud_name" error.

For the sake of demonstrating where Cloudinary recommends putting it, I am specifying cloud_name in both the initializer and the view, but this is not required. In otherwords, cloud_name does not need to be passed to unsigned_cloudinary_upload(), but would still require an empty object literal to be passed as the second argument like so:

this.$().unsigned_cloudinary_upload(
  CatsUiENV.CLOUDINARY_UPLOAD_PRESET, {}, {
    disableImageResize: false,
    imageMaxWidth: 1000,
    imageMaxHeight: 1000,
    acceptFileTypes: /(\.|\/)(gif|jpe?g|png|bmp|ico)$/i,
    maxFileSize: 5000000 // 5MB
  }
);

Conclusion

Maybe I'm missing something obvious, but what prevents someone from copying your cloud name and preset name and using them to upload files from a different application? In an ember-cli application, the settings from config/environment.js are not obfuscated and can be easily viewed in the source of the generated index.html page. The only info I could find that sort of addresses the issue was a single sentence on the Cloudinary blog stating "Unsigned uploading makes this much simpler to use in modern apps, while security measures are taken to detect and prevent abuse attempts.". They don't state exactly what security measures they're taking, so I guess you'll have to take their word for it.

Assuming they have the security thing figured out, this is a much easier way to get started with direct image uploading to Cloudinary from an Ember.js app. There is no longer a dependency on the server to generate an authentication signature, which was arguably the most confusing aspect of setting this up.

Direct image uploads from the browser to the cloud with Ember.js and Cloudinary

Update 07/20/2014: I've written an additional post on unsigned uploads that builds upon this article.

Uploading images to a web application has historically sucked. If you're in the Ruby on Rails world, there are a few gems like Paperclip and Carrierwave which are great attempts at solving this problem, but are about as fun to work with as a pile of rocks. I'm picking on these two in particular because they are the most popular Rails file upload gems, and every time I have to use one of these libraries, I get pissed off.

Some of the issues I have with these gems include:

  • They try to cover every possible use case, making it difficult to figure out what code you actually need.
  • The DSLs are designed with nearly infinite flexibility, making them hard to learn and remember.
  • They require storing image meta data in the database and it's hard to remember which columns are required.
  • You're forced to manually run a server-side process if you decide you want to resize the images after they've already been uploaded.
  • They're not opinionated enough and we end up with a slightly different implementation each time.

The last point is the main reason I'm writing this post. I like opinions and hate thinking about something boring after I've done it once. This is why I use Ember.js, Rails, OS X, a car with automatic transmission, and the popcorn setting on my microwave. I also don't set the clock on my microwave, but that has nothing to do with this blog post.

Re-evaluating Our Needs

When Agilion started working on a new customer project last month, I was less than thrilled to find out that we'd need to support image uploads. This was our first Ember.js project that required image uploads, so after a short discussion among our team, we wanted to try something different.

We decided to use Cloudinary with direct image uploads to see if it made our lives any easier. The idea behind it is that the images go directly to the cloud storage host, and never pass through your server. If you've ever used Stripe for credit card processing, it's basically the same concept but for images. None of us had ever done this before, but the prospect of not dealing with storage, resizing images, and complicated documentation sounded too good to be true.

Our main goal for trying this approach was to reduce the development effort to support image uploading. Since all of our new projects are in Ember.js, we wanted to find a solution that would work well for this framework. As contract developers, we try to reduce our implementation time to keep costs down for our customers, and this seemed like a great opportunity to do that.

Background Information and a Demo

After doing some research on direct image uploads, we learned that the basic flow works like this:

  • User visits page and clicks file upload button
  • User selects one or more images
  • Images are sent directly to Cloudinary from the browser
  • Cloudinary sends back meta data for each image one at a time, including the "public id" (later used to retrieve images)
  • Browser makes API request to the server to create a new record for each image, and includes the Cloudinary "public id" in the request
  • The image is fetched from Cloudinary on demand by constructing an image URL using the public ID.

Rather than including a bunch of client-specific code that would not be fully functional, I created a simple Ember application that is backed by a Rails API. They are in separate repos because that is how we develop Ember apps at Agilion.

At the time of writing, the demo is using:

Required Pieces of the Application

Client-side (Ember)

  • Initializer with Cloudinary configuration settings
  • Cat Model
  • Cats Controller
  • Cloudinary File Input View
  • Cats Template
  • Cloudinary Image Tag Helper

Server-side (Rails)

  • API endpoint to generate authentication signature
  • API endpoint to persist a record

I've only included the most important pieces of the Ember code to reduce the length of this post. Things like the router and Cats route are omitted but can be found in the example app. I've also left out the server-side code we use to generate the authentication signature because I want this to be a server-agnostic tutorial.

Cloudinary Config Initializer

The initializer is simply telling the Cloudinary plugin what the cloud name and public key are for this application. In an Ember-CLI project, these values are stored in the environment-specific configuration. Note that there is a private key, but this is not set in the Ember app. The private key is used when creating an authentication signature (see "Authentication Signature" section) and is only included in the Rails app settings.

app/initializers/cloudinary.js - GitHub

export default {
  name: 'cloudinary',

  initialize: function(/* container, app */) {
    $.cloudinary.config({
      cloud_name: CatsUiENV.CLOUDINARY_NAME,
      api_key:    CatsUiENV.CLOUDINARY_KEY
    });
  }
};

Cat Model

The Cat model is pretty straightforward. We have a property for the name of the cat, and a property for the Cloudinary Public ID. Both of these attributes correspond to database columns on the Cat model in the Rails application. Note that both are required in the demo.

app/models/cat.js - GitHub

export default DS.Model.extend({
  name:               DS.attr('string'),
  cloudinaryPublicId: DS.attr('string')
});

The Cats Template

In the demo, the Cats template is used as both an index page to list all of the cats, as well as the location of the new cat form. I have only included the form portion here.

app/templates/cats.hbs - GitHub

<form role="form" {{action 'createCat' on='submit'}}>
  <div class="form-group">
    <label for="cat-name">Cat Name</label>
    {{input value=newCat.name class="form-control" id="cat-name" placeholder='Cat Name'}}
  </div>
  <div class="form-group">
    <label for="cat-image">File input</label>
    {{view 'cloudinary'}}
  </div>
  <button type="submit" class="btn btn-default">Submit</button>
</form>

In the code above, you can see that we are rendering the file input tag using an Ember view called 'cloudinary'.

The Cloudinary View

The Cloudinary file input view has a few responsibilities related to setup and configuration of the Cloudinary widget. Here is the entire view in one piece, but it is broken down and explained in detail further down:

app/views/cloudinary.js - GitHub

export default Ember.View.extend({
  tagName: 'input',
  name: 'file',
  attributeBindings: ['name', 'type', 'data-form-data'],
  type: 'file',

  didInsertElement: function() {
    var _this = this,
      controller = this.get('controller');

    $.get(CatsUiENV.API_NAMESPACE + '/cloudinary_params', {timestamp: Date.now() / 1000}).done(function(response) {
      // Need to ensure that the input attribute is updated before initializing Cloudinary
      Ember.run(function() {
        _this.set('data-form-data', JSON.stringify(response));
      });

      _this.$().cloudinary_fileupload({
        disableImageResize: false,
        imageMaxWidth: 1000,
        imageMaxHeight: 1000,
        acceptFileTypes: /(\.|\/)(gif|jpe?g|png|bmp|ico)$/i,
        maxFileSize: 5000000 // 5MB
      });

      _this.$().bind('fileuploaddone', function (e, data) {
        controller.set('newCat.cloudinaryPublicId', data.result.public_id);
      });
    });
  }
});

First, it is responsible for loading the authentication signature from the Rails API:

$.get(CatsUiENV.API_NAMESPACE + '/cloudinary_params', {timestamp: Date.now() / 1000}).done(function(response) {
  Ember.run(function() {
    _this.set('data-form-data', JSON.stringify(response));
  });

  // ...

It appends the returned signature object to a data attribute on the file input HTML element called "data-form-data". Note that this is done within an Ember.run function to ensure that it is finished before initializing up the Cloudinary file upload. This was something that stumped us at first, and I'm sure there's a better approach.

After the signature data is set, the view initializes the Cloudinary file upload widget on the input. Here is where we define the settings for our project such as the maximum file size allowed, accepted file types (images) and the maximum dimensions of the image (it automatically scales them down). Additional options can be found in the jQuery File Upload documentation.

_this.$().cloudinary_fileupload({
  disableImageResize: false,
  imageMaxWidth: 1000,
  imageMaxHeight: 1000,
  acceptFileTypes: /(\.|\/)(gif|jpe?g|png|bmp|ico)$/i,
  maxFileSize: 5000000 // 5MB
});

The last functionality this view is responsible for is handling the Cloudinary response and setting a cat model's Cloudinary public ID. Cloudinary sends various events to the input including progress and completion, to which you can bind functionality. In the demo, we're just binding to the 'fileuploaddone' event and setting a controller property when it's complete. Additional callback options can be found in the jQuery File Upload documentation.

_this.$().bind('fileuploaddone', function (e, data) {
  controller.set('newCat.cloudinaryPublicId', data.result.public_id);
});

The Cats Controller

In the demo, the cats controller is a simplified version of what you would use in a production application. By this point, the view has already set the public ID for the cat's image, and we are just persisting the cat with ember-data and then creating a new Cat record so the form has a different object.

export default Ember.ArrayController.extend({
  // omitted ...

  actions: {
    createCat: function() {
      var _this = this;

      this.get('newCat').save().then(function() {
        _this.set('newCat', _this.store.createRecord('cat'));
      });
    }
  }
});

Authentication Signature

Another thing that tripped us up was the process for authenticating the Cloudinary image upload request. Cloudinary requires that you generate an authentication signature on the server and pass it to the client before initializing the file upload widget. It wasn't clear from the documentation why this needed to be done or what was actually required to do it.

The authentication signature is a property that is sent to Cloudinary with the images, and is used to authenticate your request. It seemed counter-intuitive at first that we were depending on the Rails API for something that was supposedly 100% client-side. From a security standpoint, it makes perfect sense because the private key would not be private if it was included in the JavaScript source code.

When the page loads, our application makes a request to the server (via the Cats view) that generates an authentication signature. I've omitted the controller code to generate the JSON, but the request will look something like this:

http://localhost:3000/v1/cloudinary_params?timestamp=1405265520.34

That request returns a response that looks like this:

{
  "timestamp":1405265520,
  "signature":"4a49f7e9009924b0d811e9bdc8798ca19cdb2da4",
  "api_key":"123456789012345"
}

Note that the API key is the same public key that is also set in the Cloudinary initializer above.

As described in the Cats view section, this entire signature object is appended to a data attribute on the file input field, and the Cloudinary upload widget automatically sends it with the image data when a user selects an image.

Viewing the images

Another frustrating thing about the Cloudinary documentation is that everything we needed for this project was scattered among at least three different sources. After we had image uploads working, the docs we were referencing assumed we would be using our server-side framework (Rails) to render our HTML templates. Since Ember uses Handlebars and the templates are rendered 100% client-side, we needed a different solution.

In order to view images that are stored in Cloudinary, you need to construct a custom URL that includes the image's public ID, and any options such as image size and scale. We ended up creating a helper to handle this functionality, but in retrospect, a component may have been a more semantic option for us. Additional options for constructing the URL can be found in the jQuery image manipulation documentation.

app/helpers/cloudinary-tag.js - GitHub

export default Ember.Handlebars.makeBoundHelper(function(publicId, options) {
  if (Em.isBlank(publicId)) { return ''; }

  var height = options.hash.height || 535,
    width = options.hash.width || 535,
    crop = options.hash.crop || 'fill',
    cssClass = options.hash.class || 'cloudinary-image';

  return new Ember.Handlebars.SafeString($.cloudinary.image(publicId, {
    width: width,
    height: height,
    crop: crop,
    class: cssClass
  }).prop('outerHTML'));
});

Again, I've omitted the less relevant template code and only left in what is required to render the HTML image tag:

app/templates/cats.hbs - GitHub

{{#each persistedCats}}
  <div class="col-lg-6">
    <div class="thumbnail">
      {{cloudinary-tag cloudinaryPublicId}}
      <div class="caption">
        <h3>{{name}}</h3>
      </div>
    </div>
  </div>
{{/each}}

Conclusion

While not necessarily specific to this approach, the biggest downside I can see is that you're locking yourself into a single vendor. The pricing is reasonable for smallish applications that are just getting off the ground, but if you expect your users to upload thousands of large images, you'll quickly find yourself in Cloudinary's "Enterprise/Custom Plan" (which I suspect is significantly more expensive than straight up Amazon S3). During testing, we started on the free plan which includes 500MB of total storage and 1GB of monthly bandwidth. We ended up blowing through the 1GB of bandwidth in a couple days and had to upgrade to the $35/month "Basic" plan. In Cloudinary's defense, we were uploading large images during testing, which was not really necessary since we could have just been testing with much smaller file sizes.

Despite some initial frustration with the Cloudinary documentation, my overall impression is that this is the future of image and file uploading in JavaScript web applications. By not depending on the server to process image and file uploads, the API code is simpler and easier to maintain. Pushing this responsibility onto a cloud host like Cloudinary allows us to develop faster and focus on functionality that is relevant to our customers' applications.

Introduction to Command-line Brace Expansion

The command-line is a versatile interface that provides numerous tools for managing files and directories. Due to its flexibility, renaming and copying files can be tedious and error-prone, especially when files are nested deep within a file system. Luckily we can solve this problem with a helpful feature called brace (or bracket) expansion.

Brace expansion, {}, is a method to create arbitrary strings. When combined with other commands, it can be a very powerful addition to a developer's toolbox.

Copying Files

The first example we'll look at is for copying files. Let's start by creating an empty file called a.txt that we'll copy to a file called b.txt.

touch a.txt

Just to confirm we have a single file named a.txt, we can use ls -lh and see the follow output:

-rw-r--r--  1 peterbrown  staff  0 Jul  3 19:04 a.txt

When copying files, the cp command requires two arguments (source and destination), and looks like this:

cp a.txt b.txt

The result from that command will look something like:

-rw-r--r--  1 peterbrown  staff     0B Jul  3 19:04 a.txt
-rw-r--r--  1 peterbrown  staff     0B Jul  3 20:18 b.txt

Now let's look at the same example, but this time using brace expansion:

cp {a,b}.txt

Aside from the timestamp, the result from this command will be nearly identical to the one before:

-rw-r--r--  1 peterbrown  staff     0B Jul  3 19:04 a.txt
-rw-r--r--  1 peterbrown  staff     0B Jul  3 20:19 b.txt

While the results are identical, instead of specifying two separate file names, we have used curly braces to copy the file with a single argument. The first item in the braces, a, is the part of the original file name that we want to match, and the second item, b, is what we want to replace it with in the new file. Any text immediately surrounding the braces (in this case .txt) will be combined with the expansion.

How Does it Work?

At first glance this syntax may seem foreign, so let's use the echo command to inspect what is happening behind the scenes:

echo {a,b}.txt

This results in the following output:

a.txt b.txt

In the output above, you can see that the comma-separated values within the braces are combined with the surrounding text, expanding it into two separate file names - exactly the input used previously to copy the a.txt file to b.txt.

If we combine the output from echo with cp, we have a command that is identical to the following:

cp a.txt b.txt

Creating Backup Files

Brace expansion can be used with any commands that accept multiple arguments such as cp, mv, or even git mv. One use case for it is to quickly create backup files by adding an extension such as .bak.

Instead of having to type the same file name twice, and potentially spelling it wrong, we can use braces to add the extension:

mv a.txt{,.bak}

Which results in:

-rw-r--r--  1 peterbrown  staff     0B Jul  3 19:04 a.txt.bak
-rw-r--r--  1 peterbrown  staff     0B Jul  3 20:19 b.txt

Notice that in this example there is no value before the comma. This is because we are not replacing existing parts of the file name, and are just adding a new extension to it.

Renaming Files in Git

Another common use is to rename files that are versioned with Git. Let's create a quick Git repo and add our files to it:

git init
git add a.txt.bak b.txt

With git status we can see that both files have been staged:

On branch master

Initial commit

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)

    new file:   a.txt.bak
    new file:   b.txt

Now we can use git mv with brace expansion to rename one of the files. This time there is no value after the comma because we are removing part of the file name.

git mv a.txt{.bak,}

Using git status again, we can see that a.txt.bak is now named a.txt.

On branch master

Initial commit

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)

    new file:   a.txt
    new file:   b.txt

Wrap Up

We've just barely scratched the surface of what is possible with brace expansions. As illustrated, this syntax can be combined with many of the common commands you use on a daily basis. I encourage you to explore them further and see if you can come up with a a clever use case on your own.

The History of a Four-Year-Old Ruby Gem

This is a story about learning, taking pride in your code, and tastefully sprinkling profanity into otherwise boring blog posts. It started out as an introduction to ClassyEnum, but I hate writing code examples, so this is what you get instead.

The Early Days

Four years ago I was working on a rewrite of a PHP application in Rails. I had no idea what I was doing. Subversion was still something startups used (I keep telling myself that).

We were building software for monitoring commercial-scale solar power plants, and I was working on a feature related to detecting data anomalies like low power conditions and equipment failure. When an anomaly was detected, some sort of action would be taken depending on what kind of alarm it was. Each type of alarm could be individually configured with different settings for which action should occur when the alarm was triggered. Some alarms would just be logged to the database, others would send an email, and a few even required manual verification from a user.

At the time, I was a total noob to application architecture and had been taught in school that redundancy in a relational database was a bad thing and that all data should be as normalized as possible. One of the techniques I learned was to use a "lookup" table in order to keep modifications limited to a single table. In theory this sounds like a good idea because it allows you to make changes in one place and have it update everywhere. In practice, obsessing over database normalization is a giant turd of an idea that academics and DBAs preach because that's what expensive textbooks say to do. Suck it normal forms.

Here's a simplified example of what our tables looked like:

We had the alarms table to define the various types of anomalies being monitored for a particular project, and the alarm_actions table defining what to do when an alarm was activated. The alarm_actions table consisted of four or five records with a few combinations of the different settings.

We had hired a contractor at this time with real-world software development expertise who basically told us our design sucked. He brought up the concept of enums in Java and how they would be better than dealing with join tables and managing application logic. My first reaction was "no fucking way am I writing Java code in Ruby", but I'm a rational person so I listened to him. After all if it weren't for Java we wouldn't have the Eclipse™ IDE.

(╯°□°)╯︵ ┻━┻

He put together an initial proof of concept to show us how we could replace the lookup table with an enum class. He also showed us how we could delegate logic to this class (ala polymorphism) instead of having conditionals scattered throughout our codebase. This changed everything. I took his example, put it into a module, and got rid of the lookup table.

Here's a quick survey about your application and database architecture to figure out whether you really need a lookup table:

  1. How often do you CRUD records in the lookup table?
  2. How much of your application logic depends on the state of that table?
  3. If you removed the data in this table, would your application still run?
  4. How many records are in this table?
  5. Is it hairballs or hair balls?

If you answered "never" or "rarely" to the first two questions, "fuck no" to the third and "less than ten" to the fourth, you've got a great candidate for denormalizing that table and replacing with ClassyEnum (or a perhaps a state machine). I threw the last one in there just to see if you were paying attention. I'd recommend checking out the README for an overview on how to get started with this.

For the sake of this being a complete history lesson, here's a gist of my first iteration which would later become ClassyEnum. I removed the application specific code so it's not 100% complete, but you can tell how much I sucked at Ruby because the ActiveEnumValue class explicitly inherits from Object. In my defense, the contractor had it that way in his concept code. In his defense, he was a Java developer.

After going through a few iterations in our application, ClassyEnum 0.0.1 was released on September 21, 2010 (a Tuesday if you were wondering).

I originally named it ActiveEnum, but when I extracted it to a gem, there was already a gem with the same name. Ironically that one never made it to version 1.0. Also ironically, there's another gem called enum which was released exactly 11 days before ClassyEnum, based on the exact same Java enum concept. History lessons are fun.

The Mid-life Crisis

Fast forward to the spring of 2012. Our application was in production and I was a much more seasoned Ruby developer than I had been two years prior. I knew that all classes inherit from Object and you don't need to do this explicity. I was also much more knowledgable in the subject of object oriented programming in general. As controversial a topic as test driven development is these days in the Ruby community, I have to credit it with my understanding of OOP and specifically concepts like encapsulation) and designing interfaces). I was also very interested in DSLs at the time and what makes one "better" than another.

With my new found skills, I decided to rewrite ClassyEnum from the ground up and think about the experience from the perspective of someone who is using it for the first time. There were a few goals I had in mind: 1) It had to be simpler and more intuitive, 2) compatible with every major version of Rails, 3) future proof to require less changes when new versions of Rails came out, and 4) I looked it up, and it is indeed 'hairballs'.

At a time when my favorite gems were getting more and more bloated (ahem), I wanted to simplify. I cut out features such as built-in Formtastic support, and reduced the DSL to a single model declaration. I even declined pull requests adding new features I didn't want, despite the hard work that people put into their code. Telling someone why their idea sucks in a courteous way is not always easy.

I made the mistake of releasing 2.0 too soon and was not happy with the results. I ended up cutting even more out and releasing 3.0 three months later. I take semantic versioning seriously, and was not experienced enough to know when I was satisfied with the functionality.

The Latter Days

Fast forward another two years to today. I'm no longer working on the monolithic application I started back in 2009. These days I am contracting and typically work on one or two different Rails applications a month. However, for the last year nearly every single application I have worked on has included ClassyEnum.

According to rubygems.org it has been installed over 80,000 times and had 45 releases. Yet despite the history and popularity, only 38 issues have been opened in four years (all but two are closed). The reason I mention this is because I take pride in releasing stable code that is well tested and documented. I want it to be easy for new comers to pick up, and I take every bug report seriously.

I believe my strict policy of only adding features that have inherent value and cutting ones that do not has helped me sustain development on it. While new releases have slowed in recent years, its usage has only increased, and I see this as a sign of maturity and stability. I don't want to add features just for the sake of adding them because the person who has to maintain them is me.

I really like this quote from the Semantic Versioning 2.0 FAQ:

Having to bump major versions to release incompatible changes means you'll think through the impact of your changes, and evaluate the cost/benefit ratio involved.

I now spend a lot more time letting releases "ferment" before making them final. This is why we have betas and release candidates.

I encourage you to think about a project you're working on, whether it be open source or otherwise, and ask yourself how you can make the experience better for yourself and your fellow developers today, tomorrow, and well beyond its expected life.

My Year in Review

I've seen a few great posts today from people reflecting on some of their accomplishments and favorite things from 2013, so I was inspired to put something together myself. Instead of just listing things off, I thought it would be fun to go through my Twitter feed and pull out some of my favorite tweets. Some of them were accomplishments, others were fun things I did, and some were just ridiculous things that popped into my head. Hope you enjoy!

On Quitting My Job

The end of 2012 was not a great time for me. Lots of things were going well in my life, but I knew I was not happy at my current job. I wasn't learning anything, I was constantly frustrated, and to be honest, I was pretty depressed. I decided to quit my job and form a partnership with some of my coworkers doing contract web development through our own company.

This was exactly the change that I needed. Working remotely has had a hugely positive impact on my life. It's given me freedom and flexibility, challenged me with new and exciting projects, and just made me an overall happier person. I live in VT for the quality of life, and now I feel like I am able to enjoy it more than ever.

On Having a Sick Dog

The beginning of 2013 was an exciting time for me with the new job, however, my dog got really sick the week I started. We didn't know what was wrong with him for a few days. Then finally...

The poor guy had to spend the evening at the emergency vet recovering, making sure he didn't have any real damage from the pine cone. He did make a swift recovery though, and was back on his feet in no time.

On Feeling Better

Speaking of not feeling well, I gave up dairy in the fall of 2012. It wasn't something I wanted to do, but was something I needed to do. I had been feeling sick on and off for a number of years, and finally decided enough was enough, and gave it up entirely. 2013 was the first full year on a dairy free diet and I can truly say I've never felt this well in my entire life. I've lost a ton of weight and am cold pretty much constantly, but it's a small price to pay to actually feel well.

On Holding Babies

I feel like everywhere I look, someone is having a baby, and for whatever reason, people really like holding them. I had never held one before, so I wanted to see what all the fuss was about. No one told me what to do with it, and I figured you can't pet it like a cat, so I just tried my hardest not to drop it.

On Being Sad (Panda)

A friend/neighbor/former coworker of mine lost his dog in March. And by lost, I mean he died. I was sitting by Gordie's side during the final moments of his life, and it was one of the saddest things I've ever seen. This experience made me appreciate the things I have more than ever.

On Taking Vacations

My wife and I went on some fun vacations this year. Despite my immense fear of flying and rattlesnakes, my wife convinced me to fly out to Big Sur, CA where they apparently have rattlesnakes everywhere. It was like snakes on a plane, except the snakes weren't on the plane (that I knew of).

We also went to Bar Harbor, ME in the fall. We've been going there every year since our honeymoon, and plan to go back again in 2014. My wife and I are both originally from Cape Cod, so I think what attracts us here is the combination of mountains and ocean. Acadia National park a beautiful place!

On Selling Drugs

The series finale of Breaking Bad wouldn't be for another 4 months, but my neighbors were cooking up a storm of meth right down the street from me. I live in a quiet residential neighborhood just North of downtown Burlington, and never would have thought to see something like this. Turns out they had been cooking meth with their child in the apartment. Pretty fucked up shit.

On Receiving Tweet of the Week

The optimist in me got "tweet of the week" in Seven Days last summer. It wasn't my favorite tweet of the year, but I think it sums up the weather in VT pretty well. As I'm typing this, we're looking at a low of -14°F this week. I also thought up "ForeverAloneSquare", which was my response to people who tweet FourSquare checkins. Please stop doing this folks. If I wanted to know where you were, I'd use FourSquare.

On My Dog Turning 5

I love my dog. I mean the cats are great and all, but bonding with a dog has been one of the most rewarding experiences of my life. Sebastian turned 5 this summer and I'm hoping he makes it past my 40th birthday to see what I look like when I'm fatter and balder.

On Making Friends

I joke a lot about not having friends and being alone, but in reality I have a lot of really awesome people in my life. It wasn't a resolution of mine to make new friends or be more outgoing, but that was definitely one of the themes for me this year. The conference I organized was a huge part of that. I've also been involved in organizing many of the local software user groups and have met people through those as well. I am really looking forward to doing the conference in 2014, and can't wait to share the experience with everyone again.

Laughter is the Best Medicine (besides Percocet)

Life is too short to take seriously all the time. Here are a few of my favorite tweets that still make me chuckle.

Happy New Year and thanks for reading!