Belay Board App Part 1: Database
Published: 2023-11-12 23:33:11

### 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

```yml
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"] %>
```

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
```rb
# 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

```

Run
`rake db:migrate`

```rb
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
```

# Availability Table
`rails g scaffold availability user:references start_time:datetime end_time:datetime location:string description:string is_open:boolean`

## Update migration file
```rb
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
```

##  Update Models

```rb
class User < ApplicationRecord
  #...
  has_many :availabilities, dependent: :destroy
end
```

```rb
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
```

Run
`rails db:migrate`

# Requests

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

## Migration

```rb
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
```

 ## Request model
```rb
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
```


`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
```rb
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
```

## Availability Model
```rb
class Availability < ApplicationRecord
  belongs_to :user
  has_many :requests, dependent: :destroy

  #...
end
```

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.