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

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.

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

Get into our project directory.

Create our PostgreSQL user.

Create the database.

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:

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:

(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.

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

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.

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.)

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

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.

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.)

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

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

Start Grunt:

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):

When shit 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.

Change your Gruntfile to match this:

Now kill Grunt and again run:

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:

Get the data into the database:

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.

Now add a controller for the Group resource:

And add a view.

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

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.

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

  • July 22, 2014 at 10:14 am
    Permalink

    Would you consider using a different syntax highlighting style or solution? The zebra striping is extremely distracting.

    Reply
    • July 22, 2014 at 10:17 am
      Permalink

      Good to know. Yeah, I’ll take a look at the different options.

      Reply
      • July 22, 2014 at 10:30 am
        Permalink

        Looks great now! Awesome article – thank you for writing it.

        Reply
        • July 22, 2014 at 11:04 am
          Permalink

          Thanks, man.

          Reply
  • July 22, 2014 at 10:47 am
    Permalink

    Excellent write-up, but I’m running into problems on Mac OSX 10.9.4:

    I need to create a new PG user,

    $createuser -P -s -e fake_lunch_hub


    createuser: could not connect to database postgres: could not connect to server: No such file or directory
    Is the server running locally and accepting
    connections on Unix domain socket "/tmp/.s.PGSQL.5432"?

    Reply
    • July 22, 2014 at 11:04 am
      Permalink

      Sounds like PostgreSQL isn’t running. Restart your PostgreSQL server and try again.

      Reply
  • July 22, 2014 at 10:55 am
    Permalink

    I would be interested in seeing how you deploy this to production. Im doing something similar and just wrote a whole bunch of capistrano task to support it.

    Reply
    • July 22, 2014 at 11:03 am
      Permalink

      Good idea! That would be a really good thing to include.

      Reply
  • Pingback: How to Wire Up Ruby on Rails and AngularJS as a Single-Page Application | Blog

    • July 22, 2014 at 4:31 pm
      Permalink

      Thanks! I’ll check that out.

      Reply
  • July 22, 2014 at 2:21 pm
    Permalink

    In the RSpec configuration inside ‘config/application.rb’, I’d keep the requests and routing specs enabled over controller specs. If you’re writing an API consuming client-side application like Angular, then you care about whether routes exists and responses received from requests to those routes.

    Reply
    • July 22, 2014 at 4:30 pm
      Permalink

      Hmm…that’s kind of a good point.

      Reply
  • July 22, 2014 at 7:44 pm
    Permalink

    Thanks!

    We’re in the middle of a transitory period. I convinced the CEO to let me turn first the purchasing pathway into an Angular app(so our entire dashboard up through trial exam and purchasing). We did that and now conversions are up because there is no page-reload time.

    However, I’m now stuck with a Rails 3.2 app that runs Angular some of the time and has separate pages at other times. Gradually I’m subsuming the old pages into the angular app but it’ll take months with our dev schedule. My end goal is to separate the angular app outside of the Rails app and this will be a great starting point for that once I’ve gotten all the views into Angular.

    Reply
  • July 22, 2014 at 8:34 pm
    Permalink

    Following you tutorial, controller test can’t pass, because we must use strong parameter in Rails4, so we need add a private method to require group params.

    Reply
    • July 22, 2014 at 8:37 pm
      Permalink

      And you’re using Rails::API as opposed to full-blown Rails?

      Reply
      • July 23, 2014 at 12:52 am
        Permalink

        I’m having the same problem. I’ll email details.

        Reply
  • July 23, 2014 at 9:31 am
    Permalink

    Is the example app available somewhere on Github?

    Reply
  • Pingback: How to Get Grunt to Start Your Rails Server | Angular on Rails

  • July 24, 2014 at 6:34 pm
    Permalink

    Thanks for the write up.

    Could you please explain the reason to go with Grunt instead of just rely on rails’ asset pipeline?
    Seems like going through a bit of hoops to do that (e.g. proxy, 2 servers)

    Reply
    • July 25, 2014 at 8:01 am
      Permalink

      The reason is that I don’t know that you can do an actual single-page application when using AngularJS in the asset pipeline. I could be wrong about that. If I just wanted to use AngularJS as a library, and I weren’t worried about building an SPA, then I might have gone the asset pipeline route, and in fact I’ve done that before. The structure felt a little weird to me, though. Another reason for using Grunt, etc. is that I wanted to take advantage of the structure that Yeoman provides and I didn’t see how to do that inside the asset pipeline.

      Reply
  • July 27, 2014 at 5:11 am
    Permalink

    rails api really removes protect_from_forgery from the application_controller? I thought the best practice was to set it to protect_from_forgery with: :null_session when just serving JSON.

    +1 for getting out of the asset pipeline. I’m switching over to this approach (but with gulp instead of grunt) and it seems nicer for a reusable api if you want to serve a mobile app or something else, too.

    The big hassle with this approach is authentication: from what I’ve read, APIs should be stateless, which rules out using rails’ sessions for authentication. You can use angular’s ngCookies to store tokens and whatnot on the client site, and then use an interceptor to configure authenticated requests, but there also seem to be a whole bunch of security issues with token auth. Lynn’s devise solution seems to be very well designed, but I haven’t played around with it much. I’ve been trying to roll my own auth to understand the pieces with angular and it’s a pain. Looking forward to seeing how you approach it.

    The other real cool thing about building outside of the asset pipeline is that it forces you structure your serializers well. I’m finding it avoids a lot of the n+1 gotchas that pop up in regular rails apps because you can’t query for more data in your views.

    good stuff

    Reply
    • July 27, 2014 at 9:15 pm
      Permalink

      Thanks. The fact that you’re interested in how I approach the authentication part is motivating for me to get something out there. Next will be E2E testing, and then authentication is what I’m planning next (with E2E tests around it).

      Reply
      • July 29, 2014 at 7:56 am
        Permalink

        Cool, yeah the issue I ran into with separating angular from rails like this is that I had to manually reset the rails test db after protractor e2e test runs. If you embed angular in rails, there’s a nice gem that’ll handle that reset automatically, but I’m still looking for a good solution for when the rails and angular pieces are separate.

        Reply
        • July 29, 2014 at 8:27 am
          Permalink

          I’m actually working on another post that addresses that problem.

          Reply
  • July 28, 2014 at 6:18 pm
    Permalink

    Hey all,
    Noob here. I also got hung up on the Rspec controller failure:

    uninitialized constant GroupsController

    I’ve got the GitHub repo open but could use a hint. Thanks!

    Reply
      • August 8, 2014 at 10:14 am
        Permalink

        I’m getting the same error too, it occurs after the step where the groups_controller_spec.rb file is copy/pasted and saved on my machine. Adding “require ‘rails_helper’” next to the other “require” helps, but results in failing specs.

        Reply
        • August 11, 2014 at 1:30 am
          Permalink

          Had the same problem, and it turned out I was using a very old version of rspec-rails

          Reply
  • July 29, 2014 at 5:02 pm
    Permalink

    I’m using plain rails.
    I am getting cannot get /api/groups
    it’s accesible via plain url.
    grunt file is correctly configured I think
    Please help

    Reply
    • July 29, 2014 at 5:47 pm
      Permalink

      If you post a Stack Overflow question and send me a link to it, I’ll take a look.

      Reply
  • July 31, 2014 at 1:01 am
    Permalink

    Just leaving this comment to say that I’m glad I found this site and I look forward to watching this post evolve and others appear :)

    Reply
  • Pingback: How to Deploy an Angular/Rails Single-Page Application to Heroku | Angular on Rails

  • August 6, 2014 at 6:54 pm
    Permalink

    When I do the yo angular fake_lunch_hub command, should I be inside the client/ directory?

    Reply
  • August 6, 2014 at 7:33 pm
    Permalink

    You need to change your index.html to have an ng-view. becomes otherwise your routes won’t work.

    Reply
  • August 6, 2014 at 7:34 pm
    Permalink

    It ate my HTML in my previous comment, sorry.

    You need to change your index.html to have an ng-view. <div ng-include=”‘views/main.html’” ng-controller=”MainCtrl”></div> should become <div ng-view ng-controller=”MainCtrl”></div> otherwise your routes won’t work.

    Reply
  • August 10, 2014 at 6:40 am
    Permalink

    Good tutorial, thanks! :-) Just one note: Rspec should-Syntax is deprecated, use expect-Syntax instead.

    Reply
  • August 10, 2014 at 10:57 am
    Permalink

    And there is a syntax error in your Gruntfile.js on line 213. Should be “10″ not “10n”.

    Reply
  • September 2, 2014 at 6:18 pm
    Permalink

    Thank you for a great and clear tutorial, Jason! It’s what I tried to find.
    I have a question – why do you use ng-repeat on instead ?

    Groups

    {{group.name}}

    Reply
    • September 2, 2014 at 6:20 pm
      Permalink

      Sorry, html was eaten. I meant file app/views/groups.html and using ng-repeater on ul element instead of li.

      Reply
    • September 3, 2014 at 10:44 pm
      Permalink

      I believe that’s just how ng-repeat works. I think of it as kind of being analogous to a for loop. If you mean that I made a syntactical mistake, then that’s a different thing, so let me know if it’s not working for you.

      Reply
      • September 6, 2014 at 3:08 pm
        Permalink

        It’s working, but when ng-repeat in ul-container there are a lot of ul containers and every of they contains one li element, but ul – container for li elements and right way of using ng-repeat on element you want repeat but nor on it’s parent. And in Angular docs all examples with ng-repeat point to li element.

        Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">