Channels
Displaying page 5
Posted 15 days ago at Intridea - Company Blog

Warning: I am not an experienced Node developer and I may be doing this completely wrong. However, I am happy to publish this anyway with the hope that more learned Javascripters will correct my naive ways.

The Node Knockout is this weekend and I've been trying to teach myself Node and get ready in a variety of ways. One of the most important (and least clear) aspects of preparation was figuring out how to properly vendor the latest Node libraries for use in Heroku. I've got NPM up and running locally, but as of its latest release it has no built-in support for vendoring. Here's how I managed.

NPM's Command Options

NPM provides a few helpful command options that let you specify the directory of installation when you install a library. By using the --root and --binroot options you can use a folder inside your local project instead of the default NPM root. For example, in my project I created a vendor folder and then, to install Express, specified:

npm install express --binroot ./vendor --root ./vendor

Now while this seems like it might do exactly what we want, it's not quite perfect. Rather than install everything in vendor, it installs the important stuff in vendor/.npm/.cache. This isn't the end of the world but can make for some pretty ugly require statements.

The Require Blues

So now you've got your packages all nice and installed inside your project directory (probably a lot of support files that you don't need, as well, but the Node slugs on Heroku tend to be small anyway so no biggie). Now you need to include them in your application. To do this, I wrote a quick function, vrequire that adds the necessary load path and then requires the library all at once:

vrequire = function(lib) { 
  require.paths.unshift("vendor/.npm/" + lib + "/active/package/lib");
  return require(lib); 
}

Now if I call vrequire("express") it will load up from my vendored library. If I deploy my app to Heroku, it's able to find everything it needs and (cross your fingers) launch and work correctly!

Hopefully some people find this little guide useful, and I look forward to someone pointing me to the "real" way to do this stuff! If you want to poke around the exploratory code I've been creating to play with Node, you can find it on GitHub (but don't expect much!).

back to top

Selene: Asynchronous SSL/TLS:

A common issue when working with SSL/TLS libraries is that they are synchronous, you ask the library to do something, then have to wait for it to finish before you can do anything else, Selene by Paul Querna aims to change this.

Selene is an C library that sits on top of your SSL/TLS library — currently only OpenSSL is supported — providing an asynchronous API for interacting with it. Currently selene is in an early stage of development, but aims towards the following goals:

  • Completely Asynchronous: it does no IO itself, but provides notifications that IO should be done.
  • Test Driven Development: A test framework to test all code paths.
  • Pluggable Backends for Cryptography: Currently only focusing on OpenSSL, but others are possible.

Selene is definitely something that you are probably going to want to checkout if you’re working with SSL/TLS and want to get the best performance and throughput possible.

[Source on Github] [Mailing List]

back to top
Posted 15 days ago at Riding Rails - home

The release candidate process is progressing as planned. This second candidate has very few changes over the first, which means that unless any blockers are discovered with this release, we’re targeting the final release of Rails 3.0 for this week(!!!).

So please do help us weed out any blockers. Especially in our two new main dependencies: Bundler and ARel. They’ve both progressed into release candidacy for their 1.0 releases and will be sharing the same 1.0-final release date as Rails 3.0.

You can see a complete list of all the dotted t’s and crossed i’s on the new fabulous Github comparo view of RC1 and RC2.

As always, you install this latest version with: gem install rails --pre

Also note that Rails 3.0 now has it’s own stable branch. The master branch is now reserved for Rails 3.1 development. (That’s right, we’re already going there and it’s going to be M-A-G-I-C-A-L!).

back to top
Posted 15 days ago at Ruby Inside

SimpleCov is a code coverage analysis tool for Ruby 1.9. It uses 1.9’s built-in Coverage library to gather code coverage data, but makes processing it’s results much easier by providing a clean API to filter, group, merge, format and display those results, thus giving you a complete code coverage suite with just a couple lines of code.

Christoph Olszowka

SimpleCov builds on the ideas presented in Aaron Patterson's recent, and awesome, Writing a Code Coverage Tool with Ruby 1.9 blog post. The result is a powerful yet straightforward code coverage tool that puts out some well formatted stats.

back to top
Posted 15 days ago at JustinFrench.com - notebook

