Category Archives: Programming

How to Wire Up Ruby on Rails and AngularJS as a Single-Page Application

This post is now somewhat outdated. If you’re interested in Angular/Rails/Grunt/JavaScript/HTML, read on. If you’d prefer Angular/Rails/Gulp/CoffeeScript/Jade, check out this newer post.

Why this tutorial exists

I wrote this tutorial because I had a pretty tough time getting Rails and Angular to talk to each other as an SPA. The best resource I could find out there was Ari Lerner’s Riding Rails with AngularJS. I did find that book very helpful and I thought it was really well-done, but it seems to be a little bit out-of-date by now and I couldn’t just plug in its code and have everything work. I had to do a lot of extra Googling and head-scratching to get all the way there. This tutorial is meant to be a supplement to Ari’s book, not a replacement for it. I definitely recommend buying the book because it really is very helpful.

The sample app

There’s a certain sample app I plan to use throughout AngularOnRails.com called Lunch Hub. The idea with Lunch Hub is that office workers can announce in the AM where they’d like to go for lunch rather than deciding as they gather around the door and waste half their lunch break. Since Lunch Hub is a real project with its own actual production code, I use a different project here called “Fake Lunch Hub.” You can see the Fake Lunch Hub repo here. I also created a branch specifically to match up with this tutorial here.

Setting up our Rails project

Instead of regular Rails we’re going to use Rails::API. I’ve tried to do Angular projects with full-blown Rails, but I end up with a bunch of unused views, which feels weird. First, if you haven’t already, install Rails::API.

$ gem install rails-api

Creating a new Rails::API project works the same as creating a regular Rails project.

$ rails-api new fake_lunch_hub -T -d postgresql

Get into our project directory.

$ cd fake_lunch_hub

Create our PostgreSQL user.

$ createuser -P -s -e fake_lunch_hub

Create the database.

$ rake db:create

Now we’ll create a resource so we have something to look at through our AngularJS app. (This might be a good time to commit this project to version control.)

Creating our first resource

Add gem 'rspec-rails' to your Gemfile (in the test group) and run:

$ bundle install
$ rails g rspec:install

When you generate scaffolds from now on, RSpec will want to create all kinds of spec files for you automatically, including some kinds of specs (like view specs) that in my opinion are kind of nutty and really shouldn’t be there. We can tell RSpec not to create these spec files:

# config/application.rb

require File.expand_path('../boot', __FILE__)

# Pick the frameworks you want:
require "active_model/railtie"
require "active_record/railtie"
require "action_controller/railtie"
require "action_mailer/railtie"
require "action_view/railtie"
require "sprockets/railtie"
# require "rails/test_unit/railtie"

# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups)

module FakeLunchHub
  class Application < Rails::Application
    # Settings in config/environments/* take precedence over those specified here.
    # Application configuration should go into files in config/initializers
    # -- all .rb files in that directory are automatically loaded.

    # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
    # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
    # config.time_zone = 'Central Time (US & Canada)'

    # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
    # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
    # config.i18n.default_locale = :de

    config.generators do |g|
      g.test_framework :rspec,
        fixtures: false,
        view_specs: false,
        helper_specs: false,
        routing_specs: false,
        request_specs: false,
        controller_specs: true
    end
  end
end

(Now might be another good time to make a commit.)

In Lunch Hub, I want everybody’s lunch announcements to be visible only to other people in the office where they work, not the whole world. And there’s actually a good chance a person might want to belong not only to a group tied to his or her current workplace, but perhaps a former workplace or totally arbitrary group of friends. So I decided to create the concept of a Group in Lunch Hub. Let’s create a Group resource that, for simplicity, has only one attribute: name.

$ rails g scaffold group name:string

Since groups have to have names, let’s set null: false in the migration. We’ll also include a uniqueness index.

# db/migrate/<timestamp>_create_groups.rb

class CreateGroups < ActiveRecord::Migration
  def change
    create_table :groups do |t|
      t.string :name, null: false

      t.timestamps
    end

    add_index :groups, :name, unique: true
  end
end
$ rake db:migrate

Now, if you run rails server and go to http://localhost:3000/groups, you should see empty brackets ([]). We actually want to be able to do http://localhost:3000/api/groups instead.

# config/routes.rb

Rails.application.routes.draw do
  scope '/api' do
    resources :groups, except: [:new, :edit]
  end
end

At the risk of being annoying, I wanted to include a realistic level of testing in the tutorial, at least on the server side. (I don’t have the client-side testing part 100% figured out yet.)

# spec/models/group_spec.rb

require 'rails_helper'

RSpec.describe Group, :type => :model do
  before do
    @group = Group.new(name: "Ben Franklin Labs")
  end

  subject { @group }

  describe "when name is not present" do
    before { @group.name = " " }
    it { should_not be_valid }
  end
end

To make this spec pass you’ll of course need to add a validation:

# app/models/group.rb

class Group < ActiveRecord::Base
  validates :name, presence: true
end

We also have to adjust the controller spec RSpec spit out for us because RSpec’s generators are evidently not yet fully compatible with Rails::API. I suggest you just copy my file and replace yours wholesale.

# spec/controllers/groups_controller_spec.rb

require 'rails_helper'

# This spec was generated by rspec-rails when you ran the scaffold generator.
# It demonstrates how one might use RSpec to specify the controller code that
# was generated by Rails when you ran the scaffold generator.
#
# It assumes that the implementation code is generated by the rails scaffold
# generator.  If you are using any extension libraries to generate different
# controller code, this generated spec may or may not pass.
#
# It only uses APIs available in rails and/or rspec-rails.  There are a number
# of tools you can use to make these specs even more expressive, but we're
# sticking to rails and rspec-rails APIs to keep things simple and stable.
#
# Compared to earlier versions of this generator, there is very limited use of
# stubs and message expectations in this spec.  Stubs are only used when there
# is no simpler way to get a handle on the object needed for the example.
# Message expectations are only used when there is no simpler way to specify
# that an instance is receiving a specific message.

RSpec.describe GroupsController, :type => :controller do

  # This should return the minimal set of attributes required to create a valid
  # Group. As you add validations to Group, be sure to
  # adjust the attributes here as well.
  let(:valid_attributes) {
    skip("Add a hash of attributes valid for your model")
  }

  let(:invalid_attributes) {
    skip("Add a hash of attributes invalid for your model")
  }

  # This should return the minimal set of values that should be in the session
  # in order to pass any filters (e.g. authentication) defined in
  # GroupsController. Be sure to keep this updated too.
  let(:valid_session) { {} }

  describe "GET index" do
    it "assigns all groups as @groups" do
      group = Group.create! valid_attributes
      get :index, {}, valid_session
      expect(assigns(:groups)).to eq([group])
    end
  end

  describe "GET show" do
    it "assigns the requested group as @group" do
      group = Group.create! valid_attributes
      get :show, {:id => group.to_param}, valid_session
      expect(assigns(:group)).to eq(group)
    end
  end

  describe "POST create" do
    describe "with valid params" do
      it "creates a new Group" do
        expect {
          post :create, {:group => valid_attributes}, valid_session
        }.to change(Group, :count).by(1)
      end

      it "assigns a newly created group as @group" do
        post :create, {:group => valid_attributes}, valid_session
        expect(assigns(:group)).to be_a(Group)
        expect(assigns(:group)).to be_persisted
      end

      it "redirects to the created group" do
        post :create, {:group => valid_attributes}, valid_session
        expect(response).to redirect_to(Group.last)
      end
    end

    describe "with invalid params" do
      it "assigns a newly created but unsaved group as @group" do
        post :create, {:group => invalid_attributes}, valid_session
        expect(assigns(:group)).to be_a_new(Group)
      end

      it "re-renders the 'new' template" do
        post :create, {:group => invalid_attributes}, valid_session
        expect(response).to render_template("new")
      end
    end
  end

  describe "PUT update" do
    describe "with valid params" do
      let(:new_attributes) {
        skip("Add a hash of attributes valid for your model")
      }

      it "updates the requested group" do
        group = Group.create! valid_attributes
        put :update, {:id => group.to_param, :group => new_attributes}, valid_session
        group.reload
        skip("Add assertions for updated state")
      end

      it "assigns the requested group as @group" do
        group = Group.create! valid_attributes
        put :update, {:id => group.to_param, :group => valid_attributes}, valid_session
        expect(assigns(:group)).to eq(group)
      end

      it "redirects to the group" do
        group = Group.create! valid_attributes
        put :update, {:id => group.to_param, :group => valid_attributes}, valid_session
        expect(response).to redirect_to(group)
      end
    end

    describe "with invalid params" do
      it "assigns the group as @group" do
        group = Group.create! valid_attributes
        put :update, {:id => group.to_param, :group => invalid_attributes}, valid_session
        expect(assigns(:group)).to eq(group)
      end

      it "re-renders the 'edit' template" do
        group = Group.create! valid_attributes
        put :update, {:id => group.to_param, :group => invalid_attributes}, valid_session
        expect(response).to render_template("edit")
      end
    end
  end

  describe "DELETE destroy" do
    it "destroys the requested group" do
      group = Group.create! valid_attributes
      expect {
        delete :destroy, {:id => group.to_param}, valid_session
      }.to change(Group, :count).by(-1)
    end

    it "redirects to the groups list" do
      group = Group.create! valid_attributes
      delete :destroy, {:id => group.to_param}, valid_session
      expect(response).to redirect_to(groups_url)
    end
  end

end

Now if you run all specs on the command line ($ rspec), they should all pass. We don’t have anything interesting to look at yet but our Rails API is now good to go.

Adding the client side

On the client side we’ll be using Yeoman, a front-end scaffolding tool. First, install Yeoman itself as well as Yeoman’s Angular generator. (If you don’t already have npm installed, you’ll need to do that. If you’re using Mac OS with Homebrew, run brew install npm.)

$ npm install -g yo
$ npm install -g generator-angular

We’ll keep our client-side code in a directory called client. (This is an arbitrary naming choice and you could call it anything.)

$ mkdir client
$ cd client

Now we’ll generate the Angular app itself. Just accept all the defaults.

$ yo angular fake_lunch_hub

Start Grunt:

$ grunt serve

It’s likely that you’ll the error “invalid option: –fonts-dir”. The solution (or at least solution) to this problem is to remove the following line from your Gruntfile.js (line 186 for me):

fontsDir: '<%= yeoman.app %>/styles/fonts',

When it spins up free of errors or warnings, Grunt should open a new browser tab for you at http://localhost:9000/#/ where you see the “‘Allo, ‘Allo” thing. Our Angular app is now in place. It still doesn’t know how to talk to Rails, so we still have to make that part work.

Setting up a proxy

We’ll use something called grunt-connect-proxy to forward certain requests from our Grunt server running on port 9000 to our Rails server running on port 3000.

$ npm install --save-dev grunt-connect-proxy

Change your Gruntfile to match this:

// Gruntfile.js

// Generated on 2014-07-18 using generator-angular 0.9.5
'use strict';

// # Globbing
// for performance reasons we're only matching one level down:
// 'test/spec/{,*/}*.js'
// use this if you want to recursively match all subfolders:
// 'test/spec/**/*.js'

