Create a Blog in Rails6, from the install of RoR to the deploy of the Project

Sebastián Vidal Aedo
13 min readJun 6, 2021

--

I’m using Ubuntu version: 18.04 (a bit old)

Since this is an installation from zero, the first thing you need do is enabled the Canonical repositories.

then, in terminal

sudo apt update

Step 0: Install necesary applicatios for development in Ruby on Rails

Install curl y yarn

sudo apt install curlcurl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash -sudo apt-get update && sudo apt-get install yarncurl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.listsudo apt update

Install git, sqlite (db default for RoR app) and others libraries

sudo apt install git-core zlib1g-dev build-essential libssl-dev libreadline-dev libyaml-dev libsqlite3-dev sqlite3 libxml2-dev libxslt1-dev libcurl4-openssl-dev software-properties-common libffi- dev nodejs

Install rbenv, the version manager for Ruby and his plugins

git clone https://github.com/rbenv/rbenv.git ~/.rbenvecho 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrcecho 'eval "$(rbenv init -)"' >> ~/.bashrcexec $SHELLgit clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-buildecho 'export PATH="$HOME/.rbenv/plugins/ruby-build/bin:$PATH"' >> ~/.bashrcexec $SHELL

Step 1: Ruby on Rails

1.1 Ruby

At last, using rbenv install Ruby 2.7

rbenv install 2.7.1 
rbenv global 2.7.1
ruby -v

1.2 Rails

Install Rails

gem install bundler
gem install rails -v 6
rbenv rehash
rails -v

Step 2: Data base

We have a lot of option of databases to use, but here we show you how install mysql and postgresql. When we pass to the project itself we are going to use postgresql

2.1 Mysql

sudo apt install mysql-server mysql-client libmysqlclient-dev

To con?gure the root password you must use sudo. With the next instruction you launch the wizard to con?gure the user and the access (we recommend say YES to all)

sudo mysql_secure_installation
sudo mysql -u root -p

Add new user

sudo mysql
CREATE USER ‘username’@‘localhost’ IDENTIFIED BY ‘password’;
GRANT ALL PRIVILEGES ON *.* TO ‘username’@‘localhost’ WITH GRANT OPTION;
exit

After this you can access to database using the new user and the password settled

mysql -u username -p

2.2 Postgres

I will use the version 10 because is the default version in my Ubuntu distribution. How to know what is your default package version? Just write in terminal postgresql- and then press tab. To install just do

sudo apt install postgresql-10 libpq-dev

The create a new user (for the example we name the user userpg)

sudo -u postgres createuser userpg -s

Access to postgres console and set the password for the new user

sudo -u postgres psql
\password userpg

Step 3: Blog

The database selected is added by a parameter in the rails new instruction

rails new appname -d mysql
rails new appname -d postgresql

3.1 Config database

For this part of the tutorial i will use Postgresql

For use postgresql in our Rails proyect we need to add the user and password to access the db in config/database.yml file

config/database.yml

If you see, im using ENV vars. This vars are in .env fille in the root of proyect. For use this you need to install dotenv-rails gem and create two files .env and .env.example. The first is for the vars you currently used (not versioned file) and the other is for use like an example (not ignored in .gitignore) and show the vars the project use without give the secrets to the repo and every one who pulled from git.

.env & .env.example

At last, for create the development and test database

rake db:create

3.2 Bootstrap and Jquery

The best way to add Bootstrap and Jquery is using yarn, here is how.

3.3 The Gems

3.3.1 Devise

The best solution to manage the users. Add the gem to Gemfile

gem ‘devise’

and then, in terminal do

bundle install

Then install devise and create all the devise things for User model

rails g devise:install
rails g devise User
rails g devise:views
rake db:migrate

Add a role field to users with user as default value

rails g migration AddRoleToUsers role:integer

then apply the changes

rake db:migrate

And modify the User model adding the role ?eld and the default value

after_initialize do
if self.new_record?
self.role ||= :user
end
end
enum role: [:user, :admin]

In this occation, to make an admin we will do by console

user.admin!

3.3.2 CanCanCan

For this we add the gem ‘cancancan’ to Gemfile file

gem ‘cancancan’

and then, in terminal do

bundle install

