friendly_id 2.2.7 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Changelog.md +225 -0
- data/Contributors.md +28 -0
- data/Guide.md +509 -0
- data/LICENSE +1 -1
- data/README.md +76 -0
- data/Rakefile +48 -15
- data/extras/bench.rb +59 -0
- data/extras/extras.rb +31 -0
- data/extras/prof.rb +14 -0
- data/extras/template-gem.rb +1 -1
- data/extras/template-plugin.rb +1 -1
- data/generators/friendly_id/friendly_id_generator.rb +1 -1
- data/generators/friendly_id/templates/create_slugs.rb +2 -2
- data/lib/friendly_id.rb +54 -63
- data/lib/friendly_id/active_record2.rb +47 -0
- data/lib/friendly_id/active_record2/configuration.rb +66 -0
- data/lib/friendly_id/active_record2/finders.rb +140 -0
- data/lib/friendly_id/active_record2/simple_model.rb +162 -0
- data/lib/friendly_id/active_record2/slug.rb +111 -0
- data/lib/friendly_id/active_record2/slugged_model.rb +317 -0
- data/lib/friendly_id/active_record2/tasks.rb +66 -0
- data/lib/friendly_id/active_record2/tasks/friendly_id.rake +19 -0
- data/lib/friendly_id/configuration.rb +132 -0
- data/lib/friendly_id/finders.rb +106 -0
- data/lib/friendly_id/slug_string.rb +292 -0
- data/lib/friendly_id/slugged.rb +91 -0
- data/lib/friendly_id/status.rb +35 -0
- data/lib/friendly_id/test.rb +168 -0
- data/lib/friendly_id/version.rb +5 -5
- data/rails/init.rb +2 -0
- data/test/active_record2/basic_slugged_model_test.rb +14 -0
- data/test/active_record2/cached_slug_test.rb +61 -0
- data/test/active_record2/core.rb +93 -0
- data/test/active_record2/custom_normalizer_test.rb +20 -0
- data/test/active_record2/custom_table_name_test.rb +22 -0
- data/test/active_record2/scoped_model_test.rb +111 -0
- data/test/active_record2/simple_test.rb +59 -0
- data/test/active_record2/slug_test.rb +34 -0
- data/test/active_record2/slugged.rb +30 -0
- data/test/active_record2/slugged_status_test.rb +61 -0
- data/test/active_record2/sti_test.rb +22 -0
- data/test/active_record2/support/database.mysql.yml +4 -0
- data/test/{support/database.yml.postgres → active_record2/support/database.postgres.yml} +0 -0
- data/test/{support/database.yml.sqlite3 → active_record2/support/database.sqlite3.yml} +0 -0
- data/test/{support → active_record2/support}/models.rb +28 -0
- data/test/active_record2/tasks_test.rb +82 -0
- data/test/active_record2/test_helper.rb +107 -0
- data/test/friendly_id_test.rb +23 -0
- data/test/slug_string_test.rb +74 -0
- data/test/test_helper.rb +7 -102
- metadata +64 -56
- data/History.txt +0 -194
- data/README.rdoc +0 -385
- data/generators/friendly_id_20_upgrade/friendly_id_20_upgrade_generator.rb +0 -12
- data/generators/friendly_id_20_upgrade/templates/upgrade_friendly_id_to_20.rb +0 -19
- data/init.rb +0 -1
- data/lib/friendly_id/helpers.rb +0 -12
- data/lib/friendly_id/non_sluggable_class_methods.rb +0 -34
- data/lib/friendly_id/non_sluggable_instance_methods.rb +0 -45
- data/lib/friendly_id/slug.rb +0 -98
- data/lib/friendly_id/sluggable_class_methods.rb +0 -110
- data/lib/friendly_id/sluggable_instance_methods.rb +0 -161
- data/lib/friendly_id/tasks.rb +0 -56
- data/lib/tasks/friendly_id.rake +0 -25
- data/lib/tasks/friendly_id.rb +0 -1
- data/test/cached_slug_test.rb +0 -109
- data/test/custom_slug_normalizer_test.rb +0 -36
- data/test/non_slugged_test.rb +0 -99
- data/test/scoped_model_test.rb +0 -64
- data/test/slug_test.rb +0 -105
- data/test/slugged_model_test.rb +0 -348
- data/test/sti_test.rb +0 -49
- data/test/tasks_test.rb +0 -105
@@ -0,0 +1,106 @@
|
|
1
|
+
module FriendlyId
|
2
|
+
|
3
|
+
module Finders
|
4
|
+
|
5
|
+
module Base
|
6
|
+
|
7
|
+
# Is the id friendly or numeric? Not that the return value here is
|
8
|
+
# +false+ if the +id+ is definitely not friendly, and +nil+ if it can
|
9
|
+
# not be determined.
|
10
|
+
# The return value will be:
|
11
|
+
# * +true+ - if the id is definitely friendly (i.e., any string with non-numeric characters)
|
12
|
+
# * +false+ - if the id is definitely unfriendly (i.e., an Integer, a model instance, etc.)
|
13
|
+
# * +nil+ - if it can not be determined (i.e., a numeric string like "206".)
|
14
|
+
# @return [true, false, nil]
|
15
|
+
# @see #unfriendly?
|
16
|
+
def self.friendly?(id)
|
17
|
+
if id.is_a?(Integer) or id.class.respond_to? :friendly_id_config
|
18
|
+
return false
|
19
|
+
elsif id.to_i.to_s != id.to_s
|
20
|
+
return true
|
21
|
+
else
|
22
|
+
return nil
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Is the id numeric?
|
27
|
+
# @return [true, false, nil] +true+ if definitely unfriendly, +false+ if
|
28
|
+
# definitely friendly, else +nil+.
|
29
|
+
# @see #friendly?
|
30
|
+
def self.unfriendly?(id)
|
31
|
+
!friendly?(id) unless friendly?(id) == nil
|
32
|
+
end
|
33
|
+
|
34
|
+
def initialize(ids, model_class, options={})
|
35
|
+
self.ids = ids
|
36
|
+
self.options = options
|
37
|
+
self.model_class = model_class
|
38
|
+
self.scope = options[:scope]
|
39
|
+
end
|
40
|
+
|
41
|
+
def method_missing(*args, &block)
|
42
|
+
model_class.send(*args, &block)
|
43
|
+
end
|
44
|
+
|
45
|
+
# An array of ids; can be both friendly and unfriendly.
|
46
|
+
attr_accessor :ids
|
47
|
+
|
48
|
+
# The ActiveRecord query options
|
49
|
+
attr_accessor :options
|
50
|
+
|
51
|
+
# The FriendlyId scope
|
52
|
+
attr_accessor :scope
|
53
|
+
|
54
|
+
# The model class being used to perform the query.
|
55
|
+
attr_accessor :model_class
|
56
|
+
|
57
|
+
# Perform the find.
|
58
|
+
def find
|
59
|
+
raise NotImplementedError
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def ids=(ids)
|
65
|
+
@ids = [ids].flatten
|
66
|
+
end
|
67
|
+
alias :id= :ids=
|
68
|
+
|
69
|
+
def scope=(scope)
|
70
|
+
@scope = scope.to_param unless scope.nil?
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
module Single
|
75
|
+
# Is the id definitely friendly?
|
76
|
+
# @see Finder::friendly?
|
77
|
+
def friendly?
|
78
|
+
Base.friendly?(id)
|
79
|
+
end
|
80
|
+
|
81
|
+
# Is the id definitely unfriendly?
|
82
|
+
# @see Finder::unfriendly?
|
83
|
+
def unfriendly?
|
84
|
+
Base.unfriendly?(id)
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
# The id (numeric or friendly).
|
90
|
+
def id
|
91
|
+
ids[0]
|
92
|
+
end
|
93
|
+
|
94
|
+
# The slug name; i.e. if "my-title--2", then "my-title".
|
95
|
+
def name
|
96
|
+
id.to_s.parse_friendly_id(friendly_id_config.sequence_separator)[0]
|
97
|
+
end
|
98
|
+
|
99
|
+
# The slug sequence; i.e. if "my-title--2", then "2".
|
100
|
+
def sequence
|
101
|
+
id.to_s.parse_friendly_id(friendly_id_config.sequence_separator)[1]
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,292 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module FriendlyId
|
3
|
+
|
4
|
+
# This class provides some string-manipulation methods specific to slugs.
|
5
|
+
# Its Unicode support is provided by ActiveSupport::Multibyte::Chars; this
|
6
|
+
# is needed primarily for Unicode encoding normalization and proper
|
7
|
+
# calculation of string lengths.
|
8
|
+
#
|
9
|
+
# Note that this class includes many "bang methods" such as {#clean!} and {#normalize!}
|
10
|
+
# that perform actions on the string in-place. Each of these methods has a
|
11
|
+
# corresponding "bangless" method (i.e., +SlugString#clean!+ and +SlugString#clean+)
|
12
|
+
# which does not appear in the documentation because it is generated dynamically.
|
13
|
+
#
|
14
|
+
# All of the bang methods return an instance of String, while the bangless
|
15
|
+
# versions return an instance of FriendlyId::SlugString, so that calls to
|
16
|
+
# methods specific to this class can be chained:
|
17
|
+
#
|
18
|
+
# string = SlugString.new("hello world")
|
19
|
+
# string.with_dashes! # => "hello-world"
|
20
|
+
# string.with_dashes # => <FriendlyId::SlugString:0x000001013e1590 @wrapped_string="hello-world">
|
21
|
+
#
|
22
|
+
# @see http://www.utf8-chartable.de/unicode-utf8-table.pl?utf8=dec Unicode character table
|
23
|
+
# @see FriendlyId::SlugString::dump_approximations
|
24
|
+
class SlugString < ActiveSupport::Multibyte::Chars
|
25
|
+
|
26
|
+
# All values are Unicode decimal characters or character arrays.
|
27
|
+
APPROXIMATIONS = {
|
28
|
+
:common => Hash[
|
29
|
+
192, 65, 193, 65, 194, 65, 195, 65, 196, 65, 197, 65, 198, [65, 69],
|
30
|
+
199, 67, 200, 69, 201, 69, 202, 69, 203, 69, 204, 73, 205, 73, 206,
|
31
|
+
73, 207, 73, 208, 68, 209, 78, 210, 79, 211, 79, 212, 79, 213, 79,
|
32
|
+
214, 79, 215, 120, 216, 79, 217, 85, 218, 85, 219, 85, 220, 85, 221,
|
33
|
+
89, 222, [84, 104], 223, [115, 115], 224, 97, 225, 97, 226, 97, 227,
|
34
|
+
97, 228, 97, 229, 97, 230, [97, 101], 231, 99, 232, 101, 233, 101,
|
35
|
+
234, 101, 235, 101, 236, 105, 237, 105, 238, 105, 239, 105, 240, 100,
|
36
|
+
241, 110, 242, 111, 243, 111, 244, 111, 245, 111, 246, 111, 248, 111,
|
37
|
+
249, 117, 250, 117, 251, 117, 252, 117, 253, 121, 254, [116, 104],
|
38
|
+
255, 121, 256, 65, 257, 97, 258, 65, 259, 97, 260, 65, 261, 97, 262,
|
39
|
+
67, 263, 99, 264, 67, 265, 99, 266, 67, 267, 99, 268, 67, 269, 99,
|
40
|
+
270, 68, 271, 100, 272, 68, 273, 100, 274, 69, 275, 101, 276, 69, 277,
|
41
|
+
101, 278, 69, 279, 101, 280, 69, 281, 101, 282, 69, 283, 101, 284, 71,
|
42
|
+
285, 103, 286, 71, 287, 103, 288, 71, 289, 103, 290, 71, 291, 103,
|
43
|
+
292, 72, 293, 104, 294, 72, 295, 104, 296, 73, 297, 105, 298, 73, 299,
|
44
|
+
105, 300, 73, 301, 105, 302, 73, 303, 105, 304, 73, 305, 105, 306,
|
45
|
+
[73, 74], 307, [105, 106], 308, 74, 309, 106, 310, 75, 311, 107, 312,
|
46
|
+
107, 313, 76, 314, 108, 315, 76, 316, 108, 317, 76, 318, 108, 319, 76,
|
47
|
+
320, 108, 321, 76, 322, 108, 323, 78, 324, 110, 325, 78, 326, 110,
|
48
|
+
327, 78, 328, 110, 329, [39, 110], 330, [78, 71], 331, [110, 103],
|
49
|
+
332, 79, 333, 111, 334, 79, 335, 111, 336, 79, 337, 111, 338, [79,
|
50
|
+
69], 339, [111, 101], 340, 82, 341, 114, 342, 82, 343, 114, 344, 82,
|
51
|
+
345, 114, 346, 83, 347, 115, 348, 83, 349, 115, 350, 83, 351, 115,
|
52
|
+
352, 83, 353, 115, 354, 84, 355, 116, 356, 84, 357, 116, 358, 84, 359,
|
53
|
+
116, 360, 85, 361, 117, 362, 85, 363, 117, 364, 85, 365, 117, 366, 85,
|
54
|
+
367, 117, 368, 85, 369, 117, 370, 85, 371, 117, 372, 87, 373, 119,
|
55
|
+
374, 89, 375, 121, 376, 89, 377, 90, 378, 122, 379, 90, 380, 122, 381,
|
56
|
+
90, 382, 122
|
57
|
+
].freeze,
|
58
|
+
:german => Hash[252, [117, 101], 246, [111, 101], 228, [97, 101]],
|
59
|
+
:spanish => Hash[209, [78, 110], 241, [110, 110]]
|
60
|
+
}
|
61
|
+
|
62
|
+
cattr_accessor :approximations
|
63
|
+
self.approximations = []
|
64
|
+
|
65
|
+
# This method can be used by developers wishing to debug the
|
66
|
+
# {APPROXIMATIONS} hashes, which are written in a hard-to-read format.
|
67
|
+
# @return Hash
|
68
|
+
# @example
|
69
|
+
#
|
70
|
+
# > ruby -rrubygems -rlib/friendly_id -e 'p FriendlyId::SlugString.dump_approximations'
|
71
|
+
#
|
72
|
+
# {:common =>
|
73
|
+
# {"À"=>"A", "Á"=>"A", "Â"=>"A", "Ã"=>"A", "Ä"=>"A", "Å"=>"A", "Æ"=>"AE",
|
74
|
+
# "Ç"=>"C", "È"=>"E", "É"=>"E", "Ê"=>"E", "Ë"=>"E", "Ì"=>"I", "Í"=>"I",
|
75
|
+
# "Î"=>"I", "Ï"=>"I", "Ð"=>"D", "Ñ"=>"N", "Ò"=>"O", "Ó"=>"O", "Ô"=>"O",
|
76
|
+
# "Õ"=>"O", "Ö"=>"O", "×"=>"x", "Ø"=>"O", "Ù"=>"U", "Ú"=>"U", "Û"=>"U",
|
77
|
+
# "Ü"=>"U", "Ý"=>"Y", "Þ"=>"Th", "ß"=>"ss", "à"=>"a", "á"=>"a", "â"=>"a",
|
78
|
+
# "ã"=>"a", "ä"=>"a", "å"=>"a", "æ"=>"ae", "ç"=>"c", "è"=>"e", "é"=>"e",
|
79
|
+
# "ê"=>"e", "ë"=>"e", "ì"=>"i", "í"=>"i", "î"=>"i", "ï"=>"i", "ð"=>"d",
|
80
|
+
# "ñ"=>"n", "ò"=>"o", "ó"=>"o", "ô"=>"o", "õ"=>"o", "ö"=>"o", "ø"=>"o",
|
81
|
+
# "ù"=>"u", "ú"=>"u", "û"=>"u", "ü"=>"u", "ý"=>"y", "þ"=>"th", "ÿ"=>"y",
|
82
|
+
# "Ā"=>"A", "ā"=>"a", "Ă"=>"A", "ă"=>"a", "Ą"=>"A", "ą"=>"a", "Ć"=>"C",
|
83
|
+
# "ć"=>"c", "Ĉ"=>"C", "ĉ"=>"c", "Ċ"=>"C", "ċ"=>"c", "Č"=>"C", "č"=>"c",
|
84
|
+
# "Ď"=>"D", "ď"=>"d", "Đ"=>"D", "đ"=>"d", "Ē"=>"E", "ē"=>"e", "Ĕ"=>"E",
|
85
|
+
# "ĕ"=>"e", "Ė"=>"E", "ė"=>"e", "Ę"=>"E", "ę"=>"e", "Ě"=>"E", "ě"=>"e",
|
86
|
+
# "Ĝ"=>"G", "ĝ"=>"g", "Ğ"=>"G", "ğ"=>"g", "Ġ"=>"G", "ġ"=>"g", "Ģ"=>"G",
|
87
|
+
# "ģ"=>"g", "Ĥ"=>"H", "ĥ"=>"h", "Ħ"=>"H", "ħ"=>"h", "Ĩ"=>"I", "ĩ"=>"i",
|
88
|
+
# "Ī"=>"I", "ī"=>"i", "Ĭ"=>"I", "ĭ"=>"i", "Į"=>"I", "į"=>"i", "İ"=>"I",
|
89
|
+
# "ı"=>"i", "IJ"=>"IJ", "ij"=>"ij", "Ĵ"=>"J", "ĵ"=>"j", "Ķ"=>"K", "ķ"=>"k",
|
90
|
+
# "ĸ"=>"k", "Ĺ"=>"L", "ĺ"=>"l", "Ļ"=>"L", "ļ"=>"l", "Ľ"=>"L", "ľ"=>"l",
|
91
|
+
# "Ŀ"=>"L", "ŀ"=>"l", "Ł"=>"L", "ł"=>"l", "Ń"=>"N", "ń"=>"n", "Ņ"=>"N",
|
92
|
+
# "ņ"=>"n", "Ň"=>"N", "ň"=>"n", "ʼn"=>"'n", "Ŋ"=>"NG", "ŋ"=>"ng",
|
93
|
+
# "Ō"=>"O", "ō"=>"o", "Ŏ"=>"O", "ŏ"=>"o", "Ő"=>"O", "ő"=>"o", "Œ"=>"OE",
|
94
|
+
# "œ"=>"oe", "Ŕ"=>"R", "ŕ"=>"r", "Ŗ"=>"R", "ŗ"=>"r", "Ř"=>"R", "ř"=>"r",
|
95
|
+
# "Ś"=>"S", "ś"=>"s", "Ŝ"=>"S", "ŝ"=>"s", "Ş"=>"S", "ş"=>"s", "Š"=>"S",
|
96
|
+
# "š"=>"s", "Ţ"=>"T", "ţ"=>"t", "Ť"=>"T", "ť"=>"t", "Ŧ"=>"T", "ŧ"=>"t",
|
97
|
+
# "Ũ"=>"U", "ũ"=>"u", "Ū"=>"U", "ū"=>"u", "Ŭ"=>"U", "ŭ"=>"u", "Ů"=>"U",
|
98
|
+
# "ů"=>"u", "Ű"=>"U", "ű"=>"u", "Ų"=>"U", "ų"=>"u", "Ŵ"=>"W", "ŵ"=>"w",
|
99
|
+
# "Ŷ"=>"Y", "ŷ"=>"y", "Ÿ"=>"Y", "Ź"=>"Z", "ź"=>"z", "Ż"=>"Z", "ż"=>"z",
|
100
|
+
# "Ž"=>"Z", "ž"=>"z"},
|
101
|
+
# :german => {"ü"=>"ue", "ö"=>"oe", "ä"=>"ae"},
|
102
|
+
# :spanish => {"Ñ"=>"Nn", "ñ"=>"nn"}}
|
103
|
+
def self.dump_approximations
|
104
|
+
Hash[APPROXIMATIONS.map do |name, approx|
|
105
|
+
[name, Hash[approx.map {|key, value| [[key].pack("U*"), [value].flatten.pack("U*")]}]]
|
106
|
+
end]
|
107
|
+
end
|
108
|
+
|
109
|
+
|
110
|
+
# @param string [String] The string to use as the basis of the SlugString.
|
111
|
+
def initialize(string)
|
112
|
+
super string.to_s
|
113
|
+
end
|
114
|
+
|
115
|
+
# Approximate an ASCII string. This works only for Western strings using
|
116
|
+
# characters that are Roman-alphabet characters + diacritics. Non-letter
|
117
|
+
# characters are left unmodified.
|
118
|
+
#
|
119
|
+
# string = SlugString.new "Łódź, Poland"
|
120
|
+
# string.approximate_ascii # => "Lodz, Poland"
|
121
|
+
# string = SlugString.new "日本"
|
122
|
+
# string.approximate_ascii # => "日本"
|
123
|
+
#
|
124
|
+
# You can pass any key(s) from {APPROXIMATIONS} as arguments. This allows
|
125
|
+
# for contextual approximations. By default; +:spanish+ and +:german+ are
|
126
|
+
# provided:
|
127
|
+
#
|
128
|
+
# string = SlugString.new "Jürgen Müller"
|
129
|
+
# string.approximate_ascii # => "Jurgen Muller"
|
130
|
+
# string.approximate_ascii :german # => "Juergen Mueller"
|
131
|
+
# string = SlugString.new "¡Feliz año!"
|
132
|
+
# string.approximate_ascii # => "¡Feliz ano!"
|
133
|
+
# string.approximate_ascii :spanish # => "¡Feliz anno!"
|
134
|
+
#
|
135
|
+
# You can modify the built-in approximations, or add your own:
|
136
|
+
#
|
137
|
+
# # Make Spanish use "nh" rather than "nn"
|
138
|
+
# FriendlyId::SlugString::APPROXIMATIONS[:spanish] = {
|
139
|
+
# # Ñ => "Nh"
|
140
|
+
# 209 => [78, 104],
|
141
|
+
# # ñ => "nh"
|
142
|
+
# 241 => [110, 104]
|
143
|
+
# }
|
144
|
+
#
|
145
|
+
# It's also possible to use a custom approximation for all strings:
|
146
|
+
#
|
147
|
+
# FriendlyId::SlugString.approximations << :german
|
148
|
+
#
|
149
|
+
# Notice that this method does not simply convert to ASCII; if you want
|
150
|
+
# to remove non-ASCII characters such as "¡" and "¿", use {#to_ascii!}:
|
151
|
+
#
|
152
|
+
# string.approximate_ascii!(:spanish) # => "¡Feliz anno!"
|
153
|
+
# string.to_ascii! # => "Feliz anno!"
|
154
|
+
# @param *args <Symbol>
|
155
|
+
# @return String
|
156
|
+
def approximate_ascii!(*args)
|
157
|
+
@maps = (self.class.approximations + args + [:common]).flatten.uniq
|
158
|
+
@wrapped_string = normalize_utf8(:c).unpack("U*").map { |char| approx_char(char) }.flatten.pack("U*")
|
159
|
+
end
|
160
|
+
|
161
|
+
# Removes leading and trailing spaces or dashses, and replaces multiple
|
162
|
+
# whitespace characters with a single space.
|
163
|
+
# @return String
|
164
|
+
def clean!
|
165
|
+
@wrapped_string = @wrapped_string.gsub(/\A\-|\-\z/, '').gsub(/\s+/u, ' ').strip
|
166
|
+
end
|
167
|
+
|
168
|
+
# Lowercases the string. Note that this works for Unicode strings,
|
169
|
+
# though your milage may vary with Greek and Turkic strings.
|
170
|
+
# @return String
|
171
|
+
def downcase!
|
172
|
+
@wrapped_string = apply_mapping :lowercase_mapping
|
173
|
+
end
|
174
|
+
|
175
|
+
# Remove any non-word characters.
|
176
|
+
# @return String
|
177
|
+
def word_chars!
|
178
|
+
@wrapped_string = normalize_utf8(:c).unpack("U*").map { |char|
|
179
|
+
case char
|
180
|
+
# control chars
|
181
|
+
when 0..31
|
182
|
+
# punctuation; 45 is "-" (HYPHEN-MINUS) and allowed
|
183
|
+
when 33..44
|
184
|
+
# more puncuation
|
185
|
+
when 46..47
|
186
|
+
# more puncuation and other symbols
|
187
|
+
when 58..64
|
188
|
+
# brackets and other symbols
|
189
|
+
when 91..96
|
190
|
+
# braces, pipe, tilde, etc.
|
191
|
+
when 123..191
|
192
|
+
else char
|
193
|
+
end
|
194
|
+
}.compact.pack("U*")
|
195
|
+
end
|
196
|
+
|
197
|
+
# Normalize the string for a given {FriendlyId::Configuration}.
|
198
|
+
# @param config [FriendlyId::Configuration]
|
199
|
+
# @return String
|
200
|
+
def normalize_for!(config)
|
201
|
+
if config.normalizer?
|
202
|
+
@wrapped_string = config.normalizer.call(to_s)
|
203
|
+
else
|
204
|
+
approximate_ascii! if config.approximate_ascii?
|
205
|
+
to_ascii! if config.strip_non_ascii?
|
206
|
+
normalize!
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
alias :normalize_utf8 :normalize rescue NoMethodError
|
211
|
+
|
212
|
+
# Normalize the string for use as a FriendlyId. Note that in
|
213
|
+
# this context, +normalize+ means, strip, remove non-letters/numbers,
|
214
|
+
# downcasing and converting whitespace to dashes.
|
215
|
+
# ActiveSupport::Multibyte::Chars#normalize is aliased to +normalize_utf8+
|
216
|
+
# in this subclass.
|
217
|
+
# @return String
|
218
|
+
def normalize!
|
219
|
+
clean!
|
220
|
+
word_chars!
|
221
|
+
downcase!
|
222
|
+
with_dashes!
|
223
|
+
end
|
224
|
+
|
225
|
+
# Truncate the string to +max+ length.
|
226
|
+
# @return String
|
227
|
+
def truncate!(max)
|
228
|
+
@wrapped_string = self[0...max].to_s if length > max
|
229
|
+
end
|
230
|
+
|
231
|
+
# Delete any non-ascii characters.
|
232
|
+
# @return String
|
233
|
+
def to_ascii!
|
234
|
+
@wrapped_string = normalize_utf8(:c).unpack("U*").reject {|char| char > 127}.pack("U*")
|
235
|
+
end
|
236
|
+
|
237
|
+
# Upper-cases the string. Note that this works for Unicode strings,
|
238
|
+
# though your milage may vary with Greek and Turkic strings.
|
239
|
+
# @return String
|
240
|
+
def upcase!
|
241
|
+
@wrapped_string = apply_mapping :uppercase_mapping
|
242
|
+
end
|
243
|
+
|
244
|
+
# Validate that the slug string is not blank or reserved, and truncate
|
245
|
+
# it to the max length if necessary.
|
246
|
+
# @param config [FriendlyId::Configuration]
|
247
|
+
# @return String
|
248
|
+
# @raise FriendlyId::BlankError
|
249
|
+
# @raise FriendlyId::ReservedError
|
250
|
+
def validate_for!(config)
|
251
|
+
truncate!(config.max_length)
|
252
|
+
raise FriendlyId::BlankError if blank?
|
253
|
+
raise FriendlyId::ReservedError if config.reserved?(self)
|
254
|
+
self
|
255
|
+
end
|
256
|
+
|
257
|
+
# Replaces whitespace with dashes ("-").
|
258
|
+
# @return String
|
259
|
+
def with_dashes!
|
260
|
+
@wrapped_string = @wrapped_string.gsub(/\s+/u, '-')
|
261
|
+
end
|
262
|
+
|
263
|
+
%w[approximate_ascii clean downcase word_chars normalize normalize_for to_ascii
|
264
|
+
truncate upcase with_dashes].each do |method|
|
265
|
+
class_eval(<<-EOM)
|
266
|
+
def #{method}(*args)
|
267
|
+
send_to_new_instance(:#{method}!, *args)
|
268
|
+
end
|
269
|
+
EOM
|
270
|
+
end
|
271
|
+
|
272
|
+
private
|
273
|
+
|
274
|
+
# Look up the character's approximation in the configured maps.
|
275
|
+
def approx_char(char)
|
276
|
+
@maps.each do |map|
|
277
|
+
if new_char = APPROXIMATIONS[map][char]
|
278
|
+
return new_char
|
279
|
+
end
|
280
|
+
end
|
281
|
+
char
|
282
|
+
end
|
283
|
+
|
284
|
+
# Used as the basis of the bangless methods.
|
285
|
+
def send_to_new_instance(*args)
|
286
|
+
string = SlugString.new self
|
287
|
+
string.send(*args)
|
288
|
+
string
|
289
|
+
end
|
290
|
+
|
291
|
+
end
|
292
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
module FriendlyId
|
2
|
+
module Slugged
|
3
|
+
|
4
|
+
class Status < FriendlyId::Status
|
5
|
+
|
6
|
+
attr_accessor :slug
|
7
|
+
|
8
|
+
# The slug that was used to find the model.
|
9
|
+
def slug
|
10
|
+
@slug ||= record.find_slug(name)
|
11
|
+
end
|
12
|
+
|
13
|
+
# Did the find operation use a friendly id?
|
14
|
+
def friendly?
|
15
|
+
!! (name or slug)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Did the find operation use the current slug?
|
19
|
+
def current?
|
20
|
+
!! slug && slug.current?
|
21
|
+
end
|
22
|
+
|
23
|
+
# Did the find operation use an outdated slug?
|
24
|
+
def outdated?
|
25
|
+
!current?
|
26
|
+
end
|
27
|
+
|
28
|
+
# Did the find operation use the best possible id? True if +id+ is
|
29
|
+
# numeric, but the model has no slug, or +id+ is friendly and current
|
30
|
+
def best?
|
31
|
+
current? || (numeric? && !record.slug)
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
module Model
|
37
|
+
attr_accessor :slug
|
38
|
+
|
39
|
+
def find_slug
|
40
|
+
raise NotImplementedError
|
41
|
+
end
|
42
|
+
|
43
|
+
def friendly_id_config
|
44
|
+
self.class.friendly_id_config
|
45
|
+
end
|
46
|
+
|
47
|
+
# Get the {FriendlyId::Status} after the find has been performed.
|
48
|
+
def friendly_id_status
|
49
|
+
@friendly_id_status ||= Status.new(:record => self)
|
50
|
+
end
|
51
|
+
|
52
|
+
# The friendly id.
|
53
|
+
def friendly_id
|
54
|
+
slug.to_friendly_id
|
55
|
+
end
|
56
|
+
|
57
|
+
# Clean up the string before setting it as the friendly_id. You can override
|
58
|
+
# this method to add your own custom normalization routines.
|
59
|
+
# @param string An instance of {FriendlyId::SlugString}.
|
60
|
+
# @return [String]
|
61
|
+
def normalize_friendly_id(string)
|
62
|
+
string.normalize_for!(friendly_id_config).to_s
|
63
|
+
end
|
64
|
+
|
65
|
+
# Does the instance have a slug?
|
66
|
+
def slug?
|
67
|
+
!! slug
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
# Get the processed string used as the basis of the friendly id.
|
73
|
+
def slug_text
|
74
|
+
text = normalize_friendly_id(SlugString.new(send(friendly_id_config.method)))
|
75
|
+
SlugString.new(text.to_s).validate_for!(friendly_id_config).to_s
|
76
|
+
end
|
77
|
+
|
78
|
+
# Has the slug text changed?
|
79
|
+
def slug_text_changed?
|
80
|
+
slug_text != slug.name
|
81
|
+
end
|
82
|
+
|
83
|
+
# Has the basis of our friendly id changed, requiring the generation of a
|
84
|
+
# new slug?
|
85
|
+
def new_slug_needed?
|
86
|
+
!slug? || slug_text_changed?
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|