Adding the gems
Open your Gemfile and add the following lines:
# Gemfile
gem "friendly_id"
gem "cancancan"
Then download and add them to the bundle:
bundle
Installing the gems
Run the gems’ installers and migrations.
bundle exec rails generate friendly_id
bundle exec rails generate cancan:ability
bundle exec rails db:migrate
Setting up the model
extend FriendlyId
adds the gem’s methods and behavior to the model.friendly_id :slug_candidates, use: [:slugged, :history]
tells FriendlyId to:- generate slugs by calling the
slug_candidates
method, - automatically generate slugs,
- historicize previous slugs, to avoid broken links.
- generate slugs by calling the
# app/models/my_model.rb
class MyModel < ApplicationRecord
extend FriendlyId
friendly_id :slug_candidates, use: [:slugged, :history]
validates :name, presence: true
private
def slug_candidates
[:name, [:id, :name]]
end
def should_generate_new_friendly_id?
name_changed? || super
end
end
To note
The slug_candidates
method should return an array of symbols corresponding to method, or strings, which will be used to generate unique slugs.
The should_generate_new_friendly_id?
method is overridden to force slug generation when the underlying attribute changes.
Configuring CanCanCan in controllers
CanCanCan recommends always checking for authorization, skipping checks in specific controllers and actions explicitly.
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
check_authorization
rescue_from CanCan::AccessDenied do |exception|
respond_to do |format|
format.json { head :forbidden, content_type: 'text/html' }
format.html { redirect_to main_app.root_url, notice: exception.message }
format.js { head :forbidden, content_type: 'text/html' }
end
end
end
Add load_and_authorize_resource
in controllers that need authorization.
This will automatically fetch the resources the current_user has access to, or raise a CanCan::AccessDenied
otherwise.
Add skip_authorization_check
in controllers that don’t need authorization, i.e. landing pages, custom 404, etc.
Setting up FriendlyId in controllers
By telling FriendlyId
to use the :slugged
module, Rails will automatically use slug instead of id to generate and parse URLs.
However, in order to redirect URLs using a previous slug, we need to override FriendlyId’s default behaviour,
which is to load the resource without emitting warnings or redirecting.
We’ll do that by defining a load_or_redirect
method.
This method (which should be private) loads the appropriate record in a model instance, or redirects if the given slug has been superseded.
In our controllers we use CanCanCan to load and authorize instance models by calling load_and_authorize_resource
.
This method will not attempt to set the instance model if it has already been set.
# app/controllers/my_models_controller.rb
class MyModelsController < ApplicationController
# Redirect if a previous slug is used because FriendlyId's history module
before_action :load_or_redirect, only: :show
# Tell CanCanCan to populate @need and @needs
load_and_authorize_resource
# [Usual actions: index, new, show, edit, update, etc]
private
def load_or_redirect
slug = params.fetch(:id)
@my_model = MyModel.find_by(slug: slug)
return unless @my_model.nil?
@my_model = MyModel.find_by_friendly_id(slug)
raise ActiveRecord::RecordNotFound if @my_model.nil?
redirect_to @my_model, status: :moved_permanently, notice: "You have been redirected, please use the current URL and update your bookmarks."
end