giant robots smashing into other giant robots

Written by thoughtbot

qrush

CoffeeScript: Spartan JavaScript

I’ve been writing some CoffeeScript lately, and it’s been fantastic. I wish I had started looking into this language sooner. Here’s some thoughts about it.

the good

Writing CoffeeScript instead of JavaScript feels like I’m fighting a whole new battle. JavaScript is not a verbose language. Writing JavaScript properly, and in an OO manner, requires you to be verbose. What I like best is that CoffeeScript is simply…spartan.

You bring only this snippet of code to battle?

You! What is your keyword?

It’s function, sir.

And you?

I’m _.bindAll, sir.

CoffeeScript! What are your keywords?

-> ! => ! -> !

See old friend, I brought better code than you did.

functions & binding

I’m most excited about how accessible functions have become in CoffeeScript, and how the worry of doing _.bindAll to keep the current object in this just vanishes. Even our old friend $(document).ready loses a few pounds of syntax weight:

$ ->
  new ItemsRouter()
  Backbone.history.start()

It’s ridiculously neat to me that it just works. If you’re doing a loop or something in a Backbone.View to render many things, you can use the “fat arrow” to make sure this stays as the current object.

class ItemView extends Backbone.View
  render: ->
    @options.items.each(@renderItem)
  renderItem: (item) =>
    @el.append item.get("title")

No more worrying about _.bindAll! Oh yeah, using @ for instance methods (it substitutes this.) is a great little cupcake of syntax sugar that makes code so much easier to read.

hashes/objects

We know the Ruby 1.8 syntax for hashes is dead, but CS takes this one step further. In this example, Item is a Backbone.Model:

item = new Item title: "Awesome", id: 2

Omitting parentheses is something I’ve been trying with this transition. My philosophy here is to push it to the limit and see how it feels. We’ll see if it passes code review in the next few weeks.

local variables

By default, JavaScript creates all variables in the global scope. How useful.

By default, CoffeeScript makes local variables by default and makes sure to stick var in front of declarations.

test = ->
  foo = "bar"
  console.log(foo)
>> test()
"bar"
>> foo
ReferenceError

This is reason enough to use CoffeeScript. I think anyone writing JavaScript has to pay such close attention to scope that it’s simply just a waste of time.

string interpolation

How many times have you written something like:

var html = "<option value='" + this.id + "'>" + this.get("title") + "</option>";

This is such nonsense. Why did we put up with this? In CoffeeScript, you can write:

html = "<option value='#{@id}'>#{@get("title")}</option>"

By the way, the latest version of Backbone has a make method that helps doing little element creation like this.

inheritance

The official docs on the subject put it best:

JavaScript’s prototypal inheritance has always been a bit of a brain-bender

So far I’ve used inheritance only in context with subclassing Backbone’s core classes, but I like what I’m seeing so far with the class and extends keywords.

class Item extends Backbone.Model
  url: ->
    "/events/#{@get('event_id')/items/#{@id}"

The bad

Debugging

This has been a major concern of mine since we’ve switched. Luckily it hasn’t been too bad yet, the JavaScript that is generated is actually readable. Of course, there’s the problem of there’s not a one-to-one mapping between the JS and CS, but we manage to get by with SASS/CSS just fine.

I have a feeling this will be an issue going forward, at least until Chrome or Firefox can understand CoffeeScript natively.

Silly keywords

“Silly” is probably the only way I can describe this:

true, yes, on  true
false, no, off  false

Not sure why we couldn’t just stick with true and false. There are always quirky parts to every language, but if I see @item.has("title") == yes in a pull request I’ll be sending a storm of commit comments.

Deploying

I’ve been using Barista to integrate with Rails 3.0 which has worked out quite well.

Our current config/initializers/barista_config.rb looks like:

if Rails.env.development? || Rails.env.test?
  Barista.configure do |c|
    c.root = Rails.root.join("app", "assets")
    c.output_root = Rails.root.join("public", "coffeescripts")
    c.bare = true
    c.add_preamble = false
  end
