DEV Community

loading...
Cover image for Know It All

Know It All

fentybit profile image fentybit ・Updated on ・10 min read

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

  1. Planning and Building Rails API
  2. Open Trivia DB API
  3. Generating Active Record Models
  4. Routes, Controllers and Serializers
  5. Communicating with the Server

Front-End Web Programming

  1. DOM Manipulation with JavaScript Event Listeners
  2. Re-factoring Early
  3. End Page Sequence
  4. 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
Enter fullscreen mode Exit fullscreen mode

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.

Alt Text

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"
Enter fullscreen mode Exit fullscreen mode

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.

Alt Text

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

Enter fullscreen mode Exit fullscreen mode

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) =========================
Enter fullscreen mode Exit fullscreen mode

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.

Alt Text

class User < ApplicationRecord
end

class Category < ApplicationRecord
    has_many :questions, dependent: :destroy
end

class Question < ApplicationRecord
    belongs_to :category
end
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode

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.

Alt Text

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!

GitHub logo 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.

Front-End GitHub Repo

YouTube Demo

DEV Blog

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
Enter fullscreen mode Exit fullscreen mode

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!
Know It All

Build Status and Future Improvement

Current project-built was completed in…


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!Β πŸ˜…

Alt Text

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 classes 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.

Alt Text

During this re-factoring exercise, I also removed all of my vars, 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.

Alt Text

4. Lessons Learned

After months of GitHubing, 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.

GitHub logo 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.

Back-End GitHub Repo

YouTube Demo

DEV Blog

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
Enter fullscreen mode Exit fullscreen mode

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!
Know It All

Build Status and Future Improvement

Current project-built was completed in…


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 select Category and its Sub Category. For example, science category has many sub categories including physics, mathematics, biology and so on.

    Alt Text

  • 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



fentybit | GitHub | Twitter | LinkedIn

Discussion (0)

Forem Open with the Forem app