DEV Community

Cover image for Sinatra   x   Street Fighter
fentybit
fentybit

Posted on • Edited on

Sinatra x Street Fighter

The time has come for my Module 2 Sinatra Project. Dun dun dun!

While my previous case study on Lakers < ActiveRecord::Base solely focused on the Model from MVC (Model-View-Controller), this Sinatra capstone project will encapsulate the whole MVC paradigm. One of the major concepts in MVC is to assign separation of concerns in Models, Views and Controllers. Models encapsulate the logic behind our application. Views are the user's interface, and implementation may utilize various platform from HTML, CSS, forms to ERB (Embedded Ruby). Controllers are the mediator in between our Models and Views. Each entity has its own single responsibility.

What is Sinatra?

Alt Text

Sinatra is a Domain Specific Language, or DSL.

Sinatra is a light-weight alternative to Ruby on Rails application framework, and of course, named after the infamous musician Frank Sinatra. It allows the creation of a simple app development in Ruby with minimal effort. However, understanding Sinatra is a great catalyst to Rails' massive framework application.

Ideation

Flatiron School curriculum requires our cohort to choose a domain we care about and are familiar with. I was a gamer back in the days. I remember religiously playing PS1 on mostly RPGs (FF series, Chrono Cross, Parasite Eve, Xenogears, Suikoden.. I can go on.. and on.. and on...) as well as fighting games. I was glad we were then moving on to PS2 and XBox One. Eventually, technology allows us to exercise social distancing from our monitor screen and fully decommissioned this old-school wired controller. Ahhh.. so nostalgic!

Alt Text

After a few days contemplating a domain that I would love to explore more in depth, I have decided to adopt Street Fighter as my domain modeling.

Domain and MVP

In my Street Fighter domain modeling universe, the Minimum Viable Product (MVP) I was aiming for this capstone project would be to allow a user to select a character and perform character's fighting moves on multiple stage platforms.

The extended function I am aiming for would be to save a user's session when signing up or logging in, and user's ability to record multiple selections of characters with various fighting moves and stage platforms. This is my preliminary Entity Relationship Diagram (ERD), and overall model relationships.

Alt Text

user has_many :characters

character belongs_to :user
character has_many :moves
character has_many :stages, through: :moves

stage has_and_belongs_to_many :moves
stage has_many :characters, through: :moves

move belongs_to :character
move has_and_belongs_to_many :stages

Back End::ORMs and ActiveRecord Migration Unit

Major thanks to Ruby gem Corneal in scaffolding basic initial setup. It automatically populates my app folder with models, controllers and views. Upon instantiation with corneal new APP-NAME in the terminal and bundle install, I have my basic Sinatra file structure setup.

The Object Relational Mapping (ORM) concept in vanilla ActiveRecord model classes and associations are defined as follows:

class User < ActiveRecord::Base
    has_secure_password

    validates_presence_of :username, :email, :password 
    validates_uniqueness_of :username, :email

    has_many :characters
end
Enter fullscreen mode Exit fullscreen mode
class Character < ActiveRecord::Base 
    belongs_to :user 
    has_many :moves 
    has_many :stages, through: :moves 

    def slug 
        self.name.downcase.split.join("-")
    end 

    def self.find_by_slug(slug)
        self.all.find {|character| character.slug == slug}
    end 
end
Enter fullscreen mode Exit fullscreen mode
class Stage < ActiveRecord::Base 
        validates_uniqueness_of :name

    has_and_belongs_to_many :moves
    has_many :characters, through: :moves  
end
Enter fullscreen mode Exit fullscreen mode
class Move < ActiveRecord::Base 
    validates_uniqueness_of :name

    belongs_to :character 
    has_and_belongs_to_many :stages
end
Enter fullscreen mode Exit fullscreen mode

The ActiveRecord gem allows us to create a mapping between our model and database. Each migration is recorded by ActiveRecord in the database, eliminating manual execution and any duplication errors. Classes inheriting from ActiveRecord::Base carry meta-programming methods to associate the models and their respective database tables. The AR macros of has_many, belongs_to and has_and_belongs_to_many further define model associations.

When architecting a new application, it is a good practice to start bottom up; starting from setting up the database. Once rake db:migrate triggered, schema.rb file will be auto-generated and it displays the current state of database migration.

