Blog


Mar

How to write a bug report


This is a blog post I've wanted to write for a really long time. Every couple of days I get an issue reported to one of my open-source projects which looks something like this:

Capybara doesn't work

It's totally broken. I tried everything. Why doesn't it work? Please fix it for me!

Okay, maybe it's not quite that bad. As you might guess, such a bug report is completely useless, it's just a burden to everyone involved in the project to have to tackle this kind of stuff. Worse than that, the person who posted this isn't going to get the help they need, and their possibly very legitimate issue is not going to get fixed.

Every time I receive one of these I wish there were some kind of source I could point these people to, so they might learn how to contribute with insight to open-source projects, instead of being a burden. This is that source.

Closed source

This stuff only applies to open-source, free as in beer and speech projects. Closed source projects are a different game, though some of this advice may still be useful.

The holy grail of bug report templates

This is where you should start. I can't remember which project I first saw this template on, so unfortunately I can't give credit where it's due, suffice to say I didn't invent it and am not claiming credit for it.

Answer these three very simple questions:

  • What did you do?
  • What did you expect to happen?
  • What happened instead?

Just by answering these three questions, you're already doing better than most bug reports out there.

If you encounter an error, any kind of error, make sure to include a stack trace. Those are immensely useful. Also be sure that the stack trace is complete, some frameworks such as Rails or test frameworks like RSpec and Cucumber tend to filter the stack trace. In RSpec and Cucumber, run your tests with -b to get a full stack trace. If you can't get a stack trace, make sure to expand upon the third question as much as possible.

A bug report isn't a question

If you're asking why something broke, if you're wondering what the cause of a particular behaviour is, you aren't actually writing a bug report, you're asking a question.

Asking questions is good, and I encourage you to reach out when you need help and have exhausted all other options. Asking questions on an issue tracker however is bad. Issues need to be closed, they need to be resolved; a question cannot really be resolved. It can be answered, but then that answer needs to be acknowledged and accepted and so on. There is a lot of process involved in closing each issue and it takes time away from the maintainers to focus on more important things.

Most open source project have a mailing list, use that for asking questions. If there isn't one, use StackOverflow or a more generic mailing list. If you've posted to one of these and you didn't get an answer in two weeks or so, investigate again and see if you can't find the answer yourself. If you can find it, make sure to answer your own question in the forum you asked it, so that others who have the same question in the future may find your answer through the almighty Google. If all of that's failed, that's when you're allowed to open an issue for a question.

When you send a bug report, you should be able to at least speculate at what the cause might be. Saying feature X broke doesn't help at all. Most open source projects of decent size are used by thousands of people, they have massive, comprehensive test suites. The most basic functionality in the project very likely isn't broken. So even if you have a legitimate issue, it likely happened under a specific set of circumstances. You need to pare away possible causes until you are left with the most minimal set of conditions needed to replicate the issue. Which brings us to:

Replication

While it isn't strictly necessary for a bug report, it's immensely helpful to have some way to replicate the issue. It's especially helpful to have an executable way of replicating an issue.

I generally dislike sample applications, such as Rails applications for this. There are too many possible areas where breakage could occur, and it takes too long for me to understand how all the pieces fit together. If you need a complete sample application, you likely don't understand the issue well enough yet to send a good bug report. Pare it down further until you truly understand what components cause the problem.

The absolute best way is of course to send a failing test case. You definitely don't need to actually fix the problem, though of course we appreciate it if you do. Understanding a code base can be troublesome, so it's cool if you don't want to spend the time to understand how best to resolve an issue. But if you do spend the time to write a test case, you reduce the time it takes the maintainers to solve your problem by an order of magnitude. Seriously.

A well formatted email

If you're sending a message to a mailing list, be aware that reading code or stack traces inline in an email message is quite painful. Either keep the code samples very short or link to a pastie/gist or something. Reading long code samples without syntax highlighting and possibly with automatically wrapped lines is quite painful.

Debugging

