CRUD Operations in Ruby on Rails. Let's Build a Blog App

In this post, we'll cover CRUD operations in Ruby on Rails. We'll create a simple app and implement CRUD operations using Ruby on Rails 7.1.3 and Ruby 3.3.0. We will use Bootstrap to add some styles to our application.

Set Up the Application

Let's set up our application.

Create a New Application

Let's start by creating a new app. In the terminal, we type:

rails new my_blog --css bootstrap

After the creation of the app, let's change directories into it:

cd my_blog

Create A Post Model

First, we will create a Post model. Each of our blog posts will have a title and content. To create the Post model, we use a Rails generator command. In the terminal, we type:

bin/rails g model Post title:string content:text

This command instructs Rails to generate a new model named Post with two attributes: title of type string and content of type text.

However, merely generating the model isn't enough to make our application aware of it. We need to update the database schema to include the new Post model and its attributes. This is where migrations come into play. Migrations are Ruby scripts that make changes to the database schema, such as creating or modifying tables. After generating the model, we run the migration by executing

bin/rails db:migrate 

in the terminal. This command tells Rails to apply any pending migrations and update the database accordingly. It's important to review the migration file generated by the Rails generator command before running the migration to ensure it accurately reflects the changes we want to make to the database structure.

Add Validations to the Post Model

Validations in Rails are a way to ensure that data stored in the database meets certain criteria or rules defined by the application. They act as safeguards to maintain data integrity and consistency. By applying validations to model attributes, we can enforce specific conditions or constraints on the data we save to the database.

For example, in our Post model, we want to ensure that every post created has a title and some content. Let's do this:

validates :title, presence: true
validates :content, presence: true

Set Up the Home Page

On the home page of our application, we'll have a list of all posts. To do this, we'll first create a Posts controller. In the terminal, we type:

bin/rails g controller Posts 

The first action we'll add to this controller is the index action:

def index
  @posts = Post.all
end

Here, we get all posts from the database and make them available to the controller.

In the config/routes.rb, we'll set the root route to this index action:

root "posts#index"

By doing so, we instruct the application to use the Posts controller and the index action for rendering the home page.

The last thing to do to set up our home page is to create an index view. The view is a visual representation of data in an application. In our index view, we'll display a list of post titles for now. Let's create the index.html.erb file in the app/views/posts/ directory. 

After that, we edit this file to display a list of posts:

<div class="container mt-5">
  <h1>All Posts</h1>
  <ul class="list-unstyled">
    <% @posts.each do |post| %>
      <li><%= post.title %></li>
    <% end %>
  </ul>
</div>

Declare The Routes

To allow users to interact with a web application's data, it is necessary to establish routes to different actions. One way to do this is using the resource routing, the default in Rails. This approach enables us to define all common routes at once, thus simplifying the process. We declare a resource routing by using the resources method. In config/routes.rb, let's add this:

resources :posts, except: :index

Create POsts

Creating a resource is the C in CRUD. In this part of our tutorial, we'll explain how to do this in a typical Rails app. To allow users to add a new post, we must provide them with a form. Using a form, we collect data and save it to the database.

Define new and create actions

The first thing we'll do is to add the new and create actions to the Posts controller. First, we add a new action:

def new
  @post = Post.new
end

A new instance of the Post model is instantiated and assigned to the @post instance variable.

At the bottom of our controller, let's add the private method post_params which will declare the strong parameters that will be permitted in the create action and, later, in the update action:

private
def post_params
  params.require(:post).permit(:title, :content)
end

Strong parameters are used to protect against mass assignment vulnerabilities and ensure that only specified attributes can be modified. Strong parameters help prevent unauthorized or malicious manipulation of data submitted via forms.

Now, we can define the create action:

def create
  @post = Post.new(post_params)
  if @post.save
    redirect_to root_path
  else
    render :new, status: :unprocessable_entity
  end
end