In Readme Driven Development, Tom Preston-Werner advocates writing the README file before any code is written, and indeed, before any tests are written.

I couldn’t agree more. This is exactly what I did with Formtastic. I designed the DSL and how I thought programmers should build forms long before I wrote any code.

I think this has had a huge impact on the success of Formtastic so far. People may dislike the underlying mark-up choices I made, or the underlying code implementation (which definitely needs some TLC), or the lack of in-depth documentation, but for the most part, people love the DSL.

José Valim even proudly choose to replicate the Formtastic DSL in simple_form his own form builder for Plataformatec.

What Tom is really advocating is a small amount of up front design to figure out how people will use your software in order to give it vision and clarity before you start pumping out code. A README file is cheap. Code is relatively expensive.

Here’s a quote from 37 Signals’ Interface First in Getting Real:

We start with the interface so we can see how the app looks and feels from the beginning. It’s constantly being revised throughout the process. Does it make sense? Is it easy to use? Does it solve the problem at hand? These are questions you can only truly answer when you’re dealing with real screens. Designing first keeps you flexible and gets you to those answers sooner in the process rather than later.

It’s ridiculously easy to draw comparisons here. Design your library’s interface first!

back to top
Posted 15 days ago at Ruby Inside

http://github.com/jeremyevans/home_run (or on Ruby Inside)

home_run is an implementation of ruby’s Date/DateTime classes in C, with much better performance (20-200x) than the version in the standard library, while being almost completely compatible.

Jeremy Evans

Jeremy Evans (of Sequel fame) has created home_run, a performance-focused C reimplementation of Ruby's Date and DateTime classes. They work out to be significantly faster than the native Ruby classes while retaining compatibility (mostly).

Jeremy makes the surprising claim that "the standard library Date class is slow enough to be the bottleneck in much (if not most) of code that uses it", but goes on to prove the point with a Rails related benchmark where retrieving all objects for two different models get a 2x and 3x speedup when using home_run.

I hope that Jeremy ultimately aims to get his work included into MRI (as happened with FasterCSV replacing CSV in the stdlib) as standard.

back to top
Posted 16 days ago at Engine Yard Ruby on Rails Blog

This post comes from guest community contributor Kent Fenwick. Kent is the tech co-founder of of Viewpointr, a personalized Q&A service that aims to provide an easy way to get and give help. When he isn't programming, he spends time with his family and friends in Toronto. Kent writes here and can be followed on Twitter at @kentf.
It's getting more and more difficult to pick a persistence layer for your web application. When I started in Rails four years ago, there was really only one option, MySQL. Now, there are many more, each with their own pros and cons. Some are new and some are old, some are tested, and others, not so much. What's clear is that when you are building a business around data, you want to make good decisions. That being said, often only the future will tell if you've made the right ones. I want to share with you my persistence story about how I ended up getting the best of both worlds. h2. The Problem There are too many choices and each choice has a loud evangelist of its own. When designing Viewpointr I went go back and forth daily between MongoDB, MySQL, PostgreSQL and Cassandra. Viewpointr is essentially Twitter with a focus on helping people. Therefore, we have some common data elements: a user specific time line, a user specific list of people who they are helping, and a user specific list of people helping them. Because I am ambitious, I would find myself asking questions like: bq. "Hmm... but will MySQL scale to 1,000,000 records?" Looking back on these internal conversations I find them funny; programmers always tend to think big. However, these are real concerns that developers and teams think about. While planning I would constantly consult the blogosphere for help, and to see what others were doing. Kirk Haines of Engine Yard wrote a great series of NoSQL posts highlighting and comparing different key-value stores and explaining their pros and cons. Since then, there has been a flurry of articles each week outlining different NoSQL datastores, NoSQL vs. MySQL debates and flamewars etc. h2. The Opportunity Data is not created equal and this is a good thing. The same way we do not use an array for every "list" type problem when programming, sometimes hashes or linked lists will better suit the needs of the problem. We need to start thinking about data the same way. This was the best decision we made at Viewpointr and it allowed us to move forward at a great pace. I looked at our application and broke it down into components. Viewpointr has many typical CRUD features similar to all Rails apps. These are very well designed for MySQL and a relational database. Being able to pull a list of answers based on a given question using simple and optimized SQL that I understand is a big win. However, there are some things that it doesn't model well. Friendships. The simplest way to model friendship using a relational database is to create a relation that refers to the same table with two different names. Let's say you have a users table and you want to model Twitter-like friendship where User:1 can befriend User:2 without User:2's permission. It's easy enough.
class Friend < ActiveRecord::Base

 belongs_to :user
 belongs_to :contact, :class_name => "User", :foreign_key => "contact_id"

 # user befriends contact
 def self.befriend(user,contact)
    relationship = find_by_user_id_and_contact_id(user.id,friend.id)
    if relationship.nil?
      transaction do
        Friend.create(:user => user, :contact => contact)
      end
    end
 end

