forked from henrik/permalink_fu
/
permalink_fu.rb
163 lines (144 loc) · 5.37 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
begin
require "active_support"
rescue LoadError
require "rubygems"
require "active_support"
end
module PermalinkFu
def self.escape(str)
if defined?(ActiveSupport::Multibyte::Chars)
s = str.mb_chars.normalize(:kd)
else
begin
s = ActiveSupport::Multibyte::Handlers::UTF8Handler.normalize(str.to_s, :kd)
rescue ActiveSupport::Multibyte::Handlers::EncodingError
require 'iconv'
s = Iconv.iconv('ascii//translit//IGNORE', 'utf-8', str).first.to_s
end
end
s.gsub!(/[^\w -]+/n, '') # strip unwanted characters
s.strip! # ohh la la
s.downcase!
s.gsub!(/[ -]+/, '-') # separate by single dashes
s
end
def self.included(base)
base.extend ClassMethods
end
module ClassMethods
# 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], :as => :category_permalink
#
# # add a scope
# has_permalink :title, :scope => :blog_id
#
# # add a scope and specify the permalink field name
# has_permalink :title, :as => :slug, :scope => :blog_id
# end
#
def has_permalink(*args)
options = args.extract_options!
attr_names = args.first || :to_s
write_inheritable_attribute(:permalink_attributes, Array(attr_names))
write_inheritable_attribute(:permalink_field, (options.delete(:as) || 'permalink').to_s)
write_inheritable_attribute(:permalink_options, options)
%w{permalink_attributes permalink_field permalink_options}.each do |v|
class_inheritable_reader(v.to_sym)
end
before_validation :create_unique_permalink
evaluate_attribute_method permalink_field, "def #{self.permalink_field}=(new_value);write_attribute(:#{self.permalink_field}, PermalinkFu.escape(new_value));end", "#{self.permalink_field}="
extend PermalinkFinders
case options[:param]
when false
# nothing
when :permalink
include ToParam
else
include ToParamWithID
end
end
end
module ToParam
def to_param
send(self.class.read_inheritable_attribute(:permalink_field))
end
end
module ToParamWithID
def to_param
permalink = send(self.class.read_inheritable_attribute(:permalink_field))
return super if new_record? || permalink.blank?
"#{id}-#{permalink}"
end
end
module PermalinkFinders
def find_by_permalink(value)
find(:first, :conditions => { permalink_field => value })
end
def find_by_permalink!(value)
find_by_permalink(value) ||
raise(ActiveRecord::RecordNotFound, "Couldn't find #{name} with permalink #{value.inspect}")
end
end
protected
def create_unique_permalink
return unless should_create_permalink?
if send(self.class.read_inheritable_attribute(:permalink_field)).to_s.empty?
send("#{self.class.read_inheritable_attribute(:permalink_field)}=", create_permalink_for(self.class.read_inheritable_attribute(:permalink_attributes)))
end
limit = self.class.columns_hash[self.class.read_inheritable_attribute(:permalink_field)].limit
base = send("#{self.class.read_inheritable_attribute(:permalink_field)}=",
send(self.class.read_inheritable_attribute(:permalink_field))[0..limit - 1])
return unless self.class.read_inheritable_attribute(:permalink_options)[:param]
counter = 1
# oh how i wish i could use a hash for conditions
conditions = ["#{self.class.read_inheritable_attribute(:permalink_field)} = ?", base]
unless new_record?
conditions.first << " and id != ?"
conditions << id
end
if self.class.read_inheritable_attribute(:permalink_options)[:scope]
conditions.first << " and #{self.class.read_inheritable_attribute(:permalink_options)[:scope]} = ?"
conditions << send(self.class.read_inheritable_attribute(:permalink_options)[:scope])
end
while self.class.exists?(conditions)
suffix = "-#{counter += 1}"
conditions[1] = "#{base[0..limit-suffix.size-1]}#{suffix}"
send("#{self.class.read_inheritable_attribute(:permalink_field)}=", conditions[1])
end
send("#{self.class.read_inheritable_attribute(:permalink_field)}")
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.read_inheritable_attribute(:permalink_options)[:if]
evaluate_method(self.class.read_inheritable_attribute(:permalink_options)[:if])
elsif self.class.read_inheritable_attribute(:permalink_options)[:unless]
!evaluate_method(self.class.read_inheritable_attribute(: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