Ruby on Rails is a popular web framework that operates on the MVC and CRUD architecture. In this post, I will create a simple blog to demonstrate how to create a project on Rails.

Create the project
First, create a new rails project, and install gems with the following commands. I’m using myblog
as the project slug.
rails new myblog
cd myblog
bundle install
Now, open a new terminal tab using CTRL + T, and run the following command:
rails server
This will run the web server in the background while we construct our app, which we can view by visiting http://127.0.0.1:3000.

Now, let’s generate a controller for the “index” page:
rails generate controller pages index
Now, set the path for the homepage in the config/routes.rb
file:
Rails.application.routes.draw do
root 'pages#index'
end
Disable Turbo Rails
# gem "turbo-rails" Disable turbo-rails
Remove turbo-rails from app/javascript/application.js:
//import "@hotwired/turbo-rails"
Enable images using Active Storage
Uncomment the following line from Gemfile:
gem "image_processing", ">= 1.2"
Install Active Storage:
rails active_storage:install
Users and Authentication using Devise
Now let’s install Devise so that we can have user models and authentication on our app. Add the following to Gemfile
:
gem 'devise', '~> 4.8', '>= 4.8.1'
Run the following commands:
bundle install
rails generate devise:install
Add the following line to config/environments/development.rb
:
config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
Run the following:
rails generate devise:views
rails generate devise user
rails db:migrate
Modify config/initializers/devise.rb
to the following:
config.sign_out_via = [:get, :delete]
Add a username field to Devise
Create devise controllers:
rails generate devise:controllers user
Update config/routes.rb
to the following:
devise_for :users, controllers: { registrations: "user/registrations" }
Add username field:
rails generate migration AddUsernameToUsers username:string:uniq
rails db:migrate
Update registrations_controller.rb:
# Uncomment:
before_action :configure_sign_up_params, only: [:create]
before_action :configure_account_update_params, only: [:update]
...
def configure_sign_up_params
devise_parameter_sanitizer.permit(:sign_up, keys: [:username])
end
def configure_account_update_params
devise_parameter_sanitizer.permit(:account_update, keys: [:username])
end
Add the following to registrations/new.html.erb
and registrations/edit.html.erb
:
<div class="field form-group">
<%= f.label :username %><br />
<%= f.text_field :username, class:"form-control", autofocus: true, autocomplete: "username" %>
</div>
Posts model
Now, create the posts model with the following command:
rails generate scaffold post title:string content:string thumbnail:attachment user_id:integer:index
rails db:migrate
Add associations to Post model:
class Post < ApplicationRecord
belongs_to :user
end
Add associations to User model:
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_many :post
end
Remove User ID fields from _form.html.erb
, _post.html.erb
, and _post.json.jbuilder
within app/views/posts.
Add user_id
to create method and remove user_id
from the PostController
post_params
:
class PostsController < ApplicationController
...
def create
@post = Post.new(post_params)
@post.user_id = current_user.id
respond_to do |format|
...
end
private
...
def post_params
params.require(:post).permit(:title, :content, :thumbnail)
end
...
end
end
Change the root route to posts within config/routes.rb
:
root 'posts#index'
Install bootstrap
Now, let’s install the bootstrap
gem, and uncomment the sassc-rails
gem within Gemfile
:
# Bootstrap
gem "bootstrap", "~> 5.2.2"
gem "jquery-rails"
# Use Sass to process CSS
gem "sassc-rails"
Install them with bundle install
. Then, rename app/assets/stylesheets/application.css
to application.scss
. Add the following to application.scss
and restart the server:
@import 'bootstrap';
Run importmap to set imports within config/importmap.rb
:
importmap pin bootstrap
Within importmap.rb
, change the following line:
pin "@popperjs/core", to: "https://unpkg.com/@popperjs/core@2.11.2/dist/esm/index.js"
Add the following to app/javascript/application.js
:
//= require jquery3
//= require popper
//= require bootstrap-sprockets
Add the following to config/initializers/assets.rb
:
Rails.application.config.assets.precompile += %w(bootstrap.min.js popper.js)
Create a file at app/views/partials/_navbar.html.erb
:
<nav class="navbar navbar-expand-lg bg-light mb-2 shadow-sm">
<div class="container-fluid">
<a class="navbar-brand" href="/">My Blog</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbar" aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbar">
<ul class="navbar-nav ms-auto mb-2 mb-lg-0">
<% if user_signed_in? %>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
<%= current_user.username %>
</a>
<ul class="dropdown-menu dropdown-menu-end">
<li><%= link_to "My Account", edit_user_registration_path, class:"dropdown-item" %></li>
<li><%= link_to "Log Out", destroy_user_session_path, method: :delete, class:"dropdown-item" %></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#">Admin</a></li>
</ul>
</li>
<% else %>
<li class="nav-item">
<%= link_to "Log In", new_user_session_path, class:"nav-link" %>
</li>
<li class="nav-item">
<%= link_to "Sign Up", new_user_registration_path, class:"nav-link" %>
</li>
<% end %>
</ul>
</div>
</div>
</nav>
Add the following to app/partials/_alert.html.erb
:
<div class="alert alert-warning alert-dismissible fade show" role="alert">
<%= notice %>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
Reference the partial in our application.html.erb
layout:
<body>
<%= render "partials/navbar" %>
<% if notice %>
<%= render "partials/alert" %>
<% end %>
<%= yield %>
</body>
Devise View Theming
Replace all instances of <div class="field">
with <div class="field form-group">
Replace all instances of f.email_field :email,
with f.email_field :email, class:"form-control",
Replace all instances of f.password_field :password,
with f.password_field :password, class:"form-control",
Replace all instances of f.password_field :current_password,
with f.password_field :current_password, class:"form-control",
Replace all instances of <%= render "devise/shared/links" %>
with <br/><%= render "devise/shared/links" %>
Replace all instances of (grep enabled) <%= f.submit "([^"]+)"
with <%= f.submit "$1", class:"btn btn-primary"
Replace the following within sessions/new.html.erb
: <div class="field form-group">...<%= f.check_box :remember_me
with <div class="field form-check">...<%= f.check_box :remember_me, class:"form-check-input"
Replace the following within registrations/edit.html.erb
: <% if @minimum_password_length %>...<br />
with <% if @minimum_password_length %>
Replace the following within registrations/edit.html.erb
: <%= button_to "Cancel my account",...%>
with <%= button_to "Cancel my account",...class:"btn btn-outline-danger" %>
Put the following into application.scss
:
.field {
margin: 1em 0em;
}
h1, h2, h3, h4, h5, h6 {
margin-top: 1em;
}
Posts view theming
Remove all instances of <p style="color: green"><%= notice %></p>
Update the following within _form.html.erb
: <div class="field form-group">
, <%= form.submit class: "btn btn-primary" %>
Replace the following within posts/show.html.erb
: <%= button_to "Destroy this post",...%>
with <%= button_to "Destroy this post",...class:"btn btn-outline-danger" %>
Update _post.html.erb
to the following:
<div id="<%= dom_id post %>">
<h1><%= post.title %></h1>
<span>Written by <%= link_to post.user.username, edit_user_registration_path %> </span>
<p>
<%= post.content %>
</p>
</div>
Conclusion

I still prefer to use the Django web framework, but it is nice to try out everything out there. I will hopefully make a post on FastAPI, with something on Flask in the future. Thanks for reading!