end

class User < ActiveRecord::Base

  has_many :friends, :dependent => :destroy
  has_many :contacts, :through => :friends, :order => "created_at DESC", :dependent => :destroy

end
However, I have always felt that it's clumsy. What I really want to say is: "Each user has a list of IDs that represent the people that they are friends with." Sounds like a de-normalized list right? h2. The Solution Enter Redis. Redis is a key-value store similar to memcached but more flexible since lists, sets, ordered sets and strings can all be used as values. Thanks to its simple API, the problem I described is essentially an atomic operation in Redis. Redis has a great "set" implementation and allows you to do all of the things you would imagine a set to do: addition, subtraction, unique insertion, deletion, union, intersection, etc. The operation will ultimately look like this:
SET = Redis.new
SET.set_add key, value
However, since we are working inside a Rails app, we need to make sure we have the right plumbing setup. # Create a redis.rb in your initializers folder. # Create a new Redis database for each of your needs. In our case, we want to have a dataset that keeps track of a User's helpers (other users who are helping them) and a list of a User's friends (other users that the user is helping). Since we are going to be using these Redis objects throughout the codebase, I like to declare them as global variables in the redis.rb initializer file.
HELPERS = Redis.new(:db => 0)
HELPING = Redis.new(:db => 1)
Notice that I pass in the :db key so that we make sure HELPERS and HELPING will hold two different Redis objects. You can use redis-namespace gem if you want, but I find the default syntax from the redis-rb gem works well enough for my purposes. Now that we have these global Redis objects at our disposal throughout the application, we can start using it in our Friend.befriend method.
class Friend < ActiveRecord::Base

 belongs_to :user
 belongs_to :contact, :class_name => "User", :foreign_key => "contact_id"

 # user befriends contact
 def self.befriend(user,contact)
    begin
     HELPERS.set_add contact.id, user.id
     HELPING.set_add user.id, contact.id
    rescue
     RedisLogger.info "Redis Exception"
    end
 end

end

class User < ActiveRecord::Base

  has_many :friends, :dependent => :destroy
  has_many :contacts, :through => :friends, :order => "created_at DESC", :dependent => :destroy

end
However, this isn't the best solution right out of the gate. Using a NoSQL datastore has some drawbacks that aren't apparent in development mode but reveals its ugly face in production. If you are not careful, a simple restart of your Redis server can cause you to loose all your data. Managing your Redis data in production deserves it's own post, (coming soon) but for now, let's create a safer solution that you can gradually roll out as you become more comfortable with storing, backing up and using Redis datafiles.
class Friend < ActiveRecord::Base

 belongs_to :user
 belongs_to :contact, :class_name => "User", :foreign_key => "contact_id"

 # user befriends contact
 def self.befriend(user,contact)
    relationship = find_by_user_id_and_contact_id(user.id,friend.id)
    if relationship.nil?
      transaction do
        Friend.create(:user => user, :contact => contact)
      end
    add_to_denormalized_list(user,contact)
    end
 end

  def self.add_to_denormalized_list(user,contact)
    begin
     HELPERS.set_add contact.id, user.id
     HELPING.set_add user.id, contact.id
    rescue e
      RedisLogger.info "Redis Exception"
    end
  end

end

class User < ActiveRecord::Base

  has_many :friends, :dependent => :destroy
  has_many :contacts, :through => :friends, :order => "created_at DESC", :dependent => :destroy

