DEV Community

Samuel Lubliner
Samuel Lubliner

Posted on

Belay Board Simple Part 1

https://github.com/Samuel-Lubliner/Belay-Board-Simple

https://github.com/users/Samuel-Lubliner/projects/2

While developing the first version of Belay Board I realized that I was attempting to build out too many features at once. This approaching left me with confusing logic and too many moving parts at once. I decided to rethink the app and try to build out a minimum viable product. I learned that I can make more progress by first focusing on the features of the app with the most impact and value.

I decided the MVP would need the following user stories:

  • User can sign up for an account and sign in
  • User can create an availability
  • User can request to join an availability as a guest
  • User can accept or reject guests

Belay Board

Adding postgresql with citext


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

rails generate migration enable_citext_extension

class EnableCitextExtension < ActiveRecord::Migration[7.0]
  def change
    enable_extension 'citext'
  end
end
Enter fullscreen mode Exit fullscreen mode

rails rb:migrate

Add Users with devise

rails generate devise:install
rails generate devise User username:citext

User views

rails generate devise:views

devise parameters

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

  protected

  def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:sign_up, keys: [:username])
    devise_parameter_sanitizer.permit(:account_update, keys: [:username])
  end
end
Enter fullscreen mode Exit fullscreen mode

Add Availabilities

rails g scaffold Availability event_name:string start_time:datetime end_time:datetime user:references

Update models

Add EventRequest

rails g model EventRequest user:references availability:references status:string

Update models

Set up controller to set availability creator as the host

Allow a user to join an Availability and accept or reject guests

class EventRequest < ApplicationRecord
  belongs_to :user
  belongs_to :availability

  validates :status, presence: true, inclusion: { in: %w[pending accepted rejected] }

  validates :user_id, uniqueness: { scope: :availability_id }

  def accept
    update(status: 'accepted')
  end

  def reject
    update(status: 'rejected')
  end

end
Enter fullscreen mode Exit fullscreen mode

rails generate controller EventRequests

<% if current_user && @availability.user != current_user %>
  <%= form_for(current_user.event_requests.new, url: event_requests_path) do |f| %>
    <%= f.hidden_field :availability_id, value: @availability.id %>
    <%= f.submit "Join this Event" %>
  <% end %>
<% end %>
Enter fullscreen mode Exit fullscreen mode

Add route

Rails.application.routes.draw do
  root "availabilities#index"

  devise_for :users

  resources :availabilities

  resources :event_requests, only: [:create] do
    member do
      post :accept
      post :reject
    end
  end
Enter fullscreen mode Exit fullscreen mode

Add ujs and jquery

At the bash prompt I ran these two commands to modify config/importmap.rb:

./bin/importmap pin @rails/ujs

./bin/importmap pin jquery

Then in app/javascripts/application.js, I added these lines:

// Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails
import "@hotwired/turbo-rails"
import "controllers"

import jquery from "jquery";
window.jQuery = jquery;
window.$ = jquery;
import Rails from "@rails/ujs"
Rails.start();
Enter fullscreen mode Exit fullscreen mode

Add AJAX to the join, accept and reject buttons

views/availabilities/show.html.erb

<p style="color: green"><%= notice %></p>

<%= render @availability %>

<div>
  <%= link_to "Edit this availability", edit_availability_path(@availability) %> |
  <%= link_to "Back to availabilities", availabilities_path %>

  <%= button_to "Destroy this availability", @availability, method: :delete %>
</div>


<% if current_user && @availability.user != current_user %>
  <% unless @availability.event_requests.exists?(user: current_user) %>
    <%= form_for(current_user.event_requests.new, url: event_requests_path, remote: true) do |f| %>
      <%= f.hidden_field :availability_id, value: @availability.id %>
      <%= f.submit "Join this Event", id: "join-event-button" %>
    <% end %>
  <% end %>
<% end %>


<h3>Guests</h3>
<ul id="guest_requests_list" style="list-style-type: none;">
  <% @event_requests.each do |event_request| %>
    <li id="event_request_<%= event_request.id %>">
      <%= render partial: 'event_request', locals: { event_request: event_request } %>
      <% if event_request.status == 'pending' && @availability.user == current_user %>
        <%= button_to 'Accept', accept_event_request_path(event_request), method: :post, remote: true, class: 'accept-button', data: { turbo: false } %>
        <%= button_to 'Reject', reject_event_request_path(event_request), method: :post, remote: true, class: 'reject-button', data: { turbo: false } %>
      <% end %>
    </li>
  <% end %>
</ul>
Enter fullscreen mode Exit fullscreen mode

views/event_requests/create.js.erb

<% if @event_request.persisted? %>
  $("#guest_requests_list").append("<%= j(render partial: 'availabilities/event_request', locals: { event_request: @event_request }) %>");
  $("#join-event-button").fadeOut();
<% end %>
Enter fullscreen mode Exit fullscreen mode

views/event_requests/accept.js.erb

$("#event_request_<%= @event_request.id %>").html("<%= j(render partial: 'availabilities/event_request', locals: { event_request: @event_request }) %>");
Enter fullscreen mode Exit fullscreen mode
# == Schema Information
#
# Table name: event_requests
#
#  id              :bigint           not null, primary key
#  status          :string           default("pending")
#  created_at      :datetime         not null
#  updated_at      :datetime         not null
#  availability_id :bigint           not null
#  user_id         :bigint           not null
#
# Indexes
#
#  index_event_requests_on_availability_id  (availability_id)
#  index_event_requests_on_user_id          (user_id)
#
# Foreign Keys
#
#  fk_rails_...  (availability_id => availabilities.id)
#  fk_rails_...  (user_id => users.id)
#
class EventRequest < ApplicationRecord
  belongs_to :user
  belongs_to :availability

  validates :status, presence: true, inclusion: { in: %w[pending accepted rejected] }

  validates :user_id, uniqueness: { scope: :availability_id }

  def accept
    update(status: 'accepted')
  end

  def reject
    update(status: 'rejected')
  end

end
Enter fullscreen mode Exit fullscreen mode
class EventRequestsController < ApplicationController
  before_action :set_event_request, only: %i[show edit update destroy]

  def create
    @event_request = current_user.event_requests.new(event_request_params)

    respond_to do |format|
      if @event_request.save
        format.html { redirect_to @event_request.availability, notice: 'Request submitted.' }
        format.js
      else
        format.html { render 'availabilities/show', status: :unprocessable_entity }
        format.js
      end
    end
  end

  def accept
    @event_request = EventRequest.find(params[:id])
    if @event_request.availability.user == current_user
      @event_request.update(status: 'accepted')

      respond_to do |format|
        format.html { redirect_to availability_path(@event_request.availability), notice: 'Request accepted.' }
        format.js
      end
    end
  end

  def reject
    @event_request = EventRequest.find(params[:id])
    if @event_request.availability.user == current_user
      @event_request.update(status: 'rejected')

      respond_to do |format|
        format.html { redirect_to availability_path(@event_request.availability), notice: 'Request rejected.' }
        format.js
      end
    end
  end

  private

  def event_request_params
    params.require(:event_request).permit(:availability_id)
  end

end
Enter fullscreen mode Exit fullscreen mode

Next Step

Add Calendar

Heroku

Build apps, not infrastructure.

Dealing with servers, hardware, and infrastructure can take up your valuable time. Discover the benefits of Heroku, the PaaS of choice for developers since 2007.

Visit Site

Top comments (0)

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

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay