MadJS February 2012 - How CoffeeScript & Jasmine Made Me a Better JavaScript Developer

Recently, I spoke at the MadJS group about CoffeeScript & Jasmine. My slides appear below. Since Slideshare doesn't show my notes, you can either download the keynote file above, or you can read the notes below, which I've pulled out of the slides. I feel that the notes are necessary to know what I'm talking about, and I hate reading slide decks where there's no context or notes. This isn't going to be as helpful as having seen my talk in person, but hopefully you get some value out of my talk and the notes together:

How CoffeeScript & Jasmine made me a better JS developer

First, an introduction:

  • I’m Matt Gauger
  • @mathiasx on twitter

I work at Bendyworks

  • We primarily do Ruby on Rails work, with iOS now.
  • We care very deeply about software craftsmanship and honing our agile practices.

Which leads me to my dilemma

  • Are you familiar with impostor syndrome?
  • It's the idea that even very skilled practitioners may sometimes feel like an impostor due to over-emphasizing their weaknesses.
  • Further, it’s the inability to internalize your own accomplishments.

I felt like an impostor when it came to JavaScript.

  • Of course, I could read and write the syntax, pull in jQuery, manipulate the DOM, etc.
  • I had several projects under my belt at this time that used AJAX and were fairly complex
  • I'd even read JavaScript: The Good Parts several times, taken notes, etc.

So, what does this have to do with CoffeeScript?

  • My thesis: How CoffeeScript & Jasmine made me a better JS developer (and how it can help you, too)
  • But before that: Let me warn you that I'm not going to go over every piece of syntax here.
  • I'm not going to be able to teach you all of CoffeeScript or Jasmine in this talk.
  • For that, see the resources & books at the end of the talk.

CoffeeScript History

  • 2010 I learn about CoffeeScript, and it sort of looks like Ruby and Python.
  • It grabs my interest.
  • But at that point it's still a novelty: The compiler is in Ruby, no one uses it for real dev yet.
  • It was a *toy*

Today

  • Flash-forward to today, and everyone is extolling CoffeeScript.
  • It comes with Rails 3 by default now and gets compiled on the fly into JS for your app.
  • I’ve been using CoffeeScript for about a year now.

CoffeeScript has Good Parts?

So why use CoffeeScript? What are its good parts?

  • It restricts you to a subset of JS you’d recognize from JS: The Good Parts.
  • It puts JSLint & a compiler between me and the half dozen browsers I need to support
  • It warns me when I do something wrong

This might be the most important part of the talk, and reason to use CoffeeScript

  • If you’re like me, you’ll put the compiled JS up next to the CoffeeScript
  • By reading the output of the compiler, you’re learning what good JS looks like.

Criticisms of CoffeeScript:

  • It's not what runs in the browser.
  • Difficult to debug => Finding bugs (Names are the same between CoffeeScript & JS; its readable)
  • May feel like you’re learning a whole different language (It’s not, it’s less verbose JS)

