Adam Anderson

Bootstrapping an AngularJS app in Rails 4.0 - Part 3


By Adam Anderson



Tweet Like Share

The full GitHub repository for this tutorial is available here

Introduction

This is part three of my tutorial covering building an Angular and Rails blog application from scratch. Jumping in again from where the last post left off, in this post I will cover:

As before, this series assumes a certain basic understanding of both Rails and AngularJS. If you need an introduction to Rails, I recommend checking out the excellent Ruby on Rails Tutorial by Michael Hartl. For an intro to AngularJS, I recommend checking out the homepage tutorials and, to go a bit deeper, the excellent egghead.io tutorial videos by John Lindquist.


Creating a simple Rails model

To start with, we need a database model to store our blog data. As with our example data currently in our main AngularJS controller, we will keep this model simple. Each blog entry will have a title and will have its body stored in a contents field. We will also add fields for an author and a date. Luckily, Rails makes setting this model up simple. At the command line, use the Rails generate command to create the migration that sets up this model:


$ rails generate model Post title:string contents:text author:string post_date:datetime
      invoke  active_record
      create    db/migrate/20130803203410_create_posts.rb
      create    app/models/post.rb


As you can see, Rails generates our first database migration. If you open that migration up to inspect, it should look something like this.

db/migrate/[date-time-created]_create_posts.rb

class CreatePosts < ActiveRecord::Migration
  def change
    create_table :posts do |t|
      t.string :title
      t.text :contents
      t.string :author
      t.datetime :post_date

      t.timestamps
    end
  end
end


Rails also created a file containing the Post model itself, though as you can see, there is no specific functionality or validation defined as of yet. We will come back to implement this later.

app/models/post.rb

class Post < ActiveRecord::Base

end


The only thing left to create this model is to actually run the migration. You can complete that by running rake db:migrate at the command line:

$ rake db:migrate
==  CreatePosts: migrating ====================================================
-- create_table(:posts)
   -> 0.0037s
==  CreatePosts: migrated (0.0038s) ===========================================

$


We now have a Posts model, and a table for that model set up in the database, but no data. Let's set up two quick example posts using the Rails console so we will be able to test with real data.

$ rails c
Loading development environment (Rails 4.0.0)

2.0.0p0 :001 > ########  First, let's create a new post.  #########
2.0.0p0 :002 > ####################################################

2.0.0p0 :003 > first_post = Post.new
 => #<Post id: nil, title: nil, contents: nil, author: nil, post_date: nil, created_at: nil, updated_at: nil>

2.0.0p0 :004 > first_post.title = "The first post on the database"
 => "The first post on the database"

2.0.0p0 :005 > first_post.contents = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec laoreet lobortis vulputate. Ut tempus, orci eu tempor sagittis, mauris orci ultrices arcu, in volutpat elit elit semper turpis. Maecenas id lorem quis magna lacinia tincidunt. In libero magna, pharetra in hendrerit vitae, luctus ac sem. Nulla velit augue, vestibulum a egestas et, imperdiet a lacus. Nam mi est, vulputate eu sollicitudin sed, convallis vel turpis. Cras interdum egestas turpis, ut vestibulum est placerat a. Proin quam tellus, cursus et aliquet ut, adipiscing id lacus. Aenean iaculis nulla justo."
 => "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec laoreet lobortis vulputate. Ut tempus, orci eu tempor sagittis, mauris orci ultrices arcu, in volutpat elit elit semper turpis. Maecenas id lorem quis magna lacinia tincidunt. In libero magna, pharetra in hendrerit vitae, luctus ac sem. Nulla velit augue, vestibulum a egestas et, imperdiet a lacus. Nam mi est, vulputate eu sollicitudin sed, convallis vel turpis. Cras interdum egestas turpis, ut vestibulum est placerat a. Proin quam tellus, cursus et aliquet ut, adipiscing id lacus. Aenean iaculis nulla justo."

2.0.0p0 :006 > first_post.author = "Foo"
 => "Foo"