end

This tells Barista to look for coffeescripts in app/assets, which hopefully will make the move to Rails 3.1 a little easier. We’re dumping them into public/coffeescripts, which is is ignored by git so we don’t check in the generated files. Finally, there’s some extra unnecessary noise that Barista adds so the two final configuration options turns that off.

We’re also using Kumade to deploy to Heroku’s bamboo stack. I’m really hoping this entire situation gets easier in the next few months, because right now it’s a real annoyance to set up all of this.

the end

Hopefully this gives you a good overview of the nice additions that CoffeeScript provides. We talked internally about using CoffeeScript for months before integrating it. Part of me wishes we had done it sooner, but to really enjoy it fully you have to understand the problems JavaScript has, especially in a large codebase.

Huge thanks go out to the folks at DocumentCloud and Jeremy Ashkenas for making JavaScript application development less painful. There’s plenty of features I didn’t cover and pages of beautifully documented source code on the project’s site that you should read.

cpytel

Backbone.js on Rails: a new dynamic ebook

We recently tweeted that this was the “summer of backbone.js”. 75% of our client projects are now using Backbone.js and Trajectory is using it heavily now as well.

We’re loving Backbone.js, but as we got started with Backbone.js we realized that there was definitely a learning curve. As we got more familiar with it, we realized that the standardization we were familiar with from Rails was lacking and there were quite a few tricks and tips to make Backbone.js work well, especially with a Rails backend.

So we’re pleased to announce that we’re now writing a new book, Backbone.js on Rails.

But this more than just an ebook. We’re using git-scribe and github to manage the writing of the book. When you purchase access to the book, you’re going to get pull access to the repo. As we write, you’ll be able to follow along, view diffs, comment on individual lines of the work, and use Github issues to provide feedback and issues.

Because we’re using git-scribe, the book will able to be generated in different formats: HTML, PDF, ePub (iBooks, Nook), and Mobi (Kindle). We’re supporting HTML out of the gate, and other formats as soon as they’re ready to go down smooth.

Along with the book, the git repo will contain working Backbone.js and Ruby on Rails examples to help your understanding and be really useful.

You can get access now for $39. This early access price is for a limited time, and will go up to $49 on August 1st, 2011. We also have a special group price of $199 for up to 10 readers.

You can see our working table of contents and purchase access on the Backbone.js on Rails web page.

dancroak

JavaScript integration testing example: installing and using Mixpanel

We’ve been on a quest for years to make sure our integration tests cover the JavaScript components of the app. Here’s an example: installing Mixpanel in Copycopter to track visits, user sign ups, user activity during the free trial, and subscriptions.

Gemfile:

gem 'capybara-webkit'

features/support/env.rb:

Capybara.javascript_driver = :webkit

features/mixpanel.feature:

Background:
  Given the following plan exists:
    | id | name       | price | trial |
    | 16 | Supersonic | 5     | false |
    | 5  | Trial      | 0     | true  |
  And the following limits exist:
    | plan             | name     | value |
    | name: Supersonic | users    | 13    |
    | name: Supersonic | projects | 14    |

@javascript
Scenario: Track visitor learning about Copycopter
  When I go to the homepage
  Then mixpanel should track the "visited-home" event
  When I follow "Take a tour"
  Then mixpanel should track the "clicked-tour" event
  When I follow "Next, view plans and pricing"
  Then mixpanel should track the "clicked-next-to-plans-and-pricing" event

@javascript
Scenario: Track visitor signing up for free trial
  When I go to the homepage
  And I follow "Plans and Pricing"
  Then mixpanel should track the "clicked-plans-and-pricing" event
  When I follow "Choose free trial"
  Then mixpanel should track the "viewed-plan" event with the properties:
    | plan_id | 5 |

We use the @javascript tag, which will use Capybara Webkit to drive the browser in these tests.