ActiveRecord::Schema.define(version: 2020_10_31_224114) do

  create_table "users", force: :cascade do |t|
    t.string "username", default: "username"
    t.string "email", default: "email"
    t.string "password_digest", default: "password"
  end

  create_table "characters", force: :cascade do |t|
    t.string "name"
    t.string "quote"
    t.text "bio"
    t.string "image"
    t.string "video"
    t.integer "user_id"
  end

  create_table "moves", force: :cascade do |t|
    t.string "name"
    t.integer "character_id"
  end

  create_table "moves_stages", force: :cascade do |t|
    t.integer "stage_id"
    t.integer "move_id"
  end

  create_table "stages", force: :cascade do |t|
    t.string "name"
    t.string "image"
  end

end
Enter fullscreen mode Exit fullscreen mode

The user_id and character_id are defined as foreign keys in relational database structure. It uses that primary key of another table in referring to a row of that table. user_id in "characters" table is equivalent to user.id in "users" table. character_id in "moves" table is equivalent to character.id in "characters" table.

Since a stage has_and_belongs_to_many fighting moves, and a move has_and_belongs_to_many stage platforms. The join table moves_stages fulfills the many-to-many relationships. It has two columns, stage_id and move_id. One for each of the table primary key reference.

Database Searching Saga

Alt Text

At first, I thought it would not be difficult in finding an API or HTML to parse out and sanitize the data in order to feed my rudimentary database seed.rb. Most of the gaming webpages are buggy and not consistent. I required a solid database for my CRUD study. After searching of little or no avail, I decided to hard-code my seed data from multiple sources. :silent-crying-mode

Front End::Controllers

Routes are part of the code application connecting HTTP to specific methods in my Controller Action. These specific URLs via HTTP mappings are defined as Routes. When the user makes a request, matching the route to the controller action, it triggers the code inside of the controller action block. As programmers, we should be able to define the specificity of our controllers, and navigate the corresponding users to our web application.

Thanks to Roy Fielding for Representational State Transfer (REST) routes. This pattern-oriented URLs allow conventions for developers as they work unanimously in complex settings. CRUD (Create, Read, Update and Delete) actions are semantically followed by corresponding HTTP verbs. My project RESTful routes are defined as follows:

Application Controller

Request Route CRUD action
GET '/' read

The ApplicationController is the mother of other ancillary controllers in handling all of the incoming requests. It has the application setups, routes and controller actions.

My web application requires UsersController at its inception in managing users database. The challenge became more apparent when developing associative controllers, and creating dynamic routes :. CharactersController, MovesController and StagesController utilize more of these dynamic routes in passing data from users' inputs to database, and vice versa.

Users Controller

Request Route CRUD action
GET '/signup' create
POST '/signup' create
GET '/login' read
POST '/login' read
GET '/index' read
GET '/edit' update
PATCH '/edit' update
GET '/delete' read
DELETE '/delete' delete
GET '/logout' read
POST '/logout' delete

Characters Controller

Request Route CRUD action
GET '/characters' create and read
POST '/characters' create
GET '/characters/:slug' read and update
DELETE '/characters/:slug' delete
GET '/characters/:slug/:id' create
GET '/characters/:slug/:id/battle' read

Stages Controller

Request Route CRUD action
GET '/:slug/stages/new' create
POST '/:slug/stages/new' create
GET '/:slug/stages/edit' update
POST '/:slug/stages/edit' update

Moves Controller

Request Route CRUD action
GET '/:slug/moves/new' create
POST '/:slug/moves/new' create
GET '/:slug/moves/edit' update and delete
POST '/:slug/moves/edit' update and delete

During the development of my controllers and views, Ruby gem shotgun and binding.pry became my best friends. rackup only allows one-time read of application code, while shotgun allows application code reload upon every request. It is prudent as programmers to check consistencies when building controllers and sending appropriate data to views simultaneously. I also learned the hard way not to forget use Rack::MethodOverride as delete and patch requires Sinatra Middleware to find requests with name="_method".

Manipulating params in the controllers from users' inputs, and transversing instance variables from controllers to views are ways in which both controllers and views communicate, passing data back and forth.

As my controllers overloaded with more HTTP routes, code repetitions became un-avoidable. Helper methods have been very useful. Aside from logged_in? and current_user, I added finding_character_slug in finding instance variable @character.

def finding_character_slug
    @character = current_user.characters.find_by_slug(params[:slug])
end
Enter fullscreen mode Exit fullscreen mode

Front End::Views and Forms

