module FriendlyId::History
@guide begin
## History: Avoiding 404’s When Slugs Change
FriendlyId’s {FriendlyId::History History} module adds the ability to store a log of a model’s slugs, so that when its friendly id changes, it’s still possible to perform finds by the old id.
The primary use case for this is avoiding broken URLs.
### Setup
In order to use this module, you must add a table to your database schema to store the slug records. FriendlyId
provides a generator for this purpose:
rails generate friendly_id rake db:migrate
This will add a table named ‘friendly_id_slugs`, used by the {FriendlyId::Slug} model.
### Considerations
Because recording slug history requires creating additional database records, this module has an impact on the performance of the associated model’s ‘create` method.
### Example
class Post < ActiveRecord::Base extend FriendlyId friendly_id :title, :use => :history end class PostsController < ApplicationController before_filter :find_post ... def find_post @post = Post.friendly.find params[:id] # If an old id or a numeric id was used to find the record, then # the request slug will not match the current slug, and we should do # a 301 redirect to the new path if params[:id] != @post.slug return redirect_to @post, :status => :moved_permanently end end end
@guide end
Public Class Methods
Configures the model instance to use the History
add-on.
# File lib/friendly_id/history.rb, line 72 def self.included(model_class) model_class.class_eval do has_many :slugs, -> { order(id: :desc) }, **{ as: :sluggable, dependent: @friendly_id_config.dependent_value, class_name: Slug.to_s } after_save :create_slug end end
# File lib/friendly_id/history.rb, line 62 def self.setup(model_class) model_class.instance_eval do friendly_id_config.use :slugged friendly_id_config.class.send :include, History::Configuration friendly_id_config.finder_methods = FriendlyId::History::FinderMethods FriendlyId::Finders.setup(model_class) if friendly_id_config.uses? :finders end end
Private Instance Methods
# File lib/friendly_id/history.rb, line 122 def create_slug return unless friendly_id return if history_is_up_to_date? # Allow reversion back to a previously used slug relation = slugs.where(slug: friendly_id) if friendly_id_config.uses?(:scoped) relation = relation.where(scope: serialized_scope) end relation.destroy_all unless relation.empty? slugs.create! do |record| record.slug = friendly_id record.scope = serialized_scope if friendly_id_config.uses?(:scoped) end end
# File lib/friendly_id/history.rb, line 137 def history_is_up_to_date? latest_history = slugs.first check = latest_history.try(:slug) == friendly_id if friendly_id_config.uses?(:scoped) check &&= latest_history.scope == serialized_scope end check end
If we’re updating, don’t consider historic slugs for the same record to be conflicts. This will allow a record to revert to a previously used slug.
# File lib/friendly_id/history.rb, line 111 def scope_for_slug_generator relation = super.joins(:slugs) unless new_record? relation = relation.merge(Slug.where("sluggable_id <> ?", id)) end if friendly_id_config.uses?(:scoped) relation = relation.where(Slug.arel_table[:scope].eq(serialized_scope)) end relation end