Creating a CRUD app

A full-fledged CRUD application that utilizes RESTful routes is a major undertaking made easier with Ruby frameworks like Sinatra. This domain model creates users with authentication capabilities, so they can create causes that are categorized. Let’s walk through the process to see what composes a CRUD app.

File Structure

An overview of the necessary files:

└── fundmycause
    ├── Gemfile
    ├── Gemfile.lock
    ├── README.md
    ├── Rakefile
    ├── app
    │   ├── controllers
    │   │   ├── application_controller.rb
    │   │   ├── categories_controller.rb
    │   │   ├── causes_controller.rb
    │   │   └── users_controller.rb
    │   ├── models
    │   │   ├── category.rb
    │   │   ├── cause.rb
    │   │   ├── user.rb
    │   │   └── usercause.rb
    │   └── views
    │       ├── categories
    │       │   └── index.erb
    │       ├── causes
    │       │   ├── edit.erb
    │       │   ├── index.erb
    │       │   ├── new.erb
    │       │   └── show.erb
    │       ├── index.erb
    │       ├── layout.erb
    │       └── users
    │           ├── login.erb
    │           ├── new.erb
    │           └── show.erb
    ├── config
    │   └── environment.rb
    ├── config.ru
    ├── db
    │   └── schema.rb
    └── public
        └── stylesheets
            └── index.css

Gemfile

Explore the Gemfile to see the relevant gems that this gem will require. Particular to this app is the bcrypt gem. This will store the user-created password by ‘salting’ in random characters.

Models

This app creates three models: a User, a Cause, and a Category. In creating the migrations, note the following associations:

- a `User` has many `Causes`
- a `Cause` has many `Users`
- a `Cause` belongs to a `Category`
- a `Category` has many `Causes`

User Authentication

To add authentication to the app, the two helper methods logged_in? and current_user will be utilized to check user credentials before accessing certain pages. These methods will prevent users from editing and deleting other users’ causes as well as restricting access to only their own causes.

CRUD: Create a Cause

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class CausesController < ApplicationController

  get '/causes/new' do
    if logged_in?
      erb :"causes/new"
    else
      redirect to '/login'
    end
  end

  post '/causes/new' do
    @user = current_user
    @cause = Cause.new(name: params[:cause][:name])
    @cause.created_by_user = @user.id
    @cause.description = params[:cause][:description]
    @cause.funding = params[:cause][:funding]
    if params[:cause][:category_id]
      @cause.category_id = params[:cause][:category_id]
    else
      @cause.category = Category.find_or_create_by(name: params[:new_category].capitalize)
    end

    if @cause.save
      @cause.users << @user if !@cause.users.include?(@user)
      redirect to "/causes"
    else
      erb :"causes/new", locals: {message: "The cause wasn't created."}
    end
  end
end

The first few routes will create the form and process the form input to create a new Cause. The different attributes of the Cause (like name, description, created by the user, and funding) will be defined with values from the form submission. The user will also have an option to choose from an existing Category or create a new Category to associate with the Cause.

To accomplish this, the POST method on the create action will utilize a conditional statement to check if a particular category checkbox has been marked. If not, a new Category will be created and associated with the new Cause with the value from the form input for a new category.

Finally, if the new Cause can be saved to the database, the current_user will be associated with the Cause so that the “creator” of the cause will be defined and makes it easier to formulate the restricted edit and delete actions.

CRUD: Read a Cause

1
2
3
4
5
6
7
8
9
10
11
get '/causes' do
  @causes = Cause.all
  @categories = Category.all
  erb :"causes/index"
end

get '/cause/:id' do
  @cause = Cause.find_by(id: params[:id])
  @user = current_user if logged_in?
  erb :"causes/show"
end

In order to view a list of all causes and detailed information on each cause, the index and show routes and view pages must be created. On the show page, there is a conditional that will be implemented to check if the user is logged_in? and whether or not the cause’s first user is equal to the current_user.

In essence, this checks to see if the cause’s creator is the current user. If it is, it will present the user with an option to delete the cause. If not, it will have an option for the user to join the cause. In order to see the routes and code associated with joining/removing a user from a cause, view the code at fundmycause.

CRUD: Edit a Cause

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
get '/cause/:id/edit' do
  if logged_in?
    @user = current_user
    @cause = Cause.find_by(id: params[:id])
    if @cause.created_by_user == @user.id
      erb :"causes/edit"
    else
      redirect to '/causes'
    end
  else
    redirect to '/causes'
  end
end

patch '/cause/:id/edit' do
  @cause = Cause.find_by(id: params[:id])
  @cause.name = params[:cause][:name]
  @cause.description = params[:cause][:description]
  @cause.funding = params[:cause][:funding]
  if params[:cause][:category_id]
    @cause.category_id = params[:cause][:category_id]
  else
    @cause.category = Category.find_or_create_by(name: params[:new_category].capitalize)
  end

  if @cause.save
    redirect to "/cause/#{@cause.id}"
  else
    erb :"causes/show", locals: {message: "The cause wasn't updated."}
  end
end

Before the edit cause page is loaded, the user will be authenticated with the logged_in? helper method. After authentication, there will be a conditional statement to check if the cause’s first user (a.k.a. the creator) is the current_user. If so, the page for editing a Cause will load and produce a form simliar to the “create a new cause” view page.

Upon form submission, the information stored in the params hash will be used to update the cause. Similar to the create action, the PATCH method on the edit action invokes a conditional statement that potentially assigns a different category - by choosing from available ones or creating a new one.

A note on the PATCH method: By utilizing the MethodOverride class available through Rack, the HTTP POST request method is overridden by the value of the _method parameter located in the form.

CRUD: Delete a Cause

1
2
3
4
5
6
7
8
9
10
11
12
13
14
delete '/cause/:id/delete' do
  if logged_in?
    @user = current_user
    @cause = Cause.find_by(id: params[:id])
    if @cause.created_by_user == @user.id
      @cause.destroy
      erb :"users/show", locals: {message: "The cause was deleted."}
    else
      redirect to '/causes'
    end
  else
    redirect to '/causes'
  end
end

Similar to the edit action, the delete action will authenticate the user and confirm with a conditional statement that the cause’s first user (the cause creator) is the current_user. With that authentication, the delete action will display as a button on the show view page and successfully delete the cause.

Also similar to the edit action, the MethodOverride class is utilized to turn the POST request into a DELETE request.

Summary

This domain model utilized the MVC framework and CRUD actions to develop routes and view pages that associate Users with Causes and Causes with Categories. By examining each component of a CRUD application, we are able to see how RESTful routes are beneficial and efficient in allowing easy data creation and manipulation.

See the entire project on GitHub: fundmycause