2.0.0p0 :007 > first_post.post_date = DateTime.current()
 => Sat, 03 Aug 2013 20:50:26 +0000

2.0.0p0 :008 > first_post.save
   (0.2ms)  BEGIN
  SQL (5.8ms)  INSERT INTO "posts" ("author", "contents", "created_at", "post_date", "title", "updated_at") VALUES ($1, $2, $3, $4, $5, $6) RETURNING "id"  [["author", "Foo"], ["contents", "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec laoreet lobortis vulputate. Ut tempus, orci eu tempor sagittis, mauris orci ultrices arcu, in volutpat elit elit semper turpis. Maecenas id lorem quis magna lacinia tincidunt. In libero magna, pharetra in hendrerit vitae, luctus ac sem. Nulla velit augue, vestibulum a egestas et, imperdiet a lacus. Nam mi est, vulputate eu sollicitudin sed, convallis vel turpis. Cras interdum egestas turpis, ut vestibulum est placerat a. Proin quam tellus, cursus et aliquet ut, adipiscing id lacus. Aenean iaculis nulla justo."], ["created_at", Sat, 03 Aug 2013 20:50:38 UTC +00:00], ["post_date", Sat, 03 Aug 2013 20:50:26 UTC +00:00], ["title", "The first post on the database"], ["updated_at", Sat, 03 Aug 2013 20:50:38 UTC +00:00]]
   (0.5ms)  COMMIT
 => true

2.0.0p0 :009 > ##### Now let's set up a second post ########

2.0.0p0 :010 >   second = Post.new
 => #<Post id: nil, title: nil, contents: nil, author: nil, post_date: nil, created_at: nil, updated_at: nil>

2.0.0p0 :011 > second.title = "This is the second post"
 => "This is the second post"

2.0.0p0 :012 > second.contents = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin leo sem, imperdiet in faucibus et, feugiat ultricies tellus. Vivamus pellentesque iaculis dolor, sed pellentesque est dignissim vitae. Donec euismod purus non metus condimentum porttitor suscipit nibh tempor. Etiam malesuada elit in lectus pharetra facilisis. Fusce at nisl augue. Donec at est felis. Sed a gravida diam. Nunc nunc mi, egestas non dignissim et, porta aliquam ante."
 => "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin leo sem, imperdiet in faucibus et, feugiat ultricies tellus. Vivamus pellentesque iaculis dolor, sed pellentesque est dignissim vitae. Donec euismod purus non metus condimentum porttitor suscipit nibh tempor. Etiam malesuada elit in lectus pharetra facilisis. Fusce at nisl augue. Donec at est felis. Sed a gravida diam. Nunc nunc mi, egestas non dignissim et, porta aliquam ante."

2.0.0p0 :013 > second.author = "Authorman"
 => "Authorman"

2.0.0p0 :014 > second.post_date = DateTime.current()-7
 => Sat, 27 Jul 2013 20:52:48 +0000

2.0.0p0 :015 > second.save
   (0.2ms)  BEGIN
  SQL (3.0ms)  INSERT INTO "posts" ("author", "contents", "created_at", "post_date", "updated_at") VALUES ($1, $2, $3, $4, $5) RETURNING "id"  [["author", "Authorman"], ["contents", "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin leo sem, imperdiet in faucibus et, feugiat ultricies tellus. Vivamus pellentesque iaculis dolor, sed pellentesque est dignissim vitae. Donec euismod purus non metus condimentum porttitor suscipit nibh tempor. Etiam malesuada elit in lectus pharetra facilisis. Fusce at nisl augue. Donec at est felis. Sed a gravida diam. Nunc nunc mi, egestas non dignissim et, porta aliquam ante."], ["created_at", Sat, 03 Aug 2013 20:54:19 UTC +00:00], ["post_date", Sat, 27 Jul 2013 20:54:16 UTC +00:00], ["updated_at", Sat, 03 Aug 2013 20:54:19 UTC +00:00]]
   (2.2ms)  COMMIT
 => true