module.exports = function (grunt) {

  // Load grunt tasks automatically
  require('load-grunt-tasks')(grunt);

  // Time how long tasks take. Can help when optimizing build times
  require('time-grunt')(grunt);

  // Configurable paths for the application
  var appConfig = {
    app: require('./bower.json').appPath || 'app',
    dist: 'dist'
  };

  // Define the configuration for all the tasks
  grunt.initConfig({

    // Project settings
    yeoman: appConfig,

    // Watches files for changes and runs tasks based on the changed files
    watch: {
      bower: {
        files: ['bower.json'],
        tasks: ['wiredep']
      },
      js: {
        files: ['<%= yeoman.app %>/scripts/{,*/}*.js'],
        tasks: ['newer:jshint:all'],
        options: {
          livereload: '<%= connect.options.livereload %>'
        }
      },
      jsTest: {
        files: ['test/spec/{,*/}*.js'],
        tasks: ['newer:jshint:test', 'karma']
      },
      compass: {
        files: ['<%= yeoman.app %>/styles/{,*/}*.{scss,sass}'],
        tasks: ['compass:server', 'autoprefixer']
      },
      gruntfile: {
        files: ['Gruntfile.js']
      },
      livereload: {
        options: {
          livereload: '<%= connect.options.livereload %>'
        },
        files: [
          '<%= yeoman.app %>/{,*/}*.html',
          '.tmp/styles/{,*/}*.css',
          '<%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}'
        ]
      }
    },

    // The actual grunt server settings
    connect: {
      options: {
        port: 9000,
        // Change this to '0.0.0.0' to access the server from outside.
        hostname: 'localhost',
        livereload: 35729
      },
      proxies: [
        {
          context: '/api',
          host: 'localhost',
          port: 3000
        }
      ],
      livereload: {
        options: {
          open: true,
          middleware: function (connect, options) {
            if (!Array.isArray(options.base)) {
              options.base = [options.base];
            }

            // Setup the proxy
            var middlewares = [
              require('grunt-connect-proxy/lib/utils').proxyRequest,
              connect.static('.tmp'),
              connect().use(
                '/bower_components',
                connect.static('./bower_components')
              ),
              connect.static(appConfig.app)
            ];

            // Make directory browse-able.
            var directory = options.directory || options.base[options.base.length - 1];
            middlewares.push(connect.directory(directory));

            return middlewares;
          }
        }
      },
      test: {
        options: {
          port: 9001,
          middleware: function (connect) {
            return [
              connect.static('.tmp'),
              connect.static('test'),
              connect().use(
                '/bower_components',
                connect.static('./bower_components')
              ),
              connect.static(appConfig.app)
            ];
          }
        }
      },
      dist: {
        options: {
          open: true,
          base: '<%= yeoman.dist %>'
        }
      }
    },

    // Make sure code styles are up to par and there are no obvious mistakes
    jshint: {
      options: {
        jshintrc: '.jshintrc',
        reporter: require('jshint-stylish')
      },
      all: {
        src: [
          'Gruntfile.js',
          '<%= yeoman.app %>/scripts/{,*/}*.js'
        ]
      },
      test: {
        options: {
          jshintrc: 'test/.jshintrc'
        },
        src: ['test/spec/{,*/}*.js']
      }
    },

    // Empties folders to start fresh
    clean: {
      dist: {
        files: [{
          dot: true,
          src: [
            '.tmp',
            '<%= yeoman.dist %>/{,*/}*',
            '!<%= yeoman.dist %>/.git*'
          ]
        }]
      },
      server: '.tmp'
    },

    // Add vendor prefixed styles
    autoprefixer: {
      options: {
        browsers: ['last 1 version']
      },
      dist: {
        files: [{
          expand: true,
          cwd: '.tmp/styles/',
          src: '{,*/}*.css',
          dest: '.tmp/styles/'
        }]
      }
    },

    // Automatically inject Bower components into the app
    wiredep: {
      options: {
        cwd: '<%= yeoman.app %>'
      },
      app: {
        src: ['<%= yeoman.app %>/index.html'],
        ignorePath:  /\.\.\//
      },
      sass: {
        src: ['<%= yeoman.app %>/styles/{,*/}*.{scss,sass}'],
        ignorePath: /(\.\.\/){1,2}bower_components\//
      }
    },

    // Compiles Sass to CSS and generates necessary files if requested
    compass: {
      options: {
        sassDir: '<%= yeoman.app %>/styles',
        cssDir: '.tmp/styles',
        generatedImagesDir: '.tmp/images/generated',
        imagesDir: '<%= yeoman.app %>/images',
        javascriptsDir: '<%= yeoman.app %>/scripts',
        importPath: './bower_components',
        httpImagesPath: '/images',
        httpGeneratedImagesPath: '/images/generated',
        httpFontsPath: '/styles/fonts',
        relativeAssets: false,
        assetCacheBuster: false,
        raw: 'Sass::Script::Number.precision = 10\n'
      },
      dist: {
        options: {
          generatedImagesDir: '<%= yeoman.dist %>/images/generated'
        }
      },
      server: {
        options: {
          debugInfo: true
        }
      }
    },

    // Renames files for browser caching purposes
    filerev: {
      dist: {
        src: [
          '<%= yeoman.dist %>/scripts/{,*/}*.js',
          '<%= yeoman.dist %>/styles/{,*/}*.css',
          '<%= yeoman.dist %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}',
          '<%= yeoman.dist %>/styles/fonts/*'
        ]
      }
    },

    // Reads HTML for usemin blocks to enable smart builds that automatically
    // concat, minify and revision files. Creates configurations in memory so
    // additional tasks can operate on them
    useminPrepare: {
      html: '<%= yeoman.app %>/index.html',
      options: {
        dest: '<%= yeoman.dist %>',
        flow: {
          html: {
            steps: {
              js: ['concat', 'uglifyjs'],
              css: ['cssmin']
            },
            post: {}
          }
        }
      }
    },

    // Performs rewrites based on filerev and the useminPrepare configuration
    usemin: {
      html: ['<%= yeoman.dist %>/{,*/}*.html'],
      css: ['<%= yeoman.dist %>/styles/{,*/}*.css'],
      options: {
        assetsDirs: ['<%= yeoman.dist %>','<%= yeoman.dist %>/images']
      }
    },

    // The following *-min tasks will produce minified files in the dist folder
    // By default, your `index.html`'s <!-- Usemin block --> will take care of
    // minification. These next options are pre-configured if you do not wish
    // to use the Usemin blocks.
    // cssmin: {
    //   dist: {
    //     files: {
    //       '<%= yeoman.dist %>/styles/main.css': [
    //         '.tmp/styles/{,*/}*.css'
    //       ]
    //     }
    //   }
    // },
    // uglify: {
    //   dist: {
    //     files: {
    //       '<%= yeoman.dist %>/scripts/scripts.js': [
    //         '<%= yeoman.dist %>/scripts/scripts.js'
    //       ]
    //     }
    //   }
    // },
    // concat: {
    //   dist: {}
    // },

    imagemin: {
      dist: {
        files: [{
          expand: true,
          cwd: '<%= yeoman.app %>/images',
          src: '{,*/}*.{png,jpg,jpeg,gif}',
          dest: '<%= yeoman.dist %>/images'
        }]
      }
    },

    svgmin: {
      dist: {
        files: [{
          expand: true,
          cwd: '<%= yeoman.app %>/images',
          src: '{,*/}*.svg',
          dest: '<%= yeoman.dist %>/images'
        }]
      }
    },

    htmlmin: {
      dist: {
        options: {
          collapseWhitespace: true,
          conservativeCollapse: true,
          collapseBooleanAttributes: true,
          removeCommentsFromCDATA: true,
          removeOptionalTags: true
        },
        files: [{
          expand: true,
          cwd: '<%= yeoman.dist %>',
          src: ['*.html', 'views/{,*/}*.html'],
          dest: '<%= yeoman.dist %>'
        }]
      }
    },

    // ngmin tries to make the code safe for minification automatically by
    // using the Angular long form for dependency injection. It doesn't work on
    // things like resolve or inject so those have to be done manually.
    ngmin: {
      dist: {
        files: [{
          expand: true,
          cwd: '.tmp/concat/scripts',
          src: '*.js',
          dest: '.tmp/concat/scripts'
        }]
      }
    },

    // Replace Google CDN references
    cdnify: {
      dist: {
        html: ['<%= yeoman.dist %>/*.html']
      }
    },

    // Copies remaining files to places other tasks can use
    copy: {
      dist: {
        files: [{
          expand: true,
          dot: true,
          cwd: '<%= yeoman.app %>',
          dest: '<%= yeoman.dist %>',
          src: [
            '*.{ico,png,txt}',
            '.htaccess',
            '*.html',
            'views/{,*/}*.html',
            'images/{,*/}*.{webp}',
            'fonts/*'
          ]
        }, {
          expand: true,
          cwd: '.tmp/images',
          dest: '<%= yeoman.dist %>/images',
          src: ['generated/*']
        }, {
          expand: true,
          cwd: '.',
          src: 'bower_components/bootstrap-sass-official/assets/fonts/bootstrap/*',
          dest: '<%= yeoman.dist %>'
        }]
      },
      styles: {
        expand: true,
        cwd: '<%= yeoman.app %>/styles',
        dest: '.tmp/styles/',
        src: '{,*/}*.css'
      }
    },

    // Run some tasks in parallel to speed up the build process
    concurrent: {
      server: [
        'compass:server'
      ],
      test: [
        'compass'
      ],
      dist: [
        'compass:dist',
        'imagemin',
        'svgmin'
      ]
    },

    // Test settings
    karma: {
      unit: {
        configFile: 'test/karma.conf.js',
        singleRun: true
      }
    }
  });


  grunt.registerTask('serve', 'Compile then start a connect web server', function (target) {
    if (target === 'dist') {
      return grunt.task.run(['build', 'connect:dist:keepalive']);
    }

    grunt.task.run([
      'clean:server',
      'wiredep',
      'concurrent:server',
      'autoprefixer',
      'configureProxies',
      'connect:livereload',
      'watch'
    ]);
  });

  grunt.registerTask('server', 'DEPRECATED TASK. Use the "serve" task instead', function (target) {
    grunt.log.warn('The `server` task has been deprecated. Use `grunt serve` to start a server.');
    grunt.task.run(['serve:' + target]);
  });

  grunt.registerTask('test', [
    'clean:server',
    'concurrent:test',
    'autoprefixer',
    'configureProxies',
    'connect:test',
    'karma'
  ]);

  grunt.registerTask('build', [
    'clean:dist',
    'wiredep',
    'useminPrepare',
    'concurrent:dist',
    'autoprefixer',
    'concat',
    'ngmin',
    'copy:dist',
    'cdnify',
    'cssmin',
    'uglify',
    'filerev',
    'usemin',
    'htmlmin'
  ]);

  grunt.registerTask('default', [
    'newer:jshint',
    'test',
    'build'
  ]);

  grunt.loadNpmTasks('grunt-connect-proxy');
};

Now kill Grunt and again run:

grunt serve

You should now be able to go to http://localhost:9000/api/groups and get empty brackets. (Make sure your Rails server is running.) Our Angular app is now talking to Rails.

Getting Rails data to show up in our client app

Now we’ll want to get some actual data to show up in the actual HTML of our Angular app. This is a pretty easy step now that we have the plumbing taken care of. First, create some seed data:

# db/seeds.rb

# This file should contain all the record creation needed to seed the database with its default values.
# The data can then be loaded with the rake db:seed (or created alongside the db with db:setup).
#
# Examples:
#
#   cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }])
#   Mayor.create(name: 'Emanuel', city: cities.first)

Group.create([
  { name: 'Ben Franklin Labs' },
  { name: 'Snip Salon Software' },
  { name: 'GloboChem' },
  { name: 'TechCorp' },
])

Get the data into the database:

$ rake db:seed