Debugging is a central part of software development. You cannot get by as a programmer without being good at debugging. Use the skills you use to solve problems in your applications when you encounter what you perceive as bugs in open source projects. Debugging really is a simple process:

  1. Isolate the cause
  2. Fix it

Where 99.8%¹ of the time is spent on point 1. If you don't do the first point, you are effectively asking someone else to do most of your work for you. For free.

Be polite

The people who write open-source projects do not owe you anything. They've invested a lot of time, which you get for free, but they are under no obligation to keep providing that time to you, or to provide more of their time just because you demand it.

The most common form of this I see are the emails I get to my personal email account from people asking for help. Asking me for help in a private forum such as email is asking for a handout. It's a form of begging. Please don't do it. I hate getting these emails, and I hate having to reply that I don't answer questions sent to me privately.

Some people believe that instead, you owe authors for the software they provide, but I don't really believe that either. I think the only thing you do owe them is to treat them with the same respect and politeness that you would extend to anyone else.

So no one owes each other anything really, and that's cool.

Don't be disappointed by the way, if it takes a while for your issue to get a response. It's cool if you ping the author after a couple of weeks if nothing has happened. Sometimes one can lose motivation, or become engrossed in something else.

Summary

Be polite. Be explicit. Be as knowledgeable about the causes of your issue as you can be. Answer these following questions:

  • What did you do?
  • What did you expect to happen?
  • What happened instead?

¹ totally made up number

Mar

Campfire Stories: March 12-16


I missed a week while traveling, but here's another edition of Campfire Stories, pulled from our Campfire rooms last week.

Monday, March 12

MongoDB Stockholm

Conference about MongoDB in Stockholm, April 5th.

http://www.10gen.com/events/mongodb-stockholm

Tuesday, March 13

Elabs team on Coderwall

http://coderwall.com/teams/4f271951973bf00004000661

Hating on IE6

Coderwall encourages users to update their profiles by showing you "embarrasing" defaults.

http://coderwall.com/blog/2012-02-23-hating-on-IE6

Nordic Ruby speakers

We announced the speakers for Nordic Ruby, the Ruby conference that we organise every year. We have an amazing lineup of speakers again this year, and we can't wait for the event in June.

http://nordicruby.org/speakers

Landing page for ProjectPuzzle

As we mentioned in our previous Campfire Stories post, we've been working on an app to help with project scheduling. We're getting closer to launch, but in the mean time we've put up a landing page where you can sign up if you're interested in learning more about the app when it launches.

http://projectpuzzle.com/

Elycharts

Elycharts looks like an interesting alternative to Highcharts JS.

http://elycharts.com/

Kalandae

A framework agnostic Javascript date picker.

http://github.com/ChiperSoft/Kalendae

Wednesday, March 14

Strong Parameters

Gem from the Rails core team to help deal with mass assignment security issues. An alternative to our own trusted_attributes.

http://github.com/rails/strong_parameters

Using event capturing to improve Basecamp page load times

37signals describes how they improved page load times for the new Basecamp by deferring Javascript event bindings.

http://37signals.com/svn/posts/3137-using-event-capturing-to-improve-basecamp-page-load-times

Entypo

More pictograms.

http://www.entypo.com/

Thursday, March 15

Dashboard

Detail from the internal dashboard we're working on.

Friday, March 16

Where's _why?

Wonderful article about the disappearance of "Why the lucky stiff", one of the heroes of the Ruby community.

http://www.slate.com/articles/technology/technology/2012/03/…

PaintCode

A vector drawing app that generates resolution independent Objective-C drawing code for Mac OS X and iOS.

http://www.paintcodeapp.com/

Mar

Working with time zones in Ruby on Rails


Rails provides great tools for working with time zones but there's still a lot of things that can go wrong. This blog post aims to shed some light on these gotchas and provide solutions to the most common problems.

The one that probably has tricked me the most times is the fact that Rails fools you to believe it got you all covered all the time (pardon the pun). Don't get me wrong. I want Rails to do as much work for me as possible. But I've learnt the hard way that I can't get away with not knowing when and how Rails is helping me. Another gotcha is the fact that you have more time zones in play than you might first believe. Consider the following: db, server, dev machine, system configured, user specific configured and the browser.

