Created by Charles Jewell
This is the backend for my fourth WDI project. The app I built was Blend Life which was a platform for people to create and discuss smoothie recipes.
If you would like to look at the Frontend the repo is here, the ReadMe for the frontend is here, and the live app is here.
Create a full stack application with a Ruby on Rails back-end and an AngularJS front-end. It is required to:
- Connect your Rails back-end to an SQL database and interact with it
- Create at least two models in the SQL database, one being a user model
- Have user authentication where the user's details are stored in the User model in the database
- Create API routes with CRUD functionality using Rails that are to be consumed by the AngularJS front-end
- Ruby
- Ruby on Rails
- PostgreSQL
Gems:
- rack-cors
- active_model_serializers
- bcrypt
- jwt
- figaro (Used for storing secret variables)
- activerecord-import (Used for mass creation of records)
The first step when building the API was deciding on what models to use, and the relationships that they would have which each other. I knew I needed a User model and a Recipe model, I decided to also have a seperate model for each Ingredient, as well as a Comment model. From that point I had to decide on how to set up the various relationships in a logical way.
These are the relationships that I decided on, as they made the most sense and also gave room to add additional functionality on top of what would be the MVP. These would be the tables in my database, and I have also included what the columns would be.
Generating these tables on Rails is fairly straightforward. Creating the relationships is also fairly straightforward, for the most part. In order to create the many-to-many relationship between the Recipe and the Ingredient I was required to create a join table that would contain the IDs of recipes and ingredients.
create_table "ingredients_recipes", id: false, force: :cascade do |t|
t.bigint "recipe_id", null: false
t.bigint "ingredient_id", null: false
end
You can see that the line was added to the Recipe model (one was also added to the Ingredient model. You can also see the other relationships.
class Recipe < ApplicationRecord
belongs_to :user
has_many :comments
has_and_belongs_to_many :ingredients
validates :name, presence: true, uniqueness: true
end
The relationship affected the controllers. As the controllers were generated by Rails they were not initially adequate, and I had to change them. If I wanted a recipe to belong to the user who submitted it, then it had by created like this:
def create
@recipe = @current_user.recipes.create!(recipe_params)
if @recipe.save
render json: @recipe, status: :created, location: @recipe
else
render json: @recipe.errors, status: :unprocessable_entity
end
end
There is more evidence of this in the seeds file, which does a good job of illustrating how the relationships affect creating comments or recipes, for example.
The API uses a JSON Web Token system to allow registered and logged in users access the API as they've been granted a token that is stored in the client side and send back to the API when requests are made. A Token is required to access all of the APIs routes other than Post requests to /register and /login.
The Token stores encrypted information about te user which is used to identify them.
class ApplicationController < ActionController::API
before_action :authenticate_user!
def authenticate_user!
render json: { errors: ["Unauthorized"] }, status: 401 unless user_signed_in?
end
def user_signed_in?
!!current_user
end
def current_user
@current_user ||= User.find(decoded_token[:id]) if id_found?
rescue
nil
end
private
def id_found?
token && decoded_token && decoded_token[:id]
end
def decoded_token
@decoded_token ||= Auth.decode(token) if token
end
def token
@token ||= if request.headers['Authorization'].present?
request.headers['Authorization'].split.last
end
end
end
Starting from the bottom of this piece of code, we collect a Token from the header of the request that was made. Then the Token is decoded and we check to see if a user's ID was found. If an ID wa found, that user then becomes the current user. Then finally, we protect our routes by sending an "Unauthorized" 401 error unless there is a current user.
To see what the rest of this app looks like, have a look at the frontend that I built.
The repository is here.
The Frontend ReadMe is here.
And the live app is here.
Thank you!