We explicitly set the id’s of the ActiveRecord objects so we can check that Mixpanel receives the right plan id’s using their properties feature.

features/step_definitions/mixpanel_steps.rb:

Then %r{^mixpanel should track the "(.*)" event$} do |event_name|
  mpq = JSON.parse(evaluate_script(%{JSON.stringify(mpq);}))
  mpq.should include(["track", event_name])
end

Then %r{^mixpanel should track the "(.*)" event with the properties:$} do |event_name, table|
  mpq        = JSON.parse(evaluate_script(%{JSON.stringify(mpq);}))
  properties = table.transpose.hashes.first
  mpq.should include(["track", event_name, properties])
end

This is a little funky. We’re using JSON.stringify via json2.js and then Ruby’s JSON.parse to convert Mixpanel’s mpq Javascript object into its Ruby equivalent in order to invoke expectations on it.

Therefore, we need to include json2.js in our app:

curl https://github.com/douglascrockford/JSON-js/raw/master/json2.js &gt; public/javascripts/json2.js

app/views/shared/_javascript.html.erb:

&lt;% if Rails.env.test? %&gt;
  &lt;%= javascript_include_tag "json2" %&gt;
&lt;% end %&gt;

That smells like a hack, but whatever…

Also in that partial, the actual setup for Mixpanel:

<script type="text/javascript">
  var mpq = [];
  &lt;% if Rails.env.staging? || Rails.env.production? -%&gt;
    mpq.push(["init", "&lt;%= MIXPANEL_TOKEN %&gt;"]);
    (function() {
      var mp = document.createElement("script"); mp.type = "text/javascript"; mp.async = true;
      mp.src = (document.location.protocol == 'https:' ? 'https:' : 'http:') + "//api.mixpanel.com/site_media/js/api/mixpanel.js";
      var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(mp, s);
    })();
  &lt;% end -%&gt;
</script>

That mpq object looks familiar. We’re testing against it in our integration suite. It’s just a JavaScript Array.

We only include the rest of the Mixpanel setup in staging and production. It stuffs mixpanel.js into the DOM asynchronously.

We interpolate our Mixpanel account’s token based on the environment so we can run acceptance on our user story on staging.

config/environments/staging.rb:

MIXPANEL_TOKEN = "our-staging-token".freeze

config/environments/production.rb:

MIXPANEL_TOKEN = "our-production-token".freeze

To get the rest of the integration test passing, we follow the Mixpanel API normally.

views/homes/show.html.erb:

$(function () {
  mpq.push(["track", "visited-home"]);
  $("#tour-cloud").click(function () {
    mpq.push(["track", "clicked-tour"]);
  });
  $("#plans-cloud").click(function () {
    mpq.push(["track", "clicked-plans-and-pricing"]);
  });
  $("#next-to-plans").click(function () {
    mpq.push(["track", "clicked-next-to-plans-and-pricing"]);
  });
});

views/accounts/new.html.erb:

$(function () {
  mpq.push(["track", "viewed-plan", { plan_id: "&lt;%= @plan.id %&gt;" }]);
});

This use case is relatively common. Include some external service’s JavaScript and use their Javascript API in order to get good analytics on the app.

To make it happen smoothly, there’s a lot of interpolation and Ruby mixing with HTML and JavaScript. Things could go wrong and it feels good to have integration coverage for it.

Written by .

cpytel

thoughtbot and the Holy Grail

Update: Our quest for full-stack testing continued with writing the capybara-webkit driver. We also wrote about capybara-webkit more recently.


Stop! Who would cross the Bridge of Death must answer me these questions three, ‘ere the other side he see.

Ask me the questions, bridge-keeper. I’m not afraid.

What is your name?

My name is cpytel of thoughtbot.

What is your favorite color?

Red.

What is your quest?

To seek a reliable, robust way of integration testing our entire application to prevent against regressions and to build better systems.

