Contributing to open source

Posted on May 15, 2011

Although contributing to open source projects now seems natural to me, there was a time when it was quite daunting and I had no idea where to start. Here, I’ll guide you through the steps I took to add some functionality to a rails plugin I wanted to use.

The goal

I wanted to implement soft deletion in a Rails 3 app I’m working on. As you probably know, soft deletion basically means that when a user deletes an object, it kept in the database, but no longer displayed in the application (typically using some variation of a deleted flag).

Following the philosophy of "use the simplest thing that will work", I simply added a deleted_at datetime flag on the model. The app is in the very early stages, and at that time there was only one model (Customer), so by overriding the model’s destroy method to set the deleted_at field to Time.now and setting default_scope where(:deleted_at => nil) , I essentially had soft deletion implemented.

Then, I added an Address model to the mix: each customer has one dependent address that should get destroyed when its customer is destroyed. This is where soft deletion gets interesting: when the customer is destroyed, we need to update its deleted_at field, but also the deleted_at field for all the dependent records (i.e., the address in this case). And, just as importantly, when the customer record is restored, the address should be restored also.

So I started looking for a soft delete solution, and settled on permanent records.

The problem

After a ‘bundle install’, I started trying out the soft delete functionality in the Rails console:

  
  
    
    c = Customer.last
    c.destroy
    c.revive
  
    
  

And that’s when trouble struck:

  
  
    
    NoMethodError: You have a nil object when you didn't expect it!
    You might have expected an instance of Array.
    The error occurred while evaluating nil.find
    from /home/david/.rvm/gems/ruby-1.9.2-p0/bundler/gems/permanent_records-46b6e5c557b0/lib/permanent_records.rb:130:in `block in revive_destroyed_dependent_records'
    [...snip...]
  
    
  

As you can tell in line 4, the problem is in the permanent records file installed by bundler. If we open the file in our favorite editor (or view it on GitHub), we can see that line 130 is trying to do a scoped find, which will only work for has_many cardinalities.

Getting ready

First of all, make sure you have Git installed and configured on your machine. (Note that Git is the version control system used by this particular project: other projects might use different systems.) Then, sign up for a free GitHub account and fork the project.

Then, download the code onto you machine with a simple git clone git@github.com:YOURUSERNAME/permanent_records.git and you’re set to go.

Document the improvement

As you’ll see, the project comes with a test folder that will automatically check everything is working ok. Run rake on the command line to make sure everything is set up properly.

The first thing we need to do is create a test to document what we’re doing (TDD style). The reason is simple: if we’re fixing a bug, we want to make sure it doesn’t creep back in a future version, and if we’re implementing new functionality, we want to make sure it doesn’t get broken down the road. Having a solid test suite is very helpful when several people are working on the same project (which happens often in open source): it keeps things from breaking as the code base gets bigger and more complex.

Instead of describing all the files I added/modified to add the test, I’ll let you check it out on GitHub (look at the changes made to files in the test folder). You’ll see I simply added a new model with a has_one cardinality and added tests to document the missing feature I intend to implement.

I can then run the tests with the rake command to make sure they fail. That might seem silly at first glance, but we need to make sure we’re properly documenting the desired (inexistent) behavior, and not mistakenly adding tests that already pass.

I then simply implemented the improvement, by checking the cardinality and responding appropriately if the cardinality was has_one (see code). I can then run the tests again, and verify that they now pass.

Now that the improvement has been implemented (along with the corresponding tests), it’s time to put it into the wild, by pushing it to our forked GitHub repository with a git push origin master .

The new code is now online on GitHub, and we can open a pull request on the original project page. The great part about Rails 3 and bundler is that until (and if) our improvements make into the project’s source tree, we can use our own branch on GitHub.


Would you like to see more Elixir content like this? Sign up to my mailing list so I can gauge how much interest there is in this type of content.