Mercury: My First Full Stack Project
Tags: ruby, react, webdev, flatiron
Published: 2021-10-07 18:03:51

Last week marked the end of the third phase of Flatiron: Ruby. I was very excited about this phase because it transitioned our learning to backend development. I have experience in front-end development, but back-end development seemed so complex to learn on my own. I was also interested in “putting it all together”. In other words, understanding how a front-end communicates with the back-end, in order to give a user a full web experience.

##Architectures I Used For My Back-End
###MVC (Model-View-Controller)
Since my back-end is built with Ruby and ActiveRecord, I used MVC (Model View Controller) to build my server. This means a website sends a request to my server, the controller interprets the request and then requests the corresponding information from the models. The models then process and gather the information and send it back to the controller, where it’s then returned to the front-end (view). 

###RESTful API
I also used RESTful architecture for my API. A Rest API (Representational State Transfer Application Programming Interface) follows a set of rules that help a server and client communicate with each other. It breaks down requests (URLs) and responses (data, in our case JSON)  into small actions that can be executed separately from one another. The actions consist of CRUD (Read/Get, Create/Post, Delete, Update) requests. 


##What I Used In My Project
- React framework for my front-end
- Ruby for my back-end
- ActiveRecord to handle my models and communication with my database
- Rack to create my server


##Project Overview
I created a project task management app named Mercury (the god of commerce who was the mediator between gods and mortals. The middleman between people, goods, and messages). This app allows you to organize projects into boards and arrange tasks within each board. .

###Mercury Models
####Project
- Has many boards
- Has many tasks through boards

####Boards
- Belongs to a project
- Has many tasks

####Tasks
- Belongs to a board
- Has one project through a board

```rb
Project -------- < Board -------- < Task
:title             :name            :name  
:color             :project_id      :due_date
:favorite                           :description
                                    :status
                                    :priority
                                    :completed
                                    :board_id
```

###Project Abilities
You can make all CRUD calls for Projects
- CREATE a project
- GET/READ all projects
- GET/READ an individual project
- DELETE a project
- UPDATE a project

####The Problem I Ran Into With Projects
When loading the Project Overview page, I only ended up needing the Project information and the tasks for each project to show the project progress bar. I didn’t need to show all boards associated with the project, so I didn’t. However, when you click on a project you do need the boards associated with the project to show all information. So I have to make another request to the server to get all the information needed. In hindsight, I could have sent board information on the first call to my server and then passed that individual project information down into a prop, saving me a call to the back-end.

Rack Implementation
```rb
class Application
 
 def call(env)
   resp = Rack::Response.new
   req = Rack::Request.new(env)
 
# projects get/read
 if req.path.match(/projects/) && req.get? #controller interprets the request given from the front-end
 
   #check if requesting all projects or an individual project
   if req.path.split("/projects/").length === 1
     # retrieve information from model and send back information to the front-end
     return [200, { 'Content-Type' => 'application/json' }, [ {:message => "projects successfully requested", :projects => Project.all}.to_json(:include => :tasks) ]]
   else
     project = Project.find_by_path(req.path, "/projects/")
     return [200, { 'Content-Type' => 'application/json' }, [ {:message => "project successfully requested", :project => project}.to_json(:include => { :boards => {:include => :tasks}}) ]]
   end #check if all projects or specific project
  
 end #end projects get request
    
   resp.finish
 end
 
end

```

Find_by_path was a custom method I added to my models. I wanted to move unnecessary code from my controller and into my models to keep the separation of MVC. The models are supposed to handle and parse the request. All of my models ended up needing this method so I moved it into a module and imported it into each model to DRY up my code.

```rb
module InheritMethods
   module ClassMethods
       def find_by_path(path, URL)
           id = path.split(URL).last.to_i
           find_by_id(id) #implicit self
       end
   end
end
 
require_relative './models_module'
 
class Project < ActiveRecord::Base
   extend InheritMethods::ClassMethods #extend is for class methods
   has_many :boards, dependent: :destroy
   has_many :tasks, through: :boards
end

```