Now we’ll add an AngularJS resource that will allow us to conveniently perform CRUD operations on Group. AngularJS resources match pretty nicely with Rails resources, and I’ve found that my code can be a lot more DRY using Angular resources than jQuery AJAX. We won’t get into the details here of the Angular/Rails resource interaction, though. All we’ll use is the query() method, which matches up to a Rails resource’s index action. Add the Group resource to app/scripts/app.js. I changed more than just a few lines in this file, so you might want to just copy and paste the whole thing.

// app/scripts/app.js

'use strict';

/**
 * @ngdoc overview
 * @name fakeLunchHubApp
 * @description
 * # fakeLunchHubApp
 *
 * Main module of the application.
 */
var app = angular.module('fakeLunchHubApp', [
    'ngAnimate',
    'ngCookies',
    'ngResource',
    'ngRoute',
    'ngSanitize',
    'ngTouch'
  ]);

app.config(function ($routeProvider) {
  $routeProvider
    .when('/', {
      templateUrl: 'views/main.html',
      controller: 'MainCtrl'
    })
    .when('/about', {
      templateUrl: 'views/about.html',
      controller: 'AboutCtrl'
    })
    .when('/groups', {
      templateUrl: 'views/groups.html',
      controller: 'GroupsCtrl'
    })
    .otherwise({
      redirectTo: '/'
    });
});

app.factory('Group', ['$resource', function($resource) {
  return $resource('/api/groups/:id.json', null, {
    'update': { method:'PUT' }
  });
}]);

Now add a controller for the Group resource:

// app/scripts/controllers/groups.js

'use strict';

/**
 * @ngdoc function
 * @name fakeLunchHubApp.controller:GroupsCtrl
 * @description
 * # GroupsCtrl
 * Controller of the fakeLunchHubApp
 */
angular.module('fakeLunchHubApp')
  .controller('GroupsCtrl', ['$scope', 'Group', function ($scope, Group) {
    $scope.groups = Group.query();
  }]);

And add a view.

<!-- app/views/groups.html -->

<h1>Groups</h1>

<ul ng-repeat="group in groups">
  <li>{{group.name}}</li>
</ul>

Lastly, add the following line to app/index.html, near the bottom:

<!-- app/index.html -->

<script src="scripts/controllers/groups.js"></script>

If you now go to http://localhost:9000/#/groups, you should see our list of groups.

Deployment

I’ve written a separate article about deployment called How to Deploy an Angular/Rails Single-Page Application to Heroku.

This is a work in progress

This tutorial is a work in progress, and I intend to update it as a) the technology moves and b) I learn more. I’m aware that there are certain things missing, such as testing on the client side and end-to-end tests. But right now I just want to get this tutorial out into the world because I haven’t seen anything yet that spells out the Rails/Angular combo with this level of hand-holding. I hope this has been helpful. Please leave me any questions or comments you might have. Thanks.

How to Deploy an Angular 2/Rails 5 App to Heroku

Here’s how to deploy an Angular 2 and Rails 5 app to Heroku. There’s more than one possible way this sort of thing could be structured. Before I describe how to set up the deployment I’ll explain how I chose to structure my app and why.

First, my app is a single-page application where the front-end code is completely separate from the back-end API code. The alternative would be to use Rails views and include the JavaScript and CSS in the asset pipeline. I don’t think either way is necessarily better than the other. In this case I chose the SPA way because a) I understand a lot of people are structuring their Angular/Rails apps this way and b) between the two structures, it’s probably the trickier of the two to deploy, and so it’s probably the one that could benefit most from being documented.

I could have chosen to keep my Angular app and my Rails app in two separate repos. In that case the Rails code could have been deployed to Heroku and the Angular code could have been deployed to, say, a static AWS site. What I instead chose to do was to include my Angular app in a subdirectory of the Rails project and host it all on the same Heroku app. The reason I did it this way is because a) front-end and back-end changes are often made at the same time by the same people and b) although it might have been less work initially to host the Angular app and the Rails app in two different places, it’s arguably simpler from a day-to-day workflow standpoint just to have a single way to deploy everything to one place.

The deployment steps below begin with the creation of an Angular/Rails app, so you don’t have to worry about having your own app to deploy. These steps will get you all the way there but you can also clone my example repo if you don’t want to follow the steps manually.

Initializing the App

The first step will be to initialize a new Rails 5 project. I’m calling mine rails_5_angular_2_deployment_example.

For the front-end we’ll use angular2-seed. Clone the repo into a subdirectory called client. (The name client, in this case, is important. If you call it something else, the deployment won’t work.)

git clone git@github.com:mgechev/angular2-seed.git client

We’re just interested in the angular2-seed code. If we keep it as a repository inside our project’s repository, there will be problems. Kill client/.git.

rm -rf client/.git

Now install the Node packages.

cd client
npm install

Before we try to deploy our app we’ll want to make sure it works locally. Run the following command to do a build.

npm run build.prod

This command will create a directory called client/dist/prod. If we want Rails to be able to serve something out of there, we’ll have to tell Rails about it somehow. We can do this by simply symlinking public/ to client/dist/prod/. Rails will look for public/index.html and find client/dist/prod/index.html and we’ll be in business.

cd ..
rm -rf public
ln -s client/dist/prod public

Start the Rails server. If you navigate to localhost:3000 you should see “Howdy! Here’s a list of awesome computer scientists…”

rails server

Creating the Heroku App

The first step is somewhat self-explanatory:

heroku create

We’ll need to use two buildpacks for this deployment. If we were deploying a plain old Rails app, Heroku would auto-detect Ruby and use the heroku/ruby buildpack. We also need a Node buildpack because in order to build our front-end app we need to run a Gulp command and in order to run Gulp commands we need to have Node packages installed.

We can tell Heroku about our two buildpacks like this:

heroku buildpacks:add https://github.com/jasonswett/heroku-buildpack-nodejs
heroku buildpacks:add heroku/ruby

The order matters. If we were to put the Ruby buildpack first and the Node buildpack second, Heroku would give us a single Node dyno which wouldn’t know how to run Ruby. By putting the Ruby buildpack second we get two dynos: a web dyno and a worker dyno, both of which know how to run Ruby.

The reason we’re using my https://github.com/jasonswett/heroku-buildpack-nodejs buildpack instead of the Heroku version is that I needed to modify the buildpack to look for package.json inside of the client directory instead of at the project root.

We need to do one last thing before we can push our code. Modify client/package.json to look like this:

{
  "name": "angular2-seed",
  "version": "0.0.0",
  "description": "Seed for Angular 2 apps",
  "repository": {
    "url": "https://github.com/mgechev/angular2-seed"
  },
  "scripts": {
    "build.dev": "gulp build.dev --color",
    "build.dev.watch": "gulp build.dev.watch --color",
    "build.e2e": "gulp build.e2e --color",
    "build.prod": "gulp build.prod --color",
    "build.test": "gulp build.test --color",
    "build.test.watch": "gulp build.test.watch --color",
    "docs": "npm run gulp -- build.docs --color && npm run gulp -- serve.docs --color",
    "e2e": "protractor",
    "e2e.live": "protractor --elementExplorer",
    "gulp": "gulp",
    "karma": "karma",
    "karma.start": "karma start",
    "postinstall": "typings install && gulp check.versions && npm prune && gulp build.prod",
    "reinstall": "npm cache clean && npm install",
    "serve.coverage": "remap-istanbul -b src/ -i coverage/coverage-final.json -o coverage -t html && npm run gulp -- serve.coverage --color",
    "serve.dev": "gulp serve.dev --color",
    "serve.e2e": "gulp serve.e2e --color",
    "serve.prod": "gulp serve.prod --color",
    "start": "gulp serve.dev --color",
    "tasks.list": "gulp --tasks-simple --color",
    "test": "gulp test --color",
    "webdriver-start": "webdriver-manager start",
    "webdriver-update": "webdriver-manager update"
  },
  "author": "Minko Gechev <mgechev>",
  "license": "MIT",
  "devDependencies": {
  },
  "dependencies": {
    "angular2": "2.0.0-beta.15",
    "es6-module-loader": "^0.17.8",
    "es6-promise": "^3.1.2",
    "es6-shim": "0.35.0",
    "reflect-metadata": "0.1.2",
    "rxjs": "5.0.0-beta.2",
    "systemjs": "~0.19.25",
    "zone.js": "^0.6.10",
    "async": "^1.4.2",
    "autoprefixer": "^6.3.3",
    "browser-sync": "^2.11.2",
    "chalk": "^1.1.3",
    "codelyzer": "0.0.12",
    "colorguard": "^1.1.1",
    "connect": "^3.4.1",
    "connect-history-api-fallback": "^1.1.0",
    "connect-livereload": "^0.5.3",
    "cssnano": "^3.5.2",
    "doiuse": "^2.3.0",
    "event-stream": "^3.3.2",
    "express": "~4.13.1",
    "express-history-api-fallback": "^2.0.0",
    "extend": "^3.0.0",
    "gulp": "^3.9.1",
    "gulp-cached": "^1.1.0",
    "gulp-concat": "^2.6.0",
    "gulp-filter": "^4.0.0",
    "gulp-inject": "^4.0.0",
    "gulp-inline-ng2-template": "^1.1.2",
    "gulp-load-plugins": "^1.2.0",
    "gulp-plumber": "~1.1.0",
    "gulp-postcss": "^6.1.0",
    "gulp-shell": "~0.5.2",
    "gulp-sourcemaps": "git+https://github.com/floridoo/gulp-sourcemaps.git#master",
    "gulp-template": "^3.1.0",
    "gulp-tslint": "^4.3.3",
    "gulp-typedoc": "^1.2.1",
    "gulp-typescript": "~2.12.1",
    "gulp-uglify": "^1.5.3",
    "gulp-util": "^3.0.7",
    "gulp-watch": "^4.3.5",
    "is-ci": "^1.0.8",
    "isstream": "^0.1.2",
    "jasmine-core": "~2.4.1",
    "jasmine-spec-reporter": "^2.4.0",
    "karma": "~0.13.22",
    "karma-chrome-launcher": "~0.2.2",
    "karma-coverage": "^0.5.5",
    "karma-ie-launcher": "^0.2.0",
    "karma-jasmine": "~0.3.8",
    "karma-mocha-reporter": "^2.0.0",
    "karma-phantomjs-launcher": "^1.0.0",
    "merge-stream": "^1.0.0",
    "open": "0.0.5",
    "phantomjs-prebuilt": "^2.1.4",
    "postcss-reporter": "^1.3.3",
    "protractor": "^3.0.0",
    "remap-istanbul": "git+https://github.com/SitePen/remap-istanbul.git#master",
    "rimraf": "^2.5.2",
    "run-sequence": "^1.1.0",
    "semver": "^5.1.0",
    "serve-static": "^1.10.2",
    "slash": "~1.0.0",
    "stream-series": "^0.1.1",
    "stylelint": "^5.3.0",
    "stylelint-config-standard": "^5.0.0",
    "systemjs-builder": "^0.15.14",
    "tiny-lr": "^0.2.1",
    "traceur": "^0.0.91",
    "ts-node": "^0.7.1",
    "tslint": "^3.7.0-dev.2",
    "tslint-stylish": "2.1.0-beta",
    "typedoc": "^0.3.12",
    "typescript": "~1.8.10",
    "typings": "^0.7.12",
    "vinyl-buffer": "^1.0.0",
    "vinyl-source-stream": "^1.1.0",
    "yargs": "^4.2.0"
  }
}

We’ve done two things here. First, we added gulp build.prod to the postinstall script. This will force Heroku to do a build as part of the deployment process. Second, we move everything from devDependencies to dependencies. Since it’s a production environment, Node won’t pick up the devDependencies, but we need those.