The action attempts to create a new instance of the Post model with the parameters permitted by the post_params method. If the post is successfully saved to the database, the user is redirected to the root_path. If validation errors or other issues are preventing the post from being saved, the user is re-rendered the new template to correct any errors.

By convention, the new action typically corresponds to the presentation of a form while the create action handles the form submission and saving of data to the database.    

Create the Form

We have defined the new and create actions. Now, it's time to write the form. We'll create a partial since we'll use the same form for creating and updating posts. Add the _form.html.erb partial in app/views/posts/ directory.  Let's edit this file as follows:

<%= form_with(model: post) do |form| %>
  <% if post.errors.any? %>
    <div class="text-danger mt-4">
      <h2><%= pluralize(post.errors.count, "error") %> prohibited this post from being saved:</h2>
      <ul>
        <% post.errors.each do |error| %>
          <li><%= error.full_message %></li>
        <% end %>
      </ul>
    </div>
  <% end %>
  <p class="mt-4">
    <%= form.label :title, class: "form-label" %>
    <%= form.text_field :title, class: "form-control" %>
  </p>
  <p class="mt-4">
    <%= form.label :content, class: "form-label" %>
    <%= form.text_area :content, class: "form-control" %>
  </p>
  <p class="mt-4">
    <%= form.submit class:"btn btn-primary" %>
  </p>
<% end %>

The form utilizes Rails' form_with helper method to generate a form for the Post model.

The conditional block checks for any validation errors and displays them if present, providing users with feedback on any errors preventing the post from being saved.

Each form field is appropriately labeled and styled using Bootstrap classes (form-label and form-control). The text_field and text_area helper methods generate input fields for the title and content attributes of the Post model, respectively.

Finally, the form includes a submit button styled with Bootstrap classes (btn btn-primary) to submit the form.form

Next, we will create a new view where we will display our from. In the app/views/posts/ directory, we'll add the new.html.erb file. Let's edit it like this:

<div class="container mt-5">
  <h1>New Post</h1>
  <%= render "form", post:@post %>
</div>

And then let's link to the new action in the index view. After the posts list, add this: 

<p class="mt-5">
  <%= link_to "New Post", new_post_path %>
</p>

Read Data

R in CRUD is the acronym for reading data. In this part of our tutorial, we will discuss reading data in a Ruby on Rails application.

The Read operation in Ruby on Rails involves retrieving and presenting data from the application's database to users. It is the foundation of many user interactions and can take various forms, such as displaying lists of records, showing detailed views, or implementing search functionality. For example, in a blog application, the Read operation would involve displaying a list of blog posts on the homepage and allowing users to view individual posts by clicking on them.

Show A Single Post

Next, we'll create a view that will show a single post. 

Create a Show Method

To implement the ability to read a single post, we must first add a show method to the controller. After the index method, let's add this:

 def show; end

Then, at the bottom of the controller, we'll add the private method set_post, whose purpose is to find a post by its ID:

def set_post
  @post = Post.find(params[:id])
end

Then, we'll call this method in a before filter at the top of the controller:

before_action :set_post, except: %i[index new create]

except for the index, new, and create actions, of course.

Create a Show View

Next, we will create a show view. In the app/views/posts/ directory, let's add the show.html.erb file. Let's edit it as follows:

<div class="container mt-5">
  <h1 class="text-primary"><%= @post.title %></h1>
  <div class="mt-4">
    <p>
      <span>Published at</span>
      <span class="fw-bold"><%= @post.created_at.strftime("%b %d, %Y") %></span>
    </p>
    <p>
      <span>Updated at</span>
      <span class="fw-bold"><%= @post.updated_at.strftime("%b %d, %Y") %></span>
    </p>
  </div>
  <div class="mt-5"><%= @post.content %></div>
  <p class="mt-5">
    <%= link_to "All Posts",root_path %>
  </p>
</div>

The show.html.erb view file is structured within a container and displays the title, publication date, last updated date, and content of the post. The @post instance variables are used to access the attributes of the post being displayed.