2.0.0p0 :016 > exit

$


As you can see, we manually created two sample blog entries and saved them to the database. The next step is to build an API so our client app can access them.


Creating a Rail API controller

Similar to how we created our model, we can use Rails to generate a barebones controller for our API with the generate command.

$ rails generate controller posts
      create  app/controllers/posts_controller.rb
      invoke  erb
      create    app/views/posts
      invoke  helper
      create    app/helpers/posts_helper.rb
      invoke  assets
      invoke    coffee
      create      app/assets/javascripts/posts.js.coffee
      invoke    scss
      create      app/assets/stylesheets/posts.css.scss


As you can see, this generated a number of files and directories. However, since we are not implementing any views as part of this API (we'll be responding with JSON instead), the only one we really need is posts_controller.rb. To avoid confusion later, let's delete app/views/posts, posts_helper.rb, posts.js.coffee, and posts.css.scss. Now let's take a look at our newly created PostsController.

app/controllers/posts_controller.rb

class PostsController < ApplicationController

end


As you can see, not much there. Before creating our API actions, let's add a routes for our API in our route config file. This is as simple as adding the single line resources :posts to the file.

config/routes.rb

Blog::Application.routes.draw do
  root to: 'main#index'

  resources :posts

end


Then, use the rake routes command at the command line to see the new routes we have created.

$ rake routes
   Prefix Verb   URI Pattern               Controller#Action
     root GET    /                         main#index
    posts GET    /posts(.:format)          posts#index
          POST   /posts(.:format)          posts#create
 new_post GET    /posts/new(.:format)      posts#new
edit_post GET    /posts/:id/edit(.:format) posts#edit
     post GET    /posts/:id(.:format)      posts#show
          PATCH  /posts/:id(.:format)      posts#update
          PUT    /posts/:id(.:format)      posts#update
          DELETE /posts/:id(.:format)      posts#destroy


Rails makes it simple to create all of these API actions with a single line. Pretty awesome.

Let's start with implementing a single API action—the index action—which for now we will allow to return all post data. This is obviously not a good long-term solution, but will be fine for now. We will respond to all API requests with JSON, which can be easily parsed by our Angular controller.

app/controllers/posts_controller.rb

class PostsController < ApplicationController
  respond_to :json

  def index

    # Gather all post data
    posts = Post.all

    # Respond to request with post data in json format
    respond_with(posts) do |format|
      format.json { render :json => posts.as_json }
    end

  end

end


This implementation is fairly straightforward. First, we configure the controller to respond to JSON requests for all actions. Next, we define our index action to pull all post data from our model, reformat that data as JSON, and then respond to the request with it. Now let's test whether this is working properly. Open up two command line windows. In the first, use the rails s command to launch the Rails server. Then, in the second terminal window, run the following curl command on the API:

$ curl http://localhost:3000/posts.json

[{"id":1,"title":"The first post on the database","contents":"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec laoreet lobortis vulputate. Ut tempus, orci eu tempor sagittis, mauris orci ultrices arcu, in volutpat elit elit semper turpis. Maecenas id lorem quis magna lacinia tincidunt. In libero magna, pharetra in hendrerit vitae, luctus ac sem. Nulla velit augue, vestibulum a egestas et, imperdiet a lacus. Nam mi est, vulputate eu sollicitudin sed, convallis vel turpis. Cras interdum egestas turpis, ut vestibulum est placerat a. Proin quam tellus, cursus et aliquet ut, adipiscing id lacus. Aenean iaculis nulla justo.","author":"Foo","post_date":"2013-08-03T20:50:26.000Z","created_at":"2013-08-03T20:50:38.811Z","updated_at":"2013-08-03T20:50:38.811Z"},{"id":2,"title":"This is the second post","contents":"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin leo sem, imperdiet in faucibus et, feugiat ultricies tellus. Vivamus pellentesque iaculis dolor, sed pellentesque est dignissim vitae. Donec euismod purus non metus condimentum porttitor suscipit nibh tempor. Etiam malesuada elit in lectus pharetra facilisis. Fusce at nisl augue. Donec at est felis. Sed a gravida diam. Nunc nunc mi, egestas non dignissim et, porta aliquam ante.","author":"Authorman","post_date":"2013-07-27T20:54:16.000Z","created_at":"2013-08-03T20:54:19.243Z","updated_at":"2013-08-03T20:54:19.243Z"}]%


You should see a response like this, with the sample data we created in the console being returned in JSON format. While we will eventually build this Rails API out further to handle the full CRUD implementation, let's stop here for now and turn to our client to ingest this data dump.


Accessing the blog API from the AngularJS main controller

Now let's turn back to our client main IndexCtrl controller. Loading main page content asynchronously, in this case the posts themselves, poses a small presentation problem. While that data is loading, the page will show up as blank. This is not ideal, even if we are able to pull in the post data within a few hundred milliseconds. To avoid that, let's start by replacing our sample content with a placeholder post entitled "Loading...".

app/assets/javascripts/Controllers/main/mainIndexCtrl.js.coffee

@IndexCtrl = ($scope, $location) ->

  $scope.data = 
    posts: [{title: 'Loading posts...', contents: ''}]

  $scope.viewPost = (postId) ->
    $location.url('/post/'+postId)


If you load the page, you should see something like this.

http://localhost:3000


This will let the user know to hold on for just a sec while the content loads rather than displaying him or her a blank page initially. Angular provides some more elegant ways to handle this problem that I will touch on in another post.

Now let's set up a function to load the blog posts when the controller first loads. Here is the code to implement this GET request to the API. I will walk through what is going on below.

app/assets/javascripts/Controllers/main/mainIndexCtrl.js.coffee

@IndexCtrl = ($scope, $location, $http) ->

  $scope.data = 
    posts: [{title: 'Loading posts...', contents: ''}]

  loadPosts = ->
    $http.get('./posts.json').success( (data) ->
      $scope.data.posts = data
      console.log('Successfully loaded posts.')
    ).error( ->
      console.error('Failed to load posts.')
    )

  loadPosts()

  $scope.viewPost = (postId) ->
    $location.url('/post/'+postId)


Stepping through this, you see that we injected an additional Angular module into our controller, specifically $http. This module neatly encapsulates all of the asynchronous HTTP functionality that we will need to interact with our API.

Next, we have created a loadPosts function. The first line uses $http to make a GET request to http://localhost:3000/posts.json to retrieve the JSON data we created earlier containing our initial sample posts. This $http.get() call returns a request object. Using JS dot notation, we then immediately create two callbacks: one for success and one for error. In the case of success, our callback will take a data argument that represents the JSON data object returned from the API. We set our $scope.data.posts object (which is bound to our HTML view) to contain that returned post data. Then we call console.log to let ourselves know that our API call returned successfully and that our post data is loaded (this is obviously optional). In the case of an error, we simply log an error to the console to let ourselves know that as well.

Finally, we call loadPosts() from the main Index Controller. This function will run when the controller is first created.

So now when we load our page, the following will happen, starting from the time Angular loads on the client browser:

If you reload the page now, it should look something like this. You may or may not see the "Loading..." temporary data come up, depending on how fast your browser is and how quickly your Rails instance responds to the API call.

http://localhost:3000/


And there it is. The sample post data that we entered into our database from the Rails console is now being accessed by Angular via our Rails API and displayed on the client side in the browser!

Conclusion

That's it for part 3. We now have a Rails app that exposes an API for accessing and updating blog data. That Rails backend connects to the client through the main AngularJS controller, which automagically displays the content. While this is a good high-level demonstration, the structure iof pulling down data in a main controller is not a great or sustainable solution as your app grows. So in the next part, we will cover a more sophisticated way to use AngularJS to access the API, creating a shared service that can be accessed by any controller.

Find this helpful? Check out Part 4. And follow me on Twitter for more updates.



comments powered by Disqus

Written by Adam Anderson. Published with GitHub Pages.