By the way, why not just build locally, commit the generated code, and push that? You could do that and it would work. The reason I didn’t want to is that it’s not a good idea to commit build artifacts to version control. You end up with a bunch of “changed” files every time you do a build, which not only doesn’t make sense but serves as a distraction. I’ve worked on projects before that commit build artifacts to version control and it has been painful.

After all our changes are committed we can do a push:

git push heroku master

Now open the app in Heroku.

heroku open

You should see the same Angular 2 Seed app in the production environment. Obviously, the Angular app isn’t talking to Rails but it is coexisting with it, and that’s the hard part.

How to Wire Up Ruby on Rails and AngularJS as a Single-Page Application (Updated for 2016)

Refreshed for 2016

I wrote my first Angular/Rails tutorial in 2014, then another one in 2015. My 2015 post used CoffeeScript. CoffeeScript, from my perspective, is probably not going to play a large role in the future of JavaScript. Angular 2 uses TypeScript by default. TypeScript is a superset of ES6, so if you’re using ES6 in an Angular app, that will presumably make it easier to upgrade to Angular 2/TypeScript than if you’re using CoffeeScript. So I think it makes more sense at this point in time for me to use ES6 than CoffeeScript for my tutorial.

My 2015 post has also not shockingly suffered from a little bit of software rot as the world has moved forward while it has stood still. If you follow it character-for-character today, it doesn’t work. I thought it would be nice to put out a new tutorial that actually works.

The sample app

There’s a certain sample app I plan to use throughout AngularOnRails.com called Lunch Hub. The idea with Lunch Hub is that office workers can announce in the AM where they’d like to go for lunch rather than deciding as they gather around the door and waste half their lunch break. Since Lunch Hub is a real project with its own actual production code, I use a different project here called “Fake Lunch Hub.” You can see the Fake Lunch Hub repo here.

Setting up our Rails project

Instead of regular Rails we’re going to use Rails::API. I’ve tried to do Angular projects with full-blown Rails, but I end up with a bunch of unused views, which feels weird. First, if you haven’t already, install Rails::API. (Side note: I realize that Rails 5 offers an API-only version. Rails 5 is still in beta and I want you to be able to use this tutorial as a starting point for production projects, so I’m using Rails::API 4.2.x for this tutorial.)

$ gem install rails-api

Creating a new Rails::API project works the same as creating a regular Rails project.

$ rails-api new fake_lunch_hub -T -d postgresql

Get into our project directory.

$ cd fake_lunch_hub

Create our PostgreSQL user.

$ createuser -P -s -e fake_lunch_hub

Create the database.

$ rake db:create

Now we’ll create a resource so we have something to look at through our AngularJS app. (This might be a good time to commit this project to version control.)

Creating our first resource

Add gem 'rspec-rails' to your Gemfile (in the test group) and run:

$ bundle install
$ rails g rspec:install

When you generate scaffolds from now on, RSpec will want to create all kinds of spec files for you automatically, including some kinds of specs (like view specs) that in my opinion are kind of nutty and really shouldn’t be there. We can tell RSpec not to create these spec files:

require File.expand_path('../boot', __FILE__)

# Pick the frameworks you want:
require "active_model/railtie"
require "active_record/railtie"
require "action_controller/railtie"
require "action_mailer/railtie"
require "action_view/railtie"
require "sprockets/railtie"
# require "rails/test_unit/railtie"

# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups)

module FakeLunchHub
  class Application < Rails::Application
    # Settings in config/environments/* take precedence over those specified here.
    # Application configuration should go into files in config/initializers
    # -- all .rb files in that directory are automatically loaded.

    # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
    # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
    # config.time_zone = 'Central Time (US & Canada)'

    # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
    # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
    # config.i18n.default_locale = :de

    config.generators do |g|
      g.test_framework :rspec,
        fixtures: false,
        view_specs: false,
        helper_specs: false,
        routing_specs: false,
        request_specs: false,
        controller_specs: true
    end
  end
end

(Now might be another good time to make a commit.)

In Lunch Hub, I want everybody’s lunch announcements to be visible only to other people in the office where they work, not the whole world. And there’s actually a good chance a person might want to belong not only to a group tied to his or her current workplace, but perhaps a former workplace or totally arbitrary group of friends. So I decided to create the concept of a Group in Lunch Hub. Let’s create a Group resource that, for simplicity, has only one attribute: name.

$ rails g scaffold group name:string

Since groups have to have names, let’s set null: false in the migration. We’ll also include a uniqueness index.

class CreateGroups < ActiveRecord::Migration
  def change
    create_table :groups do |t|
      t.string :name, null: false

      t.timestamps
    end

    add_index :groups, :name, unique: true
  end
end
$ rake db:migrate

Now, if you run rails server and go to http://localhost:3000/groups, you should see empty brackets ([]). We actually want to be able to do http://localhost:3000/api/groups instead.

Rails.application.routes.draw do
  scope '/api' do
    resources :groups, except: [:new, :edit]
  end
end

At the risk of being annoying, I wanted to include a realistic level of testing in the tutorial, at least on the server side.

require 'rails_helper'

RSpec.describe Group, :type => :model do
  before do
    @group = Group.new(name: "Ben Franklin Labs")
  end

  subject { @group }

  describe "when name is not present" do
    before { @group.name = " " }
    it { should_not be_valid }
  end
end

To make this spec pass you’ll of course need to add a validation:

class Group < ActiveRecord::Base
  validates :name, presence: true
end

We also have to adjust the controller spec RSpec spit out for us because RSpec’s generators are evidently not yet fully compatible with Rails::API. The generated spec contains an example for the `new` action, even though we don’t have a `new` action. You can remove that example yourself or you can just copy and paste my whole file.

require 'rails_helper'

# This spec was generated by rspec-rails when you ran the scaffold generator.
# It demonstrates how one might use RSpec to specify the controller code that
# was generated by Rails when you ran the scaffold generator.
#
# It assumes that the implementation code is generated by the rails scaffold
# generator.  If you are using any extension libraries to generate different
# controller code, this generated spec may or may not pass.
#
# It only uses APIs available in rails and/or rspec-rails.  There are a number
# of tools you can use to make these specs even more expressive, but we're
# sticking to rails and rspec-rails APIs to keep things simple and stable.
#
# Compared to earlier versions of this generator, there is very limited use of
# stubs and message expectations in this spec.  Stubs are only used when there
# is no simpler way to get a handle on the object needed for the example.
# Message expectations are only used when there is no simpler way to specify
# that an instance is receiving a specific message.

RSpec.describe GroupsController, :type => :controller do

  # This should return the minimal set of attributes required to create a valid
  # Group. As you add validations to Group, be sure to
  # adjust the attributes here as well.
  let(:valid_attributes) {
    skip("Add a hash of attributes valid for your model")
  }

  let(:invalid_attributes) {
    skip("Add a hash of attributes invalid for your model")
  }

  # This should return the minimal set of values that should be in the session
  # in order to pass any filters (e.g. authentication) defined in
  # GroupsController. Be sure to keep this updated too.
  let(:valid_session) { {} }

  describe "GET index" do
    it "assigns all groups as @groups" do
      group = Group.create! valid_attributes
      get :index, {}, valid_session
      expect(assigns(:groups)).to eq([group])
    end
  end

  describe "GET show" do
    it "assigns the requested group as @group" do
      group = Group.create! valid_attributes
      get :show, {:id => group.to_param}, valid_session
      expect(assigns(:group)).to eq(group)
    end
  end

  describe "GET edit" do
    it "assigns the requested group as @group" do
      group = Group.create! valid_attributes
      get :edit, {:id => group.to_param}, valid_session
      expect(assigns(:group)).to eq(group)
    end
  end

  describe "POST create" do
    describe "with valid params" do
      it "creates a new Group" do
        expect {
          post :create, {:group => valid_attributes}, valid_session
        }.to change(Group, :count).by(1)
      end

      it "assigns a newly created group as @group" do
        post :create, {:group => valid_attributes}, valid_session
        expect(assigns(:group)).to be_a(Group)
        expect(assigns(:group)).to be_persisted
      end

      it "redirects to the created group" do
        post :create, {:group => valid_attributes}, valid_session
        expect(response).to redirect_to(Group.last)
      end
    end

    describe "with invalid params" do
      it "assigns a newly created but unsaved group as @group" do
        post :create, {:group => invalid_attributes}, valid_session
        expect(assigns(:group)).to be_a_new(Group)
      end

      it "re-renders the 'new' template" do
        post :create, {:group => invalid_attributes}, valid_session
        expect(response).to render_template("new")
      end
    end
  end

  describe "PUT update" do
    describe "with valid params" do
      let(:new_attributes) {
        skip("Add a hash of attributes valid for your model")
      }

      it "updates the requested group" do
        group = Group.create! valid_attributes
        put :update, {:id => group.to_param, :group => new_attributes}, valid_session
        group.reload
        skip("Add assertions for updated state")
      end

      it "assigns the requested group as @group" do
        group = Group.create! valid_attributes
        put :update, {:id => group.to_param, :group => valid_attributes}, valid_session
        expect(assigns(:group)).to eq(group)
      end

      it "redirects to the group" do
        group = Group.create! valid_attributes
        put :update, {:id => group.to_param, :group => valid_attributes}, valid_session
        expect(response).to redirect_to(group)
      end
    end

    describe "with invalid params" do
      it "assigns the group as @group" do
        group = Group.create! valid_attributes
        put :update, {:id => group.to_param, :group => invalid_attributes}, valid_session
        expect(assigns(:group)).to eq(group)
      end

      it "re-renders the 'edit' template" do
        group = Group.create! valid_attributes
        put :update, {:id => group.to_param, :group => invalid_attributes}, valid_session
        expect(response).to render_template("edit")
      end
    end
  end

  describe "DELETE destroy" do
    it "destroys the requested group" do
      group = Group.create! valid_attributes
      expect {
        delete :destroy, {:id => group.to_param}, valid_session
      }.to change(Group, :count).by(-1)
    end

    it "redirects to the groups list" do
      group = Group.create! valid_attributes
      delete :destroy, {:id => group.to_param}, valid_session
      expect(response).to redirect_to(groups_url)
    end
  end

end

Now if you run all specs on the command line ($ rspec), they should all pass. We don’t have anything interesting to look at yet but our Rails API is now good to go.

Adding the client side

On the client side we’ll be using Yeoman, a front-end scaffolding tool. First, install Yeoman itself as well as generator-gulp-angular. (If you don’t already have npm installed, you’ll need to do that. If you’re using Mac OS with Homebrew, run brew install npm.)

$ npm install -g yo
$ npm install -g generator-gulp-angular

We’ll keep our client-side code in a directory called client. (This is an arbitrary naming choice and you could call it anything.)

$ mkdir client && cd $_

Now we’ll generate the Angular app itself. When I ran it, I made the following selections:

  • Angular version: 1.5.x
  • Modules: all
  • jQuery: 2.x
  • REST resource library: ngResource
  • Router: UI Router
  • UI framework: Bootstrap
  • Bootstrap component implementation: Angular UI
  • CSS preprocessor: Sass (Node)
  • JS preprocessor: ES6
  • HTML template engine: Jade
$ yo gulp-angular fake_lunch_hub

Start Gulp to see if it works:

$ gulp serve

Gulp should now open a new browser tab for you at http://localhost:3001/#/ where you see the “‘Allo, ‘Allo” thing. Our Angular app is now in place. It still doesn’t know how to talk to Rails, so we still have to make that part work.

Setting up a proxy

Thanks to Gerardo Gomez for helping me figure this one out.

'use strict';

var path = require('path');
var gulp = require('gulp');
var conf = require('./conf');

var browserSync = require('browser-sync');
var browserSyncSpa = require('browser-sync-spa');

