FlatCoin
Published: 2021-03-25 01:24:38

This App was develop as a Flatiron School Sinatra project. I developed this app to help users practice building a cryptocurrency portfolio and react to the markets in real time using fake money (10000 free FlatCoins), they can also compete against their friends or coworkers to earn their spot at the top of the leaderboards.
This app constantly gets coin prices via API. I'm reusing code from my previous project  [Crypto CLI](https://dev.to/drivera53/crypto-cli-12lg).

> **Note:** Before even sketching an app you should first research the right API, play around and see what data can you get from it. In my case I used [CoinGecko](https://www.coingecko.com/).

Sinatra Activerecord Setup: Make sure to add the three most important gems: activerecord, sinatra-activerecord and rake.
`Gemfile` 
```ruby
source 'http://rubygems.org'
git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }

gem 'sinatra'
gem 'activerecord', '~> 6.0.0', :require => 'active_record'
gem 'sinatra-activerecord', :require => 'sinatra/activerecord'
gem 'rake'
gem 'require_all'
gem 'sqlite3'
gem 'thin'
gem 'shotgun'
gem 'pry'
gem 'bcrypt'
gem "tux"
gem 'net-http'
gem 'open-uri'
gem 'json'
gem 'tty-prompt'

group :test do
  gem 'capybara'
  gem 'rack-test'
  gem 'database_cleaner'
end
```
Setting up the connection to our database:
`environment.rb`
```ruby
ENV['SINATRA_ENV'] ||= "development"

require 'net/http'
require 'open-uri'
require 'json'
require 'tty-prompt'
require 'bundler/setup'
Bundler.require(:default, ENV['SINATRA_ENV'])

ActiveRecord::Base.establish_connection(ENV['SINATRA_ENV'].to_sym)

require_all 'app'
```
`database.yml`
```ruby
default: &default
  adapter: 'sqlite3'
  database: 'db/development.sqlite'

development:
  <<: *default

test:
  <<: *default
  database: 'db/test.sqlite'
```
`config.ru`
```ruby
require './config/environment'

if ActiveRecord::Base.connection.migration_context.needs_migration?
  raise 'Migrations are pending. Run `rake db:migrate` to resolve the issue.'
end

use Rack::MethodOverride
run ApplicationController
use UsersController
use TradesController
```
Rake gives us the ability to quickly make files and set up automated tasks.
`Rakefile`
```ruby
ENV["SINATRA_ENV"] ||= "development"

require_relative './config/environment'
require 'sinatra/activerecord/rake'
```
I had to create two tables: one for `users` and another one for `trades`. After running a migration my schema looks like this:
`schema.rb`
```ruby
ActiveRecord::Schema.define(version: 2021_03_22_162004) do

  create_table "trades", force: :cascade do |t|
    t.text "coin_name"
    t.decimal "current_price"
    t.integer "quantity"
    t.integer "user_id"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
  end

  create_table "users", force: :cascade do |t|
    t.string "first_name"
    t.string "last_name"
    t.text "email"
    t.string "password_digest"
    t.decimal "balance"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
  end

end
``` 
Models:
My main difficulty was defining the right models, how can I track user's FlatCoin balance, trades and portfolio value? I knew, I need it to definitely save users and its trades in the database, but what about cryptocurrency value? Cryptocurrency value fluctuates every millisecond, so I doesn't make sense to save and keep on updating its price every single time in the database, instead I can just make an API call every time I need its current price.       
`api.rb` and `cryptocurrency.rb` are just copies of my previous project [Crypto CLI](https://dev.to/drivera53/crypto-cli-12lg).
`user.rb`
```ruby
class User < ActiveRecord::Base
  has_many :trades

  validates_uniqueness_of :email
  validates_presence_of :first_name, :last_name, :email
  has_secure_password

end
```
`trade.rb`
```ruby
class Trade < ActiveRecord::Base
    belongs_to :user 
end
```
Views: 
`layout.erb` generates the basic layout for the app, `index.erb` is the landing page. 
Users subfolder handles `balance.erb`, `login.erb`, `logout.erb` and `signup.erb`.
Trades subfolder handles `all.erb`, `confirmation.erb`, `edit.erb`, `leaderboard.erb`, `new.erb`, `pick.erb`, `rejected.erb` and `sell.erb`.
You may check the code on [GitHub](https://github.com/drivera53/flatcoin)!  
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vs2805kby3xseg88md0f.png)
Controllers:
Building up controllers was a lot of fun, once you define your `helpers` in `application_controller.rb` you can use `logged_in?` to work the logic in you app. The good thing about working in this project was that I can use `tux` to test my database and by running `shotgun` I can test my app in real time.  
`application_controller.rb`
```ruby
require './config/environment'

class ApplicationController < Sinatra::Base

  configure do
    set :public_folder, 'public'
    set :views, 'app/views'
    enable :sessions
    set :session_secret, "aYxiNrafm3ScE4xfa91z2azkuUGJ720y"
  end

  get '/' do
    erb :index
  end

  helpers do

    def logged_in?
      !!current_user
    end

    def current_user
      @current_user ||= User.find_by(id: session[:user_id]) if session[:user_id]
    end

  end

end
```
`trades_controller.rb`
```ruby
class TradesController < ApplicationController

    get '/trade' do
        if logged_in?
          erb :'trades/pick'
        else
          redirect to '/'
        end
    end

    post '/trade/new_trade' do
        @coin_id = params[:coin_id] 
        redirect to "/trade/#{@coin_id}"
    end

    get '/trade/:id' do
        if logged_in?
          Api.new.get_top_20_cryptocurrencies 
          @coin = Cryptocurrency.find_by_id(params[:id])
          if @coin != nil
            erb :'trades/new'
          else
            redirect to '/'
          end
        else
          redirect to '/'
        end
    end

    post '/trade/status' do
        if params[:quantity].to_i > 0
            @trade = current_user.trades.build(coin_name: params[:coin_name], current_price: params[:current_price], quantity: params[:quantity])
            @new_balance = current_user.balance - (params[:quantity].to_i*params[:current_price].to_f)
            if @new_balance > 0
                current_user.update(balance: @new_balance)
                @trade.save
                erb :'trades/confirmation'
            else
                erb :'trades/rejected'
            end
        else
            redirect to '/'
        end
    end

    get '/trades' do
        if logged_in?
          @trades = Trade.all
          erb :'trades/all'
        else
          redirect to '/'
        end
    end

    get '/trades/edit/:id' do
        if logged_in?
            @trade = Trade.find_by_id(params[:id])
            if @trade && @trade.user == current_user
                erb :'trades/edit'
            else
                redirect to '/users/balance'
            end
        else
            redirect to '/'
        end
    end

    patch '/trades/edit/:id' do
        if logged_in?
            @trade = Trade.find_by_id(params[:id])
            if @trade && @trade.user == current_user
                @trade.update(coin_name: params[:coin_name], current_price: params[:current_price], quantity: params[:quantity])
                redirect to '/users/balance'
            else
                redirect to '/users/balance'
            end
        else
            redirect to '/'
        end
    end

    get '/trades/sell/:id' do
        if logged_in?
            @trade = Trade.find_by_id(params[:id])
            if @trade && @trade.user == current_user
                Api.new.get_top_20_cryptocurrencies
                coin_name = @trade.coin_name
                @updated_coin = Cryptocurrency.find_by_name(coin_name)
                @new_balance = current_user.balance + (@trade.quantity.to_i * @updated_coin.current_price.to_f)
                erb :'trades/sell'
            else
                redirect to '/users/balance'
            end
        else
            redirect to '/'
        end
    end

    delete '/trades/sell/:id/delete' do
        puts params
        if logged_in?
          @trade = Trade.find_by_id(params[:id])
          if @trade && @trade.user == current_user
            current_user.update(balance: params[:new_balance])
            @trade.delete
          end
          redirect to '/users/balance'
        else
          redirect to '/'
        end
    end

    get '/leaderboard' do
        if logged_in?
          @users = User.all
          erb :'trades/leaderboard'
        else
          redirect to '/'
        end
    end

end
```
`users_controller.rb`
```ruby
class UsersController < ApplicationController
    get '/users/balance' do
      if logged_in?
        @trades = current_user.trades.all
        erb :'users/balance'
      else
        redirect to '/'
      end
    end
  
    get '/signup' do
      if !logged_in?
        erb :'users/signup', locals: {message: "Please sign up before you sign in"}
      else
        redirect to '/'
      end
    end
  
    post '/signup' do
      if params[:first_name] == "" || params[:last_name] == "" || params[:email] == "" || params[:password] == ""
        redirect to '/signup'
      else
        @user = User.new(:first_name => params[:first_name], :last_name => params[:last_name], :email => params[:email], :password => params[:password])
        @user.balance = 10000 # Free '$10000' for signing up!
        @user.save
        session[:user_id] = @user.id
        redirect to '/'
      end
    end
  
    get '/login' do
      if !logged_in?
        erb :'users/login'
      else
        redirect to '/'
      end
    end
  
    post '/login' do
      user = User.find_by(:email => params[:email])
      if user && user.authenticate(params[:password])
        session[:user_id] = user.id
        redirect to '/'
      else
        redirect to '/signup'
      end
    end
  
    get '/logout' do
      if logged_in?
        session.destroy
        redirect to '/'
      else
        redirect to '/'
      end
    end
end
```  
This project was very intense, but I'm really glad I managed to put it together. Getting a clear understanding of APIs, html and databases it's crucial for building interactive apps.        
Check out a quick [YouTube](https://www.youtube.com/watch?v=e8Jn45GQUgM) video of my project.
Check out the project here on [GitHub](https://github.com/drivera53/flatcoin)!