end
The strategy is simple, mirror the MySQL data in Redis. By adding a call to add_to_denormalized_list, we mirror the ActiveRecord call using the simple and elegant Redis set syntax discussed above. As you and your team get more practice and become more comfortable using Redis in production, you can start writing more to the denormalized list, eventually moving this part of your application away from ActiveRecord and MySQL to Redis. You could do this manually or you can use James Golick's recently released gem called Rollout that uses, you guessed it, Redis, to programatically rollout features to users. Like anything else you code, testing and benchmarking this process in production is crucial to make sure you are saving time and cycles. It might seem like a waste to duplicate your data in Redis, but you are a pragmatic polyglot persistence developer right? You want to explore the NoSQL space while making sure that a little mistake or misunderstanding doesn't sink your ship. Give something like this a try, it doesn't get any more pragmatic. When do you try it or come up with something new, let me and everyone else know about it. Thanks for reading.

back to top
Posted 16 days ago at The Geek Talk

Who is Dion Almaer?

I am a Brit who came over to the US for college. I took my American wife back to life in London for a year, and have lived in each of the timezones in the continental US, ending up in silicon valley where I reside now. I grew up playing cricket and football… and still find it strange that my kids speak with an american accent. I was a Chemical Engineer who found computers to do computational research with Fortran, and quickly found that more fun that the chemistry I was doing.

Where and when did you start programming?

My first programming was on my older brothers Sinclair Spectrum 48k. I remember getting games as code on cards, and typing in the BASIC. 20 GOTO 10 FTW! I still miss that old rubber keyboard!

About Ajaxian project?

Ajaxian started out as a place to host code samples that Ben and I used in our presentations on “Building killer websites with DHTML”. The title of these talks changed the day that Ajax was coined, and we did a lot of s/DHTML/Ajax/ on the slides. The attendance to our talks grew immediately as the developers who were so focused on the server side for innovation again realized that the client could do cool stuff now! The client side Web was now a real platform.

In my Enterprise Java past I was editor in chief of TheServerSide.com, and loved developer communities. These genes kicked in again and I started to post about the awesome hacks and experiences developers were producing with “Ajax”, and Ajaxian.com was born.

What does your typical day look like?

Multitasking. I try hard to keep to my personal brand of GTD to get through the day moving forward. No day it typical these days though. I may be speaking at an event. I may be in meetings discussing the Web platform. I may be working with my Developer Relations team on how we can participate in furthering the Web ecosystem. Or, I may be writing code, building Web applications and trying to push the envelope in some way. I normally do a double shift, and when the kids are in bed, make sure I finish the day off with a dose of TechMeme, Hacker News, and Twitter.

What do you do in your free time?

Free time. Hmm. :) Most of that is spent with my 2 kids these days. My oldest is four and is a ton of fun. We can play football together now, or Angry Birds. Trips to Legoland for Star Wars weekend are on the cards… or hitting the beach for some fun in the sand. My youngest is 10 months and is now getting into trouble as he toodles around and tries to grab anything the 4 year old cares about.

Current favorite apps?

Rapportive is a social plugin for Gmail and is awesome (as I discuss here). Everyone who uses Gmail should install it.

On webOS I enjoy the Facebook app that my team builds, TweetMe (a beautify Twitter app), and playing with some of the great games on our platform. I keep up with them via the Fast Movers tab on our PDK Hot Apps promotion.

On the iPad I enjoy watching my son play Angry Birds, Plants vs. Zombies, and setting up the Orion telescope with a sky map app.

What OS do you prefer?

I am a Unix lover, and switched to Mac OSX many moons ago. I am happily not recompiling kernels anymore…. and instead have the power of Unix under a nice shell.

Small picture for your Workplace?

I share an office with Ben Galbraith, my best friend and partner in crime at HP and beyond. He was my co-founder at Ajaxian, and we worked together at Mozilla too hacking on Bespin. We stock up our fridge, and pump up the music when the door is closed. Sometimes the “music” is something like

back to top
Posted 16 days ago at The Geek Talk

Who is Kyle Neath?

Kyle Neath is a bit of an idiot. A designer. A hacker. A drummer. Some guy who lives in San Francisco.

Where and when did you start programming?