####Projects On The Front-End
When calling all projects I only wanted all tasks to show since I don’t need board information on my overview page. Task information is used to show project completion percentage.
![See projects on load](https://media3.giphy.com/media/dHhoXFZuyE4yHjB7G0/giphy.gif?rid=giphy.gif&ct=g)

When you click on an individual task I then make another call to the backend for the specific project to get all of the projects boards and tasks. 
![See individual project](https://media3.giphy.com/media/3aThdMEiAUdJwu0sTQ/giphy.gif?rid=giphy.gif&ct=g)

###Board Abilities
You can make all CRUD calls for Boards
- CREATE a board
- GET/READ all boards
- DELETE a board
- UPDATE a board

####The Problem I Ran Into With Boards
At first, when I was creating a new board, I was making a second call to fetch projects after the board was successfully added to my back-end. I wasn’t sending the new instance back to my front-end—just a successful message. I realized I could spare an unnecessary call if I just sent back the new instance after it was successfully posted.

Back-End Implementation
```rb
class Application
 
 def call(env)
   resp = Rack::Response.new
   req = Rack::Request.new(env)
 
# boards post/create
   elsif req.path.match(/boards/) && req.post?
     # parse JSON into a readable format for my back-end
     hash = JSON.parse(req.body.read)
     # check if the project ID passed in exists
     project = Project.find_by_id(hash["project_id"])
 
     # if project id was valid move on to creating the new board
     if project
       board = Board.new(name: hash["name"], project_id: hash["project_id"])
       if board.save
         return [200, { 'Content-Type' => 'application/json' }, [ {:message => "board successfully created", :board => board}.to_json ]] # send board back to front-end
       else
         return [422, { 'Content-Type' => 'application/json' }, [ {:error => "board not added. Invalid Data"}.to_json ]]
       end #end validation of post
     else
       return [422, { 'Content-Type' => 'application/json' }, [ {:error => "board not added. Invalid Project Id."}.to_json ]]
     end #if: check if project exists
 
end #end boards post request
    
   resp.finish
 end
 
end

```

Front-End Implementation
```jsx
const handleCreateBoard = (newBoard) => {
   fetch('http://localhost:9393/boards/', {
     method: 'POST',
     headers: {
       'Content-Type': 'application/json',
       accept: 'application/json',
     },
     body: JSON.stringify({
       name: newBoard.name,
       project_id: projectId,
     }),
   })
     .then((res) => res.json())
     .then((data) => {
       if (boards.length === 0) {
         setBoards([data.board])
       } else {
         setBoards((prevBoards) => {
           return [...prevBoards, data.board]
         })
       }
     })
 }

```

#### Creating a Board On The Front-End
![create a board](https://media3.giphy.com/media/pHkd9vWbm6qGMtI6C0/giphy.gif?rid=giphy.gif&ct=g)


###Task Abilities
You can make all CRUD calls for Tasks
- CREATE a task
- GET/READ all tasks
- DELETE a task
- UPDATE a task

####The Problem I Ran Into With Tasks
Tasks had the most information stored in them (name, due date, description, status, priority, completed, board id) and I wanted to make sure that all the information was easily implemented when creating a new task. 

I could use a lot of validation on the front-end to make sure the user was inputting in the required information, but it seemed less efficient. Instead, I decided it should be the responsibility of the back-end. 

Back-End Implementation
```rb
require_relative './models_module'
 
class Task < ActiveRecord::Base
   extend InheritMethods::ClassMethods #extend is for class methods
 
   belongs_to :board
   has_one :project, through: :board
 
   def self.create_new_task_with_defaults(hash)
       name = hash["name"] ? hash["name"] : "New Task"
       status = hash["status"] ? hash["status"] : "Not Started"
       priority = hash["priority"] ? hash["priority"] : "Low"
       completed = hash["completed"] ? hash["completed"] : false
       self.new(
           name: name,
           due_date: hash["due_date"],
           description: hash["description"],
           status: status,
           priority: priority,
           completed: completed,
           board_id: hash["board_id"]
       )
   end
end
```

```rb
class Application
 
 def call(env)
   resp = Rack::Response.new
   req = Rack::Request.new(env)
 
   # tasks post/create
   elsif req.path.match(/tasks/) && req.post?
     hash = JSON.parse(req.body.read)
     board = Board.find_by_id(hash["board_id"])
 
     if board
       task = Task.create_new_task_with_defaults(hash) #custom method
 
       if task.save
         return [200, { 'Content-Type' => 'application/json' }, [ {:message => "task successfully created", :task => task}.to_json ]]
       else
         return [422, { 'Content-Type' => 'application/json' }, [ {:error => "task not added. Invalid Data"}.to_json ]]
       end #end validation of post
     else
       return [422, { 'Content-Type' => 'application/json' }, [ {:error => "task not added. Invalid Board Id."}.to_json ]]
     end #if: check if board  exists
 
end #end task post request
    
   resp.finish
 end
 
end

```

####Creating a Task On The Front-End
![create a task](https://media3.giphy.com/media/7dnQnBZVxizhqhiCqA/giphy.gif?rid=giphy.gif&ct=g)


##Final Thoughts
This has been my favorite project so far because it helped me understand how the front-end and back-end communicate with each other. It was also my first ever back-end project and it wasn’t as scary as I thought it would be. It was the unknown that seemed to be the problem rather than the material itself. 

I would like to add a sign-in/register form to create users. This would allow users to be added to projects and a project to have my users. I would need to create a joiner table that belongs to a project and belongs to a user, making my models a little more complicated but users are a very real part of websites so I would like to incorporate it into Mercury. 

Thank you for going with me on this journey! The next stop is phase 4: Ruby on Rails so stay tuned.