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 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
HTMLview. - Controller is the one that is responsible for fetching data from model objects and send it to the View.
Directory structure
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
$ 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.
$ rails s --port 3001

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.
$ 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.
# 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
- `primary_key` - `string` - `text` - `integer` - `bigint` - `float` - `decimal` - `numeric` - `datetime` - `time` - `date` - `binary` - `boolean`
Now, in order to reflect the migration changes in the database, we need to run the migration.
$ 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 to them. Routes are defined in the config/routes.rb file. Let's open up that file and add the following code:
# config/routes.rb
resources :posts
To check all the routes that the above code creates, run rails routes.
Output of ย rails routes
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
Let create PostsController with all the actions mapping to each resourceful route.
$ 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:
# 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.
<%# 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
<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
<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
<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 %>
-
updateanddestroyaction doesn't need views as they are redirecting toshowandlistactions/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.
<%# 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:
๐๐ 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:
<% # 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.
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:
# 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.
# 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.
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.




Top comments (0)