DEV Community

Samuel Lubliner
Samuel Lubliner

Posted on

Belay Board App Part 1: Database

Step one

  • Start simple and test minimal viable product.
  • Add user, availabilities, requests
  • User can create and request an open availability
  • Set up calendar

Postgres

First I configured my databse.yml file to use postgres

default: &default
  adapter: postgresql
  encoding: unicode
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>

development:
  <<: *default
  database: belay_board_development

test:
  <<: *default
  database: belay_board_test

production:
  <<: *default
  database: belay_board_production
  username: belay_board
  password: <%= ENV["BELAY_BOARD_DATABASE_PASSWORD"] %>
Enter fullscreen mode Exit fullscreen mode

Run:
rake db:create

Before creating my tables, I created a new feature branch
Belay-Board main % git checkout -b feature/users

Users with Devise

rails generate devise:install

Create the Users table
rails generate devise user username private:boolean bio:text

This created a migration file, User model, and user routes. Devise also handles password and email flow.

Users migration file

add_index speeds up lookups of records

add .citext to email and username

# frozen_string_literal: true

class DeviseCreateUsers < ActiveRecord::Migration[7.0]
  def change
    create_table :users do |t|
      enable_extension("citext")
      ## Database authenticatable
      t.citext :email,              null: false, default: ""
      t.string :encrypted_password, null: false, default: ""

      ## Recoverable
      t.string   :reset_password_token
      t.datetime :reset_password_sent_at

      ## Rememberable
      t.datetime :remember_created_at

      t.citext :username
      t.boolean :private
      t.text :bio

      t.timestamps null: false
    end

    add_index :users, :email,                unique: true
    add_index :users, :reset_password_token, unique: true
    add_index :users, :username, unique: true
  end
end

Enter fullscreen mode Exit fullscreen mode

Run
rake db:migrate

class ApplicationController < ActionController::Base
  skip_forgery_protection
  before_action :authenticate_user!
  before_action :configure_permitted_parameters, if: :devise_controller?

  def configure_permitted_parameters

    devise_parameter_sanitizer.permit(:account_update, :keys => [:private, :bio])
  end
end
Enter fullscreen mode Exit fullscreen mode

Availability Table

rails g scaffold availability user:references start_time:datetime end_time:datetime location:string description:string is_open:boolean

Update migration file

class CreateAvailabilities < ActiveRecord::Migration[7.0]
  def change
    create_table :availabilities do |t|
      t.references :user, null: false, foreign_key: true
      t.datetime :start_time, null: false
      t.datetime :end_time, null: false
      t.string :location
      t.string :description
      t.boolean :is_open, default: true

      t.timestamps
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

Update Models

class User < ApplicationRecord
  #...
  has_many :availabilities, dependent: :destroy
end
Enter fullscreen mode Exit fullscreen mode
class Availability < ApplicationRecord
  belongs_to :user

  validates :start_time, presence: true
  validates :end_time, presence: true
  validate :end_time_after_start_time

  private

  def end_time_after_start_time
    return if end_time.blank? || start_time.blank?

    if end_time < start_time
      errors.add(:end_time, "must be after the start time")
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

Run
rails db:migrate

Requests

rails g scaffold request status:integer availability:references sender:references

Migration

class CreateRequests < ActiveRecord::Migration[7.0]
  def change
    create_table :requests do |t|
      t.integer :status, default: 0
      t.references :availability, null: false, foreign_key: true
      t.references :sender, null: false, foreign_key: { to_table: :users }

      t.timestamps
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

## Request model

class CreateRequests < ActiveRecord::Migration[7.0]
  def change
    create_table :requests do |t|
      t.integer :status, default: 0
      t.references :availability, null: false, foreign_key: true
      t.references :sender, null: false, foreign_key: { to_table: :users }

      t.timestamps
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

class_name: 'User' is used because the association sender doesn't follow the Rails convention of class_name being the same as the association name.

User Model

class User < ApplicationRecord

#...

  has_many :availabilities, dependent: :destroy

  has_many :sent_requests, class_name: 'Request', foreign_key: 'sender_id', dependent: :destroy
  has_many :received_requests, through: :availabilities, source: :requests

#...
end
Enter fullscreen mode Exit fullscreen mode

Availability Model

class Availability < ApplicationRecord
  belongs_to :user
  has_many :requests, dependent: :destroy

  #...
end
Enter fullscreen mode Exit fullscreen mode

Run
rails db:migrate

Next Steps

Now I am ready to test out my domain model and create some sample data tasks. Populating my database with data will confirm that my associations are set up properly and will also expose any missing validations that should be added to my models.

Top comments (0)