Configure your Rails app

So what tools do we have at our disposal as Rails developers? The most important one is the config.time_zone configuration in your config/application.rb file. ActiveRecord will help you convert from and to (which the documentation fails to explain) UTC and the time zone of your choice. This means that if all you're doing is having users post times through a form and use Active Record to persist it you're good to go.

Processing time information

So what about actually doing something with the time information before persisting it? That's when it becomes tricky.

Parsing

When parsing time information it's important to never do it without specifying the time zone. The best way to do this is to use Time.zone.parse (which will use the time zone specified in config.time_zone) instead of just Time.parse (which will use the computer's time zone).

Work with Numerical and ActiveRecord attributes

Method calls like 2.hours.ago uses the time zone you've configured, so use these if you can! The same thing is true for time attributes on ActiveRecord models.

post = Post.first
post.published_at #=> Thu, 22 Mar 2012 00:00:00 CDT -05:00

ActiveRecord fetches the UTC time from the database and converts it to the time zone in config.time_zone for you.

Date vs Time

Time has date information but Date does NOT have time information. Even if you don't think you care you might realize that you do sooner then later. Be safe and use Time (or DateTime if you need support for times very far from the present).

But let's say you're stuck with a Date that you need to treat as a Time, at least make sure to convert it to your configured time zone:

1.day.from_now # => Fri, 03 Mar 2012 22:04:47 JST +09:00
Date.today.to_time_in_current_zone # => Fri, 02 Mar 2012 00:00:00 JST +09:00

Never use:

Date.today.to_time # => 2012-03-02 00:00:00 +0100

Querying

Since Rails know that your time information is stored as UTC in the database it will convert any time you give it to UTC.

Post.where(["posts.publised_at > ?", Time.zone.now])

Just be sure to never construct the query string by hand and always use Time.zone.now as the base and you should be safe.

Working with APIs

Supplying

Building a web API for others to consume? Make sure to always send all time data as UTC (and specify that this is the case).

Time.zone.now.utc.iso8601 #=> "2012-03-16T14:55:33Z"

Read more about why iso8601 is advisable here: http://devblog.avdi.org/2009/10/25/iso8601-dates-in-ruby/

Consuming

When you get the time information from an external API which you don't have control over you simply need to figure out the format and time zone it's sent to you with. Because Time.zone.parse might not work with the format you receive you might need to use:

Time.strptime(time_string, '%Y-%m-%dT%H:%M:%S%z').in_time_zone(Time.zone)

Why there's no #strptime method on Time.zone when there's a #parse beats me. However don't forget to call in_time_zone(Time.zone) on your result!

Working with multiple user time zones

Many systems needs to support users entering and viewing time information in a variety of time zones. To achieve this you need to store each user's time zone (probably just one of the time zone string names found in rake time:zones:all). Then to actually use that time zone the most common pattern is to simply create a private method in your ActionController and run it as an around filter.

around_filter :user_time_zone, :if => :current_user

def user_time_zone(&block)
  Time.use_zone(current_user.time_zone, &block)
end

This will do the same thing as config.time_zone but on a per request basis. I still recommend to change the default config.time_zone to a time zone that is a good default for your users. (Thank you Matt Bridges for pointing out the potential problems with using a before_filter instead of an around_filter.)

Testing

All the above is something that your tests should catch for you. The problem is that you as the user and your computer as the development server happen to reside in the same time zone. This is rarely the case once you push things to production.

Highgroove just released Zonebie, a gem that helps you deal with this. I haven't had time to try it out myself yet, but it looks very promising. If you find this to be overkill, at least make sure that your tests run with Time.zone set to another time zone than the one your development machine is in!

Bug in Time.zone.parse

Jarkko Laine (@jarkko) pointed out that there's currently a bug in Rails that can have Time.zone.parse lose an hour when your system time is in DST (daylight saving time) and your configured time zone isn't. Jarkko has posted an issue on Rails' issue tracker and written a patch to correct the bug. Until the patch has been accepted or if you're running with older versions of Rails the only safe way to avoid this bug is to either monkey patch Rails in your app with Jarkko's fix or use:

