Category Archives: Ruby on Rails

Where to put Rails modules

The TL;DR answer

You can put any module in /lib as long as you have autoloading set up properly.

The more nuanced answer

The truth is that you can put your modules anywhere. Personally, my main use for modules is to create namespaces for my Active Record models to help keep things organized. Those module definitions just end up in the same files as my Active Record models.

I have kind of a rule for myself when it comes to Rails file organization. My Rails apps tend to be structures as a mix of Active Record objects and Plain Old Ruby Objects (POROs). The rule is:

If a particular model class (whether it’s an Active Record model class or a PORO model class) pertains specifically to my application, it goes in app/models. If the model class has nothing specifically to do with my application (e.g. it’s a utility class that could conceivably be open-sourced and used in any application), it goes in lib.

How to set up autoloading for modules located in /lib

Modify your config/application.rb to include the following line.

# config/application.rb

module MyApp
  class Application < Rails::Application
    config.eager_load_paths += %W(#{config.root}/lib)
  end
end

Restart your Rails server and you should be good to go.

How to define and include a module

You can define a module like this:

# /lib/foo.rb

module Foo
  def hello
    "hello"
  end
end

When you include that module in a class, you can use the hello function in the class from which you included the module. See the below for how you would use this Foo module.

How to use a module in an ActiveRecord model

You can include a module in a class in your Rails project by using the include keyword followed by the name of your module.

# app/models/customer.rb

class Customer < ActiveRecord::Base
  include Foo
end

Now you should be able to do Customer.new.hello and get “hello” as a result.

Further reading

If you’d like to know more of my thoughts on structuring Rails applications, I’d recommend the following posts.

Extracting a tidy PORO from a messy Active Record model
For organizing Rails projects, domain objects are good and service objects are bad

Angular/Rails CRUD Tutorial Part 1

The idea

I’ve decided to write a very long, comprehensive, multi-part tutorial. I came up with a fictional app called Cadence, a music lesson scheduling app. I wanted to think of something that would be a fitting use case for a single-page application, and I think a calendar-based application would make sense.

The tutorial starts with Angular by itself, no Rails.

The initial version

The initial version of this application will not be very impressive. You’ll be able to create “appointments”, each of which just consists of a date and client name. When you click Save, the appointment details will be added to a list of appointments on the screen. That’s about it. But as we’ll see, just doing that much is involved enough that we wouldn’t really want to bite off more than that for starters.

Since this initial version of the app is just Angular without Rails, we’ll be using localStorage as the data store.

We won’t even worry about tests yet in this initial version. We’ll first write a version without tests, because we already have enough to deal with without bringing tests into the picture, and then we’ll make a second pass with tests.

What you’ll learn

  • How to create an Angular project using Angular CLI
  • How to build components (and nested components)
  • How to use *ngFor to display lists
  • How to use services for inter-component interaction
  • How to use localStorage with Angular

We’re going to initialize this project using Angular CLI. We’re going to install Angular CLI using NPM, so if you don’t have NPM installed yet, you’ll need to install it before proceeding. (If you’re not familiar, NPM is a package manager for JavaScript. It’s kind of analogous to RubyGems for Ruby.)

$ npm install -g @angular/cli

The Angular CLI executable is ng. We can initialize our project with the ng new command, like this:

$ ng new cadence --prefix cadence

The format of the ng new command is ng new <your project name>. In this case we’re calling our project cadence.

Let’s cd into the cadence directory since everything we’ll be doing from this point on will happen inside this directory.

$ cd cadence

Angular CLI can also serve our new Angular application with ng serve.

$ ng serve

After the server spins up, you can visit localhost:4200 and you should see a screen that looks like this:

Creating some components

In a moment we’ll be creating some components. What’s a component? I’ll try to give the simplest answer possible.

Imagine you have an application that has a datepicker thingy that you use in multiple places. Rather than pasting in the full datepicker code wherever you want the datepicker or referring to some include file everywhere you want the datepicker, you could just do something like this:

<my-datepicker></my-datepicker>

If you have an Angular component whose selector matches my-datepicker, Angular will go find the template for that component as well as the TypeScript code that defines the behavior for that component. To me this seems like a pretty natural and convenient way to invoke some behavior.

The Angular Style Guide recommends that we give our selectors application-specific prefixes. The idea is to avoid naming conflicts. So for our app called Cadence, we could put a cadence- prefix on each of our components. For example, we might have cadence-calendar or cadence-datepicker.

The people who built Angular CLI were apparently aware of this style guide recommendation because they built this kind of prefixing into Angular CLI. Angular CLI give us the ability to generate components via the ng generate component command. Each time we do this, Angular CLI will automatically give the component a cadence- prefix.

We can use the ng generate component command to generate our first component. Let’s generate a calendar component. As a reminder, this application we’re creating is a music lesson scheduling app. The calendar component will give us a place to put all our appointments.

$ ng generate component calendar

Now that the component exists let’s get it to actually show up. The selector for the CalendarComponent is cadence-calendar. (If you look at src/app/calendar/calendar.component.ts, you can see the part near the top of the file where the selector for that component is specified.)

If we put
<cadence-calendar></cadence-calendar>
in any template file, it will show us CalendarComponent. Let’s put this selector in the AppComponent template now.

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

<cadence-calendar></cadence-calendar>

Now that we’ve made this change we should see a screen like this:

Adding Twitter Bootstrap

I like to use Twitter Bootstrap for most of the apps I create. Let’s just get adding Bootstrap out of the way now so that everything we do from this point on will look somewhat nice.

The first step is to install the Bootstrap NPM package. (The --save means we want NPM to add the bootstrap@next package to package.json so that Bootstrap is an explicit dependency of this project and will get installed anytime anyone does an npm install.)

$ npm install --save bootstrap@next

Next we have to add a few lines to .angular-cli.json. Bootstrap comes with some styles and JavaScripts and we have to tell Angular CLI about these things in order for Bootstrap to get wired up properly.

{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "project": {
    "name": "cadence"
  },
  "apps": [
    {
      "root": "src",
      "outDir": "dist",
      "assets": [
        "assets",
        "favicon.ico"
      ],
      "index": "index.html",
      "main": "main.ts",
      "polyfills": "polyfills.ts",
      "test": "test.ts",
      "tsconfig": "tsconfig.app.json",
      "testTsconfig": "tsconfig.spec.json",
      "prefix": "cadence",
      "styles": [
        "../node_modules/bootstrap/dist/css/bootstrap.css",
        "styles.css"
      ],
      "scripts": [
        "../node_modules/jquery/dist/jquery.js",
        "../node_modules/tether/dist/js/tether.js",
        "../node_modules/bootstrap/dist/js/bootstrap.js"
      ],
      "environmentSource": "environments/environment.ts",
      "environments": {
        "dev": "environments/environment.ts",
        "prod": "environments/environment.prod.ts"
      }
    }
  ],
  "e2e": {
    "protractor": {
      "config": "./protractor.conf.js"
    }
  },
  "lint": [
    {
      "project": "src/tsconfig.app.json"
    },
    {
      "project": "src/tsconfig.spec.json"
    },
    {
      "project": "e2e/tsconfig.e2e.json"
    }
  ],
  "test": {
    "karma": {
      "config": "./karma.conf.js"
    }
  },
  "defaults": {
    "styleExt": "css",
    "component": {}
  }
}

After the very important step of restarting your server, you should see a screen like the one below. (To restart the server, kill the ng serve process and start it again.)

It’s also a good idea to put all our application’s content inside a container Let’s also add a header.

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

<div class="container">
  <h1>Cadence</h1>

  <cadence-calendar></cadence-calendar>
</div>

Adding NewAppointmentComponent

We haven’t yet put anything into CalendarComponent. The first thing we’ll actually put into it is another component. Angular recommends a component tree application structure where child components are nested inside of parent components. I thought it would make sense to make CalendarComponent contain a list of appointments as well as a component for creating new appointments. We’ll create that component for creating new appointments now.

First, let’s cd into the directory where CalendarComponent lives.

$ cd src/app/calendar

Now we can create NewAppointmentComponent itself.

$ ng generate component new-appointment

Finally, we’ll put the selector for NewAppointmentComponent in the component for CalendarComponent. We should see NewAppointmentComponent‘s template content (presently just “new-appointment works!”) show up on the page.

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

<p>
  calendar works!
</p>

<cadence-new-appointment></cadence-new-appointment>

Adding some form controls to the appointment form

Like I said near the beginning of this post, our appointments will only have two attributes for starters: date and client name. Let’s create two text inputs in NewAppointmentComponent‘s template, one for each of these attributes. Right now we’ll just accept arbitrary text and not worry about whether the date provided by the user is a valid date.

<!-- src/app/calendar/new-appointment/new-appointment.component.html -->

<div class="form-group">
  <label for="date">Date</label>
  <input type="text" class="form-control" name="date" id="date">
</div>

<div class="form-group">
  <label for="clientName">Client name</label>
  <input type="text" class="form-control" name="clientName" id="clientName">
</div>

<input type="submit" class="btn btn-primary" value="Save">

The form should look like this:

Make sure you’re in the calendar directory.

If you click Save, nothing happens, of course, We haven’t hooked up the Save button to anything.

The first step in getting the Save button to actually do something is to bind the form we just created to an Appointment object. We’ll bind the form to an Appointment, then when the user clicks Save, we’ll save that Appointment object (or, more precisely, we’ll save a representation of that Appointment object.)

If you’re wondering what this Appointment object is I’m talking about, it’s something that doesn’t exist yet. Let’s use Angular CLI to help us generate an Appointment class.

(We’re still in src/app/calendar, by the way.)

$ ng generate class appointment

Then we’ll open up the Appointment class definition and add date and clientName properties. Why do we have public date?: string instead of public date: string? The question mark means those arguments are optional. So when we create an empty Appointment instance, we can instantiate it by doing new Appointment() instead of having to do new Appointment('', ''), which would feel kind of dumb.

// src/app/calendar/appointment.ts

export class Appointment {
  constructor(public date?: string,
              public clientName?: string) {}
}

After we define the Appointment class we’ll pull it into NewAppointmentComponent.

// src/app/calendar/new-appointment/new-appointment.component.ts

import { Component, OnInit } from '@angular/core';
import { Appointment } from '../appointment';

@Component({
  selector: 'cadence-new-appointment',
  templateUrl: './new-appointment.component.html',
  styleUrls: ['./new-appointment.component.css']
})
export class NewAppointmentComponent implements OnInit {
  model = new Appointment();

  constructor() { }

  ngOnInit() {
  }

  save() {
    console.log(this.model);
  }
}

We’ve created a model property on NewAppointmentComponent and now we’ll bind model.date to the Date input and model.clientName to the Client name input. If you’re interested in learning more about data binding I’d recommend this article.

We’re also adding a form and putting an event on the form. When the form gets submitted, the save() function on NewAppointmentComponent gets invoked. For now we’re just logging the model. I don’t want to try to do anything fancier than that yet because I want to be sure that this step works first.

<!-- src/app/calendar/new-appointment/new-appointment.component.html -->

<form (submit)="save()">

  <div class="form-group">
    <label for="date">Date</label>
    <input type="text"
           class="form-control"
           name="date"
           id="date"
           [(ngModel)]="model.date">
  </div>

  <div class="form-group">
    <label for="clientName">Client name</label>
    <input type="text"
           class="form-control"
           name="clientName"
           id="clientName"
           [(ngModel)]="model.clientName">
  </div>

  <input type="submit" class="btn btn-primary" value="Save">
</form>

If we try to reload the page now we’ll get an error in the console that says, “Can’t bind to ‘ngModel’ since it isn’t a known property of ‘input’.” This is because ngModel needs FormsModule in order to work but we haven’t imported FormsModule.

// src/app/app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';

import { AppComponent } from './app.component';
import { CalendarComponent } from './calendar/calendar.component';
import { NewAppointmentComponent } from './calendar/new-appointment/new-appointment.component';

@NgModule({
  declarations: [
    AppComponent,
    CalendarComponent,
    NewAppointmentComponent
  ],
  imports: [
    BrowserModule,
    FormsModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

You can see in the screenshot below that I’ve filled out the form and clicked Save. In the console appears the Appointment object that corresponds to the values I entered in the form.

Saving data to localStorage

If you’ve never used localStorage before, don’t worry. It’s pretty simple to use and you don’t have to do anything special to use localStorage with Angular.

Below we’re creating array called appointments which, for now, only has one element: the Appointment object we’re saving from the form. Then we’re saving that array to localStorage.

// src/app/calendar/new-appointment/new-appointment.component.ts

import { Component, OnInit } from '@angular/core';
import { Appointment } from '../appointment';

@Component({
  selector: 'cadence-new-appointment',
  templateUrl: './new-appointment.component.html',
  styleUrls: ['./new-appointment.component.css']
})
export class NewAppointmentComponent implements OnInit {
  model = new Appointment();

  constructor() { }

  ngOnInit() {
  }

  save() {
    let appointments = [this.model];
    localStorage.setItem('appointments', JSON.stringify(appointments));
  }
}

If you save an appointment now, then open up the Application tab in the Chrome developer toolbar and go to Local Storage, you should be be able to see the appointment you just saved.

Showing the saved appointments in CalendarComponent

To get the saved appointments out of localStorage and into CalendarComponent, we can just do JSON.parse(localStorage.getItem('appointments')).

// src/app/calendar/calendar.component.ts

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'cadence-calendar',
  templateUrl: './calendar.component.html',
  styleUrls: ['./calendar.component.css']
})
export class CalendarComponent implements OnInit {
  appointments = [];

  constructor() { }

  ngOnInit() {
    this.appointments = JSON.parse(localStorage.getItem('appointments'));
  }

}

We can show the appointments on the page using an *ngFor loop.

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

<div *ngFor="let appointment of appointments">
  {{ appointment.date }}
  {{ appointment.clientName }}
</div>

<cadence-new-appointment></cadence-new-appointment>

Here’s what the page looks like with the appointments added. Not very nice-looking yet but we’ll improve the appearance shortly.

Preserving previously-saved appointments

Until now, NewAppointmentComponent saves an array of appointments that actually just consists of one element, the appointment that’s being saved right now. What it should really do is save an array of appointments consisting of all the existing appointments plus the appointment that’s being saved now. Let’s fix this.

// src/app/calendar/new-appointment/new-appointment.component.ts

import { Component, OnInit } from '@angular/core';
import { Appointment } from '../appointment';

@Component({
  selector: 'cadence-new-appointment',
  templateUrl: './new-appointment.component.html',
  styleUrls: ['./new-appointment.component.css']
})
export class NewAppointmentComponent implements OnInit {
  model = new Appointment();

  constructor() { }

  ngOnInit() {
  }

  save() {
    let appointments = JSON.parse(localStorage.getItem('appointments'));
    appointments.push(this.model);

    localStorage.setItem('appointments', JSON.stringify(appointments));
  }
}

Refactoring the appointment code into a service

The appropriate place to put business logic in Angular is in services. Components shouldn’t do very much. Components should only call services that do things. If there’s some saving of records going on, the component shouldn’t be aware of how that saving happens.

Let’s move our appointment-saving and appointment-retrieving code out of the components and into a service.

We’ll use Angular CLI to generate a service. Before you do so, make sure you’re in the calendar directory.

$ ng generate service appointment

This command will generate a service called AppointmentService. Let’s create a function called save() which accepts an appointment. In the body of our function goes the code we’ve been using inside NewAppointmentComponent so far to save an appointment.

// src/app/calendar/appointment.service.ts

import { Injectable } from '@angular/core';

@Injectable()
export class AppointmentService {

  constructor() { }

  save(appointment) {
    let appointments = JSON.parse(localStorage.getItem('appointments'));
    appointments.push(appointment);

    localStorage.setItem('appointments', JSON.stringify(appointments));
  }
}

Then NewAppointmentComponent can just call the service’s save() function.

// src/app/calendar/new-appointment/new-appointment.component.ts

import { Component, OnInit } from '@angular/core';
import { Appointment } from '../appointment';
import { AppointmentService } from '../appointment.service';

@Component({
  selector: 'cadence-new-appointment',
  templateUrl: './new-appointment.component.html',
  styleUrls: ['./new-appointment.component.css']
})
export class NewAppointmentComponent implements OnInit {
  model = new Appointment();

  constructor(private appointmentService: AppointmentService) { }

  ngOnInit() {
  }

  save() {
    this.appointmentService.save(this.model);
  }
}

We’ll need to add AppointmentService as a provider in order for it to be able to be injected into components.

// src/app/app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';

import { AppComponent } from './app.component';
import { CalendarComponent } from './calendar/calendar.component';
import { NewAppointmentComponent } from './calendar/new-appointment/new-appointment.component';
import { AppointmentService } from './calendar/appointment.service';

@NgModule({
  declarations: [
    AppComponent,
    CalendarComponent,
    NewAppointmentComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule
  ],
  providers: [AppointmentService],
  bootstrap: [AppComponent]
})
export class AppModule { }

Similar to how we moved the saving code from the component to the service, we can move the retrieval code as well. Let’s create a getList() function in AppointmentService.

// src/app/calendar/appointment.service.ts

import { Injectable } from '@angular/core';

@Injectable()
export class AppointmentService {

  constructor() { }

  getList() {
    return JSON.parse(localStorage.getItem('appointments'));
  }

  save(appointment) {
    let appointments = JSON.parse(localStorage.getItem('appointments'));
    appointments.push(appointment);

    localStorage.setItem('appointments', JSON.stringify(appointments));
  }
}

Then CalendarComponent can call getList().

// src/app/calendar/calendar.component.ts

import { Component, OnInit } from '@angular/core';
import { AppointmentService } from './appointment.service';

@Component({
  selector: 'cadence-calendar',
  templateUrl: './calendar.component.html',
  styleUrls: ['./calendar.component.css']
})
export class CalendarComponent implements OnInit {
  appointments = [];

  constructor(private appointmentService: AppointmentService) { }

  ngOnInit() {
    this.appointments = this.appointmentService.getList();
  }
}

Now that the getList() function exists, we can remove some duplication between the getList() function and the save() function by just calling getList() to get the list of appointments.

// src/app/calendar/appointment.service.ts

import { Injectable } from '@angular/core';

@Injectable()
export class AppointmentService {

  constructor() { }

  getList() {
    return JSON.parse(localStorage.getItem('appointments'));
  }

  save(appointment) {
    let appointments = this.getList();
    appointments.push(appointment);

    localStorage.setItem('appointments', JSON.stringify(appointments));
  }
}

Getting the components to talk to each other

At this point, saving appointments works and getting appointments back out works but we have to refresh the page in order for our appointments to show up. This is of course not what we ultimately want. We want the saved appointments to show up on the page immediately.

We need to get NewAppointmentComponent and CalendarComponent to talk to each other. The Angular docs describe a method of inter-component communication where component A sends some data to a service, then component B gets that data out of the same service. At a high level, this is a simple enough concept to understand.

Where things start to get somewhat complicated is the fact that the example from the Angular docs use observables to achieve this communication. I’d recommend you take a look at my blog post Observables Made Simple before proceeding. It took me a long time to grasp observables. I think that post will help you understand them a little more quickly than I first did.

The way we’re going to use observables in this case is the following: when an appointment is saved, AppointmentService will emit that appointment. When the appointment is emitted, CalendarComponent will become aware of it because CalendarComponent has subscribed to the observable that emits appointments.

The first step is to add the observable to AppointmentService.

// src/app/calendar/appointment.service.ts

import { Injectable } from '@angular/core';
import { Subject } from 'rxjs/Subject';

@Injectable()
export class AppointmentService {

  private appointmentCreatedSource = new Subject<string>();

  appointmentCreated$ = this.appointmentCreatedSource.asObservable();

  constructor() { }

  getList() {
    return JSON.parse(localStorage.getItem('appointments'));
  }

  save(appointment) {
    let appointments = this.getList();
    appointments.push(appointment);

    localStorage.setItem('appointments', JSON.stringify(appointments));
    this.appointmentCreatedSource.next(appointment);
  }
}

Then modify CalendarComponent so it subscribes to the appointment-emitting service and reloads the appointment list when it knows a new appointment has been saved.

// src/app/calendar/calendar.component.ts

import { Component, OnInit } from '@angular/core';
import { AppointmentService } from './appointment.service';

@Component({
  selector: 'cadence-calendar',
  templateUrl: './calendar.component.html',
  styleUrls: ['./calendar.component.css']
})
export class CalendarComponent implements OnInit {
  appointments = [];

  constructor(private appointmentService: AppointmentService) {
    appointmentService.appointmentCreated$.subscribe(appointment => {
      this.loadAppointments();
    });
  }

  loadAppointments() {
    this.appointments = this.appointmentService.getList();
  }

  ngOnInit() {
    this.loadAppointments();
  }
}

Now when you save an appointment it will show up immediately.

Making it look nicer

It would be nice if the appointment form actually cleared itself when we save an appointment. We can do this by calling reset() on the form when the submit button is clicked.

<!-- src/app/calendar/new-appointment/new-appointment.component.html -->

<form #appointmentForm="ngForm" (submit)="save(); appointmentForm.reset()">

  <div class="form-group">
    <label for="date">Date</label>
    <input type="text"
           class="form-control"
           name="date"
           id="date"
           [(ngModel)]="model.date">
  </div>

  <div class="form-group">
    <label for="clientName">Client name</label>
    <input type="text"
           class="form-control"
           name="clientName"
           id="clientName"
           [(ngModel)]="model.clientName">
  </div>

  <input type="submit" class="btn btn-primary" value="Save">
</form>

A little padding would also help.

/* src/styles.css */

body {
  padding-top: 30px;
}

Lastly, we’ll give the appointment form a containing gray box. First we’ll give it a class name.

<!-- src/app/calendar/new-appointment/new-appointment.component.html -->

<div class="new-appointment-form-container">
  <form #appointmentForm="ngForm" (submit)="save(); appointmentForm.reset()">

    <div class="form-group">
      <label for="date">Date</label>
      <input type="text"
             class="form-control"
             name="date"
             id="date"
             [(ngModel)]="model.date">
    </div>

    <div class="form-group">
      <label for="clientName">Client name</label>
      <input type="text"
             class="form-control"
             name="clientName"
             id="clientName"
             [(ngModel)]="model.clientName">
    </div>

    <input type="submit" class="btn btn-primary" value="Save">
  </form>
</div>

Then we’ll add the styles.

/* src/app/calendar/new-appointment/new-appointment.component.css */

.new-appointment-form-container {
  background-color: #DDD;
  padding: 30px;
}

Then we’ll change the markup of the calendar page to make it look a little nicer.

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

<div class="row">
  <div class="col-lg-6">
    <cadence-new-appointment></cadence-new-appointment>
  </div>

  <div class="col-lg-6">
    <div *ngFor="let appointment of appointments">
      <hr>
      {{ appointment.date }}
      {{ appointment.clientName }}
    </div>
  </div>
</div>

Your application should now roughly match the screenshot below.

How to Wire Up Ruby on Rails and AngularJS as a Single-Page Application (Gulp Version)

Important note: This tutorial no longer works if followed verbatim. I would recommend my more recent updated-for-2016 version.

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.

Refreshed for 2015

I had written a tutorial with almost the same title last summer. In that tutorial I used Grunt instead of Gulp, HTML instead of HAML or Jade, and regular JavaScript instead of CoffeeScript or ES6. Not only do cooler alternatives to those traditional technologies exist today, they did back then, too. My tutorial was sorely in need of a reboot. So here it is.

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.

$ 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.3.x
  • Modules: all
  • jQuery: 2.x
  • REST resource library: ngResource (just because it’s the default and angularjs-rails-resource isn’t an option on the list)
  • Router: UI Router
  • UI framework: Bootstrap
  • Bootstrap component implementation: Angular UI
  • CSS preprocessor: Sass (Node)
  • JS preprocessor: CoffeeScript
  • HTML template engine: Jade
$ yo gulp-angular fake_lunch_hub

If you have a Rails server running on port 3000, stop it for now because Gulp will also run on port 3000 by default. Start Gulp to see if it works:

$ gulp serve

Gulp should now open a new browser tab for you at http://localhost:3000/#/ 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

The only way our front-end app (Angular and friends) will know about our back-end server (Rails) is if we tell our front-end app about our back-end app. The basic idea is that we want to tell our front-end app to send any requests to `http://our-front-end-app/api/whatever` to `http://our-rails-server/api/whatever`. Let’s do that now.

If you look inside `client/gulp`, you’ll notice there’s a file in there called `proxy.js`. I would like to have simply tweaked this file slightly to get our proxy working, but unfortunately I found `proxy.js` very confusing and difficult to work with. So I deleted it and set up the proxy a different way. Let’s delete `proxy.js` so it doesn’t confuse future maintainers.

$ rm gulp/proxy.js

You’ll notice another file inside `client/gulp` called `server.js`. I found that minimal adjustment in this file was necessary in order to get the proxy working. Here’s what my `server.js` looks like after my modifications, which I’ll explain:

'use strict';

var gulp = require('gulp');
var browserSync = require('browser-sync');
var browserSyncSpa = require('browser-sync-spa');
var util = require('util');
var proxyMiddleware = require('http-proxy-middleware');
var exec = require('child_process').exec;

module.exports = function(options) {

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

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

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

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

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

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

  gulp.task('serve', ['watch'], function () {
    browserSyncInit([options.tmp + '/serve', options.src]);
  });

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

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

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

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

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

  1. Configured BrowserSync to run on port 9000 so Rails can run on its default port of 3000 without conflicts
  2. Added middleware that says “send requests to `/api` to `http://localhost:3000`”
  3. Added a `rails` task that simply invokes the `rails server` command
  4. 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

Three things should now happen:

  1. The front-end server should come up on port 9000 instead of 3000.
  2. If you navigate to `http://localhost:9000/api/foo`, you should get a Rails page that says `No route matches [GET] “/api/foo”`, which means
  3. 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.coffee` to include a state for groups:

angular.module 'fakeLunchHub', ['ngAnimate', 'ngCookies', 'ngTouch', 'ngSanitize', 'ngResource', 'ui.router', 'ui.bootstrap']
  .config ($stateProvider, $urlRouterProvider) ->
    $stateProvider
      .state "home",
        url: "/",
        templateUrl: "app/main/main.html",
        controller: "MainCtrl"
      .state "groups",
        url: "/groups",
        templateUrl: "app/views/groups.html",
        controller: "GroupsCtrl"

    $urlRouterProvider.otherwise '/'

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

angular.module "fakeLunchHub"
  .controller "GroupsCtrl", ($scope) ->

(I manually created a new directory for this, `src/app/controllers`.)

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

div.container
  div(ng-include="'components/navbar/navbar.html'")
  h1 Groups

If you now navigate to `http://localhost:9000/#/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.coffee`: the `rails` module and a resource called `Group`.

angular.module 'fakeLunchHub', ['ngAnimate', 'ngCookies', 'ngTouch', 'ngSanitize', 'ngResource', 'ui.router', 'ui.bootstrap', 'rails']
  .config ($stateProvider, $urlRouterProvider) ->
    $stateProvider
      .state "home",
        url: "/",
        templateUrl: "app/main/main.html",
        controller: "MainCtrl"
      .state "groups",
        url: "/groups",
        templateUrl: "app/views/groups.html",
        controller: "GroupsCtrl"

    $urlRouterProvider.otherwise '/'

  .factory "Group", (RailsResource) ->
    class Group extends RailsResource
      @configure url: "/api/groups", name: "group"

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

angular.module "fakeLunchHub"
  .controller "GroupsCtrl", ($scope, Group) ->
    Group.query().then (groups) -> $scope.groups = groups

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

div.container
  div(ng-include="'components/navbar/navbar.html'")
  h1 Groups

  ul(ng-repeat="group in groups")
    li {{ group.name }}

If you now visit `http://localhost:9000/#/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.

That’s all for now

I’ve heard requests for tutorials on basic CRUD operations in Angular/Rails, so keep your eye out for that in the near future. You can subscribe to my posts by leaving your email in the upper right corner.

Also, if you enjoyed this tutorial, you may also like my webinar version of this same tutorial (and then some). Thanks for reading.

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.