The Car Salesman — My First Sinatra Project

Well, 7 weeks in, and I finally finished up my first Sinatra project. I created an online car store, similar to craigslist. I am planning on adding a lot more functionality to the project so stay posted.

Check out my project at https://github.com/Lukeghenco/online-car-store-sinatra-app.

As I started this project, I originally wanted to build a car collection, but it morphed into an online car store as I started wire-framing.

The Setup:

My first step was to decide on the gems I needed for the project. I clearly needed Sinatra, but what other gems did I need to make this project happen? I wanted to use ActiveRecord for my Object Relational Mapping(ORM), SQLite3 for my database, Shotgun to run my local server, BCrypt for user authentication and I needed Rake to migrate my database. Here is my full Gemfile:

source ‘http://rubygems.org'
gem ‘sinatra’
gem ‘activerecord’, :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”
group :test do
gem ‘rspec’
gem ‘capybara’
gem ‘rack-test’
gem ‘database_cleaner’, git: ‘https://github.com/bmabey/database_cleaner.git'
end

Now that I had the Gemfile setup, I needed to make my file system structure. I am making a Model-View-Controller(MVC) so I set up my folders to look like this:

app -
|- Controllers
|
|- Models
|
|- Views
config -
|- environment.rb
db - 
|- migrate
spec -
|- controllers
.gitignore
.rspec
config.ru
Gemfile
Gemfile.lock
Rakefile
README.md

Now that all of the preliminary steps are done. It was time to start coding

Creating Objects and Making Relationships:

My next step was to figure out what objects I wanted to use, and how were they connected. I decided on using just 2 objects with a many-to-one and a one-to-many relationship. I am using ActiveRecord for my Object Relational Mapping(ORM). Here were the first two objects I created:

User:

class User < ActiveRecord::Base 
has_many :cars # my one-to-many relationship with cars
has_secure_password # used to create a secure session for logged in users
end

Car:

class Car < ActiveRecord::Base 
belongs_to :user # my many-to-one relationship with a user
end

I created two migration tables in a SQLite3 database:

Users Table:

class CreateUsers < ActiveRecord::Migration 
def change
create_table :users do |t|
t.string :username
t.string :email
t.string :password_digest

t.timestamp null: false
end
end
end

Cars Table:

class CreateCars < ActiveRecord::Migration
def change
create_table :cars do |t|
t.string :brand
t.string :name
t.integer :make_year
t.integer :user_id
      t.timestamp null: false
end
end
end

So I now have two objects. A car that belongs to a user, and a user that has many cars. I need to run

$ rake db:migrate

So that I can create the database. Oh wait I forgot to add a price column for the car. Lets fix that and a new migration file.

class AddPriceToCars < ActiveRecord::Migration
def change
add_column :cars, :price, :integer
end
end

No lets run $ rake db:migrate again and update our database. Now it’s time to start making a program.

Controllers:

I started with an application controller where I put my helper methods and a basic index page.

require ‘./config/environment’
class ApplicationController < Sinatra::Base
  configure do
set :public_folder, ‘public’
set :views, ‘app/views’
enable :sessions
set :session_secret, “carcollection”
end
  helpers do
    def logged_out?
if !logged_in?
redirect ‘/login?error=Please LOG IN to view that content’
end
end
    def logged_in?
!!session[:id]
end
    def current_user
User.find(session[:id])
end
    def error
@error_message = params[:error]
end
end
  get ‘/’ do
error
erb :index
end
end

You will see several of those helper methods in my other controllers. Since I have two objects and I want to use both of those object with my views. I need to set up logic and routes for those objects to interact with my views.

First we have the user who needs to be able to signup, log in, and log out.

class UsersController < ApplicationController
  get ‘/signup’ do
if logged_in?
redirect to ‘/cars’
else
erb :’users/user_signup’
end
end
  post ‘/signup’ do
if params.values.include?(“”)
redirect to ‘/signup’
else
@user = User.create(params)
session[:id] = @user.id
redirect ‘/cars’
end
end
  get ‘/login’ do
error
if logged_in?
redirect to ‘/cars’
else
erb :’users/user_login’
end
end
  post ‘/login’ do
if params[:username] == nil || params[:password == nil]
redirect “/login”
else
@user = User.find_by(:username => params[:username])
if @user && @user.authenticate(params[:password])
session[:id] = @user.id
redirect “/cars”
else
redirect “/login”
end
end
end
  get ‘/logout’ do
if logged_in?
session.clear
end
redirect ‘/login’
end
end

Then we have a car which we need to Create, Read, Update, and Delete(CRUD)

class CarsController < ApplicationController
  get ‘/cars’ do
error
@cars = Car.all
if logged_in?
@user = User.find_by(session[:id])
erb :’cars/cars’
else
redirect ‘/login’
end
end
  get ‘/cars/new’ do
error
if logged_in?
erb :’cars/new’
else
logged_out?
end
end
  post ‘/cars/new’ do
if params[“content”] != “”
@car = Car.create(:name => params[“name”], :brand => params[“brand”], :make_year => params[“make_year”], :price => params[“price”])
@car.user = User.find(session[:id])
@car.save
redirect to ‘/cars’
else
redirect ‘/cars/new’
end
end
  get ‘/cars/:id’ do
@car = Car.find(params[:id])
error
if logged_in?
erb :’cars/show’
else
redirect to ‘/login’
end
end
  get ‘/cars/:id/edit’ do
@car = Car.find(params[:id])
error
if logged_in? && @car.user_id == current_user.id
erb :’cars/edit’
elsif @car.user_id != current_user.id
redirect ‘/cars?error=THAT IS NOT YOUR CAR’
else
redirect to ‘/login’
end
end
  post ‘/cars/:id’ do
@car = Car.find(params[:id])
if logged_in?
if params[“brand”] != “” && params[“name”] != “” && params[“make_year”] != “” && params[“price”] != “”
@car.update(:name => params[“name”], :brand => params[“brand”], :make_year => params[“make_year”], :price => params[“price”])
@car.save
redirect to ‘/cars’
else
redirect to “/cars/#{@car.id}/edit?error=Please Fill In All Forms”
end
else
redirect to ‘/login’
end
end
  get ‘/cars/:id/delete’ do
@car = Car.find(params[:id])
error
if logged_in? && @car.user_id == current_user.id
erb :’cars/delete’
elsif logged_in?
redirect “/cars/#{@car.id}?error=THAT IS NOT YOUR CAR”
else
redirect to ‘/login’
end
end
  post ‘/cars/:id/delete’ do
@car = Car.find(params[:id])
@car.delete
redirect to ‘/cars’
end
end

Views:

I won’t list all of my ERB files. You can check that out on my Github repo. I basically decided to create a form to create a user and to log in. I also set up a form for adding a car to the inventory list and to edit a car’s information.

Results:

Everything didn’t originally work well. The information you see above is finished code, but I had a lot of issues with users being able to delete other users cars or deleting the first car on the inventory list instead of the car I meant to delete. I also needed to work on error messages so that the would show up correctly on the required page. I eventually switched to putting this in my views/layout.erb, so that I could make custom errors in the controllers, and have them display right below my navbar.

<% if !@error_message.nil? %>
<div class=”alert”>
<%= @error_message %>
<div>
<% end %>

This project took a lot of work, and I am really happy with what I have done so far. I am planing on adding Bootstrap to the project in the near future, as well as add the photo uploader I mentioned earlier. For now, everything in the app works as I had hoped.

Please leave me a comment (especially if you notice something I could improve upon with my code). I would really like to hear from you.