-
-
Notifications
You must be signed in to change notification settings - Fork 588
/
history.rb
143 lines (130 loc) · 4.24 KB
/
history.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
module FriendlyId
#
## 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
module History
module Configuration
def dependent_value
dependent.nil? ? :destroy : dependent
end
end
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
# Configures the model instance to use the History add-on.
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
module FinderMethods
include ::FriendlyId::FinderMethods
def exists_by_friendly_id?(id)
super || joins(:slugs).where(slug_history_clause(id)).exists?
end
private
def first_by_friendly_id(id)
super || slug_table_record(id)
end
def slug_table_record(id)
select(quoted_table_name + ".*").joins(:slugs).where(slug_history_clause(id)).order(Slug.arel_table[:id].desc).first
end
def slug_history_clause(id)
Slug.arel_table[:sluggable_type].eq(base_class.to_s).and(Slug.arel_table[:slug].eq(id))
end
end
private
# 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.
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
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
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
end
end