The publication date (created_at) and last updated date (updated_at) are formatted using the strftime method to display them in a more human-readable format.

Additionally, a link back to the list of all posts is included at the bottom of the view, allowing users to navigate back to the index page easily.

Since we have the show view, let's edit the create action to redirect to the newly created post instead of the home page. Let's edit the create action in the controller:

def create
  @post = Post.new(post_params)
  if @post.save
    redirect_to post_path(@post)
  else
    render :new, status: :unprocessable_entity
  end
end

Edit the Index View

Let's add some more info about the posts on the home page. Let's edit the index view to display more information about each post, including the publication date and a truncated version of the content:

<div class="container mt-5">
  <h1>All Posts</h1>
  <ul class="list-unstyled mt-3">
    <% @posts.each do |post| %>
      <li class="mt-5">
        <p class="fs-1"><%= link_to post.title, post_path(post) %></p>
        <p>
          <span>Published at</span>
          <span class="fw-bold"><%= post.created_at.strftime("%b %d, %Y") %></span>
        </p>
        <div>
          <%= post.content.truncate(200) %>
        </div>
      </li>
    <% end %>
  </ul>
  <p class="mt-5">
  <%= link_to "New Post", new_post_path %>
  </p>
</div>

The index.html.erb view file is updated to iterate over each post and display its title, publication date, and truncated content. The link_to helper method is used to generate links to the show view of each post, allowing users to navigate to the full content of each post.

Update Data

U in CRUD is the acronym for updating data. In this part of our tutorial, we will discuss updating data in a Ruby on Rails app. 

Define edit and update methods

Let's first add an edit method after the new method in the Posts controller:

 def edit; end

And after the create method, we'll add the update method:

def update
  if @post.update(post_params)
    redirect_to post_path
  else
    render :edit, status: :unprocessable_entity
  end
end

The edit method is added to allow users to access a form for editing a specific post. The method itself doesn't contain any logic other than rendering the edit template, which will display the form for editing the post's attributes.

The update method is added to handle the form submission for updating a post. It attempts to update the attributes of the post using the parameters submitted via the form. If the update is successful, the user is redirected to the show action of the updated post. If validation errors or other issues are preventing the post from being updated, the edit template is rendered again to allow the user to correct any errors.

Create an Edit View

Let's now add an edit view in the app/views/posts/ directory. Let's edit it to include the form:

<div class="container mt-5">
  <h1>Edit Post</h1>
  <%= render "form", post:@post %>
</div>

And now, we can link to the edit action in the show view. After the div containing the post content, we'll add this:

<div class="mt-5 d-flex align-items-center">
  <%= link_to "Edit Post", edit_post_path(@post), class:"text-info" %>
</div>

We'll add such a button to the index view as well after the div with the truncated post content:

<div class="mt-4 d-flex align-items-center">
  <%= link_to "Edit Post", edit_post_path(post), class:"text-info" %>
</div>

Delete Data

D in CRUD is the acronym for deleting data. In this part of our tutorial, we will discuss deleting data in a Ruby on Rails app. 

First, we'll add a destroy action to the controller: 

def destroy
  @post.destroy!
  redirect_to root_path
end

The destroy action is defined to delete the post instance and then redirect users to the root_path after successful deletion. The use of the destroy! method ensures that an exception is raised if the post cannot be deleted, providing a fail-safe mechanism.

Then, we can link to this action first from the show view. After the link to the edit action add this:

<%= button_to "Delete Post", @post, method: :delete, class: "btn btn-outline-danger ms-4" %>

And the same in the index view, after the link to the edit action:

<%= button_to "Delete Post", post, method: :delete, class: "btn btn-outline-danger ms-4" %>

Conclusion

In this tutorial, we explored the fundamental CRUD operations (Create, Read, Update, Delete) in a Ruby on Rails application. We learned how to create, read, update, and delete posts using Rails scaffolding and manual implementation. By following along, you've gained a better understanding of how to build a basic blogging application with full CRUD functionality.

Post last updated on April 29, 2024