Walkthrough of Inventory Tracker
See the working solution here:
If you already have
inventory_tracker on your computer, move into that app's directory and run:
$ git fetch $ git checkout rails-starter
Otherwise, clone down this repo and checkout to the
Review the app for a minute to re-familiarize yourself.
This is an Angular app that has no data persistence: when you refresh the page, all your changes are gone.
Our goal is to put this app "on Rails".
Note: Each header corresponds to a relative commit in the
rails-solutionbranch of this repo.
To start things off, we need to create a new Rails app in the current directory:
$ rails new . -d postgresql
**Q**. Why are we doing `rails new .` ?
``` We want to create a new Rails app and have all those files go in the current directory ```
Move the Angular Files
Now that we have a Rails skeleton in our application, we are going to need to do some setup and configuration so that our Angular files are loaded and served from the right places. In order to do that, at at a high level we need to do 3 things:
- Use the
products"index" view to load our Angular app
- Turn the contents of the
data.jsfile into a JSON file, which we'll use for seed data.
- Move our static assets(
angular.js, etc.) into the appropriate locations
When it comes down to deciding where to put our static content (images/css/js), Rails has several options related to how we should organize our assets.
Each of the following directories contain an important role in the Rails Asset Pipeline:
app/assets: Code that is specific to certain models in your app: for example, the CSS and JS just for your
Productmodel would go in
product.js. Putting everything in
application.cssis technically a bad practice (but is just fine for small, simple apps).
vendor/assets: Third-party libraries, like jQuery, Angular, D3, Bootstrap, Foundation, and so on. "Vendor" means third-parties that provide you with code (not someone selling you stuff).
public/: Rarely used (directly). Where your assets go in production after being compiled. Anything in
publicis accessible by going to
localhost:3000/the_file.whatever. For example,
public/404.htmlis accessible at
localhost:3000/404.html(so when you want to customize your error pages, you do it here). HTML in
public/has access only to those assets that are also in
Then move these files to their appropriate locations
Rename the files as described. If a folder doesn't already exist, you'll need to create it.
Index View to Show Up
Great, now that you know a little bit more about the Rails Asset Pipeline, and our Angular assets are in the right place for now, let's work on building out the Rails view that will eventually serve our Angular app.
We need to:
- Create a new controller for
productsand define and
- Create routes for
After we do that, let's start our server and view the page in our browser:
$ rake db:create $ rails s
Think: What URL will you need to go to? You haven't defined a root URL!
Think: Why aren't assets working?
Load Angular assets on Products
Ok, we can see remnants of our Angular app, but it doesn't exactly look right and Angular is not loading...we'll fix that in this step.
However, we don't want the Inventory Tracker Angular stuff to be loaded on every page -- just the
Loading it on every page would be grossly inefficient since we want to use it only on one page.
We no longer want to do this.
First, tell Rails to not automatically put all the CSS and JS into one file
To do this, remove only the
require_tree line from:
Q. If you had to guess, what does
Note: the Rails asset pipeline uses a special comment syntax to load asset files. If you get funny behavior, make sure you haven't deleted any other comments with
Second, include the third-party libraries
**Q**. What external dependencies does our app need to run?
``` Angular, ng-resource, and Bootstrap ```
Using the same formats as in
application.css (they're each a little different because they're different languages), you can "require" any file that's in any of the "assets" folders, including
vendor/assets. This will cause it to be included in the precompilation.
products.js include Angular and ngResource, and create a
products.css that includes Bootstrap.
Third, tell Rails to precompile the Product CSS and JS files
Since we removed the
require_tree line from our
js files, we need to tell Rails to explictly precompile our custom static assets:
Read the comment at the end of
Fourth, include the Product JS and CSS files in your Product#index view
Hint in order to link to assets that will be precompiled, we need to use Rails' special link helpers. We can use
Also, make the HTML valid. Remember that everything in this file will be inserted into
layouts/application.html.erb (which we're not touching at all). You don't want a
<body> inside a
Note: Because these
<link>tags aren't in the
<head>, they won't validate. In order to make them validate you can add a
Fifth, restart your server
Rails won't pick up all these changes automatically.
Add model, migrations, and seeds
Great now that we have our assets loading correctly, we need to work on getting data to our Angular app. To do this, we are going to build out our internal API.
1. Create a Product migration
Note: think about which columns we need to add
2. Create a Product model
3. Convert the
db/products_data.json to valid JSON
Hint: You only need to change 11 characters.
4. Load the JSON in the seeds file
Drop, Create, Migrate, and Seed your database
You know it worked if you can go into
rails c and can view all
Products#index action render either JSON or HTML
Awesome, now that we have our database working and correctly populated, we need to work on having Rails serve that data.
To do this, we need to modify our products
index controller action to return the appropriate response depending on the format of the request.
Thankfully, Rails gives us the
respond_to helper method which can be used in a controller, and is a method that tells Rails to do one thing when a route is requested with
.json at the end of it, and another thing without
We're going to use the same route to show either the Inventory Tracker app, or the
JSON of all the Products, depending on the format of the request.
app/controllers/products_controller, modify the
index action like so:
Q. Where does value of the
format part of the
respond_to block come from?
You know it worked if you visit
http://localhost:3000/products.json in your browser and you see all your
Make Angular load the products from the database
So we now have the ability to serve data from our back-end, we need to focus on how we are going to get Angular to load in those
products from our database. How did we do it when we were connecting to a third-party api?
**Q**. What library can we use to allow us to easily consume a RESTful api in Angular?
The ng-resource library, and specifically the $resource module
Ok, all that to say, we need to update
products.js to use ngResource.
Q. What steps do we need to take in order to use
ngResource and its components?
We need to:
We need to inject
$resource as a dependency for our
InventoryController and then configure it to hit our api endpoint. The object that is returned as a result will serve as the interface for our back-end data fetching queries.
Great, now our Inventory Tracker should load real data!
Think: What changes are made to the database when we click the "X" (remove) button?
product.cost show up
Looks like all of our data is displaying...except for a product's
cost. Why is that?
This is because the data we received from Rails is
cost as a string. We'll need to convert it to a float.
- Loop through each product returned from the
- Set each
product's cost to the parsed float of the cost
Next up on our to-do list is to make the User's actions on the front end update the data in our database in real-time. To do this, we are going to use our factory model to make the appropriate AJAX requests for each relevant CRUD action.
First, let Angular destroy a Product
- Use your factory to remove the correct Product
- After the product has been removed, remove the product from
Think: Why doesn't it work?
Second, provide a controller method for destroying a Product
- Define a controller action for Destroy
- Find the product and destroy it
- Render as a JSON a hash
Think: Why doesn't it work?
Third, tell the Application Controller to allow CRUD via AJAX
Note: we will not be doing this often, where we comment out the
application_controller.rb. This can lead to security concerns in the long-run, since we are opening up our app to AJAX requests from anyone. But in the interest of time and since we are still in development, we are going to take the easy way out.
If you are interested in another more elegant work around, there is a way to turn the protection off depending on the request type / origin, etc. More info can be found here
Add Create method
Continuing on to build out our app's Create functionality, we need to modify our Angular
create method to make the appropriate call to our DB, and on the Rails side, we need to setup the back-end endpoint to handle the request.
First, enable it in Angular
- Use the factory to save our new product
- Convert the returned response's cost to a float
- Add the response to
Second, enable it in the Controller
We can still use strong params!
- Define a Create method in the products controller
- Define and use strong params to create a product
- Render that product as json
Add Update method
Angular doesn't "know" when we want to save our changes to the database, and it doesn't know how.
There is this thing called
$scope.$watchthat you may see referenced that automates this process, but generally it's advised against.
First, enable it in Angular
We'll create a method that sends a PUT request to the database.
Second, enable it in the view
We can tell Angular when to fire the update method using
Think: Oh no! The screenshot is cut off!
Guess you'll have to figure out for yourself what goes in there.
Third, enable it in the controller
First, add 12Factor to the Gemfile
Don't forget to
bundle install afterward and restart your server.
Second, push to Heroku
$ heroku create $ git push heroku master $ heroku rake db:migrate $ heroku rake db:seed $ heroku open
You get an error! Use
heroku logs -t to find the cause.
Second, set your secret key
You may have noticed a
secrets.yml file before in your other Rails apps. It doesn't exist here -- it's in
secrets.yml contains a secret code that is used to generate authenticity tokens for your app -- basically, to make sure it's you who's running your app. Rails won't deploy without that code, but it doesn't necessarily need to be in
Good practice is to NOT push this secret code to Github nor anything else, just as it's good practice to not push API keys.
We haven't worried about this so far because it's really only important when you have lots of users and/or are storing sensitive data.
Add the following line to the
This tells Rails to look for the secret key in the environment variables.
Then, you'll generate a new secret key using Rails' built in
rake secret, and tell Heroku to use that as an environment variable.
$ rake secret # Copy the output $ heroku config:set SECRET_KEY_BASE=whatyoucopied
Now try again to view it in your browser.