Create the model ability

rails g cancan:ability

Thats all for now.

3.4 The Model Diagram

I made the diagram in an online platform called DbDiagram. I recommend it a lot, its very easy to use. This diagram dont show the default ?elds Rails create (and devise), only the necessaries to understand the Model (of course, is just an initial design).

Simple Model

3.5 The Scaffold: passing the model to code

A scaffold make all the CRUD, with the next commandos we create all the views for post and comments

rails g scaffold category namerails g scaffold post title image slug content:text user:references category:referencesrails g scaffold comment content:text user:references post:referencesrake db:migrate

Now update the models (adding dependent: :delete_all for destroy in cascade) and indicating image is an attachment.

class Category < ApplicationRecord
has_many :posts
end
class Post < ApplicationRecord
belongs_to :user
belongs_to :category
has_many :comments, dependent: :delete_all
has_one_attached :image
end
class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_many :posts, dependent: :delete_all
has_many :comments, dependent: :delete_all
end

3.6 Define Root Path

Create a controller and a view for the root_path

rails g controller home index

Then update the routes file config/routes.rb

root to: "home#index"

3.7 Define the access for the roles

Add the restrictions in app/models/ability.rb

app/models/ability.rb

With this we say to the application the admin can do anything and the other registered users can create Comments and see a singlePost. In this occasion we can’t delete comments.

In app/controllers/application_controller.rb add the restrictions configured by CanCanCan to apply before every controller will run.

app/controllers/application_controller.rb

Update app/controllers/posts_controllers, just below the before_action add load_and_authorize_resource to apply the restrictions of access (just linke in Categories and Comments controls).

Modify app/controllers/home_controller.rb, add the skip_authorization_check, with this any person can see the root_page.

3.8 Custom Controllers, Models and View 3.8.1 Post

Remove the User and Slug from _form.html.erb, make the CategoryField a Select List and Image a file_field. At last asign the User and the Slug to the Post in the create controller.

For uploads files correctly you only need to enabled active_storage

rails active_storage:install
rake db:migrate
app/views/posts/_form.html.erb
app/controllers/posts_controller.rb

I assign the slug in create method (pretty urls), for that you must change the routes of Post too

resources :posts, param: :slug

also edit the method set_post and the strong_params in PostController

def set_post
@post = Post.where(slug: params[:slug]).first
end
def post_params
params.require(:post).permit(:title, :image, :content, :category_id)
end

and for last, update the Post Model redefining the to_param method

class Post < ApplicationRecord
belongs_to :user
belongs_to :category
has_many :comments, dependent: :delete_all
has_one_attached :image
def to_param
"#{slug}"
end
end

3.8.2 Comments

Nothing for now.

3.8.3 Categories

Nothing for now.

Step 4: The Template

Here im going to use a simple html/css styles i build some weeks ago. You can see it in simple-blog-bootstrap and download here

https://sebavidal10.github.io/simple-blog-bootstrap/

The first step is copy the styles.css from template to app/assets/stylesheets and remove the content of the scaffolds.scss. The images (logo.png and sva.png) let in directory /public.

4.1 Home

The next step is add font_awesome and the google fonts (Roboto and Cabin) in the head of application.html.erb. After that you have to create the partials for navbar and footer and add to the layout (updating the src of images in both files). At last put the tag <%= yield %> in a div element with class content.

app/views/layouts/application.html.erb
app/views/partials/_navbar.html.erb
app/views/partials/_footer.html.erb

To show the content in the home page, we just copy the content from banner in the app/views/home/index.html.erb. The src of the image is in Unsplash and the text are fakes, we dont need change it, but if you want to use it, you must accomplish with the rules of references and licences declared by any owner of any photo in Unsplash

app/views/home/index.html.erb

After continue you need to create a lot of post, I make 9 post with different images and categories. Then create a partial for card and repeat for any post in the app/views/home/index.html.erb and update de home_controller to pass the posts.

app/controllers/home_controller.rb
app/views/home/index.html.erb
app/views/partials/_card.html.erb

4.2 Single Post

All the content is inside of the div “content”, just copy all. Now, to begin we need to create 2 partials: _categories, _last_posts, and for display data in it the controller of show need to pass all categories and the last 5 posts (without the current post).

