Ruby on Rails Tutorial: How to Create a Project

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!


About the author



Hi, I'm Nathan. Thanks for reading! Keep an eye out for more content being posted soon.


Leave a Reply

Your email address will not be published. Required fields are marked *