# use
ActiveSupport::TimeWithZone.new(nil, Time.zone, DateTime.parse("2012-03-25 03:29"))
# => Sun, 25 Mar 2012 03:29:00 PDT -07:00

# or if possible pass the time zone in the string
Time.zone.parse("2012-03-25 03:29 PDT")
# => Sun, 25 Mar 2012 03:29:00 PDT -07:00

# instead of
Time.zone.parse("2012-03-25 03:29")
# => Sun, 25 Mar 2012 04:29:00 PDT -07:00

It should however be mentioned that it's pretty rare that this bug surfaces and when it does it can only lose you one hour. If you can live with that you probably do best by just waiting for the patch to be accepted.

Cheat Sheet

DOs

2.hours.ago # => Fri, 02 Mar 2012 20:04:47 JST +09:00
1.day.from_now # => Fri, 03 Mar 2012 22:04:47 JST +09:00
Date.today.to_time_in_current_zone # => Fri, 02 Mar 2012 22:04:47 JST +09:00
Date.current # => Fri, 02 Mar
Time.zone.parse("2012-03-02 16:05:37") # => Fri, 02 Mar 2012 16:05:37 JST +09:00
Time.zone.now # => Fri, 02 Mar 2012 22:04:47 JST +09:00
Time.current # Same thing but shorter. (Thank you Lukas Sarnacki pointing this out.)
Time.zone.today # If you really can't have a Time or DateTime for some reason
Time.zone.now.utc.iso8601 # When supliyng an API (you can actually skip .zone here, but I find it better to always use it, than miss it when it's needed)
Time.strptime(time_string, '%Y-%m-%dT%H:%M:%S%z').in_time_zone(Time.zone) # If you can't use Time#parse

DON'Ts

Time.now # => Returns system time and ignores your configured time zone.
Time.parse("2012-03-02 16:05:37") # => Will assume time string given is in the system's time zone.
Time.strptime(time_string, '%Y-%m-%dT%H:%M:%S%z') # Same problem as with Time#parse.
Date.today # This could be yesterday or tomorrow depending on the machine's time zone.
Date.today.to_time # => # Still not the configured time zone.

Epilogue

I hope you've learned something from this post. I sure did while writing it! If you have any feedback on how it can be improved, or if you spot any errors, please let me know by posting a comment below!

Mar

Campfire Stories: Feb 27 - Mar 2


We love Campfire at Elabs. Even though we're all in the same office, we use Campfire to share links, images, videos, etc. Some are useful, some are funny, and some are just disturbing. We thought we'd share them with you.

Monday, February 27

Polstjärna

Polstjärna is a Swedish foundation whose vision is to support children and young people in Sweden in the risk zone of social exclusion.

We designed some banners to help them spread the word.

http://polstjarna.se/polstjarna/vanner/

Best friends

Enrique's picture of our office

Elabs' office by @ecomba

Last year we had our friend Enrique working with us for a couple of days. In addition to being a great developer and all-around nice guy, Enrique is also a good photographer. Here's a picture he took of our office during his stay here.

You can read more about Enrique's experience working with us here: http://www.ecomba.org/post/17558226325/elabs.

Tuesday, February 28

Harvest for Mac

Harvest, the tool we use for time tracking, has released a desktop client for Mac OS X, helping us keep our time tracking even more accurate.

http://www.getharvest.com/mac

Minimalist coffee table

Vic Coffee Table

http://minimalissimo.com/2012/02/vic/

Frank Chimero's new site

http://www.frankchimero.com/

ProjectPuzzle competitor

In between our consulting projects, we've been working on an app to help with project scheduling. We call it ProjectPuzzle. Last week, one of our competitors launched their attempt at solving the same problem: http://floatschedule.com/.

They even got a TechCrunch article written about them: http://techcrunch.com/2012/02/28/float-does-simple-scheduling-for-teams-and-simple-is-hard/.

Wednesday, February 29