ERB (or, Embedded Ruby) provides application for both HTML and Ruby code syntax. It is a great alternative to .html format where its lexical environment is restricted to HTML tags. ERB has two types: <%= %> substitution tag and <% %> scripting tag. <%= %> displays the results while <% %> only evaluates.

Defining form methods, actions and retrieving inputs is essential for users in passing data. These form fields have various data type inputs. Any user input data passed from the URL forms to the controllers is in Ruby hash data types, or params. As developers we need to provide directions on where and how to send the data from the user. After all, creating dynamic web application allows user interaction, it gives more value than simply having static pages.

When creating these views, my biggest challenge was to create a flow for the user. As the user navigates through the site and populates user-driven attributes, I had to break a couple of RESTful conventions.

Authentication, Authorization and Validations

Alt Text

Open-source gem bcrypt allows encrypted password in hashing algorithm. This is to avoid saving a password in its raw form - developer's nightmare! The User model needs to include has_secure_password in order to allow AR macro method authenticate when a user logs in.

HTTP (Hyper-Text Transfer Protocol) is a stateless protocol, meaning user's requests are treated as independent transactions. I need my web app to remember the user's identity from page to page. Welcome to Session Cookies! Session is an object (a hash datatype) storing user's data on the server when interacting with the web application, and passes the data to the client as a cookie. It persists user's information while the user navigates through pages. It typically expires when the user logs out or closes the browser. By checking concurrence with the user's session hash, we authorize the corresponding user to access the app.

I have only used two validations for this project. validates_presence_of is to confirm user's input (no empty data, or != "") on username, email, and password. Otherwise the system will not allow the user object to be saved in the database. validates_uniqueness_of is to confirm no duplication on username or email. They are both useful in managing users. Along with these validations, I included gem rack-flash3 flash messages to display validation failures.

Execution

When either shotgun or rackup is triggered, config.ru will be executed. My config.ru requires my config/environment where I have established sqlite3 database connection and controllers. Short video of back-end demo below.

Difficult Lessons Learned

At the inception of this project, I had both User and Character model relationships as has_and_belongs_to_many. The idea was to have every single user the ability to access all characters, and each character belongs to many users. As I progressed further, I was faced with a challenge of my character attributes. Let's assume User A selects attributes of fighting moves and stages for a specific character. When User B selects the same character, it inherits pre-determined attributes. A brand new user would want a fresh set of characters with no pre-existing attributes.

My amazing cohort leader, Ally Kadel, helped me brainstorm a possible solution. Eventually, I revised the User and Character model relationships to has_many and belongs_to. User has_many characters, and each character belongs_to a user. In the CharactersController, I duplicated the user's selected character using .dup method, leaving the original character selections un-touched. User A will have duplicated sets of the original characters, and this allows User B to have another set of duplicate characters. The solution was rather simplistic, but I was stuck for the longest time - multiple brain farts do exist!

CSS the Fun Way

I got a chance to contribute a tiny bit of my design skills. I am an architect after all. Though, the project does not require any CSS requirements, but I figured why not. It's fun, and was my first CSS exposure. I decided to keep things simple and outsource from Bootswatch. I have learned div, class, style, text-align, display along with few other css attributes. I found using css selectors on web browser's inspect proved to be very useful in experimenting display results. I did not get a chance to do a deep dive into CSS documentation, but surely I will be more exposed in future modules. It gets complicated to apply consistent style and layout across all pages. Short video of front-end demo below.

Conclusion

Overall, I am satisfied with my rudimentary application of Content Management System (CMS) with Sinatra CRUD and MVC. I would love to eventually learn how to build integration testings RSpec (another Domain Specific Language) with Capybara Ruby Library when time permits. Sinatra is another milestone in my developer journey. Moving on to Rails!

"Don't fight for victory—fight to improve yourself. Victory will come."
-Ryu, Super Street Fighter II Turbo HD Remix

GitHub logo fentybit / SinatraStreetFighter

Welcome to my simplistic version of Street Fighter simulation. You can select a character, add fighting moves and a final stage where your epic battle commences!


Post Scriptum:
This is my Module 2 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:
Street Fighter V Wiki
Ruby Gem Corneal
Free themes for Bootstrap
Active Record Associations


[**fentybit**](https://fentybit.me/) | [GitHub](https://github.com/fentybit) | [Twitter](https://twitter.com/fentybit) | [LinkedIn](https://www.linkedin.com/in/fentybit/)

Top comments (1)

Collapse
 
almokhtar profile image
almokhtar bekkour

just wow, thanks for sharing