DEV Community

Cover image for FlatCoin
Danny Rivera
Danny Rivera

Posted on

FlatCoin

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.

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.

Sinatra Activerecord Setup: Make sure to add the three most important gems: activerecord, sinatra-activerecord and rake.
Gemfile

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

Setting up the connection to our database:
environment.rb

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

database.yml

default: &default
  adapter: 'sqlite3'
  database: 'db/development.sqlite'

development:
  <<: *default

test:
  <<: *default
  database: 'db/test.sqlite'
Enter fullscreen mode Exit fullscreen mode

config.ru

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

Rake gives us the ability to quickly make files and set up automated tasks.
Rakefile

ENV["SINATRA_ENV"] ||= "development"

require_relative './config/environment'
require 'sinatra/activerecord/rake'
Enter fullscreen mode Exit fullscreen mode

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

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

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.
user.rb

class User < ActiveRecord::Base
  has_many :trades

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

end
Enter fullscreen mode Exit fullscreen mode

trade.rb

class Trade < ActiveRecord::Base
    belongs_to :user 
end
Enter fullscreen mode Exit fullscreen mode

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!

Alt Text
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

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

trades_controller.rb

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

users_controller.rb

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

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 video of my project.
Check out the project here on GitHub!

Top comments (0)