var util = require('util');

var exec = require('child_process').exec;

var proxyMiddleware = require('http-proxy-middleware');

function browserSyncInit(baseDir, browser) {
  browser = browser === undefined ? 'default' : browser;

  var routes = null;
  if(baseDir === conf.paths.src || (util.isArray(baseDir) && baseDir.indexOf(conf.paths.src) !== -1)) {
    routes = {
      '/bower_components': 'bower_components'
    };
  }

  var server = {
    baseDir: baseDir,
    routes: routes,
    middleware: [
      proxyMiddleware('/api', { target: 'http://localhost:3000' })
    ]
  };

  /*
   * You can add a proxy to your backend by uncommenting the line below.
   * You just have to configure a context which will we redirected and the target url.
   * Example: $http.get('/users') requests will be automatically proxified.
   *
   * For more details and option, https://github.com/chimurai/http-proxy-middleware/blob/v0.9.0/README.md
   */
  // server.middleware = proxyMiddleware('/users', {target: 'http://jsonplaceholder.typicode.com', changeOrigin: true});

  browserSync.instance = browserSync.init({
    startPath: '/',
    server: server,
    browser: browser
  });
}

browserSync.use(browserSyncSpa({
  selector: '[ng-app]'// Only needed for angular apps
}));

gulp.task('serve', ['watch'], function () {
  browserSyncInit([path.join(conf.paths.tmp, '/serve'), conf.paths.src]);
});

gulp.task('rails', function() {
  exec('rails server');
});

gulp.task('serve:full-stack', ['rails', 'serve']);

gulp.task('serve:dist', ['build'], function () {
  browserSyncInit(conf.paths.dist);
});

gulp.task('serve:e2e', ['inject'], function () {
  browserSyncInit([conf.paths.tmp + '/serve', conf.paths.src], []);
});

gulp.task('serve:e2e-dist', ['build'], function () {
  browserSyncInit(conf.paths.dist, []);
});

Here are the things I changed, it no particular order:

  1. Added middleware that says “send requests to `/api` to `http://localhost:3000`”
  2. Added a `rails` task that simply invokes the `rails server` command
  3. Added a `serve:full-stack` task that runs the regular old `serve` task, but first runs the `rails` task

You’ll have to install `http-proxy-middleware` before continuing:

$ npm install --save-dev http-proxy-middleware

Now we can run our cool new task. Make sure neither Rails nor Gulp is already running somewhere.

$ gulp serve:full-stack

Two things should now happen:

  1. If you navigate to `http://localhost:3001/api/foo`, you should get a Rails page that says `No route matches [GET] “/api/foo”`, which means
  2. Rails is running on port 3000.

Getting Rails data to show up in our client app

Now we’ll want to get some actual data to show up in the actual HTML of our Angular app. This is a pretty easy step now that we have the plumbing taken care of. First, create some seed data:

# This file should contain all the record creation needed to seed the database with its default values.
# The data can then be loaded with the rake db:seed (or created alongside the db with db:setup).
#
# Examples:
#
#   cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }])
#   Mayor.create(name: 'Emanuel', city: cities.first)

Group.create([
  { name: 'Ben Franklin Labs' },
  { name: 'Snip Salon Software' },
  { name: 'GloboChem' },
  { name: 'TechCorp' },
])

Get the data into the database:

$ rake db:seed

Now let’s modify `src/app/index.route.js` to include a state for groups:

export function routerConfig ($stateProvider, $urlRouterProvider) {
  'ngInject';
  $stateProvider
    .state('home', {
      url: '/',
      templateUrl: 'app/main/main.html',
      controller: 'MainController',
      controllerAs: 'main'
    }).state('groups', {
      url: '/groups',
      templateUrl: 'app/components/groups.html',
      controller: 'GroupsController',
      controllerAs: 'groups'
    });

  $urlRouterProvider.otherwise('/');
}

Then we add `GroupsController`, which at this point is almost nothing:

export class GroupsController {
  constructor () {
  }
}

Lastly, let’s create a view at `src/app/components/groups.jade`:

div(ng-include='', src="'app/components/navbar/navbar.html'")

.container
  h1 Groups

If you now navigate to `http://localhost:3001/#/groups`, you should see a big `h1` that says “Groups”. So far we’re not talking to Rails at all yet. That’s the very next step.

A good library for Angular/Rails resources is called, straightforwardly, angularjs-rails-resource. It can be installed thusly:

$ bower install --save angularjs-rails-resource

Now let’s add two things to `src/app/index.module.js`: the `rails` module and a resource called `Group`.

/* global malarkey:false, moment:false */

import { config } from './index.config';
import { routerConfig } from './index.route';
import { runBlock } from './index.run';
import { MainController } from './main/main.controller';
import { GroupsController } from './components/groups.controller';
import { GithubContributorService } from '../app/components/githubContributor/githubContributor.service';
import { WebDevTecService } from '../app/components/webDevTec/webDevTec.service';
import { NavbarDirective } from '../app/components/navbar/navbar.directive';
import { MalarkeyDirective } from '../app/components/malarkey/malarkey.directive';

angular.module('fakeLunchHub', [
  'ngAnimate',
  'ngCookies',
  'ngTouch',
  'ngSanitize',
  'ngMessages',
  'ngAria',
  'ngResource',
  'ui.router',
  'ui.bootstrap',
  'toastr',
  'rails'
]).constant('malarkey', malarkey)
  .constant('moment', moment)
  .config(config)
  .config(routerConfig)
  .run(runBlock)
  .service('githubContributor', GithubContributorService)
  .service('webDevTec', WebDevTecService)
  .controller('MainController', MainController)
  .controller('GroupsController', GroupsController)
  .directive('acmeNavbar', NavbarDirective)
  .directive('acmeMalarkey', MalarkeyDirective)
  .factory('Group', railsResourceFactory => {
    return railsResourceFactory({
      url: '/api/groups',
      name: 'group'
    });
});

Now let’s add a line to our controller to make the HTTP request:

export class GroupsController {
  constructor ($scope, Group) {
    'ngInject';

    Group.query().then(groups => $scope.groups = groups);
  }
}

And some code in our template to show the group names:

div(ng-include="'app/components/navbar/navbar.html'")

.container
  h1 Groups
  ul
    li(ng-repeat='group in groups') {{ group.name }}

If you now visit `http://localhost:3001/#/groups`, you should see your group names there. Congratulations! You just wrote a single-page application. It’s a trivial and useless single-page application, but you’re off to a good start. In my experience the plumbing is the hardest part.

How to Deploy an Angular CLI Webpack Project to Heroku

Angular CLI recently switched from SystemJS to Webpack. This affects the Heroku deployment process.

Here I’ll show you how to initiate an Angular CLI/Webpack project and deploy it to Heroku. (By the way, this is an Angular-only post. I won’t be talking about Rails at all here. Even if you use the Angular/Rails combo I’d recommend deploying an Angular-only app first just so you understand how the process works. If you’re ready for the Rails version, that can be found here.)

First, make sure you have the following versions of the following things installed:

Angular CLI: 1.0.0-beta.11-webpack.8
NPM: 3.10.6
Node: 6.5.0

First let’s initiate the project, which I’ll give the random name of bananas:

$ ng new bananas

When we deploy this, we’ll want to invoke the ng build command after installation. When you run ng build it creates a dist directory with your TypeScript transpiled to JavaScript and all that stuff. It’s a “static” set of files that can be served up as-is.

Because the error-free operation of ng build is critical to our success, let’s make sure we can run it locally before we try to invoke it on Heroku:

$ cd bananas
$ ng build

In all likelihood this did not run error-free for you. You probably got Cannot find global type 'Array'. and a bunch of other similar stuff like these people did.

The way I fixed that was to first have the right Node and NPM versions installed. We’ve already taken care of that at the beginning. The second step is to change your typescript version in package.json from ^2.0.0 to 2.0.0.

Here’s what my package.json looks like after the change:

{
  "name": "bananas",
  "version": "0.0.0",
  "license": "MIT",
  "angular-cli": {},
  "scripts": {
    "start": "ng serve",
    "lint": "tslint \"src/**/*.ts\"",
    "test": "ng test",
    "pree2e": "webdriver-manager update",
    "e2e": "protractor"
  },
  "private": true,
  "dependencies": {
    "@angular/common": "2.0.0-rc.5",
    "@angular/compiler": "2.0.0-rc.5",
    "@angular/core": "2.0.0-rc.5",
    "@angular/forms": "0.3.0",
    "@angular/http": "2.0.0-rc.5",
    "@angular/platform-browser": "2.0.0-rc.5",
    "@angular/platform-browser-dynamic": "2.0.0-rc.5",
    "@angular/router": "3.0.0-rc.1",
    "core-js": "^2.4.0",
    "rxjs": "5.0.0-beta.11",
    "ts-helpers": "^1.1.1",
    "zone.js": "0.6.12"
  },
  "devDependencies": {
    "@types/jasmine": "^2.2.30",
    "angular-cli": "1.0.0-beta.11-webpack.8",
    "codelyzer": "~0.0.26",
    "jasmine-core": "2.4.1",
    "jasmine-spec-reporter": "2.5.0",
    "karma": "0.13.22",
    "karma-chrome-launcher": "0.2.3",
    "karma-jasmine": "0.3.8",
    "karma-remap-istanbul": "^0.2.1",
    "protractor": "4.0.3",
    "ts-node": "1.2.1",
    "tslint": "3.13.0",
    "typescript": "2.0.0"
  }
}

Now do an npm install and ng build again. You should be error-free this time.

$ npm install
$ ng build

Next you’ll need to change the scripts section of package.json as follows:

  "scripts": {
    "start": "http-server",
    "lint": "tslint \"src/**/*.ts\"",
    "test": "ng test",
    "pree2e": "webdriver-manager update",
    "e2e": "protractor",
    "preinstall": "npm install -g http-server",
    "postinstall": "ng build && mv dist/* ."
  },

We did three things: First, we told Heroku to install http-server globally, which we’ll use to serve our application. Second, we told Heroku to run ng build after installation as well as to move everything in the dist directory to the project root. Third, we told Heroku to serve the app by running http-server. (Heroku automatically runs npm start for Node projects.)

We also have to move our devDependencies into dependencies. Heroku won’t pick up anything in devDependencies but we need some of it. Here’s my final package.json:

{
  "name": "bananas",
  "version": "0.0.0",
  "license": "MIT",
  "angular-cli": {},
  "scripts": {
    "start": "http-server",
    "lint": "tslint \"src/**/*.ts\"",
    "test": "ng test",
    "pree2e": "webdriver-manager update",
    "e2e": "protractor",
    "preinstall": "npm install -g http-server",
    "postinstall": "ng build && mv dist/* ."
  },
  "private": true,
  "dependencies": {
    "@angular/common": "2.0.0-rc.5",
    "@angular/compiler": "2.0.0-rc.5",
    "@angular/core": "2.0.0-rc.5",
    "@angular/forms": "0.3.0",
    "@angular/http": "2.0.0-rc.5",
    "@angular/platform-browser": "2.0.0-rc.5",
    "@angular/platform-browser-dynamic": "2.0.0-rc.5",
    "@angular/router": "3.0.0-rc.1",
    "core-js": "^2.4.0",
    "rxjs": "5.0.0-beta.11",
    "ts-helpers": "^1.1.1",
    "zone.js": "0.6.12",
    "@types/jasmine": "^2.2.30",
    "angular-cli": "1.0.0-beta.11-webpack.8",
    "codelyzer": "~0.0.26",
    "jasmine-core": "2.4.1",
    "jasmine-spec-reporter": "2.5.0",
    "karma": "0.13.22",
    "karma-chrome-launcher": "0.2.3",
    "karma-jasmine": "0.3.8",
    "karma-remap-istanbul": "^0.2.1",
    "protractor": "4.0.3",
    "ts-node": "1.2.1",
    "tslint": "3.13.0",
    "typescript": "2.0.0"
  },
  "devDependencies": {
  }
}