If you don’t count fooling around with QBASIC (never really did anything of value there) — sometime in middle school (around ’96). I was really into online games like [Utopia][u] and [Dominion][d]. Eventually I wanted to build my own, so me and some friends started writing up stories and started coding up a prototype in ASP 3.

u: http://utopia-game.com/shared/
d: http://www.kamikazegames.com/dominion/

What does your typical day look like?

I don’t have anything close to a regular sleeping schedule. Some days I wake up at 6am, others I’ll get up at 3pm. I threw out of my alarm clock a couple months ago and it was probably the best decision of my life.

After I wake up, I make a pot of French Press and sit down at the computer reading emails, catching up on twitter replies, and any support requests that come in.  Most days I head to the office. I usually walk most of the way there (it takes me about 20 minutes) — I’ve found it’s a great time to clear my head.

When I get to the office I walk in and one of my co-workers probably screams out some obscenity about dick noodles or the like.  I work for a while and head home. Some days I stop by the grocery store on the way home, other times I try and make dinner out of whatever nonsense is in my cupboard.

What do you do in your free time?

I hack a lot of crazy ideas that never see the light of day. I try to play drums fairly regularly (I have a practice studio downtown). I enjoy cooking. I wish I read more than I did.

Current favorite apps?

By far, my favorite set of applications is a fleet of OSX, Rails and iOS apps developed by my friend [John Maddox][maddox]. They’re not public (and probably will never be), but they’re amazing.  After that, probably the usual suspects (Photoshop, Things, Chrome) and the Kindle apps. Being able to read on my iPad, iPhone and Mac all in sync without trying is mind blowing.

maddox: http://www.jonmaddox.com/

What OS do you prefer?

OSX and iOS.

Small picture for your Workplace?

At the office:

Office

At home:

Favorite: Language, JS Framework?

Currently probably Ruby and Javascript. I don’t expect that to last long. JS Framework is probably MooTools. I wish had more chances to play with it.

Name something that has inspired you recently?

The Pixar Story (documentary).

What do you prefer (and why)? Freelance work or full time employment?

Full Time employment by a long shot. I can’t help myself from thinking about work all the time, and it’s super nice not to have to translate random thoughts in the park into billable hours toward a client.

What are your personal projects and goals for 2010?

I’m not really sure. I’ve been trying to make it a habit not to make yearly resolutions or goals because I’ve found how bad I am about following through.  I suppose things I’m always working on that I’d like to be better at:

  • My drumming skills
  • Exploring new places
  • (Re)learning Spanish
  • Owning less stuff

back to top
Posted 16 days ago at Railscasts

Here I walk you through adding the ability to sort table columns in ascending or descending order by clicking the header.

back to top
Favorite
Posted 16 days ago at Ruby Quicktips

Ruby arrays have some nifty behavior when passed negative array indices. Let’s say you have an array

array = [:a, :b, :c, :d, :e]

You can access the last element in the array by calling

array[-1] # => :e

The second-to-last element has index -2, and so on. Negative indices also work for slicing operations. For example, if you wanted to leave out the last two elements in the array, you would call

array[0..-3] # => [:a, :b, :c]

and if you wanted to remove just the first element of the array,

array[1..-1] # => [:b, :c, :d, :e]

This tip was submitted by Rockwell Schrock.

back to top
Posted 16 days ago at Intridea - Company Blog

Fixing Common Bundler Problems

When bundler first came out, I really wanted to like it. It promised a clean way to declare dependencies for your application in a single and definitive place, regardless of what kind of box your app was running on. Unfortunately, bundler has not lived up to the hype, and I've had plenty of headaches from bundler problems. Read on for a list of tips I've pulled together to save you some headache.

Ensure your local bundler is the same version as your server

Different versions of bundler may act differently:

bundle --version  # on your local machine and your server
sudo gem install bundler --version="0.9.26"

Explicitly specify gem versions

Did you know in HTTParty 0.4.5, there is no 'parsed_response' method on a response object? Well, neither did I when it worked fine on my local laptop (0.6.1), but not on the server (0.4.5)

gem "httparty"  # bad times if your system gem is out of date...
gem "httparty", "~> 0.6.1"  # better, but...
gem "httparty", "0.6.1"     # ...why not just specify the version everyone should use?

