In this portion of the Rails4 and AngularJS tutorial we build the base of the rails backend. We’ll come back later and add Devise and CanCan to it, for now we’re just focusing on having an application that runs and serves json, at the end of this segment you should end up with a Rails server that is providing clubs.json and teams.json, ready to have the Angular frontend added.
If you’re interested in the rest of this tutorial, you can visit the index page, or you can hit the tutorials menu above and look at the rails 4 tutorial category. If you haven’t yet installed Rails 4 you may want to look at the previous page on installation, and if you’re fine with the Rails portion, you may want to move onto the next page that starts the AngularJS install. Finally, the code for the position at the end of this section of the tutorial can be found on github:PaulL:tutorial_1_and_2.
In your browser, the aim of this section of the tutorial is to get something like the screenshot below:
The base application has two entities, loosely based around a sports league. It has a set of clubs, with each club having multiple teams. In later sections of the tutorial we’ll set it up so that only some users can edit some clubs, but for now we’re completely ignoring the security.
If you didn’t already do so in the previous page, create a new rails application:
rails new league-tutorial-rails4
Run the installer to make sure everything is installed:
Initialise your git project if you want to use git (I won’t note where you should be committing along the way, but it’s good practice to do regular commits to allow you to get back to what you had before):
git init --shared=group
Start off by creating the club entity. A club is very simple – it has a name, a contact_officer, and a date_created:
rails generate scaffold club name:string contact_officer:string date_created:datetime
Migrate the database:
We next want to change our controller to:
- Explicitly reject html, and accept only json
- Drop all the html options in the responses
Edit the rails controller
app/controllers/clubs_controller.rb as follows:
class ClubsController < ApplicationController before_filter :intercept_html_requests layout false respond_to :json before_action :set_club, only: [:show, :edit, :update, :destroy] # GET /clubs # GET /clubs.json def index @clubs = Club.all render json: @clubs end # GET /clubs/1 # GET /clubs/1.json def show render json: @club end # POST /clubs # POST /clubs.json def create @club = Club.new(club_params) if @club.save render json: @club, status: :created else render json: @club.errors, status: :unprocessable_entity end end # PATCH/PUT /clubs/1 # PATCH/PUT /clubs/1.json def update if @club.update(club_params) head :no_content else render json: @club.errors, status: :unprocessable_entity end end # DELETE /clubs/1 # DELETE /clubs/1.json def destroy @club.destroy head :no_content end private # Use callbacks to share common setup or constraints between actions. def set_club @club = Club.find(params[:id]) end # Never trust parameters from the scary internet, only allow the white list through. def club_params params.require(:club).permit(:name, :contact_officer, :date_created) end # if someone asks for html, redirect them to the home page, we only serve json def intercept_html_requests redirect_to('/') if request.format == Mime::HTML end end
We then update the routes to match:
LeagueTutorialRails4::Application.routes.draw do resources :clubs, :except => [:new, :edit] end
You can delete the views directory as we won’t be using them. Add some clubs directly to the database so that we can do a bit of testing.
sqlite3 db/development.sqlite3 insert into clubs values (1, 'First club', 'A Person', '2012-01-01', '2012-02-02', '2012-03-03'); insert into clubs values (2, 'Second club', 'J Jones', '2012-01-01', '2012-02-02', '2012-03-03'); .quit
Run the application
and see what you get when you visit http://localhost:3000/clubs.json. You should get a json response that looks like the records you put in the database.
Similarly create a teams entity:
rails generate scaffold team club:references name:string captain:string date_created:datetime
Set it up to return .json as before:
class TeamsController < ApplicationController before_filter :intercept_html_requests layout false respond_to :json before_action :set_team, only: [:show, :edit, :update, :destroy] # GET /teams # GET /teams.json def index @teams = Team.all render json: @teams end # GET /teams/1 # GET /teams/1.json def show render json: @team end # POST /teams # POST /teams.json def create @team = Team.new(team_params) if @team.save render json: 'show', status: :created else render json: @team.errors, status: :unprocessable_entity end end # PATCH/PUT /teams/1 # PATCH/PUT /teams/1.json def update if @team.update(team_params) head :no_content else render json: @team.errors, status: :unprocessable_entity end end # DELETE /teams/1 # DELETE /teams/1.json def destroy @team.destroy head :no_content end private # Use callbacks to share common setup or constraints between actions. def set_team @team = Team.find(params[:id]) end # Never trust parameters from the scary internet, only allow the white list through. def team_params params.require(:team).permit(:club_id, :name, :captain, :date_created) end end
You’ll notice the intercept_html_requests method isn’t here, we don’t want to keep duplicating it so we’re moving it into application_controller.rb, so also take out of the clubs_controller.rb. The application_controller should look like:
class ApplicationController < ActionController::Base # Prevent CSRF attacks by raising an exception. # For APIs, you may want to use :null_session instead. protect_from_forgery with: :exception # if someone asks for html, redirect them to the home page, we only serve json def intercept_html_requests redirect_to('/') if request.format == Mime::HTML end end
Update the routes to add teams:
LeagueTutorialRails4::Application.routes.draw do resources :teams, :except => [:new, :edit] resources :clubs, :except => [:new, :edit] end
Migrate the database, and put some records in:
rake db:migrate sqlite3 db/development.sqlite3 insert into teams values (1, 1, 'First team', 'C.F.Captain', '2012-01-01', '2012-02-02', '2012-03-03'); insert into teams values (2, 1, 'Second team', 'A.N.Captain', '2012-01-01', '2012-02-02', '2012-03-03'); .quit
Check that you can see teams by visiting http://localhost:3000/teams.json.
You’ll notice that we didn’t make teams a nested route of clubs – we’re assuming that teams can either be inside a club, or can exist outside a club. So we’re going to leave the club_id as optional on the team, and create the relationship within our angular app later.
This post will be updated for CSRF and JSONP vulnerabilities once we have some AngularJS working against it and can verify whether that all works or not.