DEV Community

Cover image for Sinatra ToDo App
Chuck
Chuck

Posted on

2

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

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more

Top comments (0)

Image of Docusign

🛠️ Bring your solution into Docusign. Reach over 1.6M customers.

Docusign is now extensible. Overcome challenges with disconnected products and inaccessible data by bringing your solutions into Docusign and publishing to 1.6M customers in the App Center.

Learn more