Understand the basics of Ruby on Rails by building a blog app
Tags: rails, ruby, programming
Published: 2021-08-18 13:46:48

**Table of contents:**
- What is MVC?
- Directory structure of Ruby on Rails application
- Build a blog post application that supports all CRUD operations
  - How to generate models
  - How to write migration files
  - How to generate a controller and its views

Ruby on Rails is a web application framework written in Ruby. If you want a little bit more intro stuff, please go through [Rome can be built in a day](https://blog.akhilgautam.me/ruby-on-rails-rome-can-be-built-in-a-day) which I wrote as a part of my RoR(Ruby on Rails) series.


## What is MVC?
MVC stands for Model-View-Controller. MVC is an architectural pattern that makes it easy to put a specific piece of code at a specific place.

- **Model** is the one that usually maps to a database table and handles data related logic.
- **View** is the markup part that renders an `HTML` view.
- **Controller** is the one that is responsible for fetching data from model objects and send it to the **View**.

## Directory structure
![Screenshot 2021-08-17 at 6.51.49 PM.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1629206526512/VztZthy7y.png)

The above screenshot shows the directory structure of a vanilla Rails application. Most of the application code resides under `app` organized in their respective directories. The `Model-View-Controller` each has its respective directory as `models`, `views` and `directories`.

With the default configuration, let's start building a blog-post application.

## System setup
For this blog, we are going to use `https://ssh.cloud.google.com/` which is free and comes with Ruby and Rails installed. It also has a cloud IDE, so that we don't have to spend time setting up our local.

### STEP 1: Initialize a new project
Run the following in the IDE's terminal to create a new project
```bash
$ rails new blog_app
$ cd blog_app
```
Once the project is created, run `cd blog_app` to go to that directory. Open up the project directory in the IDE too. Now, we can try running the default app to see if everything went well.
```bash
$ rails s --port 3001
```

![Screenshot 2021-08-17 at 11.03.11 PM.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1629221623996/6Kp6iIFIW8.png)
On top of the IDE, click on the `web preview` as shown in the screenshot to open your application. If everything is well and good, you should see **Yay! You’re on Rails!** page.

### STEP 2: Generate the Post model
If your server is still running, turn it down and run the following command to generate the `Post` model along with the required migration file.
```bash
$ rails generate model Post
Running via Spring preloader in process 4985
      invoke  active_record
      create    db/migrate/20210817174254_create_posts.rb
      create    app/models/post.rb
      invoke    test_unit
      create      test/models/post_test.rb
      create      test/fixtures/posts.yml
```
Migrations are like version control for databases, allowing us to modify and share the application's database schema. `20210817174254_create_posts.rb` will be used define the schema of `posts` table.
We need to store `title`, and `description` of posts. `title` will be a *string* while `description` will be `text` column.
```ruby
# db/migrate/20210817174254_create_posts.rb

class CreatePosts < ActiveRecord::Migration[6.1]
  def change
    create_table :posts do |t|
      t.string :title
      t.text :description

      t.timestamps
    end
  end
end
```

By default, `t.timestamps` is present in every migration file which adds `created_at` and `updated_at` column to tables. 
#### Column types supported
<pre>
- `primary_key`
- `string`
- `text`
- `integer`
- `bigint`
- `float`
- `decimal`
- `numeric`
- `datetime`
- `time`
- `date`
- `binary`
- `boolean`
</pre>

Now, in order to reflect the migration changes in the database, we need to run the migration.
```bash
$ rails db:migrate
== 20210817174254 CreatePosts: migrating ======================================
-- create_table(:posts)
   -> 0.0035s
== 20210817174254 CreatePosts: migrated (0.0036s) =============================
```

### STEP 3: Generate PostsController and Views
When a route is hit by a client, it passes the execution to appropriate action of a controller. Let's say we open `localhost:3001/users` in the browser, the router will dispatch it to `index` action of `users_controller`. Action are methods defined inside the controller class.
For every `CRUD` operation, there has to be a route defined mapping to a controller's action. Rails by convention uses [resourceful routes](https://guides.rubyonrails.org/routing.html#crud-verbs-and-actions) to them. Routes are defined in the `config/routes.rb` file. Let's open up that file and add the following code:
```ruby
# config/routes.rb
resources :posts
```
To check all the routes that the above code creates, run `rails routes`.
<details>
<summary>Output of &nbsp;`rails routes`</summary>
<pre>
Prefix      Verb     URI Pattern                     Controller#Action
posts       GET     /posts(.:format)                 posts#index
            POST    /posts(.:format)                 posts#create
new_post    GET     /posts/new(.:format)             posts#new
edit_post   GET     /posts/:id/edit(.:format)        posts#edit
post        GET     /posts/:id(.:format)             posts#show
            PATCH   /posts/:id(.:format)             posts#update
            PUT     /posts/:id(.:format)             posts#update
            DELETE  /posts/:id(.:format)             posts#destroy
</pre>
</details>

Let create `PostsController` with all the actions mapping to each resourceful route.
```bash
$ rails generate controller posts
Running via Spring preloader in process 1559      
      create  app/controllers/posts_controller.rb
      invoke  erb      
      create    app/views/posts
      invoke  test_unit
      create    test/controllers/posts_controller_test.rb
      invoke  helper
      create    app/helpers/posts_helper.rb
      invoke    test_unit
      invoke  assets
      invoke    scss
      create      app/assets/stylesheets/posts.scss
```
The `posts_controller.rb` file will contain code to fetch data from the model objects and present it to the respective view. Add the following code to the controller file:
```ruby
# app/controllers/posts_controller.rb

class PostsController < ApplicationController
  before_action :set_post, only: %i[ show edit update destroy ]

  # GET /posts
  def index
    @posts = Post.all
  end

  # GET /posts/1
  def show
  end

  # GET /posts/new
  def new
    @post = Post.new
  end

  # GET /posts/1/edit
  def edit
  end

  # POST /posts
  def create
    @post = Post.new(post_params)

    respond_to do |format|
      if @post.save
        redirect_to @post, notice: "Post was successfully created."
      else
        render :new
      end
    end
  end

  # PATCH/PUT /posts/1
  def update
    respond_to do |format|
      if @post.update(post_params)
        redirect_to @post, notice: "Post was successfully updated."
      else
        render :edit
      end
    end
  end

  # DELETE /posts/1
  def destroy
    @post.destroy
    respond_to do |format|
      redirect_to posts_url, notice: "Post was successfully destroyed."
    end
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_post
      @post = Post.find(params[:id])
    end

    # Only allow a list of trusted parameters through.
    def post_params
      params.require(:post).permit(:title, :description)
    end
end
```
Every controller should inherit from `ApplicationController` so that it has access to helper methods and objects. In the `PostsController`, the first line is `before_action :set_post, only: %i[ show edit update destroy ]` which says Rails to first invoke `set_post` method whenever there is a request for `show`, `edit`, `update` or `destroy` action. 

`index` action assigns all the posts fetched from the database to `@posts` that will be used by its respective view. But wait, there is no view created yet. Let's also create a view for `index` action.
Views are named as `<action_name>.html.erb`. So for the `index` action, the view will be `index.html.erb`. `erb` is the template engine used by Rails to pass objects' values to templates, based on which the final `HTML` will be built and rendered.

```ruby
<%# app/views/posts/index.html.erb %>

<p id="notice"><%= notice %></p>

<h1>Posts</h1>

<table>
  <thead>
    <tr>
      <th>Title</th>
      <th>Description</th>
      <th colspan="3"></th>
    </tr>
  </thead>

  <tbody>
    <% @posts.each do |post| %>
      <tr>
        <td><%= post.title %></td>
        <td><%= post.description %></td>
        <td><%= link_to 'Show', post %></td>
        <td><%= link_to 'Edit', edit_post_path(post) %></td>
        <td><%= link_to 'Destroy', post, method: :delete, data: { confirm: 'Are you sure?' } %></td>
      </tr>
    <% end %>
  </tbody>
</table>

<br>

<%= link_to 'New Post', new_post_path %>
```
We are using a table to print all the posts. On top, we are printing `notice` that will contain the notice passed from the action.

Similarly, let's create view files for each of the actions.

- `show`

```ruby
<p id="notice"><%= notice %></p>

<p>
  <strong>Title:</strong>
  <%= @post.title %>
</p>

<p>
  <strong>Description:</strong>
  <%= @post.description %>
</p>

<%= link_to 'Edit', edit_post_path(@post) %> |
<%= link_to 'Back', posts_path %>
```
- `new`

```ruby
<h1>New Post</h1>

<%= form_with(model: @post) do |form| %>
  <% if post.errors.any? %>
    <div id="error_explanation">
      <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 %>

  <div class="field">
    <%= form.label :title %>
    <%= form.text_field :title %>
  </div>

  <div class="field">
    <%= form.label :description %>
    <%= form.text_area :description %>
  </div>

  <div class="actions">
    <%= form.submit %>
  </div>
<% end %>

<%= link_to 'Back', posts_path %>
```
- `edit`

```ruby
<h1>Editing Post</h1>

<%= form_with(model: @post) do |form| %>
  <% if post.errors.any? %>
    <div id="error_explanation">
      <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 %>

  <div class="field">
    <%= form.label :title %>
    <%= form.text_field :title %>
  </div>

  <div class="field">
    <%= form.label :description %>
    <%= form.text_area :description %>
  </div>

  <div class="actions">
    <%= form.submit %>
  </div>
<% end %>

<%= link_to 'Show', @post %> |
<%= link_to 'Back', posts_path %>

```
- `update` and `destroy` action doesn't need views as they are redirecting to `show` and `list` actions/pages respectively.

P.S: Both `new.html.erb` and `edit.html.erb` share a lot of common code. In that case we can extract out that common code to a `partial` and then render it in other templates.
```ruby
<%# app/views/posts/_form.html.erb %>

<%= form_with(model: post) do |form| %>
  <% if post.errors.any? %>
    <div id="error_explanation">
      <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 %>

  <div class="field">
    <%= form.label :title %>
    <%= form.text_field :title %>
  </div>

  <div class="field">
    <%= form.label :description %>
    <%= form.text_area :description %>
  </div>

  <div class="actions">
    <%= form.submit %>
  </div>
<% end %>
```
And then replace the common code in `new.html.erb` and `edit.html.erb` templates with just `<%= render 'form', post: @post %>`.

#### STEP 4: Run the application
We are all set now and we just need to re-run the app as we did in the beginning. Run `rails s -p3001` to run Rails on 3001 port and then open `localhost:3001/posts`. You will see something like this:

![Screenshot 2021-08-18 at 5.01.41 PM.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1629286425422/pobSXUkpq.png)

🎉🎉 We are done. Try creating, editing, and doing all the fun stuff with the app. 


## Bonus
Our app currently accepts text and shows it as it is. We can add markdown support to make it look like an actual blog application. For that first add `gem 'kramdown'` to `Gemfile`. The edit the show page as following:
```ruby
<% # app/views/posts/show.html.erb %>

<p id="notice"><%= notice %></p>

<p>
  <strong>Title:</strong>
  <%= @post.title %>
</p>

<p>
  <strong>Description:</strong>
  <%= sanitize Kramdown::Document.new(@post.description).to_html %>
</p>

<%= link_to 'Edit', edit_post_path(@post) %> |
<%= link_to 'Back', posts_path %>
```
DONE!! Now if we write the post's description in `markdown` format, it will be able to render it.


![Screenshot 2021-08-18 at 5.20.40 PM.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1629287462483/CP-MGtrSr.png)


### What if we want the resources to be served as REST API also?
In order to do that, we need to respond with JSON response whenever it is requested. To make serializing to JSON easy, we will use a gem called `jbuilder`. Let's first add `gem 'jbuilder', '~> 2.7'` to the `Gemfile`. Then we need to change our controller to the following:
```ruby
# app/controllers/posts_controller.rb

class PostsController < ApplicationController
  before_action :set_post, only: %i[ show edit update destroy ]

  # GET /posts or /posts.json
  def index
    @posts = Post.all
  end

  # GET /posts/1 or /posts/1.json
  def show
  end

  # GET /posts/new
  def new
    @post = Post.new
  end

  # GET /posts/1/edit
  def edit
  end

  # POST /posts or /posts.json
  def create
    @post = Post.new(post_params)

    respond_to do |format|
      if @post.save
        format.html { redirect_to @post, notice: "Post was successfully created." }
        format.json { render :show, status: :created, location: @post }
      else
        format.html { render :new, status: :unprocessable_entity }
        format.json { render json: @post.errors, status: :unprocessable_entity }
      end
    end
  end

  # PATCH/PUT /posts/1 or /posts/1.json
  def update
    respond_to do |format|
      if @post.update(post_params)
        format.html { redirect_to @post, notice: "Post was successfully updated." }
        format.json { render :show, status: :ok, location: @post }
      else
        format.html { render :edit, status: :unprocessable_entity }
        format.json { render json: @post.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /posts/1 or /posts/1.json
  def destroy
    @post.destroy
    respond_to do |format|
      format.html { redirect_to posts_url, notice: "Post was successfully destroyed." }
      format.json { head :no_content }
    end
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_post
      @post = Post.find(params[:id])
    end

    # Only allow a list of trusted parameters through.
    def post_params
      params.require(:post).permit(:title, :description)
    end
end

```
Now when the request comes for JSON, it will fetch the data and render the respective `jbuilder` file which in turn will return serialized JSON. Let's now add the respective `jbuilder` files under `app/views/posts` along with the partial containing common `jbuilder` code.
```ruby
# app/views/posts/_post.json.jbuilder
json.extract! post, :id, :title, :description, :created_at, :updated_at
json.url post_url(post, format: :json)


# app/views/posts/index.json.jbuilder
json.array! @posts, partial: "posts/post", as: :post

# app/views/posts/show.json.jbuilder
json.partial! "posts/post", post: @post
```
Now, we can make fetch requests to get JSON responses from the API. Let's try running `fetch('https://localhost:3001/posts/1.json').then(res => res.json()).then(json_res => console.log(json_res))` in the browser console with `localhost:3001` open. You will get the `json` response as configured in the `show.json.jbuilder` file.

![Screenshot 2021-08-18 at 6.02.57 PM.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1629289992684/a2QGKasQj.png)

### Optional resources
- Ruby/Rails naming convention: https://gist.github.com/iangreenleaf/b206d09c587e8fc6399e
- Rails guides: https://guides.rubyonrails.org/index.html
- Jbuilder: https://github.com/rails/jbuilder
- Kramdown: https://github.com/gettalong/kramdown

That is it for this. I know I am not able to cover everything but it is not even possible in one blog.
If you really liked this blog, don't forget to like it and follow me. Thanks for reading.