Happy Spring, fellow developers!
Since my recent Ruby on Rails project on Plan My MD Visit, I have immersed myself in JavaScript Fundamentals from recognizing JavaScript events, DOM manipulation, ES6 Syntax Sugar and introduction of Object-Orientation. I plan to supplement my JavaScript learning materials after this project submission.
I went through a few iterations in my head on Single-Page Application (SPA) prior to settling on an idea. Overthinking as usual, but in my defense it is one HTML
file, and lends itself too much freedom. π
Moving on, my husband loves trivia, and nothing is better than to surprise him by creating my own version of a trivia app, Know It All. The challenge becomes finding a completely free JSON API for use. This capstone project focuses on creating Ruby on Rails back-end and JavaScript/HTML/CSS front-end.
Back-End Development
- Planning and Building Rails API
- Open Trivia DB API
- Generating Active Record Models
- Routes, Controllers and Serializers
- Communicating with the Server
Front-End Web Programming
- DOM Manipulation with JavaScript Event Listeners
- Re-factoring Early
- End Page Sequence
- Lessons Learned
Build Status and Future Improvement
Back-End Development
1. Planning and Building Rails API
With --api
, Rails removes a lot of default features and middleware, and our controllers by default inherit from ActionController::API
. This differs slightly from traditional Ruby on Rails application. In my previous RoR project, I had my controllers inheriting from ActionController::Base
with responsibilities in creating routes and rendering many _.html.erb
files.
rails new know_it_all_backend --database=postgresql --api
The above command will generate a Rails API using PostgreSQL database. The intent is to deploy my backend application eventually on Heroku, which does not support SQLite database. One more important thing to add is to bundle install
gem rack-cors
. This is useful for handling Cross-Origin Resource Sharing (CORS) configuration, allowing my front-end application to perform asynchronous requests.
I approached this project in a vertical manner, building out one model and/or feature at a time. This strategy streamlines any effort when dealing with complex relationships from back-end to front-end, and vice versa.
2. Open Trivia DB API
After traversing through the API universe, I got excited when finding an Open Trivia Database without the need for an API Key. Awesome sauce. π π»ββοΈ
The challenge is less on acquiring the JSON API, but setting up the Api
adapter class on Rails back-end. I utilized the .shuffle
Ruby method to randomize the provided multiple choice. In the JavaScript front-end, I should be able to set up if/else
conditionals when comparing the user's selected answer to the correct_answer
. I managed to JSON.parse
in irb
, and confirmed responses back from the open/free API.
> data["results"][0]
=> {"category"=>"Animals",
"type"=>"multiple",
"difficulty"=>"hard",
"question"=>"What was the name of the Ethiopian Wolf before they knew it was related to wolves?",
"correct_answer"=>"Simien Jackel",
"incorrect_answers"=>["Ethiopian Coyote",
"Amharic Fox", "Canis Simiensis"]}
> [data["results"][0]["correct_answer"], data["results"][0]["incorrect_answers"][0], data["results"][0]["incorrect_answers"][1], data["results"][0]["incorrect_answers"][2]].shuffle
=> ["Amharic Fox", "Canis Simiensis", "Simien Jackel", "Ethiopian Coyote"]
> multiple_choice = _
=> ["Amharic Fox", "Canis Simiensis", "Simien Jackel", "Ethiopian Coyote"]
> multiple_choice[0]
=> "Amharic Fox"
> multiple_choice[1]
=> "Canis Simiensis"
> multiple_choice[2]
=> "Simien Jackel"
> multiple_choice[3]
=> "Ethiopian Coyote"
There will be a total of eight (8) Trivia categories: Animals, Celebrities, Computer Science, Geography, History, Mathematics, Music and Sports. Once the Api
adapter class was fully set up, I initiated the creation of both Category
and Question
models in the seeds.rb
.
3. Generating Active Record Models
$ rails g model User name avatar animals_score:integer celebrities_score:integer computer_science_score:integer geography_score:integer history_score:integer mathematics_score:integer music_score:integer sports_score:integer
invoke active_record
create db/migrate/20210224154513_create_users.rb
create app/models/user.rb
$ rails g model Category name
invoke active_record
create db/migrate/20210224045712_create_categories.rb
create app/models/category.rb
$ rails g model Question category_id:integer question:text choice1 choice2 choice3 choice4 answer
invoke active_record
create db/migrate/20210227220035_create_questions.rb
create app/models/question.rb
In the terminal, I can now run rails db:create && rails db:migrate
. The rails db:create
is necessary for PostgreSQL database. At first, I had an erroneous terminal return, and had to update my PostgreSQL 13. Once re-installed and π running, the command should create the database and run migration swiftly.
$ rails db:create && rails db:migrate
Created database 'know_it_all_backend_development'
Created database 'know_it_all_backend_test'
== 20210224045712 CreateCategories: migrating =================================
-- create_table(:categories)
-> 0.0545s
== 20210224045712 CreateCategories: migrated (0.0547s) ========================
== 20210224154513 CreateUsers: migrating ======================================
-- create_table(:users)
-> 0.0575s
== 20210224154513 CreateUsers: migrated (0.0575s) =============================
== 20210227220035 CreateQuestions: migrating ==================================
-- create_table(:questions)
-> 0.0571s
== 20210227220035 CreateQuestions: migrated (0.0572s) =========================
The next step would be to test my models and associations. My association between Category
and Question
would be as simple as category has_many
questions, and a question belongs_to
a category.
class User < ApplicationRecord
end
class Category < ApplicationRecord
has_many :questions, dependent: :destroy
end
class Question < ApplicationRecord
belongs_to :category
end
The dependent: :destroy
would be helpful for .destroy_all
method in seeds.rb
file. This is useful when triggering the rails db:seed
command.
As an Active Record veteran, it is still a good practice to validate every single instance of association relationships. Note β presented model attributes resulted from extensive trial and error. I approached this project with one feature working simultaneously on the back-end and front-end, adding one model attribute at a time.
001 > animals = Category.create(name: "Animals")
(0.2ms) BEGIN
Category Create (4.8ms) INSERT INTO "categories" ("name", "created_at", "updated_at") VALUES ($1, $2, $3) RETURNING "id" [["name", "Animals"], ["created_at", "2021-02-28 18:30:29.016555"], ["updated_at", "2021-02-28 18:30:29.016555"]]
(40.4ms) COMMIT
=> #<Category id: 1, name: "Animals", created_at: "2021-02-28 18:30:29", updated_at: "2021-02-28 18:30:29">
002 > animals_trivia = animals.questions.create(JSON.parse(File.read("animals.json")))
(0.2ms) BEGIN
Category Create (4.8ms) INSERT INTO "categories" ("name", "created_at", "updated_at") VALUES ($1, $2, $3) RETURNING "id" [["name", "Animals"], ["created_at", "2021-02-28 18:30:29.016555"], ["updated_at", "2021-02-28 18:30:29.016555"]]
(40.4ms) COMMIT
=> #<Category id: 1, name: "Animals", created_at: "2021-02-28 18:30:29", updated_at: "2021-02-28 18:30:29">
(0.3ms) BEGIN
Question Create (4.8ms) INSERT INTO "questions" ("question", "choice1", "choice2", "choice3", "choice4", "answer", "category_id", "created_at", "updated_at") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING "id" [["question", "What was the name of the Ethiopian Wolf before they knew it was related to wolves?"], ["choice1", "Canis Simiensis"], ["choice2", "Simien Jackel"], ["choice3", "Ethiopian Coyote"], ["choice4", "Amharic Fox"], ["answer", "Simien Jackel"], ["category_id", 1], ["created_at", "2021-02-28 18:30:42.398662"], ["updated_at", "2021-02-28 18:30:42.398662"]]
(55.1ms) COMMIT
(0.2ms) BEGIN
...
003 > animals_trivia.all.count
=> 50
4. Routes, Controllers and Serializers
Routes
With the front-end application hosted on a specific domain, I would think it is prudent to namespace my back-end routes. It provides an indication that these back-end routes are associated with the API. For example, https://knowitall.com/api/v1/categories
. The api/v1
suggests my Rails API version 1. I might return and continue my effort on future build status (version 2, etc). In the config/routes.rb
, I provided the intended namespaced routes and confirmed with rails routes
command.
Rails.application.routes.draw do
namespace :api do
resources :users, only: [:index, :create, :show, :update]
end
namespace :api do
namespace :v1 do
resources :categories, only: [:index] do
resources :questions, only: [:index]
end
end
end
end
Controllers
rails g controller api/Users
, rails g controller api/v1/Questions
and rails g controller api/v1/Categories
create UsersController
, QuestionsController
and CategoriesController
. These namespaced routes and their respective controllers nomenclature help tremendously in setting up filenames hierarchy.
Note β make sure the PostgreSQL π is running while configuring routes and controllers.
class Api::UsersController < ApplicationController
def index
users = User.all
render json: UserSerializer.new(users)
end
def create
user = User.create(user_params)
if user.save
render json: UserSerializer.new(user), status: :accepted
else
render json: {errors: user.errors.full_messages}, status: :unprocessable_entity
end
end
def show
user = User.find_by(id: params[:id])
if user
render json: user
else
render json: { message: 'User not found.' }
end
end
def update
user = User.find_by(id: params[:id])
user.update(user_params)
if user.save
render json: user
else
render json: { message: 'User not saved.' }
end
end
private
def user_params
params.require(:user).permit(:name, :avatar, :animals_score, :celebrities_score, :computer_science_score, :geography_score, :history_score, :mathematics_score, :music_score, :sports_score)
end
end
I will only have the UsersController
displayed here, and briefly convey the render json
. My rails routes only strictly render JSON strings. This is useful when building JavaScript front-end on DOM manipulation and performing asynchronous requests. The user_params
on name
, avatar
and all category scores
will be included in the body of POST
and PATCH
requests when executing fetch
. status: :accepted
helps to inform the user of the success 202 HTML status when submitting user input forms on the front-end application. If it fails to save, status: :unprocessable_entity
notifies the client error 422 HTML status.
Serializers
gem 'fast_jsonapi'
is a JSON serializer for Rails APIs. It allows us to generate serializer classes. The goal of a serializer class is to keep controllers clear of excess logic, including arranging my JSON data to display certain object attributes. It does not hurt to practice serializer early on, even though the current state of my Minimum Viable Product (MVP) does not necessarily require one.
5. Communicating with the Server
In order to make sure the Rails server back-end API worked, I tested a few Asynchronous JavaScript and XML (AJAX) calls on my browser console. While I have been using a lot of fetch()
for this project, I have yet to challenge myself with async
/ await
function. I am glad my initial attempt of fetch()
in browser console made successful requests. Moving on to front-end!
fentybit / KnowItAll_backend
The MVP of Know It All app is to create a basic Trivia game on Single-Page Application (SPA). This project is built with Ruby on Rails back-end and JavaScript front-end.
Know It All :: Back-End
Domain Modeling :: Trivia Games
Welcome to my simplistic version of Online Trivia Games.
About
The Minimum Viable Product (MVP) of Know It All is to provide the User with few trivia Categories to select from.
Features
Models
User, Category
user
has_many
:categories
category
belongs_to
:user
Controller
ApplicationController
UsersController
CategoriesController
QuestionsController
API Database
Free to use, user-contributed trivia question database.
Installation
Back-End
$ git clone πΎ
$ bundle install
$ rails db:create && rails db:migrate
$ rails db:seed
$ rails s
Open Chrome browser, and redirect to 'http://localhost:3000' to start the Rails API.
Front-End
Open Chrome browser, and redirect to 'http://127.0.0.1:5500/index.html' to start the app.
Alternatively, it is fully deployed on Netlify!
Build Status
β¦Front-End Web Programming
I have to say this part is my most challenging part! I was struggling to gather all of my new knowledge of The Three Pillars of Web Programming: recognizing JavaScript events, Document Object Model (DOM) manipulation, and communicating with the server on a Single-Page Application (SPA). Separation of concerns, as a fundamental programming concept, is still applicable. HTML defines the structure of the website, JavaScript provides functionality and CSS defines the visual presentation.
1. DOM Manipulation with JavaScript Event Listeners
It took me a few days practicing on a set of hard-coded trivia questions and having my trivia cards update as the user progresses to the next question. Know It All includes a score tracker, questions quantity, progress bar, along with passing and/or failing User Interface (UI) alerts. Having Single-Page Application (SPA) required me to create an element with document.createElement('...')
multiple times, and using either .append()
or .appendChild()
often. Also, trying to incorporate Bootstrap CSS early resulted in a slow and unproductive debugging process. A part of me loves spending gazillion hours on CSS elements. Note to self β do not waste your time on CSS!Β π
One particular challenge I found was to gather user input fields and update their back-end values with asynchronous JavaScript PATCH
. Later I found that I got stuck on an erroneous fetch
url, and corrected my string template literals to ${this.url}/${currentUser.id}
. While I used a lot of standard
and static
methods in my OO JavaScript, I plan to explore both get
and set
methods.
2. Re-factoring Early
After spending sometime working on basic event handlings, my index.js
file piled up easily with 200+ lines of code. While I have spent the past month on JavaScript Functional Programming, Object-Oriented (OO) JavaScript offers better data control, easy to replicate (with constructor
method and new
syntax), and grants us the ability to write code that convey these relationships. I decided to build class
es and their execution contexts in separate files, api.js
, category.js
, user.js
and question.js
. Each class
has its own lexical scope of variables and functions, leaving index.js
with global variables and callback functions necessary to support index.html
.
During this re-factoring exercise, I also removed all of my var
s, and replaced them with either const
or let
. Variables declared with const
and let
are block-scoped.
3. End Page Sequence
Drum roll... π₯ We are now coming close to an end. After each set of trivia questions, users should be able to see their final score, and whether or not they beat their previous score. If they do, the new (or higher) score will be saved in the Rails API user database. There will be two options for the user to either Play Again
or return back to Home
page.
4. Lessons Learned
After months of GitHub
ing, I am getting really comfortable with working on a separate branch, and merge to master. The git command git co -b <branch_name>
becomes my go-to git
command.
Understanding JavaScript's syntax and semantics after months dwelling on Ruby has been fun. For example, in JavaScript, functions are treated as first-class data, and understanding some of the concepts of hoisting and scope chain. JavaScript engine works in compilation phase and execution phase. Since I used a lot of JavaScript event click
and submit
for this project build, I would love to explore other browser events. This YouTube tutorial helped me tremendously to better understand the weird parts of JavaScript.
fentybit / KnowItAll_frontend
The MVP of Know It All app is to create a basic Trivia game on Single-Page Application (SPA). This project is built with Ruby on Rails back-end and JavaScript front-end.
Know It All :: Front-End
Domain Modeling :: Trivia Games
Welcome to my simplistic version of Online Trivia Games.
About
The Minimum Viable Product (MVP) of Know It All is to provide the User with few trivia Categories to select from.
Features
Models
User, Category
user
has_many
:categories
category
belongs_to
:user
Controller
ApplicationController
UsersController
CategoriesController
QuestionsController
API Database
Free to use, user-contributed trivia question database.
Installation
Back-End
$ git clone πΎ
$ bundle install
$ rails db:create && rails db:migrate
$ rails db:seed
$ rails s
Open Chrome browser, and redirect to 'http://localhost:3000' to start the Rails API.
Front-End
Open Chrome browser, and redirect to 'http://127.0.0.1:5500/index.html' to start the app.
Alternatively, it is fully deployed on Netlify!
Build Status
β¦Build Status and Future Improvement
Know It All was completed in a 2-week timeframe from API data search, Ruby on Rails back-end, and JavaScript front-end User Interface. Future cycle of product development as follows:
-
Add
Sub Category
to model associations. User should be able to selectCategory
and itsSub Category
. For example, science category has many sub categories including physics, mathematics, biology and so on. Outsource APIs for the aforementioned
Sub Category
trivia questions.Gather user inputs on their favorite
Category
on future app improvement.Utilize
setInterval
and/or time limit of 15-20 seconds on each Trivia question.User authentication.
Create a toggle track for dark mode π
Post Scriptum:
This is my Module 3 capstone project with Flatiron School. I believe one of the catalyst to becoming a good programmer is to welcome constructive criticism. Feel free to drop a message. π
Keep Calm, and Code On.
External Sources:
Open Trivia Database
CSS Bootstrap
Unsplash
Top comments (0)