Angular seems to be the big craze as of late. Some may agree and some may not, but AngularJS is one of the next big contenders for being the number one choice of developers. At the time of writing this article, AngularJS is the 12th most watched project on GitHub.
Here I want to create a useful Rails application using Angular. The goal is to have a single-page application which allows us to select a screencast link on the left and view it on the right. An example of this would be found at http://ember101.com.
Originally I had presented this topic at our local ruby users group. My typical workflow is to write a blog post before presenting and have that post be a reference to my presentation. Since then, I have received a lot of feedback on how I could have enhanced the app. These posts (part 1 and 2) been re-written to reflect those changes. Special thanks goes to Tad Thorley for providing the excellent example application based off of the original. Also thanks goes out to those who have commented on these posts.
Creating the Rails Application
I had a hard time deciding when I began this project on whether to use a full Rails application or a very lightweight ruby web stack like Sinatra. I also experimented with a middle-ground solution called Rails::API (see Railscast). In the end, I used standard Rails (version 4.0.0.rc1). This gave me the flexibility I want; and for the scope of this tutorial I didn’t want to distract from learning how to use Angular in an Rails application.
Before anything, we need to create a new Rails application called Angular Casts
$ rails new angular_casts ... $ cd angular_casts
Creating the Model and Controller
Our application will be pretty simple. We are only going to worry about storing screencast information in our database. Rails by default comes with a very lightweight database server called SQLite. If you aren’t familiar with this, you can visit http://guides.rubyonrails.org/getting_started.html#configuring-a-database to learn more.
Before we create our model, we need to determine what information we want to store. As a user of this application, I think the most useful would be:
- title: What is the name of the screencast?
- summary: What is the screencast about?
- duration: How long is the screencast?
- link: How do I get to the original screencast?
- published: When was the screencast published?
- source: Who is the provider of the screencast?
Let’s create a model and controller based on this information. We need to add the video_url field as well, which will be used to embed the video into our app.
$ rails g resource screencast title summary:text duration link published_at:datetime source video_url
By running the resource generator, we now have a model and a controller. The controller will provide our REST API. We also can see that the screencasts resources have been added to our routes:
1 2 3 4
Run the migration tasks for both development and test environments:
$ rake db:migrate; rake db:migrate RAILS_ENV=test
Testing the Model
Testing is not the primary topic of these blog posts, so less time and explanation will be given to them. If you want to learn more about testing, I recommend http://railscasts.com/episodes/275-how-i-test.
For us to have a long-lasting, maintainable application it is imperative that we keep our code tested. This will also help us stay focused on what our application and code is supposed to do.
Start by running the rake task for testing to see make sure the tests run:
$ rake test
If all worked well, you should see something like 0 tests, 0 assertions, 0 failures, 0 errors, 0 skips. This means that the test suite ran, but it didn’t find any tests to run.
There are a couple of things we will want to test:
- Make sure that all the required data exists for each screencast.
- Make sure that we do not have two of the same screencast (duplicates).
Before we write our tests, let’s update our fixtures file with some testable data:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
Open up the auto-generated file test/models/screencast_test.rb and add the following tests. If you are using Rails 3.x, the test file will appear under test/unit/screencast_test.rb.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
Once this is in place, run the tests again with the command
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
You can see we have have 3 tests with 3 assertions and 2 failures. The reason we didn’t get 3 failures is because the second test, “should be valid if required data exists”, will always pass until we set up some restrictions on the model.
Let’s update the model to make these tests pass.
1 2 3 4
Run the tests again to see them pass successfully!
Importing the Video Data
Because we are going to import video feeds from external sites, we need to use a feed parsing library. The best one available is feedzirra. Let’s add it to our Gemfile and remove turbolinks, jbuilder and sdoc. We also remove jquery-rails because we will use it via CDN instead of inside the Asset Pipeline. This will be further explained in part 2.
1 2 3 4 5 6 7 8
Now install the gems:
$ bundle install
Create an Import Library
Now that we have a place to put all of the screencast information, we need to be able to import it from external feeds. Here we are going to create a simple Ruby class that uses the feedzirra gem to grab the feed data, parse it and then add it to our database.
Let’s start off by creating a new class called ScreencastImporter. Paste the following code into lib/screencast_importer.rb.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
Note that on lines 17-18, we strip out the episode number from the Railscast title. So “#412 Fast Rails Commands” would become “Fast Rails Commands”. See this Rubular to see how I determined the RegExp pattern.
Now if we were to go into our Rails console, we could trigger this import manually. Give it a shot!
$ rails c Loading development environment (Rails 4.0.0.rc1) >> require 'screencast_importer' => true >> ScreencastImporter.import_railscasts .... lots ... of ... feedback .... => 345 >> Screencast.count => 345
At the time of writing this article, there were 345 public Railscasts. This number will increase as time goes on.
Trigger Import via Rake
Instead of loading up the Rails console and performing the import manually, it would me much easier to have a simple command that we could run to perform the imports. This is where rake tasks come in. Let’s create a rake task that will perform the import.
1 2 3 4 5 6 7 8 9
Now that we have our rake task set up, go ahead and run the command:
$ rake screencast_sync:railscasts There are now 345 screencasts from Railscasts.com
It worked! But no time to celebrate.. let’s move on.
Making Episodes Accessible via API
Because we are planning on using Angular for our front-end, we only need to expose our data as JSON. This will allow Angular to talk to the backend via ajax calls.
We are going to only use two calls to the API:
- /screencasts.json - returns a full list of episodes
- /screencasts/ID.json - returns data for a specified screencast (where ID is the unique ID of the screencast in our db)
Because we used the resource generator, we already have our controller and routes.
Let’s do some cleanup to the routes and make sure we only are allowing what we want to use. On top of that, let’s scope our calls to the API with api.
1 2 3 4 5 6
Run the command
rake routes to see our changes.
$ rake routes Prefix Verb URI Pattern Controller#Action GET /api/screencasts(.:format) screencasts#index GET /api/screencasts/:id(.:format) screencasts#show
Now update the controller to render the correct JSON data for the two URL’s.
1 2 3 4 5 6 7 8 9 10 11 12 13
Now start up your Rails application and visit this link: http://localhost:3000/api/screencasts.json. If all went well, you should see JSON data. You should also be able to view http://localhost:3000/api/screencasts/1.json and see the data belonging to a single screencast.
1 2 3 4 5 6 7 8 9 10 11 12
Testing the API
Of course we are going to test the API! It actually isn’t as complicated as it may seem. I am not going to go over much explanation beyond the inline comments.
Create a new integration test at test/integration/api_screencasts_test.rb.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
Go ahead and run your tests:
$ rake test ... 5 tests, 18 assertions, 0 failures, 0 errors, 0 skips
Looks like our test are passing.
Let’s stop for now. Our next steps will be getting our hands dirty with AngularJS.