DEV Community

Cover image for Sinatra ToDo App
Chuck
Chuck

Posted on

Sinatra ToDo App

This post begins a series of articles about designing, creating, and deploying a Sinatra ToDo app as part of the Flatiron School curriculum

Introduction

Requirements

  • Build an MVC Sinatra application.
  • Use ActiveRecord with Sinatra.
  • Use multiple models.
  • Use at least one has_many relationship on a User model and one belongs_to relationship on another model.
  • Must have user accounts - users must be able to sign up, sign in, and sign out.
  • Validate uniqueness of user login attribute (username or email).
  • Once logged in, a user must have the ability to create, read, update and destroy (CRUD) the resource that belongs_to user.
  • Ensure that users can edit and delete only their own resources - not resources created by other users.
  • Validate user input so bad data cannot be persisted to the database.
  • BONUS: Display validation failures to user with error messages. (This is an optional feature, challenge yourself and give it a shot!)

TLTR: Feel free to get the source code.

App Design

The overall plan for this app was to design a web site with a full-stack design, using Sinatra back-end, ERB files to display the front-end, and using Bootstrap to quickly prototype the site.

Based on the requirements the site includes a user account model which allows the end user to create a Todo list secure from other users.

Security

To handle the security of the user model, this app utilizes the bcrypt gem, and securerandom to secure the session_secret. In addition, it uses DOTENV locally during store the session_secret, and of course the .env file is excluded from the .gitignore.

Data Design

The database design is fairly simple. First the user table uses password_digest as the password field to utilize the salt and hash provided by bcrypt:

class CreateUsers < ActiveRecord::Migration
  def change
    create_table :users do |t|
      t.string :name
      t.string :email
      t.string :password_digest
      t.timestamps null: false
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

The table for the ToDo's belongs_to the user_id so each list item can be connects to the user:

class CreateTodos < ActiveRecord::Migration
  def change
    create_table :todos do |t|
      t.string :title
      t.integer :user_id
      t.timestamp null: false
    end
  end
end

Enter fullscreen mode Exit fullscreen mode

Controller Design

It would be easy to limit the number of controllers but when I was designing the application, I decided to separate the concerns a little more.

The application_controller controls the app. It includes the security setup, flash messages using rack-flash, sets the root path, and includes a few helpers methods for the user model. Specifically, the current_user method which is used to compare the user and session_id to the todo item later in the source code.

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

The sessions_controller validates the user login:

post '/login' do
    user = User.find_by(name: params[:name])
    if user&.authenticate(params[:password])
      session[:user_id] = user.id
      redirect '/todos'
    else
      flash[:danger] = 'Invalid login credentials!'
      redirect '/login'
    end
  end
Enter fullscreen mode Exit fullscreen mode

The new user account creation is handled in the user_controller. Therefore, exiting accounts and new accounts have their own separate controllers:

post '/signup' do
    @user = User.new(params)
    if @user.save
      session[:user_id] = @user.id
      flash[:success] = 'Successfully created user account.'
      redirect '/todos'
    else
      flash[:danger] = 'Please enter valid registration data!'
      redirect '/signup'
    end
  end
Enter fullscreen mode Exit fullscreen mode

The todos_controller, well you guessed it 🤔 handles the Todo list items.

  get '/todos' do
    if logged_in?
      @user = User.find(session[:user_id])
      @todos = Todo.where(user_id: current_user)
      erb :'/todos/index'
    else
      redirect '/login'
    end
  end
Enter fullscreen mode Exit fullscreen mode

It is in this method that the current logged in user is retrieved and compared to the user_id of each ToDo item. Based on the associations in the database tables, this is the method would pulls together the corr3ct list.

In the next article I will write about the challenges to deploy to Heroku

Top comments (0)