/
handler.rb
139 lines (125 loc) · 3.76 KB
/
handler.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
module Handler
##
# Included hook: extend including class.
#
def self.included(base)
base.extend ClassMethods
end
##
# Transliterate a string: change accented Unicode characters to ASCII
# approximations. Tries several methods, attempting the best first:
#
# 1. unidecode, if installed (http://rubyforge.org/projects/unidecode)
# 2. iconv (included with Ruby, doesn't work with all Ruby versions)
# 3. normalize, then remove un-normalizable characters
#
def self.transliterate(string)
transliterate_with_unidecode(string) or
transliterate_with_iconv(string) or
transliterate_with_normalization(string) or
string
end
##
# Transliterate a string with the unidecode library.
# Return nil if unsuccessful (eg, library not available).
#
def self.transliterate_with_unidecode(string)
begin
require 'unidecode'
string.to_ascii
rescue LoadError
nil
end
end
##
# Transliterate a string with the iconv library.
# Return nil if unsuccessful (eg, library not available).
#
def self.transliterate_with_iconv(string)
begin
Iconv.iconv('ascii//ignore//translit', 'utf-8', string).to_s
rescue
nil
end
end
##
# Transliterate a string using multibyte normalization,
# then remove remaining non-ASCII characters. Taken from
# <tt>ActiveSupport::Inflector.transliterate</tt>.
#
def self.transliterate_with_normalization(string)
string.mb_chars.normalize.gsub(/[^\x00-\x7F]+/, '').to_s
end
##
# Generate a handle from a string.
#
def self.generate_handle(title, separator)
str = title
return nil unless str.is_a?(String)
str = transliterate(str).to_s
str = str.downcase
str = str.strip
str = str.gsub('&', ' and ') # add space for, e.g., "Y&T"
str = str.delete('.\'"') # no space
str = str.gsub(/\W/, ' ') # space
str = str.strip
str = str.gsub(/ +/, separator)
str
end
##
# Get the next handle in the sequence (for avoiding duplicates).
#
def self.next_handle(handle, separator)
if handle =~ /#{separator}\d+$/
handle.sub(/\d+$/){ |i| i.to_i + 1 }
else
handle + separator + "2"
end
end
module ClassMethod
##
# Declare that a model generates a handle based on
# a given attribute or method. Options include:
#
# <tt>:separator</tt> - character to place between words
# <tt>:store</tt> - attribute in which to store handle
# <tt>:unique</tt> - generate a handle which is unique among all records
#
def handle_based_on(attribute, options = {})
options[:separator] ||= "_"
options[:write_to] ||= :handle
options[:unique] = true if options[:unique].nil?
##
# Generate a URL-friendly name.
#
define_method :generate_handle do
h = Handler.generate_handle(send(attribute), options[:separator])
if options[:unique]
# generate a condition for finding an existing record with a
# given handle
find_dupe = lambda{ |h|
conds = ["#{options[:write_to]} = ?", h]
unless new_record?
conds[0] << " AND id != ?"
conds << id
end
conds
}
# increase number while *other* records exist with the same handle
# (record might be saved and should keep its handle)
while self.class.all(:conditions => find_dupe.call(h)).size > 0
h = Handler.next_handle(h, options[:separator])
end
end
h
end
##
# Assign the generated handle to the specified attribute.
#
define_method :assign_handle do
write_attribute(options[:write_to], generate_handle)
end
end
end
end
ActiveRecord::Base.class_eval{ include Handler }