That isn't to say that CoffeeScript eliminates all bugs

  • At this point we may want to differentiate between bugs that are caused by poor syntax and mistakes (mistake bugs), and bugs that come from the interaction between complicated data & edge cases (ie computer is in a state you didn't predict when you wrote the code)
  • CoffeeScript can help cut down on a lot of the former.

Some examples of CoffeeScript helping you with bugs:

Coercing the wrong types

  • This will print it’s true happily.
  • That’s not quite what you expect when the data is more complicated than 1 and the string 1.
  • Type coercion is the number 1 reason for WTFJS
  • Ok, so that’s a very simple example.
  • But how often are you going to get bitten by more complicated versions of that same bug?
  • And are you always going to remember to use triple equals? === I am now.

Scope

  • CoffeeScript scope is essentially the same as in JS
  • JS and CoffeeScript have “lexical scope”
  • 1. Every function creates a scope, and the only way to create a scope is to define a function.
  • 2. A variable lives in the outermost scope in which an assignment has been made to that variable.
  • 3. Outside of its scope, a variable is invisible.
  • The neat thing is that CoffeeScript’s compiler places the vars for each scope at the top of that scope
  • Define a variable at a specific scope by giving it a sensible initial value
  • Hopefully this is better than ‘null’, but you could do worse and just not initialize it at all.
  • JavaScript won’t force you to initialize it, but doing so can help you to figure out scope issues.

a ?= b

  • the ?= is syntactic sugar, the ? is called the existential operator in CoffeeScript
  • Combined with =, the existential operator means “a equals b unless a?” or
    • “Let b be the default value for a.”

Lastly, wrapping up your code.

  • CoffeeScript can wrap each compiled file into a scope
  • This may be the default, depending on the version of coffeescript you’re using - you might need to pass an option now to either wrap or not wrap your code in a scope.
  • This is actually pretty cool -- if you’re including a lot of JavaScripts on a website, you can’t mix scope there -- no accidental leakage into the global scope space.
  • Compiled CoffeeScript Example:
(function() {
  console.log "hello world!";
}).call(this);

Simpler Looping

  • You write list comprehensions rather than for loops in CoffeeScript
  • Comprehensions are expressions, and can be returned and assigned

Jeremy Ashkenas’s Example

  • Loop over every item in a list, in CoffeeScript:
for item in list
  process item

Intention gets obscured by managing the loop, in JS:

for (var i = 0, l = list.length; i < l; i++) {
  var item = list[i];
  process(item);
}
  • CoffeeScript allows “reasonably solid” JavaScript developers to accomplish the latter by simply writing the former.
  • In Review: CoffeeScript will help you:

    • Write OO, Prototype-based code
    • Avoid bugs in comparisons
    • Stop using ==, only use ===
    • Manage scope and avoid state through scope creep
    • Reduce off-by-one errors in looping, and generally write better loops than you were writing before

    Jasmine

    (My) History (with Jasmine)

    • I started using Jasmine last summer on a client project.
    • It’s enough like the BDD tool we use in Rails, Cucumber, that I consider it a BDD tool.
    • It makes the most sense to me of the BDD/TDD tools in JS I’ve used

    Why Jasmine?

    • All code should be tested => that’s what I believe.
    • You can spend some up-front time testing your code, or you can spend a lot of time bug fixing later
    • I realize that not all legacy codebases are going to have full test coverage overnight.

    The example on the Jasmine site:

    describe("Jasmine", function() {
      it("makes testing awesome!", function() {
        expect(yourCode).toBeLotsBetter();
      });
    });
    • This example sucks!
    • A better example:
    describe ('addition', function() {
      it('adds two numbers', function() {
        expect(1 + 2).toEqual(3);
      });
    });
    • Better? Not really. But we can see what the syntax is doing here and I’m using a real assertion!

    How should we test JS?

    • Functions should not depend on the DOM
    • Our Logic needs to be in separate pieces
      • to make it easier to test the logic, things like AJAX calls, etc
      • without interacting with the DOM

    Easier to test = better code

    • it just so turns out, that the abstraction for testing is a better abstraction overall
    • I've heard "The first implementation of your code is the unit tests" so it may not be DRY, but tests should show how to implement your code!

    Follow TDD/BDD:

    red, green, refactor

    • You can still do this in Jasmine, in fact, I find it kind of natural.

    Jasmine is designed to be standalone

    • This means you don’t need jQuery and you don’t need to run it in a real browser (but you can)

    Some really cool features of Jasmine:

    Matchers:

    • .toBe()
    • .toBeNull()
    • .toBeTruthy()
    • .toBeDefined()
    • .toBeUndefined()

    Setup and teardown:

    beforeEach()
    afterEach()
    • You can use after Each to run a teardown function after each successful test
    • If you need a teardown function after a test whether it passed or failed, use after()

    Spies: built-in mocking & stubbing

    • In Ruby, we’ve been doing mocking and stubbing for awhile.
    • Jasmine’s spies make it easy!
    • These let you do things like watch to see if a method was called
    • Or to stub out other methods so you don’t do real AJAX calls, etc.

    But what about legacy codebases?

    • So you’re thinking, "CoffeeScript and Jasmine sound great, but I have a legacy codebase."
    • Or, "I’ll never get to use either; and they don’t help my big legacy codebase."
    • Well, we’ve run into this and I have a plan.

    Start simple.

    • First, get your tools lined up.
    • Get the CoffeeScript compiler in your tool chain
    • Get Jasmine set up and passing a dummy test.
    • You still haven’t done anything with your legacy code at this point.

    Fix one bug.
    (red, green, refactor)

    • It all starts with one bug. Or one feature, if you’re feeling adventurous. 
    • You might not be able to pull out an entire feature and rewrite it. I understand that. Don't give in to this temptation yet!
    • The way to start this is to write a test around the bug and see it fail. (this might be hard -> depending on how tied your code is to the DOM -- see Jasmine-JQuery for DOM Fixtures)
    • Then fix the bug in regular old JavaScript. See the Jasmine test pass.

    Rewrite the affected code in CoffeeScript.

      You’ve got a working test around this bug.
    • (You know the test works because you saw it red then green.)
    • Now’s your chance to rewrite it in CoffeeScript. It may only be one function at this point. That’s ok.

    Start grouping in files / modules.

    • We found that even with a legacy codebase of a lot of JavaScript, we were able to figure out logical chunks that should live together in CoffeeScript files.

    Keep improving the codebase.

    • This is the hardest part.
    • The temptation is there to just give up and fix bugs only in JS, not to write unit tests, etc.
    • The other temptation is the one you usually can’t give into, which is to try to rewrite everything all at once -> this rarely is accomplishable, it’s better to stage the changes.
    • The big rewrite doesn't work!

    Lessons Learned:

    •  These tools can help you learn JS better.
    • Legacy codebases can slowly grow better through using CoffeeScript & Jasmine.
    • Taking advantage of these is up to you!

    Thanks!

    Resources to learn more:

    Invalid gemspec in [.rvm/gems/ruby-1.9.2-p180@gemset/specifications/actionmailer-3.2.0.gemspec]: Illformed requirement ["# 3.2.0"]

    Recently while trying to create a new Rails 3.2 project, I ran into this error after creating a new RVM gemset in Ruby 1.9.2-p180 and a Gemfile requiring only Rails 3.2.0:

    $ bundle
    Fetching source index for http://rubygems.org/
    Installing rake (0.9.2.2) 
    Installing i18n (0.6.0) 
    Installing multi_json (1.0.4) 
    Installing activesupport (3.2.0) 
    Installing builder (3.0.0) 
    Installing activemodel (3.2.0)
    Invalid gemspec in [/Users/mathiasx/Developer/.rvm/gems/ruby-1.9.2-p180@big_fan/specifications/activemodel-3.2.0.gemspec]: Illformed requirement ["# 3.2.0"]
    ... These Illformed requirement errors continue for every package Rails wants ...

    I thought that I might be able to continue on and ignore these errors, but I hadn't seen anything like them anymore.

    $ rails new .
    Invalid gemspec in [/Users/mathiasx/Developer/.rvm/gems/ruby-1.9.2-p180@big_fan/specifications/actionmailer-3.2.0.gemspec]: Illformed requirement ["# 3.2.0"]
    ... again, it throws this error many times ...

    That didn't generate a new Rails project in my current directory, so something was clearly wrong. But what does Illformed request: Syck:DefaultKey mean? Well, it turns out that the gemspecs of requirements in Rails 3.2.0 are using a new format that older Rubygems can't parse. The first indicator was that my version of Rubygems was out of date:

    $ gem -v
    Invalid gemspec in [/Users/mathiasx/Developer/.rvm/gems/ruby-1.9.2-p180@big_fan/specifications/actionmailer-3.2.0.gemspec]: Illformed requirement ["# 3.2.0"]
    ... I've cut out a bunch of the output from the invalid gemspecs here ...
    1.8.8

    We'd like to be on Rubygems 1.8.13 or newer, but I also don't want to see those invalid gemspec warnings anymore, so I clear out my gemset with:

    $ rvm gemset empty
    WARN: Are you SURE you wish to remove the installed gemset for gemset 'ruby-1.9.2-p180@big_fan' (/Users/mathiasx/Developer/.rvm/gems/ruby-1.9.2-p180@big_fan)?
    (anything other than 'yes' will cancel) > yes
    $ cd ..
    $ cd project/
    Using /Users/mathiasx/Developer/.rvm/gems/ruby-1.9.2-p180 with gemset big_fan

    Note that I have my rvmrc file like this so that it created and trusted the gemset upon encountering it:

    $ cat .rvmrc
    rvm use 1.9.2@big_fan --create

    Getting everything working again:

    Upgrade your Rubygems (this will only apply to this version of Ruby in RVM, not all versions of Ruby)

    $ gem update --system
    == 1.8.15 / 2012-01-06
    
    * 1 bug fix:
    
      * Don't eager load yaml, it creates a bad loop. Fixes #256
    
    
    ------------------------------------------------------------------------------
    
    RubyGems installed the following executables:
            /Users/mathiasx/Developer/.rvm/rubies/ruby-1.9.2-p180/bin/gem
    
    RubyGems system software updated

    Then just to be safe, make sure the gems we have are pristine (According to the manpage, gem pristine: "Restores installed gems to pristine condition from files located in the gem cache.")

    $ gem pristine --all

    And it is safe to now bundle:

    $ bundle
    Fetching source index for http://rubygems.org/
    Installing rake (0.9.2.2) 
    Installing i18n (0.6.0) 
    Installing multi_json (1.0.4) 
    Installing activesupport (3.2.0) 
    Installing builder (3.0.0) 
    Installing activemodel (3.2.0) 
    Installing erubis (2.7.0) 
    Installing journey (1.0.0) 
    Installing rack (1.4.0) 
    Installing rack-cache (1.1) 
    Installing rack-test (0.6.1) 
    Installing hike (1.2.1) 
    Installing tilt (1.3.3) 
    Installing sprockets (2.1.2) 
    Installing actionpack (3.2.0) 
    Installing mime-types (1.17.2) 
    Installing polyglot (0.3.3) 
    Installing treetop (1.4.10) 
    Installing mail (2.4.1) 
    Installing actionmailer (3.2.0) 
    Installing arel (3.0.0) 
    Installing tzinfo (0.3.31) 
    Installing activerecord (3.2.0) 
    Installing activeresource (3.2.0) 
    Using bundler (1.0.18) 
    Installing json (1.6.5) with native extensions 
    Installing rack-ssl (1.3.2) 
    Installing rdoc (3.12) 
    Installing thor (0.14.6) 
    Installing railties (3.2.0) 
    Installing rails (3.2.0) 
    Your bundle is complete! Use `bundle show [gemname]` to see where a bundled gem is installed.

    Now we should have a happy gemset and the error will be gone. Let me know if you have any questions. Happy hacking!

    Getting MongoDB and Devise to play well on Rails 3

    While there's a good guide on the MongoDB site about getting mongo_mapper to work in Rails 3, I ran into some additional issues getting the popular devise authentication engine for Rails to work with Mongo. This documents how to create a Rails app from scratch that uses both MongoDB and Devise. So if you don't want to reinvent the wheel on authentication (read: Users, login, logout, etc) and want to run your app on MongoDB, this should be useful.

    First of all, you'll need Rails 3. I'm on Rails 3.0.3. I created a new gemset for this app, just to keep things clean. Run the rails new command with the --skip-active-record switch.

    $ rails new awesome_app --skip-active-record

    Open up the Gemfile of the new app. It's going to be pretty empty to start. This is what I ended up with, after reading the Mongo guide mentioned at the beginning of the post:

    require 'rubygems'
    require 'mongo'
    
    source :rubygems
    
    gem 'mongo_mapper'
    gem 'rails', '3.0.3'
    gem 'devise', '1.1.3'
    gem 'devise-mongo_mapper',
      :git    => 'git://github.com/collectiveidea/devise-mongo_mapper'
    
    group :test, :development do
      [whatever testing gems you want in here]
    end

    You'll notice the devise-mongo_mapper gem in there. That's the secret sauce that lets us use mongo_mapper as the ORM for Devise. As I haven't really played with the mongoid gem (and therefore don't have any experience with it) I didn't try to get mongoid to work.

    Go ahead and run a bundle install:

    $ bundle install

    Then run this to install devise files into the Rails app:

    $ rails generate devise:install

    There's two initializer files we'll need. We add the one for mongo, which I put in config/initializers/mongo.rb:

    MongoMapper.connection = Mongo::Connection.new('localhost', 27017)
    MongoMapper.database = "awesome-app-#{Rails.env}"
    
    if defined?(PhusionPassenger)
       PhusionPassenger.on_event(:starting_worker_process) do |forked|
         MongoMapper.connection.connect_to_master if forked
       end
    end

    And one for devise, which you'll find created for you in config/initializers/devise.rb. Change the ORM Configuration settings to this:

    # ==>; ORM configuration
    # Load and configure the ORM. Supports :active_record (default) and
    # :mongoid (bson_ext recommended) by default. Other ORMs may be
    # available as additional gems.
      require 'devise/orm/mongo_mapper'

    Be sure to add this line to config/application.rb and edit in the appropriate address. This will keep Devise from complaining later:

    config.action_mailer.default_url_options = { :host => "yourdomain.com" }

    The last step is to create an User model and tell Devise and mongo_mapper to do their thing. Tell Devise to make a Users model and then install the Devise Views to our app so that we can modify them later, if we wish:

    $ rails generate devise users
    $ rails generate devise:views

    In app/models/user.rb:

    class User
      include MongoMapper::Document         
      plugin MongoMapper::Devise
    
      devise :database_authenticatable, :confirmable, :lockable, 
             :recoverable, :rememberable, :registerable, :trackable, 
             :timeoutable, :validatable, :token_authenticatable
    
      attr_accessible :email, :password, :password_confirmation
       
    end

    You can, of course, choose which of those Devise options to enable for your user model. Refer to the devise documentation for more information.

    In app/controllers/application_controller.rb:

    class ApplicationController < ActionController::Base
      protect_from_forgery
      
      filter_parameter_logging :password, :password_confirmation
      
      def after_sign_out_path_for(resource_or_scope)
        new_user_session_path
      end
    end

    In config/routes.rb add these lines:

    devise_for :users, :admin
    resource :user

    At this point, you should probably check your app by running a quick `rails server` and seeing it if spits out any errors to your terminal. If it's all good, then you are probably thinking you'll want to actually use this authentication system now. Let's add a very basic "Home" controller:

    $ rails generate controller Home index token

    In app/controllers/home_controller.rb, add the following before_filter line to the beginning of the class so that it looks like this:

    class HomeController < ApplicationController
      before_filter :authenticate_user!, :only => :token
      
      def index
      end
    
      def token
      end
    
    end

    In app/views/home/index.haml (I'm using HAML, but I've also included an ERb example after this:

    - if user_signed_in?
      %ul
        %li= current_user.email
        %li= link_to 'My info', edit_user_path
        %li= link_to 'Sign out', destroy_user_session_path
    - else
      %ul
        %li= link_to 'Sign in', new_user_session_path
        %li= link_to 'Sign up', new_user_path
    # ERb version of app/views/home/index.html.erb:
    <% if user_signed_in? -%>
      <ul>
        <li><%= current_user.email %></li>
        <li> link_to 'My info', edit_user_registration_path %></li>
        <li><%= link_to 'Sign out', destroy_user_session_path %></li>
      </ul>
    <% else -%>
      </code><ul><code>
        <li><%= link_to 'Sign in', new_user_session_path %></li>
        <li><%= link_to 'Sign up', new_user_path %></li>
    <% end -%>

    This will enable very basic login / logouts using the Devise views that we installed earlier. If you run rails server again, you'll be able to create an account. But, if your system isn't set up to send mail (like mine) then you may get an error, or simply won't get a confirmation code, so you won't be able to login with that user. Here's a quick solution. Drop into the mongo shell:

    $ mongo
    MongoDB shell version: 1.6.4

    Use your database, which you set above in config/initializers/mongo.rb. In this case, it's awesome-app-development:

    > use awesome-app-development
    switched to db awesome-app-development

    Find all the entries in the Users document:

    > db.users.find();
    { "_id" : ObjectId("4d216ae217cacc289c000005"), "email" : "matt.gauger@gmail.com", "encrypted_password" : "$2aasdf", "password_salt" : "$2aasdf", "authentication_token" : null, "remember_token" : null, "remember_created_at" : null, "reset_password_token" : null, "confirmation_token" : "YsFg8CFBwNIm5kof7xC9", "confirmed_at" : null, "confirmation_sent_at" : "Mon Jan 03 2011 00:21:22 GMT-0600 (CST)", "failed_attempts" : 0, "unlock_token" : null, "locked_at" : null, "sign_in_count" : 0, "current_sign_in_at" : null, "last_sign_in_at" : null, "current_sign_in_ip" : null, "last_sign_in_ip" : null }

    The bit we need is the confirmation_token: "YsFg8CFBwNIm5kof7xC9". Copy the token and go to the following in your browser:

    http://localhost:3000/users/confirmation?confirmation_token=YsFg8CFBwNIm5kof7xC9

    Remember to replace your token with the one in that URL. You could alternatively make it so your app can send mail, or just turn off :confirmable in your Users model. This was a quick little solution that I found and wanted to share.

     

    Hopefully this gets you going with Mongo and Devise quickly and without any snags. There's a lot more to Devise, so I'd recommend you start looking at some of the Example applications and the documentation.

    Techniques for learning a new skill

    I just found these notes in an old notebook and figured they deserved a blog post . This isn't self-help type stuff, it's just practical. I'm blogging this so that some day I find it when reading my old blog posts, just as I found the list in an old notebook.

    Techniques helpful in learning any skill:

    • Know your starting point (measure it, somehow) => "You can't manage what you can't measure."
    • Set objectives.
    • Have techniques for improvement available.
    • Assess your progress after a reasonable period.
    • Have strategies for continuing improvement and maintaining the skill.

    I'd add in there to have a good way to record your progress. Something like Daytum will probably work if you want a web-based tool. Excel spreadsheets and notebooks work great, too.

    Open URL from Terminal.app and the 'open' command

    More and more as I blog, I find I like to post little tips, and beginners thank me for showing them something new, More advanced users may have simply overlooked whatever I'm sharing, so they can find it useful, too. So in that vein, I just reminded myself of a little Mac OSX Terminal.app trick I use all the time, and wanted to post it here.

    When you have a URL in Terminal.app, say in a README file you're reading, or coming back from a dig/whois command, you can just right-click (option-click) on the URL and click Open URL. It's fast, it works, and it saves me a few milliseconds of copy/pasting it. Awesomest trick ever? Not really. Useful? Definitely.

    Terminal-right-clic

    An alternative form of this is to use the Mac OSX "open" command on any number of things: Files, applications, URLs. Here's how it looks: 
    banshee:diaspora mathiasx$ open http://joindiaspora.com
    banshee:diaspora mathiasx$ open README.md 
    banshee:diaspora mathiasx$ open /Applications/Google\ Chrome.app/

    That will open the joindiaspora.com site in your default browser, a file called README.md in whatever your default text editor is, and the final command will open Google Chrome if it isn't open, otherwise it will simply switch focus to it.

    Git & Github: A Beginner's Guide

     Let me start off by saying that this blog post isn't going to introduce you to the intricacies of git. There is certainly a lot to be learned and a lot that I could write about branching, managing git servers, and any number of the git commands and ways to use them. I've linked to more resources where you can learn all of that at the bottom of this post.

    Instead, I'm going to focus on the beginner user, maybe someone that has never contributed to open source but wants to learn and wants to help out. They're not interested in learning the in's and out's of every single command. They just want to get stuff done.

    It's likely that a person would run into git for the first time by visiting Github. There are a lot of big, popular open source projects hosted there. Too many to list here, in fact, but here's a quick teaser: jQuery, scriptaculous, yui3, prototype, and mootools are all on Github. So are Ruby on Rails, Symfony, Django, web.py, and many other web app frameworks. The list goes on and on.

    But why is Github so popular? It certainly has a nice design. But design alone doesn't necessarily attract developers by the hundreds; most of them still use the arcane commandline with bitmapped fonts, after all.

    I'll put forward that Github is popular with developers because it optimizes for the things they need to do, without getting in the way. And because it's fun. There's very little barrier of entry for a developer to contribute back to open source, or upload new code, or even find projects that are interesting to them. It takes all the best features of git (which we'll get to in a minute) and smooths out all the rough, annoying edges of setting up a server, handling SSH key authentication, and tracking URLs to reference.

    Let's assume our beginner has stumbled upon Pete Prodoehl's Heard project, which lets you mirror your Last.fm scrobbles and host it in a decent way outside of Last.fm. The user wants to use it to back up their Last.fm, too. They likely came across Heard in Pete's blog. The blog post contains a link to the Heard Github repo, which looks like this:

    Raster_heard_-_github-smaller

    This is the first time the user has been to Github, so they don't quite know how it works. If the user hovers over the Watch or Fork buttons, they will be instructed to sign in. They might just click the big Download button and never come back, but let's assume this person wants to contribute back. They know they want to add a feature to Heard so that it draws gRaphaël graphs of their listening history, because they think graphs are pretty cool and gRaphaël draws very beautiful graphs:

    Graphael_line_charts

    The user then creates an account on Github, since they don't have one and it's required to click those Watch or Fork buttons. They go back to Pete's Heard project on Github and click Watch. The text changes from Watch to Unwatch but they don't notice anything immediately. Watch is a way to subscribe to repo changes in the Dashboard of Github, but our user doesn't know that yet. Looking for more immediate satisfaction from clicking buttons, our user clicks Fork. And then something magical happens. (Parents please make your children leave the room during this part of the program.)

    Hardcore-forking-action

    The user is now in their own copy of Heard. Github has forked the open source project, a task which once took Herculean strength and immense popularity to pull off in the dark days of Sourceforge! The user realizes this forked project is their very own kingdom to do with as they please. And the user, motivated by their hunger for pretty graphs and encouraged by the fact that Github is hosting their fork for free, is ready to jump into coding. (All open source projects get free hosting on Github, and open source does not count against your paid account's number of repositories.)

    The first step Github gives after setting up your account is to install git. We'll leave it up the to the reader to track down and install the latest version of git (at the time of this writing, v1.7.3.3) from http://git-scm.com/ for their particular operating system.Once our daring, brave fictional hero gets git compiled and installed, Github instructs them to generate an SSH key and enter it into their Account Settings on Github:

    Your_account_-_github-1

     

    Sidenote: What are SSH keys and why do I need them?

    SSH is a secure communications method that uses public/private key encryption. Explaining public/private key encryption with Bob, Alice and the gang is beyond the scope of this blog post, but in short, it works like this: You generate two keys, a public and a private key. A key is just a bunch of characters in a text file, but it's used as a very strong cryptographic key.The public and private key are linked in such a way that you can mathematically prove data came from the person holding the private key.

    The private key must be kept private! No one but you should ever possess it! When you cryptography "sign" a file, text, or other data with your private key, another party can verify that you (and only you) could have sent that data, by using your public key. You can also guarantee the identity of a server you're communicating with, because their public key allows you to verify data was signed by their private key. Make sense?

    So when you put your public key into Github, it can verify that any data sent to it claiming to be you is actually from you (and signed by your private key). It's much more complicated than this, but that's the idea.

    For more information on OpenSSH, SSH keys, and the like, refer to the OpenSSH FAQ.

    Back to Github:

    Our user runs the

    ssh-keygen

    command in their shell, hits enter for the defaults, and generates some files that live in /home/[username]/.ssh in Linux/BSD or /Users/[username]/.ssh if you're on Mac OSX. If you're still on Windows, it'll be in Cygwin somewhere, likely. Sorry. Can't help you there.

    One file is called id_rsa and the other is id_rsa.pub. The file with the .pub extension is, naturally, the public key. That's the one that you want to copy into Github's Account Settings page. Once you enter a public key (again, make sure it isn't the private key!) into Github, it will know who you are when you communicate with Github using the git commands. The great thing about this is, every time we communicate with the server, we don't have to type a password. Isn't that cool?

    At this point our beginner hasn't actually gotten any files, made any changes, or asked Pete nicely to accept his changes. That may seem like all this effort was a waste, but in practice it is fairly quick and once you've done it once (and setup a Github account) you're well on your way to doing all those things. Git also prefers to have your name and email address to identify changes as coming from you, and Github helpfully provides the commands for those when setting up a new account.

    The user is ready to work. At the top of their fork of the Heard project, Github gives the user a link says it is read+write. Which probably means that by using it somehow, we can write our changes back to Github and get one step closer to getting those changes back to Pete. Github even helpfully offers to copy the link for you.

    Mathias-presentation_heard_-_github

     Our user dives into the gitref.org page and discovers what they are supposed to do with this URL ending in .git: They must use the git clone command to clone it from Github down to wherever they are in the shell, like so:
    $ git clone git@github.com:mathias-presentation/Heard.git
    Cloning into Heard...
    remote: Counting objects: 22, done.
    remote: Compressing objects: 100% (22/22), done.
    remote: Total 22 (delta 7), reused 0 (delta 0)
    Unpacking objects: 100% (22/22), done.

    If the user's SSH key was right, this will work. If not, they'll have to go back and fix it. Looks like it worked, though, so we'll move on.

    The git clone command has created a directory named Heard in the user's home directory. There's also a hidden .git directory in there, but that's where git keeps its affairs in order, and for the purposes of this post, it can be ignored. The nice thing about git is that the .git/ directory is the only place it adds something to your project, unlike Subversion, which puts a .svn directory in every directory.

    The user is now ready to use git, with an eye towards contributing his changes back to Pete as fast as possible.

    Git Basics

    Our user makes some basic changes to the PHP files in their local Heard directory. They reason that if git keeps tracks of versions of files, it will need to create a new version containing these changes. Git tracks changes on an object level, not a file or project/directory level. That means it is really smart about where you changed something. See some of the reference material for a full explanation of how git understands changes. For our purposes, the beginner finds the

    git commit

    command. The documentation says that the commit command is used to take a snapshot of the code in the current state. That sounds perfect. But typing the command yields:

    $ git commit
    # On branch master
    # Changed but not updated:
    #   (use "git add ..." to update what will be committed)
    #   (use "git checkout -- ..." to discard changes in working directory)
    #
    #        modified:   config.php
    #        modified:   init.php
    #        modified:   tracks.php
    #
    no changes added to commit (use "git add" and/or "git commit -a")

    Nothing happened, as indicated by the message "no changes added to commit." Git has helpfully suggested some ways to commit at the bottom there. The first is to use the

    git add

    command, which sounds like a way to add files to a commit. The other is to pass in the -a argument to git commit, like this:

    git commit -a

    The -a is for all, so when we tell git to git commit -a, we're telling it to commit everything that changed, all at once. The user types that in and is dropped into.. a text editor?

    This step can be a little confusing for new users. In fact, that this is default behavior is, in my opinion, a huge roadblock to people to get up and running with git. On Mac OSX or Linux, this is likely to be vim, which is not the most user-friendly to new users.

    What is the text editor for? Git is asking for the user to write a commit message, that is, a description of what changed for the official history of this project. For something more user-friendly to write this commit message in, I suggest you type this arcane incantation from the wizards of old:

    git config core.editor "nano"

    The meaning of this spell is lost to the ages, but the nice thing is, it will drop you into the much more user-friendly nano editor, which list the CTRL- characters to save, quit, etc. along the bottom of the terminal.

    A good commit message will help later when trying to find what changed over the course of time. Git is already tracking which files changed and what changed in them, so the message is usually written at a more abstract level that says what the user was doing and thinking. There's plenty of good resources out there on the web about writing good commit messages, and even more about opinions about what should and shouldn't be in a commit message.

    This is where I want to break the narrative a little to explain the rest of the commands that our fictional beginner will need to contribute back to Pete's Heard project. Think of it as a blog post montage of lots of coding getting done.

    The first is simply another argument to add to the git commit command so that we don't get dropped into a text editor to write our commit message. Usually, commit messages are only one line, so it makes sense to write the commit message right there on the command line:

    $ git commit -am "Added the gRaphael library to be loaded, but haven't integrated it yet."
    [master 1a4014f] Added the gRaphael library to be loaded, but haven't integrated it yet.
     3 files changed, 3 insertions(+), 3 deletions(-)

    As you can see, that worked, and it accepted our commit message right there on the command line. Notice that the commit message is wrapped in double quotes: " " This is because the shell will try to interpret words on their own as commands, and we don't want that. So, to be safe, we wrap the whole thing in double quotes, so that the shell knows that this is a string to pass into the git command and not something to try and execute.

    What else does the output from git commit tell us? Well, we committed all three files at once. In other version control systems, you must commit all changes to files at once. This can get annoying. In git, you can choose which files you want to add to any given commit, and leave out anything else that changed. To do that, we use the git add command.

    [do some work]
    $ git add config.php
    $ git add init.php

    You'll notice there's no output from these commands. That's because nothing has changed yet. Those changes still need to be committed. It would probably be helpful at this point to have a look at what changed and what didn't. For that, we use the git status command.

    $ git status
    # On branch master
    # Your branch is ahead of 'origin/master' by 1 commit.
    #
    # Changes to be committed:
    #   (use "git reset HEAD ..." to unstage)
    #
    #        modified:   config.php
    #        modified:   init.php
    #
    # Changed but not updated:
    #   (use "git add ..." to update what will be committed)
    #   (use "git checkout -- ..." to discard changes in working directory)
    #
    #        modified:   tracks.php
    #

    As you can probably see, we're not going to commit all the changes like we did in the first commit. We're only going to commit the changes in the config.php and init.php files this time around, while tracks.php will be left out of the commit. Git status is a very useful command, and you will probably use it a lot to figure out the current state of your project and to see what should and shouldn't be in a commit. Since we don't want to commit all changes this time, we leave off the -a argument but keep the -m so we can specify a commit message:

    $ git commit -m "A little refactoring."
    [master 6dd5c59] A little refactoring.
     2 files changed, 2 insertions(+), 2 deletions(-)
    $ git status
    # On branch master
    # Your branch is ahead of 'origin/master' by 2 commits.
    #
    # Changed but not updated:
    #   (use "git add ..." to update what will be committed)
    #   (use "git checkout -- ..." to discard changes in working directory)
    #
    #        modified:   tracks.php
    #
    no changes added to commit (use "git add" and/or "git commit -a")

    Notice that the changes in tracks.php are still waiting for us when we perform another git status.

    Git log is a command used to see the history of a project.

    $ git log
    commit e47cd6eb917d2a68ec6d1197a38faa1a1ff5e564
    Author: Matt Gauger 
    Date:   Thu Dec 9 14:27:04 2010 -0600
    
        Some other places where it's nice to have newlines.
    
    commit fb2cc2b00bfd797cca355a4215b2c289e281e040
    Author: Matt Gauger 
    Date:   Thu Dec 9 14:26:23 2010 -0600
    
        Add a newline here.
    
    commit 4cce3dff9c8d03b7db1710f7addaa7d556921a44
    Author: raster 
    Date:   Sun Nov 14 11:38:50 2010 -0600
    
        Minor changes, still learning git
    
    [truncated for length]

    At this point you may be asking, how does our user upload their changes back to Github?

    The command required is: 

    $ git push

    And in its default form, it knows what to push and where: It defaults to Github when you use the git clone command as above. A more complicated form of git push will allow you to choose the branch and the server to push to, but for our beginner user that is only using the master branch and Github server, it is unnecessary to understand.

    Similarly, to get changes from the server, use the command:

    $ git fetch

    This is, again, some default behavior that knows to go talk to the Github server. It will pull down changes from the user's repo on Github, in this default manner, and it is important to note that this won't include the changes made by other Github users. This part can initially be confusing to people who are looking to keep up with a project's main development or other developer's contributions, and Github's documentation explains this stuff (and it is managed largely through the Github website) so I'll leave it beyond the scope of this blog post.

    The git fetch command is useful if you have two computers with SSH keys in Github, for example, and you commit and push from your desktop, but want to grab your most recent changes on your laptop.

    The other question you may be asking yourself at this point is: What happens if I screw up? What if you accidentally delete a file from your project, or you save something you didn't mean to save. In this case, you can restore the project to the state of the last commit. Git is, after all, storing all the states of the project, so you can roll it backwards as necessary. The command to reset a project back to its previously committed state is:

    $ git reset --hard HEAD

    There's a lot going on here to point out. The git reset part is obviously the command that we use to reset a project to a previous state. The state we're telling to to go back to is called HEAD, which is just a placeholder name for the last commit. There's lots of these placeholder names in git, and it's nice to learn the common ones. For the purposes of this blog post, we can ignore the --hard, and refer you to the reference materials and "man git-reset" on your shell for further explanation.

    You want to be careful with git reset, obviously. It will reset all the files in your project to the state they were in at the last commit. So all changes since the last commit will go away. This isn't always ideal. You could lose a lot of work this way. If you want to only reset one file, this is the way that I do it, and I imagine there are other ways in the very many different git commands:

     

    $ git checkout -- init.php
    $ git checkout HEAD init.php

    In this case we're taking the state of init.php back to the last commit, which again, is referenced with the placeholder name HEAD. 

    That's the most common commands that this beginner user will encounter and have to grasp in using Github and git. So now what? Well, remember that our user had the end goal of contributing back to Pete Prodoehl's Heard project. The way to do is to go back to our user's fork of Heard on Github, and use the Pull Request button. 

    Pull_request

    Which is highlighted in blue above.

    By submitting a Pull Request back to Pete for the changes the user has made, Pete can choose which commits to pull into his version of Heard, which is basically the official repo for the project at this time. By getting Pete to accept a Pull Request, our user has contributed back to Heard, which was the user's initial goal. It wasn't the easiest road to contributing back, but once developers learn these skills and they have set up both git and Github to their liking, it is actually quite fast, fun, and powerful to use Github to contribute to open source.

    Thanks for reading! Please leave comments on places where this could be improved or any other thoughts you have.

    Slides from my talk:

     

    Further reading:

    • Github has great documentation online at help.github.com. I highly suggest you start there.
    • Github will point you in the direction of the gitref.org page for a quick overview of Git.
    • Scott Chacon released his book, Pro Git, online for free. It is also available from Apress on Amazon: Pro Git. (Full disclosure: Amazon Associates link. That is, I earn a small commission on stuff sold through these links on my blog.)
    • The Pro Git blog is a good place to continue to pick up Git tricks and tips as you learn.
    • It hasn't been updated in a long time, but the git ready blog has taught me many tricks that I use with git.

    All other resources mentioned:

    What I'm doing in December

    Aside from being really busy at work, I'll be giving three talks at users groups in December. How did I end up agreeing to give three talks in one month? People heard me talking about some topic and they asked me to talk to their group on it. If asked, I'll usually agree. I'm trying to tie them together thematically to make it a three-parter (and encourage people to come to all three meetups) so I'm calling the series: 'Tis the Season to Write Web Apps, A Three Part Adventure.

     

    Development Tools: Git and Github (aka, 'Tis the Season to Write Web Apps Part 1)

    Web414

    7PM, December 9th, 2010 @ Bucketworks

    I've been asked to talk about Git and Github and give a basic intro on how to use it. I'll also talk about my developer workflow and technical team collaboration. I'd also like to point out that Ralph Holzmann will be talking about LABjs and yepnope.js after my talk. It should be a great night with lots of technical content!

     

     

    PHP vs Rails (aka, 'Tis the Season to Write Web Apps Part 2)

    Milwaukee PHP Users Group

    6PM, December 14th, 2010 @ Bucketworks

    This talk started as the idea of comparing how traditional LAMP servers differ from how application servers behave. I'll be covering Apache and phpmod, some information about nginx, and compare performance. A very basic web app will be used as an example and I'll introduce some tools used for benchmarking web applications. Note that the topic has seemingly mutated from "LAMP servers versus Application Servers" to PHP vs. Rails, possibly to draw out more folks by using good keywords ;)

     

    What's new and great in Rails 3! (aka, 'Tis the Season to Write Web Apps Part 3!)

    RubyMKE

    7PM, December 20th, 2010 @ Bucketworks

    I'll be talking about what's different in Rails 3 and what stays the same. A perfect follow-up to those that want to know about writing for an application server after the MKE PUG talk. I'll have a ported version of the basic web app from the MKEPUG talk.

     

    All code and slides will be available here and on my Github after the talks. I'll hopefully have time to put together summary blog posts afterwards, too.

    Also: Be sure to join us at MilwaukeeDevHouse5 on December 3rd, 2010, 5PM-midnight. The description from Web414:

     

    This will be the first MilwaukeeDevHouse with a purpose... In the past we just called it a "party with laptops" and invited you to come and work on whatever you wanted to. You can still do that, but we're also going to focus on ways to make BarCampMilwaukee (and Bucketworks) better. Call it "Hack the BarCamp!" or maybe "Hack the Bucket!"

     

     

    See you there!

    /Library/Ruby/Site/1.8/rubygems.rb:779:in `report_activate_error': Could not find RubyGem railties (>= 0) (Gem::LoadError)

    I tend to blog things that I want to remember, but couldn't find in one quick Google search.

    This is an error I got while starting the Rails Tutorial, because I was using an old system (I counted about 160 gems installed) and starting to use rvm for managing my Ruby installs, because the Rails Tutorial recommends it. I haven't had this kind of issue before, because rails, gem, and ruby commands have all just worked out-of-the-box for me since Mac OS X 10.5 was released.

    When I tried to create a new Rails app, I got the error in the title of the blog post:

    minimite:railstutorial.org mathiasx$ rails new first_app
    /Library/Ruby/Site/1.8/rubygems.rb:779:in `report_activate_error': Could not find RubyGem railties (>= 0) (Gem::LoadError)
            from /Library/Ruby/Site/1.8/rubygems.rb:214:in `activate'
            from /Library/Ruby/Site/1.8/rubygems.rb:1082:in `gem'
            from /usr/bin/rails:18
    minimite:railstutorial.org mathiasx$ rails -v
    /Library/Ruby/Site/1.8/rubygems.rb:779:in `report_activate_error': Could not find RubyGem railties (>= 0) (Gem::LoadError)
            from /Library/Ruby/Site/1.8/rubygems.rb:214:in `activate'
            from /Library/Ruby/Site/1.8/rubygems.rb:1082:in `gem'
            from /usr/bin/rails:18

    That didn't make sense. Where is the Rails command being run from?

    minimite:railstutorial.org mathiasx$ which ruby
    /Users/mathiasx/.rvm/rubies/ruby-1.9.2-p0/bin/ruby
    minimite:railstutorial.org mathiasx$ which gem
    /Users/mathiasx/.rvm/rubies/ruby-1.9.2-p0/bin/gem
    minimite:railstutorial.org mathiasx$ which rails
    /usr/bin/rails

    Aha. Obviously the previously-installed Rails gem, from before installing rvm, was an issue. So I figured I'd just clean it up.

    minimite:railstutorial.org mathiasx$ rvm list
    
    rvm rubies
    
       ruby-1.8.7-p302 [ i386 ]
    => ruby-1.9.2-p0 [ i386 ]
    
    minimite:railstutorial.org mathiasx$ rvm use 1.8.7
    Using /Users/mathiasx/.rvm/gems/ruby-1.8.7-p302
    minimite:railstutorial.org mathiasx$ sudo gem uninstall rails
    Remove executables:
            rails
    
    in addition to the gem? [Yn]  y
    Removing rails
    Successfully uninstalled rails-3.0.1
    minimite:railstutorial.org mathiasx$ which rails
    /usr/bin/rails

    But I hadn't been paying attention. I still had not removed the Rails executable, because I was calling the rvm version of Ruby 1.8.7 and not the system-level one. Knowing that Rails was in /usr/bin/rails, I assumed that the gem executable that managed it would also be in /usr/bin:

    minimite:railstutorial.org mathiasx$ sudo /usr/bin/gem uninstall rails
    Remove executables:
            rails
    
    in addition to the gem? [Yn]  y
    Removing rails
    
    You have requested to uninstall the gem:
            rails-3.0.0
    clearance-0.9.0.rc9 depends on [rails (~> 3.0.0)]
    factory_girl_rails-1.0 depends on [rails (>= 3.0.0.beta4)]
    suspenders-0.1.0.beta.4 depends on [rails (>= 3.0.0)]
    If you remove this gems, one or more dependencies will not be met.
    Continue with Uninstall? [Yn]  y
    Successfully uninstalled rails-3.0.0

    I'd finally removed the system-level Rails script and can switch back to using rvm for everything:

    minimite:railstutorial.org mathiasx$ rvm use 1.9.2
    Using /Users/mathiasx/.rvm/gems/ruby-1.9.2-p0
    minimite:railstutorial.org mathiasx$ which rails
    /Users/mathiasx/.rvm/gems/ruby-1.9.2-p0/bin/rails
    Using /Users/mathiasx/.rvm/gems/ruby-1.9.2-p0
    minimite:railstutorial.org mathiasx$ rails new first_app
          create  
          <-- Lots of output -->

    It works!

    Lesson learned: Always pay attention. The answer is probably sitting right in front of you.