app/controllers/posts_controller.erb
app/views/partials/_categories.html.erb
app/views/partials/_last_posts.html.erb

4.2.1 Next and Previous Post

Create method next and previous in the Post model (app/models/post.rb)

4.2.2 New comment with ujs

Customize the app/views/comments/_form.html.erb for create comments and incorporate it like in his “view” for new

<%= form_with(model: Comment.new) do |form| %>
<%= form.hidden_field :post_id, :value => @post.id %>
<label>i say:</label>
<%= form.text_area :content, class: "form-control" %>
<div class=”text-right”>
<%= form.submit class: "mt-1 btn btn-dark" %>
</div>
<% end %>

(Is not a good practice pass hidden the post_id, this part needs refactor)

The Comment Controller need a few updates. First remove the user_id from strong_params and then add it in the create method ascurrent_user. By last, add the js format

def create
@comment = Comment.new(comment_params)
@comment.user = current_user
respond_to do |format|
if @comment.save
format.html { redirect_to @comment, notice: ‘Comment was successfully created.’ }
format.js
format.json { render :show, status: :created, location: @comment }
else
format.html { render :new }
format.json { render json: @comment.errors, status: :unprocessable_entity }
end
end
end

Then create in app/views/posts/ a create.js.erb file and a partial with the structure of one comment.

document.querySelector('#all_comments').insertAdjacentHTML('beforeen d', '<%= escape_javascript(render @comment) %>') ;
document.querySelector('#comment_content').value = "";
document.querySelector('#total_comments').innerHTML = "<%= @comment.post.comments.count %>";

The alert will be a small label added in a partial

app/views/comments/_alert_comment.html.erb

And in app/views/comments/create.js.erb add a new line

document.querySelector('#all_comments').insertAdjacentHTML('beforeend', '<%= escape_javascript(render "alert_comment") %>');
a small label info in a partial

This is not the most elegant way, but it works. Someday i will refactor here.

4.2.3 Show all comments

Partial called _comment.html.erb in comments views. Then load this partial in show view for Post

app/views/comments/_comment.html.erb

Until now, the show view for post is this

app/views/posts/show.html.erb

4.3 About me

For this page i will use the default content from the template, and replace it the info directly, all in a new view from in the Home Controller

def about_me
end

You need add the new route

get "/about_me" to: "home#about_me" as: "about_me"

In the about page exist Modals for the images. The full documentation is here. In the template exist a js function, move it to layout or js ?le.

4.4 Contact

Here im using a link to default app mail in your system, something like this

<a href="mailto:user@mail.com">contact</a>

(maybe some day i’ll make an update here)

4.5 Alerts

Make a partial called _notify.html.erb

<% if notice %>
<div class="toast" id="myToast" style="position: fixed; z-index: 999; bottom: 0px; right: 0px;">
<div class="toast-header bg-custom-black text-white">
<strong class="mr-auto"><i class="fa fa-grav"></i> Hi!</strong>
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast">&times;</button>
</div>
<div class="toast-body">
<%= notice %>
</div>
</div>
<% end %>

and add to layout

<body>
<%= render "partials/notify" %>

and in js (to start and has 10 seconds to read the notify)

$('.toast').toast({
delay: 10000
})
$('.toast').toast('show');

Step 5: Login with Facebook

Go to https://developers.facebook.com and create an app. Get the AppId and the SecretKey. Thats all in Facebook :D

Then add Omniauth Facebook to Gemfile

gem 'omniauth-facebook'
bundle install

We add a field for the name (which will be brought from facebook) and another to store the provider (reference to the user’s source)

rails g migration AddOmniauthToUsers provider:string uid:string name:stringrails db:migrate

Modify config/initializers/devise.rb

config.omniauth :facebook, "AppId", "SecretKey", callback_url: http://localhost:3000/auth/facebook/callback"

Update the User model

devise :omniauthable, :omniauth_providers => [:facebook]

Devise automatically adds a button to authenticate with Facebook in your login view.

Sign in with Facebook

For the authentication process be complete we create a directory (users) and a file (omniauth_callbacks_controller.rb)

app/controller/users/omniauth_callbacks_controller.rb

and we add the following method (facebook and failure)

