Outline
Now that we have the basic front-end for our Flapper News coded up with Angular, we're going to start work on our backend. Because Angular is able to handle the templating and rendering, our backend server will mainly serve as a communication layer with our database. Over the next few sections, we are going to focus on modeling our data needs and creating a REST API that our Angular app can use to interact with this data.
If you haven't already done so, make sure you have rails
, and sqlite3
installed on your system. At the time of writing, Rails is currently on version
4.2.7.
Generating a new Rails app
Rails by default ships with turbolinks and jquery. We won't be needing either of those packages since we're using Angular, so let's generate a new rails app without any javascript at all using the following command:
rails new flapper-news --skip-javascript
This will create a folder flapper-news
with our Rails application. If we go
into the folder and run rails s
you should be able to see the our Rails app
running at http://localhost:3000
.
The Anatomy of a Rails Project
Here's a quick overview of what each folder/file in our Rails project is:
app/
- This folder is where most of your application-specific code will go into.bin/
- This folder contains executable scripts for your rails project. These are the files that get executed when you runbundle
,rails
, orrake
from the command line.config/
- This is for your application's configuration files. Theenvironment
folder contains environment-specific (development, production, test) configuration, and all files in theinitializers
folder will be run when your application boots.db/
- This folder contains files for managing your database. Generated migrations will be put into themigrate
folder, and a master copy of your application's database schema is contained inschema.rb
, which is automatically updated when you run migrations.lib/
- This folder contains code that doesn't belong in theapp
orvendor
folder. Any.rake
files inlib/tasks
will be made available to therake
command.log/
- This is where you will find logs produced by your Rails project.public/
- This folder is where static files will go. It is recommended to use theapp/assets
folder to serve assets via the asset pipeline.test/
- This folder is for testing related files.tmp/
- This folder contains temporary files used by your Rails project.vendor/
- This folder is for third-party code. Any code included in the project that isn't managed by you (e.g. code from bower or locally bundled gems) should be put in this folder, and you should avoid editing vendored files..gitignore
- A manifest of files to be ignored by gitGemfile
- Contains the RubyGems required by this project.Gemfile.lock
- Contains the specific version of all gems and their dependencies used in this project in order to assure the same version is installed on all systems. This file gets updated automatically when you run thebundle
command.README.rdoc
- The README file for your project. Should include instructions on how to get your application up and running.Rakefile
- The Rakefile for running tasks for your project.config.ru
- The rackup file for your project. Rails is a Rack based framework, and uses this file to boot your web server.
Importing our Angular project
Since we created the rails project with the --skip-javascript
flag, you may
have noticed that our project doesn't have a javascripts
folder or an
application.js
(This file is supposed to contain all the javascript used by
our project). Let's go ahead and create the app/assets/javascripts
folder,
along with a file application.js
with //= require_tree .
to restore the
default behavior of the javascripts
folder. Then we can drop in our app.js
from the Angular app we created earlier into the javascripts
folder so that
it'll be included in application.js
app/assets/javascripts/application.js
//= require_tree .
to application.js
app.js
into app/assets/javascripts
. Feel free to rename app.js
to something else if it bothers you that there's an application.js
and app.js
in the same folder, but we'll keep referring to it as app.js
.
We'll then need to include the javascript tag in our application layout at
app/views/layouts/application.html.erb
.
<head>
section of our layout, add <%= javascript_include_tag 'application' %>
on the line after the stylesheet_link_tag
like so:
<head>
<title>FlapperNews</title>
<%= stylesheet_link_tag 'application', media: 'all' %>
<%= javascript_include_tag 'application' %>
<%= csrf_meta_tags %>
</head>
app/views/layouts/application.html.erb
, let's go ahead
and copy over the rest of our head and entire body section from our angular
project. Notice that we're replacing the yield statement, this is because we'll
be rendering the same layout for our angular app. The resulting layout file
should look like:
<!DOCTYPE html>
<html>
<head>
<title>FlapperNews</title>
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css" rel="stylesheet">
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.5.7/angular.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.2.10/angular-ui-router.js"></script>
<%= stylesheet_link_tag 'application', media: 'all' %>
<%= javascript_include_tag 'application' %>
<%= csrf_meta_tags %>
</head>
<body ng-app="flapperNews">
<div class="row">
<div class="col-md-6 col-md-offset-3">
<ui-view></ui-view>
</div>
</div>
<script type="text/ng-template" id="/home.html">
<div class="page-header">
<h1>Flapper News</h1>
</div>
<div ng-repeat="post in posts | orderBy:'-upvotes'">
<span class="glyphicon glyphicon-thumbs-up"
ng-click="incrementUpvotes(post)"></span>
{{post.upvotes}}
<span style="font-size:20px; margin-left:10px;">
<a ng-show="post.link" href="{{post.link}}">
{{post.title}}
</a>
<span ng-hide="post.link">
{{post.title}}
</span>
</span>
<span>
<a href="#/posts/{{$index}}">Comments</a>
</span>
</div>
<form ng-submit="addPost()"
style="margin-top:30px;">
<h3>Add a new post</h3>
<div class="form-group">
<input type="text"
class="form-control"
placeholder="Title"
ng-model="title"></input>
</div>
<div class="form-group">
<input type="text"
class="form-control"
placeholder="Link"
ng-model="link"></input>
</div>
<button type="submit" class="btn btn-primary">Post</button>
</form>
</script>
<script type="text/ng-template" id="/posts.html">
<div class="page-header">
<h3>
<a ng-show="post.link" href="{{post.link}}">
{{post.title}}
</a>
<span ng-hide="post.link">
{{post.title}}
</span>
</h3>
</div>
<div ng-repeat="comment in post.comments | orderBy:'-upvotes'">
<span class="glyphicon glyphicon-thumbs-up"
ng-click="incrementUpvotes(comment)"></span>
{{comment.upvotes}} - by {{comment.author}}
<span style="font-size:20px; margin-left:10px;">
{{comment.body}}
</span>
</div>
<form ng-submit="addComment()"
style="margin-top:30px;">
<h3>Add a new comment</h3>
<div class="form-group">
<input type="text"
class="form-control"
placeholder="Comment"
ng-model="body"></input>
</div>
<button type="submit" class="btn btn-primary">Post</button>
</form>
</script>
</body>
</html>
Now that our application layout is set up, we need to make sure Rails is
serving our AngularJS application instead of the default Rails landing page.
We can do this by creating an action in ApplicationController
which will
only render the layout, and then route requests to the root of our Rails app to
that action.
Generally you want to avoid adding actions to
ApplicationController
, as all controllers on our backend will inherit from
ApplicationController
. An alternative option would be to create another
controller with a single action instead.
angular
action to ApplicationController
that renders the layout:
def angular
render 'layouts/application'
end
end
root to: 'application#angular'