With apologies to Monty Pyton, we’ve been on this quest for several years now. After many false starts and tribulations I feel we’ve reached an important plateau in this quest, and I feel its prudent to take a step back, look at where we’ve been, where we are now, and pass on some of this information for the benefit of all.

Selenium

Throughout this quest, Selenium has been the siren song that continually calls out to us. Unfortunately, in practice we’ve been unable to get Selenium to run reliably for real applications, on both developers machines and on the continuous integration server.

This failure with Selenium has caused us to search for alternative solutions.

Holy Grail

The first promising solution was the aptly named, Holy Grail. This library uses Harmony, which in turn wraps Johnson, env.js and Envjs to execute browser-less, console-based, javascript and DOM code right from within your Rails test suite. Our own Jason Morrison then wrote cucumber-holygrail to allow Cucumber to drive the Holy Grail integration tests.

Unfortunately, this solution wasn’t really designed to accomplish what we wanted; Holy Grail lets you run javascript on a single page and routes xhr requests to a controller in a functional test, and therefore it didn’t work like we expected: we wanted to drive a virtual browser session from action to action that supported javascript and ui interactions.

Envjs

From there, we discovered capybara-envjs. Capybara is a replacement for Webrat (the driver underneath Cucumber) which has a more flexible driver subsystem. capybara env-js provides more of what we needed, it had the goal of being a virtual browser, like we wanted. In order to make it work like we wanted, we needed to provide a number of fixes which we encapsulated into the capybara-envjs-fixes gem (as a holding place while we waited for pull requests to be merged in).

Capybara and capybara-envjs, and capybara-envjs-fixes were (and mostly still are) a deadly combination. We’re actively using it on projects still. However, the more we used it, the more we started to discover cracks in the foundation.

  • It doesn’t work with all javascript (for example, we had issues on sites that still use Prototype)
  • It doesn’t support jQuery live
  • It is sometimes very inconsistent with how real-world browsers behave, or how things should properly work, forcing us to write our code like we wouldn’t normally write it

The three above points, particularly the lack of support for jQuery live (which is used by more and more of the internal of jQuery, jQuery mobile, and even the new Rails unobtrusive javascript) caused us to continue to search for a better solution.

Akephalos

Thankfully, we then found Akephalos. Akephalos provides a Capybara driver that allows you to run your cucumber integration tests in the headless browser HtmlUnit. HtmlUnit is a “GUI-Less browser for Java programs”. It models HTML documents and provides an API that allows you to invoke pages, fill out forms, click links, etc… just like you do in your “normal” browser. With our fork of Akephalos to resolve a couple of issues that we ran into along the way, we were up and running with very reliable, headless browser tests.

HtmlUnit is written in Java, and Akephalos uses jruby-jars to start up and interact with the HtmlUnit browser. It has fairly good JavaScript support (it was able to deal with everything we were able to throw at it, including jQuery 1.4.2 and 1.4.3, jQuery Mobile, and jQuery live).

Note: We also tried culerity which is a cucumber driver that works with Celerity, which also is underpinned by HtmlUnit, but we couldn’t get it to really work well.

One of the great things about HtmlUnit and Akephalos is that it uses technology which has been existence in a while, is actively developed, and well documented. We were able to look at the code and understand what was going on and contributate changes if necessary much more easily than we were able to do with any of the previous attempts.

If you want to get started with Akephalos, I recommend that you use our fork on github until the changes are pulled in. We’ve used Akephalos on both Rails 2 and 3.

The relevant part of our Gemfile looks like this:

group :test, :cucumber do
  gem 'akephalos', :git => 'git://github.com/thoughtbot/akephalos.git'
  gem "cucumber-rails"
  gem "capybara"
  gem "database_cleaner"
  gem "treetop"
  gem "launchy"
end

Then, to a features/support/akephalos.rb add:

require 'akephalos'

