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
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'
database.yml
default: &default
adapter: 'sqlite3'
database: 'db/development.sqlite'
development:
<<: *default
test:
<<: *default
database: 'db/test.sqlite'
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
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'
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
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
trade.rb
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!
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
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
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
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)