Check that you are actually using gems installed by bundler

Once in a while, bundler will report success on install, but you'll get the wrong gems loaded in your load path. Grep your load path to double check libraries you're having trouble with:

# in script/console
>> $:.grep /http/
=> ["/Users/jch/.bundle/ruby/1.8/gems/httparty-0.6.1/lib"]

Gemfile conditionals

bundler allows you to specify groups so only gems you need in one environment are loaded:

# we don't call the group :test because we don't want them auto-required
group :test do
  gem 'database_cleaner', '~> 0.5.0'
  gem 'rspec'
  gem 'rspec-rails', '~> 1.3.2', :require => 'spec/rails'
end

All gems you specify in your Gemfile WILL be installed regardless of what RAILS_ENV you're currently on. There's a very deceptively named option called --without that does not work as you would expect:

# weird, but this will install gems in group test
bundle install --without=test

This can turn out to be a disaster if your linux production environment tries to install a OSX specific gem with native extensions that you use for development. An ugly fix in the meantime is to add conditionals that look for an environment variable:

if ['test', 'cucumber'].include?(ENV['RAILS_ENV'])
  group :test do
    # your gems
  end
end

Update your capistrano

Don't forget to bundle when you deploy:

after  "deploy:update_code", "deploy:bundle"
namespace :deploy do
  desc "Freeze dependencies"
  task :bundle, :roles => :app do
    run "cd #{release_path} && bundle install --relock --without=test"
  end
end

NameErrors and autoloading issues

For some gems, bundler will not autoload properly. If you start getting NameErrors or LoadErrors for a gem, read this issue. The fix is to skip the require in your Gemfile and manually do the require in your environment.rb:

# Gemfile
gem 'misbehaving_gem', :require_as => []

# environment.rb
Rails::Initializer.run do |config|
  # ...
  config.gem 'misbehaving_gem'
  # ...
end

Nuke .bundler

When all else fails, and you've pulled out what precious little hair you have left:

rm -rf RAILS_ROOT/.bundle      # removes gems for this project
rm -rf ~/.bundle               # removes cached gems for your current user
rm -rf RAILS_ROOT/Gemfile.lock # lets you do a fresh 'bundle install'

# do a fresh bundle install
bundle install

Other

Bundler is in its infancy, and it continues to get better with each release, so many of these issues might not exist in the near future. In the meantime, I hope this list will save you some time with bundler related headaches. Let me know in the comments if you've encountered other tips for resolving these problems.

back to top

GithubFinder: JavaScript-powered Github repo browser:

For those times you need to quickly browse a GitHub repo but don’t want to clone it locally, check out GitHubFinder from Alex Le.

Screenshot

Modeled after the Mac OSX finder, GitHubFinder provides a familiar drill-down navigation to browse all the files in a given repo. The project has a plugin-based architecture for extensibility and includes keyboard navigation, file diffs, basic syntax highlighting, and a resizable, panel-based interface out of the box.

Written in JavaScript as part of the 10K Apart Contest, the app uses the same JSONP API we use here on The Changelog and weighs in at just 8.5KB compressed. Bravo, Alex!

[Source on GitHub]

back to top
Posted 17 days ago at The Geek Talk

Who is PJ Hyett?

A simple man with big dreams.

Where and when did you start programming?

I bought “Teach Yourself to Create Web Pages in 24 hours” from an Office Max bargain bin in Naperville, IL circa 1998.

About working in GitHub?

I joined Chris and Tom a few months after they started hacking on it on weekends. Chris and I had built another website around the same time, but it became clear that GitHub was the far more interesting (and potentially profitable) product so I started devoting all of my time to that. I can’t believe that was almost three years ago, it’s been a blast.

What does your typical day look like?

I don’t know if I really have a typical day. It could involve waking up at 6am to talk to potential GitHub:FI customers in London or sleeping until 6pm because I’m jet-lagged from our last trip.

What do you do in your free time?

Avoiding wedding planning and drinking bourbon.

Current favorite apps?

Web apps: Google Voice, GrubHub and Kayak. Desktop apps: Mailplane, VMWare Fusion and PDFpen (although it could
be a lot better)

