forked from technoweenie/permalink_fu
-
Notifications
You must be signed in to change notification settings - Fork 0
/
permalink_fu.rb
165 lines (152 loc) · 5.49 KB
/
permalink_fu.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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
begin
require 'iconv'
rescue Object
puts "no iconv, you might want to look into it."
end
require 'digest/sha1'
module PermalinkFu
class << self
attr_accessor :translation_to
attr_accessor :translation_from
# This method does the actual permalink escaping.
def escape(string)
result = ((translation_to && translation_from) ? Iconv.iconv(translation_to, translation_from, string) : string).to_s
result.gsub!(/[^\x00-\x7F]+/, '') # Remove anything non-ASCII entirely (e.g. diacritics).
result.gsub!(/[^\w_ \-]+/i, '') # Remove unwanted chars.
result.gsub!(/[ \-]+/i, '-') # No more than one of the separator in a row.
result.gsub!(/^\-|\-$/i, '') # Remove leading/trailing separator.
result.downcase!
result
end
end
# This is the plugin method available on all ActiveRecord models.
module PluginMethods
# Specifies the given field(s) as a permalink, meaning it is passed through PermalinkFu.escape and set to the permalink_field. This
# is done
#
# class Foo < ActiveRecord::Base
# # stores permalink form of #title to the #permalink attribute
# has_permalink :title
#
# # stores a permalink form of "#{category}-#{title}" to the #permalink attribute
#
# has_permalink [:category, :title]
#
# # stores permalink form of #title to the #category_permalink attribute
# has_permalink [:category, :title], :category_permalink
#
# # add a scope
# has_permalink :title, :scope => :blog_id
#
# # add a scope and specify the permalink field name
# has_permalink :title, :slug, :scope => :blog_id
#
# # do not bother checking for a unique scope
# has_permalink :title, :unique => false
# end
#
def has_permalink(attr_names = [], permalink_field = nil, options = {})
if permalink_field.is_a?(Hash)
options = permalink_field
permalink_field = nil
end
ClassMethods.setup_permalink_fu_on self do
self.permalink_attributes = Array(attr_names)
self.permalink_field = (permalink_field || 'permalink').to_s
self.permalink_options = {:unique => true}.update(options)
end
end
end
# Contains class methods for ActiveRecord models that have permalinks
module ClassMethods
def self.setup_permalink_fu_on(base)
base.extend self
class << base
attr_accessor :permalink_options
attr_accessor :permalink_attributes
attr_accessor :permalink_field
end
base.send :include, InstanceMethods
yield
if base.permalink_options[:unique]
base.before_validation :create_unique_permalink
else
base.before_validation :create_common_permalink
end
class << base
alias_method :define_attribute_methods_without_permalinks, :define_attribute_methods
alias_method :define_attribute_methods, :define_attribute_methods_with_permalinks
end
end
def define_attribute_methods_with_permalinks
if value = define_attribute_methods_without_permalinks
evaluate_attribute_method permalink_field, "def #{self.permalink_field}=(new_value);write_attribute(:#{self.permalink_field}, PermalinkFu.escape(new_value));end", "#{self.permalink_field}="
end
value
end
end
# This contains instance methods for ActiveRecord models that have permalinks.
module InstanceMethods
protected
def create_common_permalink
return unless should_create_permalink?
if read_attribute(self.class.permalink_field).to_s.empty?
send("#{self.class.permalink_field}=", create_permalink_for(self.class.permalink_attributes))
end
limit = self.class.columns_hash[self.class.permalink_field].limit
base = send("#{self.class.permalink_field}=", read_attribute(self.class.permalink_field)[0..limit - 1])
[limit, base]
end
def create_unique_permalink
limit, base = create_common_permalink
return if limit.nil?
counter = 1
# oh how i wish i could use a hash for conditions
conditions = ["#{self.class.permalink_field} = ?", base]
unless new_record?
conditions.first << " and id != ?"
conditions << id
end
if self.class.permalink_options[:scope]
scopes = [self.class.permalink_options[:scope]]
scopes.flatten!
scopes.each do |scope|
conditions.first << " and #{scope} = ?"
conditions << send(scope)
end
end
while self.class.exists?(conditions)
suffix = "-#{counter += 1}"
conditions[1] = "#{base[0..limit-suffix.size-1]}#{suffix}"
send("#{self.class.permalink_field}=", conditions[1])
end
end
def create_permalink_for(attr_names)
attr_names.collect { |attr_name| send(attr_name).to_s } * " "
end
private
def should_create_permalink?
if self.class.permalink_options[:if]
evaluate_method(self.class.permalink_options[:if])
elsif self.class.permalink_options[:unless]
!evaluate_method(self.class.permalink_options[:unless])
else
true
end
end
def evaluate_method(method)
case method
when Symbol
send(method)
when String
eval(method, instance_eval { binding })
when Proc, Method
method.call(self)
end
end
end
end
if Object.const_defined?(:Iconv)
PermalinkFu.translation_to = 'ascii//translit//IGNORE'
PermalinkFu.translation_from = 'utf-8'
end