Creating an Event Manager with Ruby on Rails
Published: 2023-07-19 17:59:16

Hello! In this blog I would like to write about my most recent bootcamp project, a full stack application that manages events. This will be a brief introduction of the backbone of my project, and I’d like to emphasize a bit more on what I thought was most confusing - user authentication and validations.

A basic explanation of what this application does is that users may sign up and log in, and create events or browse events that other users have created. A user may view a specific event, edit it, or make a registration.

Repository: https://github.com/fusion407/event-management-system

Deployment: https://event-management-system-qmse.onrender.com/

The 3 models I have created are Users, Registrations, and Events. 

The relationship of these models may be visualized like so:

Users * → 1- Registrations -1 ← * Events

Users and Events share a many-to-one relationship to registration, which will be the join table carrying the two foreign keys from users and events. As a result, Users and Events have a many-to-many relationship through the registration model. This means that Users can have many registrations, and many Events. Events can have many registrations and many users. But Registrations can only have a single user and event. Here is an example of my models with their associations.
```ruby
class Event < ApplicationRecord
    has_many :registrations
    has_many :users, through: :registrations
```
```ruby
class Registration < ApplicationRecord
    belongs_to :user
    belongs_to :event
```
```ruby
class User < ApplicationRecord
    has_many :registrations
    has_many :events, through: :registrations
    has_secure_password
```

Once I have finished writing my migrations, here is what my schema is going to look like.
```ruby
  create_table "events", force: :cascade do |t|
    t.string "title"
    t.string "description"
    t.string "location"
    t.string "start_date"
    t.string "end_date"
    t.string "created_by"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
  end


  create_table "registrations", force: :cascade do |t|
    t.integer "user_id"
    t.integer "event_id"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.integer "participants"
  end


  create_table "users", force: :cascade do |t|
    t.string "username"
    t.string "password_digest"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
  end
```

In my events table I have all the basic parameters for an event, such as title, description, location, date, and created by. My registration has the two foreign keys from the other models, user_id, and event_id. There is also a participants column that carries an integer that is used to express the number of ‘attendees’ for that user's registration. Finally, the users table will carry the username of the user, and the password_digest - for when a user signs up or logs in the application.

Now that I have my schema, it’s time to make serializers that will control what attributes I would like to share, since I obviously don’t need to include information like password_digest and the created_at / updated_at in all of my models.
```ruby
class EventSerializer < ActiveModel::Serializer
  attributes :id, :title, :description, :location, :start_date, :end_date, :created_by
  has_many :users
  has_many :registrations
end
```
```ruby
class RegistrationSerializer < ActiveModel::Serializer
  attributes :id, :user_id, :event_id, :participants, :created_at
  has_one :user
  has_one :event
end
```
```ruby
class UserSerializer < ActiveModel::Serializer
  attributes :id, :username
  has_many :registrations
  has_many :events
end
```

Now it’s time to write my controllers and give my application some authorization. I am going to need a controller for each of my models, and I will need a sessions controller that will be used to authenticate a user and use cookies in order to keep that user in session whenever they close or refresh the page so they may be automatically logged in when they return.

Before I do all that, let me show show you my application_controller which will show how I                       manage my errors and user authorization.
```ruby
class ApplicationController < ActionController::API
  include ActionController::Cookies
  rescue_from ActiveRecord::RecordInvalid, with: :render_unprocessable_entity_response
  before_action :authorize


  private


  def authorize
    @current_user = User.find_by(id: session[:user_id])
    render json: { errors: ["Not authorized"] }, status: :unauthorized unless @current_user
  end


  def render_unprocessable_entity_response(exception)
    render json: { errors: exception.record.errors.full_messages }, status: :unprocessable_entity
  end
end
```
What this basically says is before any actions are made, the authorize function is going to run and identify the session of the current user. If no session is found the json will render “Not authorized”. The other function render_unprocessable_entity_response is used when my other models run into an error with validations, like if a certain parameter is not present, it will return the error response.

The rules for these validations will go into the model for which you want to write the validation for, here is an example from my events model and the validations I’ve written for it:
```ruby
    validates :title, :description, :location, :start_date, :end_date, presence: true
    validates :title, uniqueness: true
```

So when a user is creating a new event, the server is going to check if these validations have passed or not, so what my validations say is that each parameter must be present, and the title must be unique - meaning the title can’t be the same as any other title in the database.

Back to the controllers. BUT before I do that, you need to understand what routes that are going to be used. Here is a snippet of my routes.rb file:
```ruby
Rails.application.routes.draw do
  resources :events
  resources :registrations
  get "/users", to: "users#show"
  post "/login", to: "sessions#create"
  delete "/logout", to: "sessions#destroy"
  post "/signup", to: "users#create"
  get "/me", to: "users#show"
  get "/me/registrations", to: "users#showMyRegs"
end
```
In short, the events and registrations have ‘resources’ for each route for full CRUD implementation ready to be coded. Each route after that is made explicitly through the sessions and users model because full CRUD is not needed for those models.

How does a user create their account? Here is the code from my users_controller for the create route.

```ruby
class UsersController < ApplicationController
    skip_before_action :authorize, only: [:create]


    def create
        user = User.create(user_params)
        if user.valid?
          session[:user_id] = user.id
          render json: user, status: :created
        else
          render json: { error: user.errors.full_messages }, status: :unprocessable_entity
        end
    end
```
What this will do is create the new User, check if that user passes its validations and then cookies are used to make a session using the id of the newly created user, and then JSON returns the data for the new user.

Ok, so now how does a user log in to an account that's already created? This will go down in the sessions_controller using the same route ‘create’.
```ruby
class SessionsController < ApplicationController
  skip_before_action :authorize, only: [:create]


    def create
      user = User.find_by(username: params[:username])
      if user&.authenticate(params[:password])
        session[:user_id] = user.id
        render json: user, status: :created
      else
        render json: { error: "Invalid username or password" }, status: :unauthorized
      end
    end
```
Except instead of a new user being created, the server must find the username through the database and then it needs to authenticate it with its password. If this authentication succeeds and the user has input the correct username and password, a session will be created for that user and rendering the json for that user, else it will return an error saying that the user has input an “Invalid username or password”. 

When a user wishes to log out, within the sessions controller the destroy method is called:
```ruby
    def destroy
        session.delete :user_id
        head :no_content
    end
```
That will delete the session entirely. 

This is all I would like to write for today, the rest of my code is mostly writing basic CRUD for my other models, feel free to check out my repository. Thank you for reading, happy coding!