Now let’s create the Heroku project. (I’m assuming you already have a Heroku account and Heroku toolbelt set up.)

$ heroku create

Make sure you have all your changes commit and push your code.

$ git push heroku master

When that has finished, run:

$ heroku open

You should see “app works!”.

Page reload problem

There is one problem, though. If your Angular/Rails application has any routes defined, you’ll notice that if you navigate to a route by clicking a link, then refresh the page, the page will no longer work. The reasons for this are nuanced but I explain them in depth in my HTML5 pushState post.

The fix to the problem is pretty easy.

First, add the rack-rewrite gem to your Gemfile.

# Gemfile

gem 'rack-rewrite'

Then bundle install, of course.

$ bundle install

Then you’ll add a redirect rule to your config.ru that looks like this:

# config.ru

# This file is used by Rack-based servers to start the application.

use Rack::Rewrite do
  rewrite %r{^(?!.*(api|\.)).*$}, '/index.html'
end

require ::File.expand_path('../config/environment',  __FILE__)
run Rails.application

After you commit these changes and deploy again you should be good.

How to Add a Test Coverage Report to an Angular CLI Project

Angular CLI has test coverage reporting somewhat built in.

First install the dependencies.

$ npm install karma karma-jasmine karma-chrome-launcher karma-jasmine-html-reporter karma-coverage-istanbul-reporter

Then run ng test.

$ ng test --code-coverage

Then run the server that shows you your report.

$ http-server -c-1 -o -p 9875 ./coverage

You should see something like this.

Get Started with Angular 4 and Rails 5

This guide will walk you through creating just about the simplest Angular + Rails application possible.

We’re going to create an Angular app, then create a Rails app, then get the two to talk to each other. It should only take a few minutes. Here are the steps:

Get set up

Get an Angular app initialized and running

The app we’re going to create is a pretend app called Home Library that exists for the purpose of organizing one’s private book collection.

We’ll be using the Angular CLI (command-line interface) to create the Angular app. Before we can do anything else, we need to install Angular CLI.

(If you don’t have NPM installed, you need to install that before installing Angular CLI.)

$ npm install -g @angular/cli

The Angular app and the Rails app will sit side-by-side in a directory. We’ll call this getting_started although you could call it anything you’d like.

$ mkdir getting_started
$ cd getting_started

This is the command to initialize the Angular app:

$ ng new home-library

Once this command finishes, cd into home-library and run ng serve.

$ cd home-library
$ ng serve

You should be able to visit http://localhost:4200 and see a screen that looks like this:

Initialize a Rails app

Now, in a separate terminal, create the Rails app and then create its database. (The –api flag means this will be an API-only Rails app.)

$ cd ..
$ rails new home_library --api -T -d postgresql
$ cd home_library
$ rails db:create

Prepare some data

Create a resource in the Rails app

Since our “Home Library” app is oriented around books, let’s create a resource called Book.

$ rails generate scaffold book name:string
$ rails db:migrate

Add some data to the Rails app

Let’s put a couple books in a seed file so we have some data to work with.

# db/seeds.rb

Book.create!([
  { name: 'Copying and Pasting from Stack Overflow' },
  { name: 'Trying Stuff Until it Works' }
])
$ rails db:seed

Connect Angular with Rails

Run the Rails server

In order for Angular to talk to Rails, the Rails server of course has to be running. Let’s start the Rails server.

$ rails server

You should see the good old “Yay! You’re on Rails” screen.

And if you visit http://localhost:3000/books.json, you should see the list of books we just created.

Enable CORS so the Angular app can talk to the Rails app

There is a small amount of plumbing work involved to get our Angular and Rails app working. We need to enable CORS (cross-origin resource sharing).

Don’t worry if you’re not familiar with that term. In plain English, we have to tell Rails that it’s okay to let outside apps talk to it.

The first step is to uncomment rack-cors in the Gemfile.

# Gemfile

source 'https://rubygems.org'

git_source(:github) do |repo_name|
  repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/")
  "https://github.com/#{repo_name}.git"
end


# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '~> 5.0.2'
# Use postgresql as the database for Active Record
gem 'pg', '~> 0.18'
# Use Puma as the app server
gem 'puma', '~> 3.0'
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
# gem 'jbuilder', '~> 2.5'
# Use Redis adapter to run Action Cable in production
# gem 'redis', '~> 3.0'
# Use ActiveModel has_secure_password
# gem 'bcrypt', '~> 3.1.7'

# Use Capistrano for deployment
# gem 'capistrano-rails', group: :development

# Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible
gem 'rack-cors'

group :development, :test do
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
  gem 'byebug', platform: :mri
end

group :development do
  gem 'listen', '~> 3.0.5'
  # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
  gem 'spring'
  gem 'spring-watcher-listen', '~> 2.0.0'
end

# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
$ bundle install

Then make config/initializers/cors.rb look like this:

# config/initializers/cors.rb

# Be sure to restart your server when you modify this file.
# Avoid CORS issues when API is called from the frontend app.
# Handle Cross-Origin Resource Sharing (CORS) in order to accept
# cross-origin AJAX requests.
# Read more: https://github.com/cyu/rack-cors
Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins '*'

    resource '*',
      headers: :any,
      expose:  ['access-token', 'expiry', 'token-type', 'uid', 'client'],
      methods: [:get, :post, :put, :patch, :delete, :options, :head]
  end
end

And remember to restart the Rails server after you do that.

Get Angular to talk to Rails

We’re almost done. One of the last steps is to add an HTTP request from the Angular app to the Rails app.

Modify src/app/app.component.ts to look like this:

// src/app/app.component.ts

import { Component } from '@angular/core';
import { Http } from '@angular/http';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'app works!';
  books;

  constructor(private http: Http) {
    http.get('http://localhost:3000/books.json')
      .subscribe(res => this.books = res.json());
  }
}

This will handle getting the data from the Rails server. Now we need to show it on the page.

Show the data from Rails inside the Angular app

Modify src/app/app.component.html to look like this:

<!-- src/app/app.component.html -->

<h1>
  {{title}}
</h1>

<ul>
  <li *ngFor="let book of books">{{ book.name }}</li>
</ul>

Now, if you refresh the page at http://localhost:4200, you should see our two books on the page:

Congratulations. You’re now in possession of an Angular app that talks to a Rails app.

My Second Attempt at a Genetic Algorithm

In my previous post I tried to create a genetic algorithm (GA). The idea was that the GA would slap together some numbers and symbols to try to hit a target number. For example, some conceivable expressions would be 5 + 9, 5 * 2 or even something halfway nonsensical like - - 3.

I was able to write a program that would generate these random expressions and see if their results hit my target number. For example, if my target number was 15, an expression of 3 * 5 would be recognized as a success and my program would stop executing. This was mildly amusing but it was not yet a genetic algorithm. I hadn’t yet fully understood what my GA was supposed to be.

The crucial thing my GA was missing was some form of evolution via sexual reproduction and/or mutation. What I needed was a program that would create a generation of chromosomes, then let that generation create a new generation of fitter offspring, then let that new generation create yet another generation of even fitter offspring, and so on. In this particular case, fitness would be determined by how close each chromosome got to the target number.

After some fiddling I arrived at the following bit of code.

class Chromosome
  GENE_MAP = {
    '0000' => '0',
    '0001' => '1',
    '0010' => '2',
    '0011' => '3',
    '0100' => '4',
    '0101' => '5',
    '0110' => '6',
    '0111' => '7',
    '1000' => '8',
    '1001' => '9',
    '1010' => '+',
    '1011' => '-',
    '1100' => '*',
    '1101' => '/',
  }

  GENE_LENGTH = 4
  GENE_COUNT = 8
  TARGET_NUMBER = 100
  CHROMOSOME_LENGTH = GENE_COUNT * GENE_LENGTH

  attr_accessor :encoding

  def initialize(encoding = nil)
    if encoding
      @encoding = encoding
    else
      @encoding = CHROMOSOME_LENGTH.times.collect { rand(2) }.join
    end
  end

  def expression
    pattern = /.{#{GENE_LENGTH}}/

    self.encoding.scan(pattern).collect { |gene|
      GENE_MAP[gene]
    }.join(' ')
  end

  def result
    eval_expression(self.expression)
  end

  def fitness_score
    return unless self.result
    1 / ((TARGET_NUMBER - self.result).abs + 1).to_f
  end

  def first_part_of_encoding(grab_length)
    @encoding[0..(grab_length - 1)]
  end

  def second_part_of_encoding(grab_length)
    @encoding[grab_length..(CHROMOSOME_LENGTH - 1)]
  end

  def mate_with(chromosome)
    grab_length = rand(1..(GENE_COUNT - 1)) * GENE_LENGTH

    self.class.new(
      first_part_of_encoding(grab_length) +
      chromosome.second_part_of_encoding(grab_length)
    )
  end
end

class Generation
  GENERATION_SIZE = 30
  attr_accessor :chromosomes

  def initialize(chromosomes = [])
    number_of_attempts = 0
    @chromosomes = chromosomes

    if chromosomes.any?
      return
    end

    while number_of_attempts <= GENERATION_SIZE
      chromosome = Chromosome.new

      if chromosome.result
        number_of_attempts += 1
        @chromosomes << chromosome
      end
    end

    @chromosomes.sort_by!(&:fitness_score).reverse!
  end

  def fittest
    @chromosomes.first
  end

  def reproduce
    chromosomes = @chromosomes.drop(1).collect { |chromosome|
      [fittest.mate_with(chromosome), chromosome.mate_with(fittest)]
    }.flatten
     .select { |c| c.result != nil }
     .sort_by!(&:fitness_score)
     .reverse![0..(GENERATION_SIZE - 1)]

    Generation.new(chromosomes)
  end
end

def print_generation(generation)
  generation.chromosomes.each do |chromosome|
    print "#{chromosome.encoding} | "
    print "#{chromosome.expression.ljust(20, ' ')}| "
    print "#{chromosome.result.to_s.rjust(4, ' ')} | "
    print "#{chromosome.fitness_score.to_s.rjust(4, '  ')}"
    puts
  end

  nil
end
alias :pg :print_generation

def eval_expression(expression)
  begin
    value = eval(expression)

    if value.is_a? Numeric
      value
    else
      nil
    end
  rescue SyntaxError, NoMethodError, ZeroDivisionError, TypeError
    nil
  end
end

Here’s the first generation of chromosomes my program created.

10001101000111001000101011100100 | 8 / 1 * 8 +  4      |   68 | 0.030303030303030304          
10001100100010110011110101011111 | 8 * 8 - 3 / 5       |   64 | 0.02702702702702703           
10101110010111101110110011110111 | +  5   *  7         |   35 | 0.015151515151515152          
01001100001011111100101110110011 | 4 * 2  * - - 3      |   24 | 0.012987012987012988          
01101110110000101111101010011111 | 6  * 2  + 9         |   21 | 0.0125                        
01011010101110111001101110110110 | 5 + - - 9 - - 6     |   20 | 0.012345679012345678          
10001010010110111011011110110011 | 8 + 5 - - 7 - 3     |   17 | 0.011904761904761904          
11101111101011101010100010101000 |   +  + 8 + 8        |   16 | 0.011764705882352941          
00111101101001111010010010101001 | 3 / + 7 + 4 + 9     |   13 | 0.011363636363636364                                                                                                         
01101110111011111111110010100010 | 6     * + 2         |   12 | 0.011235955056179775                                                                                                         
10011010010110110101111110100011 | 9 + 5 - 5  + 3      |   12 | 0.011235955056179775                                                                                                         
01111110101000011111111011000100 | 7  + 1   * 4        |   11 | 0.011111111111111112                                                                                                         
01111011111010101010011010101001 | 7 -  + + 6 + 9      |   10 | 0.01098901098901099                                                                                                          
10001010111000001100111110011110 | 8 +  0 *  9         |    8 | 0.010752688172043012                                                                                                         
01111010111000011110111011010100 | 7 +  1   / 4        |    7 | 0.010638297872340425                                                                                                         
10001111101100011010001010110100 | 8  - 1 + 2 - 4      |    5 | 0.010416666666666666                                                                                                         
00111010111111100111111011000000 | 3 +   7  * 0        |    3 | 0.01020408163265306                                                                                                          
10111010001011010111101011110010 | - + 2 / 7 +  2      |    1 | 0.01                                                                                                                         
11111010000011011011001111010111 |  + 0 / - 3 / 7      |    0 | 0.009900990099009901                                                                                                         
11110001110101001101010011010110 |  1 / 4 / 4 / 6      |    0 | 0.009900990099009901                                                                                                         
00111111111111010101110110110110 | 3   / 5 / - 6       |    0 | 0.009900990099009901          
01101111110111101010111110001111 | 6  /  +  8          |    0 | 0.009900990099009901          
10110001110010110001111110110010 | - 1 * - 1  - 2      |   -1 | 0.00980392156862745           
00111010010010101011100110100000 | 3 + 4 + - 9 + 0     |   -2 | 0.009708737864077669          
00111101111000101101010010110100 | 3 /  2 / 4 - 4      |   -4 | 0.009523809523809525          
10100001101011100111110010110001 | + 1 +  7 * - 1      |   -6 | 0.009345794392523364          
10001101011010111010111010011110 | 8 / 6 - +  9        |   -8 | 0.009174311926605505          
01011100000111001001110110110011 | 5 * 1 * 9 / - 3     |  -15 | 0.008620689655172414
01001010010010110100111011001001 | 4 + 4 - 4  * 9      |  -28 | 0.007751937984496124
01101101001111101011011011000111 | 6 / 3  - 6 * 7      |  -40 | 0.0070921985815602835
11111001110010110110111110110010 |  9 * - 6  - 2       |  -56 | 0.006369426751592357

The columns are 1) the chromosome encoding, 2) the expression, 3) the expression’s result, and 4) the chromosome’s fitness score. The fitness score is determined by how close the result is to the target number. Since my target number in this case is 100 and the top chromosome spit out 68, the score in this case is 1 / ((100 – 68) + 1) = 0.0303. (You can see that a result of 100 would be 1 / ((100 – 100) + 1) = 1)

The way the next generation is created is to take the fittest chromosome (8 / 1 * 8 + 4) and let it bang all the other chromosomes to produce offspring. Here’s the result from that:

10001101000111001000101010011110 | 8 / 1 * 8 + 9       |   73 | 0.03571428571428571           
10001101000111001000101010101000 | 8 / 1 * 8 + + 8     |   72 | 0.034482758620689655          
10001101000111001000101011100111 | 8 / 1 * 8 +  7      |   71 | 0.03333333333333333           
10001101000111001000101011100111 | 8 / 1 * 8 +  7      |   71 | 0.03333333333333333           
10001101000111001000101011100100 | 8 / 1 * 8 +  4      |   68 | 0.030303030303030304          
10001101000111001000101011100100 | 8 / 1 * 8 +  4      |   68 | 0.030303030303030304          
10001101000111001000101011100010 | 8 / 1 * 8 +  2      |   66 | 0.02857142857142857           
10001100100010110011101011100100 | 8 * 8 - 3 +  4      |   65 | 0.027777777777777776          
10001101000111001000101010110011 | 8 / 1 * 8 + - 3     |   61 | 0.025                         
10001101000111001000101010110011 | 8 / 1 * 8 + - 3     |   61 | 0.025                         
10001101000111001010011010101001 | 8 / 1 * + 6 + 9     |   57 | 0.022727272727272728          
01101101000111001000101011100100 | 6 / 1 * 8 +  4      |   52 | 0.02040816326530612           
01101101000111001000101011100100 | 6 / 1 * 8 +  4      |   52 | 0.02040816326530612           
01011100000111001001101011100100 | 5 * 1 * 9 +  4      |   49 | 0.019230769230769232          
00111010010011001000101011100100 | 3 + 4 * 8 +  4      |   39 | 0.016129032258064516          
00111010111111100111111011000100 | 3 +   7  * 4        |   31 | 0.014285714285714285          
10100001101011100111110011100100 | + 1 +  7 *  4       |   29 | 0.013888888888888888          
00111101000111001000101011100100 | 3 / 1 * 8 +  4      |   28 | 0.0136986301369863            
00111111111111001000101011100100 | 3   * 8 +  4        |   28 | 0.0136986301369863            
10001010010110111011011110110011 | 8 + 5 - - 7 - 3     |   17 | 0.011904761904761904          
10001010010110111011011110110100 | 8 + 5 - - 7 - 4     |   16 | 0.011764705882352941          
10001101000111111111110010100010 | 8 / 1   * + 2       |   16 | 0.011764705882352941          
10001101101001111010010010101001 | 8 / + 7 + 4 + 9     |   14 | 0.011494252873563218          
10011010010110110101101011100100 | 9 + 5 - 5 +  4      |   13 | 0.011363636363636364          
10001110101000011111111011000100 | 8  + 1   * 4        |   12 | 0.011235955056179775          
11101111000111001000101011100100 |   1 * 8 +  4        |   12 | 0.011235955056179775          
01011010101110111001101111100100 | 5 + - - 9 -  4      |   10 | 0.01098901098901099           
10101110010111101110101011100100 | +  5   +  4         |    9 | 0.010869565217391304          
10001101000110110011110101011111 | 8 / 1 - 3 / 5       |    8 | 0.010752688172043012          
01111010111000011110111011010100 | 7 +  1   / 4        |    7 | 0.010638297872340425

As you can see, the average fitness has gone up. The fittest member of this generation is also fitter than the fittest member of the previous generation. Let’s run it again.

10001101000111001000101010011110 | 8 / 1 * 8 + 9       |   73 | 0.03571428571428571
10001101000111001000101010011110 | 8 / 1 * 8 + 9       |   73 | 0.03571428571428571
10001101000111001000101010011110 | 8 / 1 * 8 + 9       |   73 | 0.03571428571428571
10001101000111001000101010011110 | 8 / 1 * 8 + 9       |   73 | 0.03571428571428571
10001101000111001000101010011110 | 8 / 1 * 8 + 9       |   73 | 0.03571428571428571
10001101000111001000101010011110 | 8 / 1 * 8 + 9       |   73 | 0.03571428571428571
10001101000111001000101010011110 | 8 / 1 * 8 + 9       |   73 | 0.03571428571428571
10001101000111001000101010011110 | 8 / 1 * 8 + 9       |   73 | 0.03571428571428571
10001101000111001000101010101000 | 8 / 1 * 8 + + 8     |   72 | 0.034482758620689655
10001101000111001000101011100111 | 8 / 1 * 8 +  7      |   71 | 0.03333333333333333
10001101000111001000101011100111 | 8 / 1 * 8 +  7      |   71 | 0.03333333333333333
10001100100010110011101010011110 | 8 * 8 - 3 + 9       |   70 | 0.03225806451612903
10001101000111001000101011100100 | 8 / 1 * 8 +  4      |   68 | 0.030303030303030304
10001101000111001000101011100100 | 8 / 1 * 8 +  4      |   68 | 0.030303030303030304
10001101000111001000101011100100 | 8 / 1 * 8 +  4      |   68 | 0.030303030303030304
10001101000111001000101011100100 | 8 / 1 * 8 +  4      |   68 | 0.030303030303030304
10001101000111001000101011100100 | 8 / 1 * 8 +  4      |   68 | 0.030303030303030304
10001101000111001000101011100100 | 8 / 1 * 8 +  4      |   68 | 0.030303030303030304
10001101000111001000101011100100 | 8 / 1 * 8 +  4      |   68 | 0.030303030303030304
10001101000111001000101011100100 | 8 / 1 * 8 +  4      |   68 | 0.030303030303030304
10001101000111001000101011100100 | 8 / 1 * 8 +  4      |   68 | 0.030303030303030304
10001101000111001000101011100010 | 8 / 1 * 8 +  2      |   66 | 0.02857142857142857
10001101000111001000101010110011 | 8 / 1 * 8 + - 3     |   61 | 0.025
10001101000111001000101010110100 | 8 / 1 * 8 + - 4     |   60 | 0.024390243902439025
10001101000111001000101111100100 | 8 / 1 * 8 -  4      |   60 | 0.024390243902439025
01101101000111001000101010011110 | 6 / 1 * 8 + 9       |   57 | 0.022727272727272728
10001101000111001010011010101001 | 8 / 1 * + 6 + 9     |   57 | 0.022727272727272728
10101110010111001000101010011110 | +  5 * 8 + 9        |   49 | 0.019230769230769232
01011100000111001000101010011110 | 5 * 1 * 8 + 9       |   49 | 0.019230769230769232
01011101000111001000101010011110 | 5 / 1 * 8 + 9       |   49 | 0.019230769230769232

This time the fittest member is no fitter than the fittest member of the previous generation but the average fitness has gone up. If we run the code again, all the chromosomes end up with equal fitness.

10001101000111001000101010011110 | 8 / 1 * 8 + 9       |   73 | 0.03571428571428571
10001100000111001000101010011110 | 8 * 1 * 8 + 9       |   73 | 0.03571428571428571
10001101000111001000101010011110 | 8 / 1 * 8 + 9       |   73 | 0.03571428571428571
10001101000111001000101010011110 | 8 / 1 * 8 + 9       |   73 | 0.03571428571428571
10001101000111001000101010011110 | 8 / 1 * 8 + 9       |   73 | 0.03571428571428571
10001101000111001000101010011110 | 8 / 1 * 8 + 9       |   73 | 0.03571428571428571
10001101000111001000101010011110 | 8 / 1 * 8 + 9       |   73 | 0.03571428571428571
10001101000111001000101010011110 | 8 / 1 * 8 + 9       |   73 | 0.03571428571428571
10001101000111001000101010011110 | 8 / 1 * 8 + 9       |   73 | 0.03571428571428571
10001101000111001000101010011110 | 8 / 1 * 8 + 9       |   73 | 0.03571428571428571
10001101000111001000101010011110 | 8 / 1 * 8 + 9       |   73 | 0.03571428571428571
10001101000111001000101010011110 | 8 / 1 * 8 + 9       |   73 | 0.03571428571428571
10001101000111001000101010011110 | 8 / 1 * 8 + 9       |   73 | 0.03571428571428571
10001101000111001000101010011110 | 8 / 1 * 8 + 9       |   73 | 0.03571428571428571
10001101000111001000101010011110 | 8 / 1 * 8 + 9       |   73 | 0.03571428571428571
10001101000111001000101010011110 | 8 / 1 * 8 + 9       |   73 | 0.03571428571428571
10001101000111001000101010011110 | 8 / 1 * 8 + 9       |   73 | 0.03571428571428571
10001101000111001000101010011110 | 8 / 1 * 8 + 9       |   73 | 0.03571428571428571
10001101000111001000101010011110 | 8 / 1 * 8 + 9       |   73 | 0.03571428571428571
10001101000111001000101010011110 | 8 / 1 * 8 + 9       |   73 | 0.03571428571428571
10001101000111001000101010011110 | 8 / 1 * 8 + 9       |   73 | 0.03571428571428571
10001101000111001000101010011110 | 8 / 1 * 8 + 9       |   73 | 0.03571428571428571
10001101000111001000101010011110 | 8 / 1 * 8 + 9       |   73 | 0.03571428571428571
10001101000111001000101010011110 | 8 / 1 * 8 + 9       |   73 | 0.03571428571428571
10001101000111001000101010011110 | 8 / 1 * 8 + 9       |   73 | 0.03571428571428571
10001101000111001000101010011110 | 8 / 1 * 8 + 9       |   73 | 0.03571428571428571
10001101000111001000101010011110 | 8 / 1 * 8 + 9       |   73 | 0.03571428571428571
10001101000111001000101010011110 | 8 / 1 * 8 + 9       |   73 | 0.03571428571428571
10001101000111001000101010011110 | 8 / 1 * 8 + 9       |   73 | 0.03571428571428571
10001101000111001000101010011110 | 8 / 1 * 8 + 9       |   73 | 0.03571428571428571