You can then tag any scenarios you want to run with Akephalos with the @akephalos tag. If you want to be able to tag your scenarios with @javascript and have it execute in Akephalos (the default javascript driver in Capybara is Selenium) you’ll want to add the following additional line to your features/support/akephalos.rb file.

Capybara.javascript_driver = :Akephalos

There are a couple of important gotchas right now that will effect you, particularly if you have an existing application.

There is a known bug in HtmlUnit where any jQuery live events bound to the click event stop any jQuery live events bound to submit defined after them. In particular, this means that if you’re using Rails 3 unobtrusive jQuery form helpers, you’ll want to edit your rails.js file and move the definition of the follow event:

$('form[data-remote]').live('submit'

Up above any jQuery live click bindings.

Additionally, ajax requests execute asynchronously, just like in a real browser. In order for the tests to not get out of sequence and fail, ajax requests need to be executed synchronously instead. You can do this by adding the following code after your inclusion of jQuery in only your test or cucumber environments:

    $.ajaxSetup({ async: false });  

Akephalos doesn’t have the ability to set cookies, or access the session. These are capabilities in other drivers, and so you may find that your cucumber steps are doing this, and they’ll need to be rewritten. For example, the built in clearance step definitions read the cookies in order to expose a current_user helper for step definitions to use, and deleted cookies in order to force a sign out. There is newly committed support for reading cookies in Akephalos, but I couldn’t get it to work, and I figured as a matter of practice it’d be better not to rely on it anyway.

Have we reached the end of our quest?

We’re not the only ones on this quest, I’m sure, and I encourage those of you who are to take a look at Akephalos and give it a try, I don’t think you’ll be disappointed.

Unfortunately, I don’t think the quest is over.  But Akephalos represents a significant step forward in a reliable headless browser that can allow us to fully integration test our applications. There are probably otther options available to us to try, and I expect there will be more as time goes on. For example, we’ve recently started to use evergreen to do javascript unit testing right alongside the normal tests.

We’ll continue to report back on what we find and consider the current state-of-the-art. Let us know if you’ve found and love something we’ve missed here.

jdclayton

Hoptoad and Javascript, Sitting in a Tree, S-E-N-D-I-N-G

With an open API, Hoptoad, the app error app, can track errors from any sort of app you want: Rails, Sinatra, Rack, PHP, C#… the list goes on. We didn’t want to leave front-end developers in the dark when their Javascript throws errors, so we’ve added a Hoptoad notifier for Javascript!

I’m sure you’re asking, “How do I participate in this awesomeness?”. It’s easy.

If you’re using the hoptoad_notifier gem, upgrade to the latest version (2.3.7 at the time of this update) and update your config/initializers/hoptoad.rb:

HoptoadNotifier.configure do |config|
  config.api_key = 'YOUR-PROJECT-API-KEY'
  config.js_notifier = true # ADD ONE LINE FOR JAVASCRIPT ERROR TRACKING!
end

Yes, that’s it.

The gem injects Javascript right after your <head> tag. Done!

Not using hoptoad_notifier gem?

If you’re not using the hoptoad_notifier gem, you’ll just need to include a couple of lines (at the top of your <head>):

<script type="text/javascript" src="http://hoptoadapp.com/javascripts/notifier.js"></script>
<script type="text/javascript">
  Hoptoad.setKey('YOUR-PROJECT-API-KEY');
</script>

The details

You may be proclaiming, “Oh no, Javascript at the top of <head>! What are you thinking?” I’ll let you in on a little secret: we want our Javascript loaded before any other Javascript. That way, if one of those libraries throws an exception, we’re there to let you know!

The nitty-gritty details: we’re currently only supporting browsers that support the onerror event (Firefox 2+, IE6+). You’ll also be including your project API key on the page. This is different from your user API key, so there’s no need to worry about someone deleting your projects or users. We’ll continue to expand this feature and let you know as we progress. If you have any suggestions or spot a bug, let us know!

So, what’re you waiting for? It takes 30 seconds to sign up; go grab an account and start tracking your Javascript errors now!