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.
Files changed (73) hide show
  1. data/Changelog.md +225 -0
  2. data/Contributors.md +28 -0
  3. data/Guide.md +509 -0
  4. data/LICENSE +1 -1
  5. data/README.md +76 -0
  6. data/Rakefile +48 -15
  7. data/extras/bench.rb +59 -0
  8. data/extras/extras.rb +31 -0
  9. data/extras/prof.rb +14 -0
  10. data/extras/template-gem.rb +1 -1
  11. data/extras/template-plugin.rb +1 -1
  12. data/generators/friendly_id/friendly_id_generator.rb +1 -1
  13. data/generators/friendly_id/templates/create_slugs.rb +2 -2
  14. data/lib/friendly_id.rb +54 -63
  15. data/lib/friendly_id/active_record2.rb +47 -0
  16. data/lib/friendly_id/active_record2/configuration.rb +66 -0
  17. data/lib/friendly_id/active_record2/finders.rb +140 -0
  18. data/lib/friendly_id/active_record2/simple_model.rb +162 -0
  19. data/lib/friendly_id/active_record2/slug.rb +111 -0
  20. data/lib/friendly_id/active_record2/slugged_model.rb +317 -0
  21. data/lib/friendly_id/active_record2/tasks.rb +66 -0
  22. data/lib/friendly_id/active_record2/tasks/friendly_id.rake +19 -0
  23. data/lib/friendly_id/configuration.rb +132 -0
  24. data/lib/friendly_id/finders.rb +106 -0
  25. data/lib/friendly_id/slug_string.rb +292 -0
  26. data/lib/friendly_id/slugged.rb +91 -0
  27. data/lib/friendly_id/status.rb +35 -0
  28. data/lib/friendly_id/test.rb +168 -0
  29. data/lib/friendly_id/version.rb +5 -5
  30. data/rails/init.rb +2 -0
  31. data/test/active_record2/basic_slugged_model_test.rb +14 -0
  32. data/test/active_record2/cached_slug_test.rb +61 -0
  33. data/test/active_record2/core.rb +93 -0
  34. data/test/active_record2/custom_normalizer_test.rb +20 -0
  35. data/test/active_record2/custom_table_name_test.rb +22 -0
  36. data/test/active_record2/scoped_model_test.rb +111 -0
  37. data/test/active_record2/simple_test.rb +59 -0
  38. data/test/active_record2/slug_test.rb +34 -0
  39. data/test/active_record2/slugged.rb +30 -0
  40. data/test/active_record2/slugged_status_test.rb +61 -0
  41. data/test/active_record2/sti_test.rb +22 -0
  42. data/test/active_record2/support/database.mysql.yml +4 -0
  43. data/test/{support/database.yml.postgres → active_record2/support/database.postgres.yml} +0 -0
  44. data/test/{support/database.yml.sqlite3 → active_record2/support/database.sqlite3.yml} +0 -0
  45. data/test/{support → active_record2/support}/models.rb +28 -0
  46. data/test/active_record2/tasks_test.rb +82 -0
  47. data/test/active_record2/test_helper.rb +107 -0
  48. data/test/friendly_id_test.rb +23 -0
  49. data/test/slug_string_test.rb +74 -0
  50. data/test/test_helper.rb +7 -102
  51. metadata +64 -56
  52. data/History.txt +0 -194
  53. data/README.rdoc +0 -385
  54. data/generators/friendly_id_20_upgrade/friendly_id_20_upgrade_generator.rb +0 -12
  55. data/generators/friendly_id_20_upgrade/templates/upgrade_friendly_id_to_20.rb +0 -19
  56. data/init.rb +0 -1
  57. data/lib/friendly_id/helpers.rb +0 -12
  58. data/lib/friendly_id/non_sluggable_class_methods.rb +0 -34
  59. data/lib/friendly_id/non_sluggable_instance_methods.rb +0 -45
  60. data/lib/friendly_id/slug.rb +0 -98
  61. data/lib/friendly_id/sluggable_class_methods.rb +0 -110
  62. data/lib/friendly_id/sluggable_instance_methods.rb +0 -161
  63. data/lib/friendly_id/tasks.rb +0 -56
  64. data/lib/tasks/friendly_id.rake +0 -25
  65. data/lib/tasks/friendly_id.rb +0 -1
  66. data/test/cached_slug_test.rb +0 -109
  67. data/test/custom_slug_normalizer_test.rb +0 -36
  68. data/test/non_slugged_test.rb +0 -99
  69. data/test/scoped_model_test.rb +0 -64
  70. data/test/slug_test.rb +0 -105
  71. data/test/slugged_model_test.rb +0 -348
  72. data/test/sti_test.rb +0 -49
  73. 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