At this point no more evolution is possible without mutation, and since my program doesn’t involve mutation, no more evolution is possible with my program. So 73 is as close to 100 as we’re going to get on this try.

What Next?

I originally embarked upon this exercise because I had been vaguely curious for years about how GAs work. This exercise has given me enough understanding to satisfy that curiosity.

I can imagine some interesting places to go next. First, it seems like a flaw that there’s a less-than-100% chance of my program generating a 100%-fit chromosome for this relatively simple case. Maxing out at 73 seems like a pretty weak result.

Second, it would be interesting to apply a GA to something more entertaining than finding a number close to 100. It would be cool to hook a GA up to some sort of physics engine and create organisms that walk or slither or swim.

My First Attempt at a Genetic Algorithm

What Led Up to This

I’ve been vaguely interested in AI for a long time but I’ve never really done anything with it. I remember in 2008 or so a co-worker mentioned something to me called “genetic algorithms”. That sounded interesting to me.

As someone who’s also interested in biology and paleontology I’m intrigued by the idea of creating little virtual organisms that somehow reproduce and evolve on their own, creating new lifeforms that the programmer could not have anticipated.

I realize that the image conjured up by my mind is not exactly a typical application of genetic programming. But that’s the image that makes the idea appealing to me.

My Program

Well, finally, about ten years later, I’ve decided to finally mess around with genetic algorithms. I somehow stumbled upon a great tutorial called Genetic Algorithms in Plain English which I’ve decided to use as the basis for my work.

I’ll let you read the tutorial if you want the full background but I’ll also provide a little bit of background here. And before I proceed, I should let you know that I have absolutely no idea what I’m doing. So don’t take anything I say here as fact.

I decided to program my genetic algorithm in Ruby. I didn’t want to have to learn genetic algorithms and some new programming language on top of each other, so I decided to use a language I was already quite comfortable with.

The idea with this program is that it will generate an expression and the goal is for the expression to add up to the number 15.

The expression my program generates could be something valid like 3 + 2 or 8 * 3, or it could be something invalid like / + 9 or even * /. If the expression is valid, it gets evaluated and the result is checked to see if it’s 15. If the expression is not valid, it gets skipped.

Here’s the content of my program.

CHARACTER_MAP = {
  '0000' => '0',
  '0001' => '1',
  '0010' => '2',
  '0011' => '3',
  '0100' => '4',
  '0101' => '5',
  '0110' => '6',
  '0111' => '7',
  '1000' => '8',
  '1001' => '9',
  '1010' => '+',
  '1011' => '-',
  '1100' => '*',
  '1101' => '/',
}

TARGET_NUMBER = 15

def random_binary_string
  12.times.collect { rand(2) }.join
end

def binary_string_into_expression(starting_string)
  starting_string.scan(/.{4}/).collect { |character_string|
    CHARACTER_MAP[character_string]
  }.join(' ')
end

def eval_expression(expression)
  begin
    eval(expression)
  rescue SyntaxError, NoMethodError, ZeroDivisionError
    nil
  end
end

def run
  result = nil
  number_of_attempts = 0

  while result != TARGET_NUMBER
    number_of_attempts += 1
    binary_string = random_binary_string
    expression = binary_string_into_expression(binary_string)
    result = eval_expression(expression)

    if result
      puts "#{binary_string} | #{expression.ljust(8, ' ')}| #{result}"
    end
  end

  puts "Got target number #{TARGET_NUMBER} in #{number_of_attempts} attempts"
end

Here’s the tail of a few sample outputs.

101110111000 | - - 8   | 8
011111001000 | 7 * 8   | 56
010010110111 | 4 - 7   | -3
000110111000 | 1 - 8   | -7
101110110110 | - - 6   | 6
011111010100 | 7 / 4   | 1
000011000101 | 0 * 5   | 0
011111001001 | 7 * 9   | 63
000011010001 | 0 / 1   | 0
101011100001 | +  1    | 1
001010110101 | 2 - 5   | -3
000111000001 | 1 * 1   | 1
011110101000 | 7 + 8   | 15
Got target number 15 in 936 attempts
000111001001 | 1 * 9   | 9
111011100010 |   2     | 2
011111010011 | 7 / 3   | 2
011011010011 | 6 / 3   | 2
010011000010 | 4 * 2   | 8
110111011111 | / /     | (?-mix: )
011110111001 | 7 - 9   | -2
011011000001 | 6 * 1   | 6
000010111001 | 0 - 9   | -9
011010110000 | 6 - 0   | 6
000010110000 | 0 - 0   | 0
010110100111 | 5 + 7   | 12
101011100110 | +  6    | 6
011010101001 | 6 + 9   | 15
Got target number 15 in 1736 attempts
011110110100 | 7 - 4   | 3
010011011001 | 4 / 9   | 0
101010100000 | + + 0   | 0
001011010100 | 2 / 4   | 0
101001001110 | + 4     | 4
000010110000 | 0 - 0   | 0
100011111110 | 8       | 8
100010110101 | 8 - 5   | 3
000011000000 | 0 * 0   | 0
100010101000 | 8 + 8   | 16
101001011110 | + 5     | 5
101110111000 | - - 8   | 8
010010101000 | 4 + 8   | 12
010011010001 | 4 / 1   | 4
001111000101 | 3 * 5   | 15
Got target number 15 in 167 attempts
111001111111 |  7      | 7
010110110011 | 5 - 3   | 2
011111001000 | 7 * 8   | 56
100111010110 | 9 / 6   | 1
010010110011 | 4 - 3   | 1
011011010010 | 6 / 2   | 3
101111110110 | -  6    | -6
011111000111 | 7 * 7   | 49
101101111111 | - 7     | -7
011111010100 | 7 / 4   | 1
011110100110 | 7 + 6   | 13
101010100110 | + + 6   | 6
111111110100 |   4     | 4
111010011111 |  9      | 9
011011101110 | 6       | 6
000010110101 | 0 - 5   | -5
011110110101 | 7 - 5   | 2
011110101000 | 7 + 8   | 15
Got target number 15 in 285 attempts

Is it Really a Genetic Algorithm?

This is an amusing program but I’m not really sure it’s a genetic algorithm. What would the “genetic” part be? Seems like there should be some swapping of chromosomes happening at some point. These numbers should be banging the hell out of each other. Unfortunately, my program seems pretty G-rated.

My next step will be to examine the genetic algorithm tutorial a little more closely to see how I might be able to make this genetic algorithm a little more legit.

Next Steps

After this I got a real genetic algorithm working.

My Favorite Debugging Techniques

Tracking down the source of a bug can sometimes be really tough, especially when it’s somebody else’s bug in somebody else’s codebase.

The heart of my debugging approaches is the principle that it’s much easier to find out what piece of code introduced the bug than it is to actually understand the flow of execution around that bug. Whenever there are two ways of doing something, I choose the path that requires the smallest application of brainpower.

git bisect

One of my favorite debugging techniques is to use git bisect, a form of diff debugging.

If you’re not familiar with git bisect, here’s how it works. You basically tell Git, “Okay, Git, I know that the bug is present now, I know that the bug was not present as of a month ago. Can you help me find out exactly which commit introduced the bug?”

Git will then take you back in time. Let’s say you happen to know that as of 32 days ago, your bug did not exist. In this case git bisect would take you back to 16 days ago, halfway between now and when the code was good. You then tell Git whether the codebase was “good” or “bad” as of 16 days ago.

Let’s say that, as of 16 days ago, your code was good. Now we know that the bug was introduced sometime within the last 16 days. We’ve narrowed down the “mystery space” from 32 days to 16 days. The next step is that git bisect will take you to 8 days ago, halfway between now and the most recent good state. This process repeats until you’ve identified the commit that introduced the bug. Git keeps bisecting history until there’s nothing left to bisect. This, in case you didn’t know, is called a binary search.

Once the bad commit is identified, it’s usually much easier to figure out what exactly is going wrong than it would have been to just stare at today’s code and try to ponder its innerworkings.

git revert –no-commit

I use git revert --no-commit less frequently than git bisect but I still use it a fair amount. I often use git revert --no-commit in conjunction with git bisect after I’ve nailed down the offending commit.

The challenge with git bisect is that sometimes the bad commit is a really big one. (Big commits are a bad idea, by the way. One reason big commits are bad is that it’s easier for a bug to hide in a big commit than in a small one.) What if 20 files changed in the bad commit? How do you know where the bug lies?

This is where git revert --no-commit comes in. I of course assume you know that git revert will add a new commit to your history that’s the exact opposite of whatever commit you specify. That works out great when a commit introduces a bug and nothing else. But what if there’s a commit that introduces both a bug and a set of really important features that can’t be discarded?

Doing a git revert --no-commit <SHA of bad commit> will undo all the changes of the bad commit, but it won’t actually make a new commit. (Minor note: after a git revert --no-commit, the changes will be staged. I always do a git reset to unstage the changes.)

Once I’ve done my revert and git reset, I test my application to verify that the bug I’m investigating is not present. Remember, the revert commits the reverse of the bad commit, so undoing the bad commit should make the bug go away. It’s important to verify that the bug did in fact go away or else we’d be operating off of bad premises.

After I’ve verified that the bug is not present, I do a git diff to see what exactly the commit changed. I usually discover that there are some changes that are clearly superficial or cosmetic and don’t have anything to do with the bug. If there’s a whole file that contains irrelevant changes, I git checkout that file.

Let’s say that I have 20 modified files after I do my git revert --no-commit, and let’s say that 15 of them contained clearly superficial changes. I would git checkout all 15 of the irrelevant files and then verify again that the bug is not present. Now my “mystery space” has been narrowed down to 5 files.

Let’s say that this is a codebase I don’t understand at all, so studying the code is fairly useless to me, or at least a very slow way to make progress. Out of these 5 files that might contain the bad code (which we’ll call files A, B, C, D and E), I would probably do git checkout A and see if the bug is still not present. If the bug is still not present, I’d do git checkout B and see if that brought the bug back or not. I’d continue to do this until I found which file contains the bug-introducing code.

Let’s say that after all my investigation I’ve determined that file E contains the code that introduced the bug, but I still don’t know exactly which part. I’d then do a manual micro-bisect. I’d comment out half the file and test for the bug. Then I’d uncomment half of what’s commented. Then I’d uncomment half of that, and so on, until I found the offending code.

By this process I can usually identify and fix a bug, even if I have no understanding of the codebase.