I made my second full stack project using React and Rails. It has been quite the journey. I implemented my knowledge of React, HTML, CSS, JavaScript for the frontend. For the backend, I utilized my new knowledge of Ruby on Rails. Today, we will focus on the backend.
During this journey, I learned about Rails fundamentals, CRUD with Rails, validations, error handling, Active Record associations, serialization, authorization, and authentication.
Whew. That's a lot. In this blog post, I'll walk you through each step I took to create my the backend of my full stack web app, Everything ELA. Everything ELA is a virtual space where students can read, comment, and post about English Language Arts (ELA) topics. It is a website for the students in my virtual school. Students are able to register, login, create posts, read posts, and log out. Comments are able to be created, read, updated, and deleted.
First, I made sure I had Ruby 2.7.4, NodeJS (v16), and npm set up. I cloned a project template repository and created a new remote repository on Github. I ran bundle install, rails db:create, and npm install.
At that point, I was all set up! This is where the fun started.
Models
I created my three models: Student, Comment, and Post and their tables.
create_table "comments", force: :cascade do |t|
t.string "body"
t.integer "post_id"
t.integer "student_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "posts", force: :cascade do |t|
t.string "title"
t.string "image"
t.string "body"
t.integer "student_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "students", force: :cascade do |t|
t.string "username"
t.string "password_digest"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
I knew I needed Active Record Associations so I planned that out.
class Comment < ApplicationRecord
belongs_to :student
belongs_to :post
end
class Post < ApplicationRecord
has_many :comments
has_many :students, through: :comments
end
class Student < ApplicationRecord
has_many :comments
has_many :posts, through: :comments
end
Controllers
I then created my controllers using the rails g command. I knew that I wanted full CRUD capabilities for the Comments, create and read capabilities for the Students and Posts, and I needed a SessionsController for login and logout capabilities. I set up these methods in my controllers after creating them.
class CommentsController < ApplicationController
def index
end
def create
end
def update
end
def destroy
end
end
class PostsController < ApplicationController
def index
end
def show
end
def create
end
end
class SessionsController < ApplicationController
def create
end
def destroy
end
end
class StudentsController < ApplicationController
def show
end
def create
end
end
Rails Routing
In the config/routes.rb file, I created my routes.
resources :comments
resources :posts, only: [:index, :show, :create]
post "/signup", to: "students#create"
get "/me", to: "students#show"
post "/login", to: "sessions#create"
delete "/logout", to: "sessions#destroy"
Validations
Now, onto validations. Validations are special method calls consist of code that performs the job of protecting the database from invalid data.
For example, I want to make sure that all comments are longer than 1 character, posts have a title, and students have unique usernames.
class Comment < ApplicationRecord
validates :body, length: { minimum: 1 }
belongs_to :student
belongs_to :post
end
class Post < ApplicationRecord
validates :title, presence: true
has_many :comments
has_many :students, through: :comments
end
class Student < ApplicationRecord
has_many :comments, dependent: :destroy
has_many :posts, through: :comments
validates :username, presence: true, uniqueness: true
end
Error Handling
I then updated the controller action to check the validity of our model when it is created, and respond appropriately:
class ApplicationController < ActionController::API
include ActionController::Cookies
rescue_from ActiveRecord::RecordInvalid, with: :render_unprocessable_entity_response
rescue_from ActiveRecord::RecordNotFound, with: :render_not_found_response
def render_unprocessable_entity_response(e)
render json: {errors: e.record.errors.full_messages}, status: :unprocessable_entity
end
def render_not_found_response(invalid)
render json: { errors: invalid }, status: :not_found
end
end
Serialization
Serialization is the process of translating data structures or objects into a format that can be stored or transmitted and reconstructed later. We need to send a standardized response to the API consumers to make sense of the transferred data. We need Active Model Serializers to structure the data we send back to the client appropriately. (In this case, JSON.)
I created serializers using the rails g command in the terminal. I then added the desired attributes and the necessary Active Record associations.
class CommentSerializer < ActiveModel::Serializer
attributes :id, :body
has_one :student
end
class PostSerializer < ActiveModel::Serializer
attributes :id, :title, :image, :body, :student_id
end
class PostWithCommentsSerializer < ActiveModel::Serializer
attributes :id, :title, :body
has_many :comments
end
class StudentSerializer < ActiveModel::Serializer
attributes :id, :username
end
Authentication
Authentication is how our application can confirm that our users are who they say they are. Simply put, we use usernames and passwords to verify our users. (In our case, students.)
class SessionsController < ApplicationController
skip_before_action :authorize, only: :create
def create
student = Student.find_by(username: params[:username])
if student&.authenticate(params[:password])
session[:student_id] = student.id
render json: student, status: :created
else
render json: {errors: ["Invalid username or password"] }, status: :unauthorized
end
end
def destroy
session.delete :student_id
head :no_content
end
end
Authorization
Authorization gives certain users permission to access specific resources. To authorize a student for specific actions, we can use the :student_id saved in the session hash. We can use a before_action filter to run some code that will check the :students_id in the session and only authorize students to run those actions if they are logged in.
And that's it! I used Rails fundamentals, CRUD with Rails, validations, error handling, Active Record associations, serialization, authorization, and authentication to build the backend of my web app.
Top comments (0)