class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController  def facebook
@user = User.from_omniauth(request.env["omniauth.auth"])
if @user.persisted?
sign_in_and_redirect @user, :event => :authentication
set_flash_message(:notice, :success, :kind => "Facebook") if is_navigational_format?
else
session["devise.facebook_data"] = request.env[“omniauth.auth”]
redirect_to new_user_registration_url
end
end
def failure
redirect_to root_path
end
end

Finally we add the facebook methods to the User model

def self.new_with_session(params, session) 
super.tap do |user|
if data = session[“devise.facebook_data”] && session[“devise.facebook_data”][“extra”][“raw_info”]
user.email = data[“email”] if user.email.blank?
end
end
end
def self.from_omniauth(auth)
where(provider: auth.provider, uid: auth.uid).first_or_create do
|user|
user.email = auth.info.email
user.password = Devise.friendly_token[0,20]
user.name = auth.info.name
end
end

Update the routes for devise

# devise_for :users
devise_for :users, :controllers => {
:omniauth_callbacks => "users/omniauth_callbacks" }

With all this we can login using the facebook account to the Blog.

Step 6: Some additional things

6.1 Update social links

Don’t forget remove or update the links to Facebook, Twitter and Instagram in navbar and footer.

This are in about_me too.

6.2 Remove access to views that won’t be used and add Buttons to admin the content

In navbar partial, validate if user is authenticated and show the buttons to manage categories and posts and a login/logout button when apply.

app/views/partials/_navbar.html.erb

In routes file disable the routes we don’t use, in this case just need to do

resources :comments, only: [:create]

And for disable the register of new user remove the method :registerable from devise in user.rb

6.3 Default bootstrap styles for others views

Use the Bootstrap styles to custom and make better the default forms created by scaffolds.

new login

Update the table option with icons from font_awesome and bootstrap class

tale and icons

For the Post new/edit form, we area going to use Trix Editor (included in bootstrap 6). To enabled:

rails action_text:install
reake db:migrate

For use images inside the richText field install this gem

gem 'image_processing', '~> 1.0'

Then set in Post Model the field we will use as RichText

has_rich_text :content

At last, update the passing form.text_area to form.rich_text_area (removing bootstrap) and create a lead method in Post Model to show a few words of the content in cards view and in single post view.

def lead
lead = ""
if content.body
lead = content.body.html_safe
lead.gsub!(/(<[^>]*>)|\n|\t/s) {" "}
end
return lead
end

Not elegant, but’s working. (another refactor here :) )

6.4 Categories

To manage the colors of categories i made two function (could be just one) in the Model of Category

def color_category 
case id
when 1
"label-orange"
when 2
"label-blue"
when 3
"label-green"
else
"label-red"
end
end
def color case id
when 1
"color-orange"
when 2
"color-blue"
when 3
"color-green"
else
"color-red"
end
end

Step 7: Deploy

Of course you need an account in heroku. Then, in your local machine go to console and login to heroku

the console open login in browser

7.1 Create heroku project

For the deploy to be successful it is mandatory that your project has git. If you have it, run

heroku create

This scommand creates a project associated with your heroku account create a project in your and return his name.

7.2 Deploy to Heroku

git push heroku master

the promt should end with

* [new branch] master -> master

7.3 The Database

heroku run rake db:migrate

Here i have a error

Caused by:
PG::ConnectionBad: FATAL: database "xxxxxxxxx" does not exist

Solution: Update the content of config/database.yml

production:
<<: *default
database: blog_production
username: blog
password: <%= ENV['BLOG_DATABASE_PASSWORD'] %>

then run a git commit and after that

git push heroku master
heroku run rake db:migrate

and the things works!

To check is running you can run heroku ps:scale web=1 and for launch it

heroku open

7.4 Admin User

To create an Admin User you can use rails console in heroku

heroku run rails c

Create an user

u = User.new
u.email = "admin@admin.com"
u.name = "Admin"
u.password = "xxxxxxxxxxx"
u.save

Set user as admin

u.admin!

and we are READY!

Photo by Clark Tibbs on Unsplash

I hope this help you to get inside (and love) the Rails world. Any comments or refactoring, im here to listen and apply :)

Bye!

--

--

No responses yet