Throne of JS

One of our favourite conferences ever was FutureRuby, hosted by Unspace in Toronto in 2009. Now Unspace is back with a new conference, and we're very excited: http://throneofjs.com/.

Twitter conversation

Minimalist Pixar Posters

Wall-e

http://www.probablybest.co/2012/02/minimalist-posters-of-pixar-films/

CJ reaches Inbox 0

Inbox 0

Didn't last long unfortunately.

Thursday, March 1

"Font-Embedding Icons: This Is a Big Deal"

http://somerandomdude.com/2010/05/04/font-embedding-icons/

Hand crafted, infinitely scalable & royalty-free icons for user interface designers

http://pictos.cc/

Friday, March 2

"Give it five minutes"

Dismissing an idea is so easy because it doesn’t involve any work. You can scoff at it. You can ignore it. You can puff some smoke at it. That’s easy. The hard thing to do is protect it, think about it, let it marinate, explore it, riff on it, and try it. The right idea could start out life as the wrong idea.

http://37signals.com/svn/posts/3124-give-it-five-minutes

Coffee

Coffee

Lots of excitement about coffee at the office. This week Camilla from Coffee & Memories will be delivering our new coffee making equipment.

Gridset

Create advanced grid systems on the web.

http://www.gridsetapp.com/

"Ruby patterns from GitHub's codebase"

Zach Holman shares some tips and techniques from GitHub's own codebases.

http://zachholman.com/talk/ruby-patterns

Curious that he doesn't talk about how they handle mass-assignment issues.

MailChimp Guides with book covers

MailChimp guides

MailChimp has designed individual book covers for each of their how-to guides. Beautiful work.

http://mailchimp.com/resources/

Feedback

We're considering making this a regular feature on the blog. This is an experiment, so please let us know what you think!

Feb

Capybara and testing APIs


Before you read this blog post, go read the Capybara README. Back? Good. Did you see any mention of methods like get, post or response? No? That's because those don't exist in Capybara. Let's be very clear about this, Capybara does not have those methods. So when you're writing a test like this:

visit '/'
response.body.should have_content("Hello")

Or possibly:

get '/'
page.should have_content("Hello")

Now you know why this couldn't possibly ever work. Because while visit and have_content are indeed part of the Capybara API, get and response are not. If these methods are defined inside your test/spec context, some other framework is defining them, most likely RackTest, and that framework and Capybara do not interact. Capybara knows nothing about what happens when you call get and likewise, RackTest knows nothing about what happens when you call visit.

For most people who have used Capybara for a while, this is old news. They tried variations like the above and they didn't work, they moved on and learned from those mistakes. The interesting question is why these methods do not exist in Capybara. Omitting these methods was a very conscious decision, a decision based on the following premise: Capybara is not a library suited to testing APIs.

There you have it. Do not test APIs with Capybara. It wasn't designed for it.

Capybara simulates user behaviour. Its API was designed to run against as large a variety of backends as possible. To achieve this, we have placed severe restrictions on what you can and cannot do with Capybara. We have limited the API to the most common subset of actions that a user would want to perform in a web application. This immediately precludes exposing the details of HTTP. A user knows exactly how to submit a form, but they have no idea that that form is sent as an HTTP POST request with certain HTTP headers.

I've seen so many people hack into the internals of the RackTest driver in Capybara. I want to make it clear that doing something like the following is using a private API, which is liable to change at any point (yes, even point releases):

page.driver.browser.post('/foo', data: "here")

Don't ever do this! Why would you? You could just use RackTest directly. In fact, as we saw before, it's probably already included in your spec context. So why make it harder than it needs to be:

post '/foo', data: "here"
last_response.body.should contain("Hello World")

Why would you write page.driver.browser.post when you could just write post? See that doesn't make any sense.

By extension, Capybara assumes the response to be HTML, if you're parsing page.body or page.source as JSON or XML, why not just use RackTest instead? You'll have a much nicer, cleaner API which won't break in the future.

tl;dr:

Use RackTest for testing APIs, use Capybara for testing user behaviour.