Create a Blog in Rails6, from the install of RoR to the deploy of the Project
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
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.
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).
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
endclass Post < ApplicationRecord
belongs_to :user
belongs_to :category
has_many :comments, dependent: :delete_all
has_one_attached :image
endclass 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
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.
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
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
enddef 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
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.
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
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.
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).
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
And in app/views/comments/create.js.erb add a new line
document.querySelector('#all_comments').insertAdjacentHTML('beforeend', '<%= escape_javascript(render "alert_comment") %>');
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
Until now, the show view for post is this
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">×</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.
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
enddef 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.
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.
Update the table option with icons from font_awesome and bootstrap class
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
enddef 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
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!
I hope this help you to get inside (and love) the Rails world. Any comments or refactoring, im here to listen and apply :)
Bye!