What OS do you prefer?

OS X, but I don’t mind Ubuntu.

Small picture for your Workplace?

I work at this table at our office:

otherwise I’m working at home on my couch or at my desk.

Favorite: Language, JS Framework?

French, JQuery.

Name something that has inspired you recently?

Traveling internationally. For all of the things you take for granted and all of the things you’ve yet to achieve.

What do you prefer (and why)? Freelance work or full time employment?

I prefer full-time work. Jumping between projects is tiresome especially when you don’t have any skin the game. Helping to run GitHub is exactly what I want to be doing.

What are your personal projects and goals for 2010?

Get married and help fix the economy of Greece by honeymooning there.

back to top
Favorite
Posted 18 days ago at Katz Got Your Tongue?

TL;DR Use ~> instead.

Having spent far, far too much time with Rubygems dependencies, and the problems that arise with unusual combinations, I am ready to come right out and say it: you basically never, ever want to use a >= dependency in your gems.

When you specify a dependency for your gem, it should mean that you are fairly sure that the unmodified code in the released gem will continue to work with any future version of the dependency that matches the version you specified. So for instance, let’s take a look at the dependencies listed in the actionpack gem:

activemodel (= 3.0.0.rc, runtime)
activesupport (= 3.0.0.rc, runtime)
builder (~> 2.1.2, runtime)
erubis (~> 2.6.6, runtime)
i18n (~> 0.4.1, runtime)
rack (~> 1.2.1, runtime)
rack-mount (~> 0.6.9, runtime)
rack-test (~> 0.5.4, runtime)
tzinfo (~> 0.3.22, runtime)

Since we release the Rails gems as a unit, we declare hard dependencies on activemodel and activesupport. We declare soft dependencies on builder, erubis, i18n, rack, rack-mount, rack-test, and tzinfo.

You might not know what exactly the ~> version specifier means. Essentially, it decomposes into two specifiers. So ~> 2.1.2 means &gt;= 2.1.2, &lt; 2.2.0. In other words, it means “2.1.x, but not less than 2.1.2″. Specifying ~> 1.0, like many people do for Rack, means “any 1.x”.

You should make your dependencies as soft as the versioning scheme and release practices of your dependencies will allow. If you’re monkey-patching a library outside of its public API (not a very good practice for libraries), you should probably stick with an = dependency.

One thing for certain though: you cannot be sure that your gem works with every future version of your dependencies. Sanely versioned gems take the opportunity of a major release to break things, and until you have actually tested against the new versions, it's madness to claim compatibility. One example: a number of gems have dependencies on activesupport >= 2.3. In a large number of cases, these gems do not work correctly with ActiveSuport 3.0, since we changed how components of ActiveSupport get loaded to make it easier to cherry-pick.

Now, instead of receiving a version conflict, users of these gems will get cryptic runtime error messages. Even worse, everything might appear to work, until some weird edge-case is exercised in production, and which your tests would have caught.

But What Happens When a New Version is Released?

One reason that people use the activesupport >= 2.3 is that, assuming Rails maintains backward-compatibility, their gem will continue to work in newer Rails environments without any difficulty. If everything happens to work, it saves you the time of running their unit tests against newer versions of dependencies and cutting a new release.

As I said before, this is a deadly practice. By specifying appropriate dependencies (based on your confidence in the underlying library's versioning scheme), you will have a natural opportunity to run your test suite against the new versions, and release a new gem that you know actually works.

This does mean that you will likely want to release patch releases of old versions of your gem. For instance, if I have AuthMagic 1.0, which worked against Rails 2.3, and I release AuthMagic 2.0 once Rails 3.0 comes out, it makes sense to continue patching AuthMagic 1.0 for a little while, so your Rails 2.3 users aren't left out in the cold.

Applications and Gemfiles

I should be clear that this versioning advice doesn't necessarily apply to an application using Bundler. That's because the Gemfile.lock, which you should check into version control, essentially converts all &gt;= dependencies into hard dependencies. However, because you may want to run bundle update at some point in the future, which will update everything to the latest possible versions, you might want to use version specifiers in your Gemfile that seem likely to work into the future.

back to top