ultra-clean-lib 0.0.1

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.
@@ -0,0 +1,114 @@
1
+ require "i18n"
2
+
3
+ module FriendlyId
4
+ # @guide begin
5
+ #
6
+ # ## Translating Slugs Using Simple I18n
7
+ #
8
+ # The {FriendlyId::SimpleI18n SimpleI18n} module adds very basic i18n support to
9
+ # FriendlyId.
10
+ #
11
+ # In order to use this module, your model must have a slug column for each locale.
12
+ # By default FriendlyId looks for columns named, for example, "slug_en",
13
+ # "slug_es", "slug_pt_br", etc. The first part of the name can be configured by
14
+ # passing the `:slug_column` option if you choose. Note that the column for the
15
+ # default locale must also include the locale in its name.
16
+ #
17
+ # This module is most suitable to applications that need to support few locales.
18
+ # If you need to support two or more locales, you may wish to use the
19
+ # friendly_id_globalize gem instead.
20
+ #
21
+ # ### Example migration
22
+ #
23
+ # def self.up
24
+ # create_table :posts do |t|
25
+ # t.string :title
26
+ # t.string :slug_en
27
+ # t.string :slug_es
28
+ # t.string :slug_pt_br
29
+ # t.text :body
30
+ # end
31
+ # add_index :posts, :slug_en
32
+ # add_index :posts, :slug_es
33
+ # add_index :posts, :slug_pt_br
34
+ # end
35
+ #
36
+ # ### Finds
37
+ #
38
+ # Finds will take into consideration the current locale:
39
+ #
40
+ # I18n.locale = :es
41
+ # Post.friendly.find("la-guerra-de-las-galaxias")
42
+ # I18n.locale = :en
43
+ # Post.friendly.find("star-wars")
44
+ # I18n.locale = :"pt-BR"
45
+ # Post.friendly.find("guerra-das-estrelas")
46
+ #
47
+ # To find a slug by an explicit locale, perform the find inside a block
48
+ # passed to I18n's `with_locale` method:
49
+ #
50
+ # I18n.with_locale(:es) do
51
+ # Post.friendly.find("la-guerra-de-las-galaxias")
52
+ # end
53
+ #
54
+ # ### Creating Records
55
+ #
56
+ # When new records are created, the slug is generated for the current locale only.
57
+ #
58
+ # ### Translating Slugs
59
+ #
60
+ # To translate an existing record's friendly_id, use
61
+ # {FriendlyId::SimpleI18n::Model#set_friendly_id}. This will ensure that the slug
62
+ # you add is properly escaped, transliterated and sequenced:
63
+ #
64
+ # post = Post.create :name => "Star Wars"
65
+ # post.set_friendly_id("La guerra de las galaxias", :es)
66
+ #
67
+ # If you don't pass in a locale argument, FriendlyId::SimpleI18n will just use the
68
+ # current locale:
69
+ #
70
+ # I18n.with_locale(:es) do
71
+ # post.set_friendly_id("La guerra de las galaxias")
72
+ # end
73
+ #
74
+ # @guide end
75
+ module SimpleI18n
76
+ # FriendlyId::Config.use will invoke this method when present, to allow
77
+ # loading dependent modules prior to overriding them when necessary.
78
+ def self.setup(model_class)
79
+ model_class.friendly_id_config.use :slugged
80
+ end
81
+
82
+ def self.included(model_class)
83
+ model_class.class_eval do
84
+ friendly_id_config.class.send :include, Configuration
85
+ include Model
86
+ end
87
+ end
88
+
89
+ module Model
90
+ def set_friendly_id(text, locale = nil)
91
+ I18n.with_locale(locale || I18n.locale) do
92
+ set_slug(normalize_friendly_id(text))
93
+ end
94
+ end
95
+
96
+ def slug=(value)
97
+ super
98
+ write_attribute friendly_id_config.slug_column, value
99
+ end
100
+ end
101
+
102
+ module Configuration
103
+ def slug_column
104
+ "#{super}_#{locale_suffix}"
105
+ end
106
+
107
+ private
108
+
109
+ def locale_suffix
110
+ I18n.locale.to_s.underscore
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,16 @@
1
+ module FriendlyId
2
+ # A FriendlyId slug stored in an external table.
3
+ #
4
+ # @see FriendlyId::History
5
+ class Slug < ActiveRecord::Base
6
+ belongs_to :sluggable, polymorphic: true
7
+
8
+ def sluggable
9
+ sluggable_type.constantize.unscoped { super }
10
+ end
11
+
12
+ def to_param
13
+ slug
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,38 @@
1
+ module FriendlyId
2
+ # The default slug generator offers functionality to check slug candidates for
3
+ # availability.
4
+ class SlugGenerator
5
+ def initialize(scope, config)
6
+ @scope = scope
7
+ @config = config
8
+ end
9
+
10
+ def available?(slug)
11
+ if @config.uses?(::FriendlyId::Reserved) && @config.reserved_words.present? && @config.treat_reserved_as_conflict
12
+ return false if @config.reserved_words.include?(slug)
13
+ end
14
+
15
+ if @config.treat_numeric_as_conflict && purely_numeric_slug?(slug)
16
+ return false
17
+ end
18
+
19
+ !@scope.exists_by_friendly_id?(slug)
20
+ end
21
+
22
+ def generate(candidates)
23
+ candidates.each { |c| return c if available?(c) }
24
+ nil
25
+ end
26
+
27
+ private
28
+
29
+ def purely_numeric_slug?(slug)
30
+ return false unless slug
31
+ begin
32
+ Integer(slug, 10).to_s == slug.to_s
33
+ rescue ArgumentError, TypeError
34
+ false
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,436 @@
1
+ require "friendly_id/slug_generator"
2
+ require "friendly_id/candidates"
3
+
4
+ module FriendlyId
5
+ # @guide begin
6
+ #
7
+ # ## Slugged Models
8
+ #
9
+ # FriendlyId can use a separate column to store slugs for models which require
10
+ # some text processing.
11
+ #
12
+ # For example, blog applications typically use a post title to provide the basis
13
+ # of a search engine friendly URL. Such identifiers typically lack uppercase
14
+ # characters, use ASCII to approximate UTF-8 characters, and strip out other
15
+ # characters which may make them aesthetically unappealing or error-prone when
16
+ # used in a URL.
17
+ #
18
+ # class Post < ActiveRecord::Base
19
+ # extend FriendlyId
20
+ # friendly_id :title, :use => :slugged
21
+ # end
22
+ #
23
+ # @post = Post.create(:title => "This is the first post!")
24
+ # @post.friendly_id # returns "this-is-the-first-post"
25
+ # redirect_to @post # the URL will be /posts/this-is-the-first-post
26
+ #
27
+ # In general, use slugs by default unless you know for sure you don't need them.
28
+ # To activate the slugging functionality, use the {FriendlyId::Slugged} module.
29
+ #
30
+ # FriendlyId will generate slugs from a method or column that you specify, and
31
+ # store them in a field in your model. By default, this field must be named
32
+ # `:slug`, though you may change this using the
33
+ # {FriendlyId::Slugged::Configuration#slug_column slug_column} configuration
34
+ # option. You should add an index to this column, and in most cases, make it
35
+ # unique. You may also wish to constrain it to NOT NULL, but this depends on your
36
+ # app's behavior and requirements.
37
+ #
38
+ # ### Example Setup
39
+ #
40
+ # # your model
41
+ # class Post < ActiveRecord::Base
42
+ # extend FriendlyId
43
+ # friendly_id :title, :use => :slugged
44
+ # validates_presence_of :title, :slug, :body
45
+ # end
46
+ #
47
+ # # a migration
48
+ # class CreatePosts < ActiveRecord::Migration
49
+ # def self.up
50
+ # create_table :posts do |t|
51
+ # t.string :title, :null => false
52
+ # t.string :slug, :null => false
53
+ # t.text :body
54
+ # end
55
+ #
56
+ # add_index :posts, :slug, :unique => true
57
+ # end
58
+ #
59
+ # def self.down
60
+ # drop_table :posts
61
+ # end
62
+ # end
63
+ #
64
+ # ### Working With Slugs
65
+ #
66
+ # #### Formatting
67
+ #
68
+ # By default, FriendlyId uses Active Support's
69
+ # [parameterize](http://api.rubyonrails.org/classes/ActiveSupport/Inflector.html#method-i-parameterize)
70
+ # method to create slugs. This method will intelligently replace spaces with
71
+ # dashes, and Unicode Latin characters with ASCII approximations:
72
+ #
73
+ # movie = Movie.create! :title => "Der Preis fürs Überleben"
74
+ # movie.slug #=> "der-preis-furs-uberleben"
75
+ #
76
+ # #### Column or Method?
77
+ #
78
+ # FriendlyId always uses a method as the basis of the slug text - not a column. At
79
+ # first glance, this may sound confusing, but remember that Active Record provides
80
+ # methods for each column in a model's associated table, and that's what
81
+ # FriendlyId uses.
82
+ #
83
+ # Here's an example of a class that uses a custom method to generate the slug:
84
+ #
85
+ # class Person < ActiveRecord::Base
86
+ # extend FriendlyId
87
+ # friendly_id :name_and_location, use: :slugged
88
+ #
89
+ # def name_and_location
90
+ # "#{name} from #{location}"
91
+ # end
92
+ # end
93
+ #
94
+ # bob = Person.create! :name => "Bob Smith", :location => "New York City"
95
+ # bob.friendly_id #=> "bob-smith-from-new-york-city"
96
+ #
97
+ # FriendlyId refers to this internally as the "base" method.
98
+ #
99
+ # #### Uniqueness
100
+ #
101
+ # When you try to insert a record that would generate a duplicate friendly id,
102
+ # FriendlyId will append a UUID to the generated slug to ensure uniqueness:
103
+ #
104
+ # car = Car.create :title => "Peugeot 206"
105
+ # car2 = Car.create :title => "Peugeot 206"
106
+ #
107
+ # car.friendly_id #=> "peugeot-206"
108
+ # car2.friendly_id #=> "peugeot-206-f9f3789a-daec-4156-af1d-fab81aa16ee5"
109
+ #
110
+ # Previous versions of FriendlyId appended a numeric sequence to make slugs
111
+ # unique, but this was removed to simplify using FriendlyId in concurrent code.
112
+ #
113
+ # #### Candidates
114
+ #
115
+ # Since UUIDs are ugly, FriendlyId provides a "slug candidates" functionality to
116
+ # let you specify alternate slugs to use in the event the one you want to use is
117
+ # already taken. For example:
118
+ #
119
+ # class Restaurant < ActiveRecord::Base
120
+ # extend FriendlyId
121
+ # friendly_id :slug_candidates, use: :slugged
122
+ #
123
+ # # Try building a slug based on the following fields in
124
+ # # increasing order of specificity.
125
+ # def slug_candidates
126
+ # [
127
+ # :name,
128
+ # [:name, :city],
129
+ # [:name, :street, :city],
130
+ # [:name, :street_number, :street, :city]
131
+ # ]
132
+ # end
133
+ # end
134
+ #
135
+ # r1 = Restaurant.create! name: 'Plaza Diner', city: 'New Paltz'
136
+ # r2 = Restaurant.create! name: 'Plaza Diner', city: 'Kingston'
137
+ #
138
+ # r1.friendly_id #=> 'plaza-diner'
139
+ # r2.friendly_id #=> 'plaza-diner-kingston'
140
+ #
141
+ # To use candidates, make your FriendlyId base method return an array. The
142
+ # method need not be named `slug_candidates`; it can be anything you want. The
143
+ # array may contain any combination of symbols, strings, procs or lambdas and
144
+ # will be evaluated lazily and in order. If you include symbols, FriendlyId will
145
+ # invoke a method on your model class with the same name. Strings will be
146
+ # interpreted literally. Procs and lambdas will be called and their return values
147
+ # used as the basis of the friendly id. If none of the candidates can generate a
148
+ # unique slug, then FriendlyId will append a UUID to the first candidate as a
149
+ # last resort.
150
+ #
151
+ # #### Sequence Separator
152
+ #
153
+ # By default, FriendlyId uses a dash to separate the slug from a sequence.
154
+ #
155
+ # You can change this with the {FriendlyId::Slugged::Configuration#sequence_separator
156
+ # sequence_separator} configuration option.
157
+ #
158
+ # #### Avoiding Numeric Slugs
159
+ #
160
+ # Purely numeric slugs like "123" can create ambiguity in Rails routing - they could
161
+ # be interpreted as either a friendly slug or a database ID. To prevent this, you can
162
+ # configure FriendlyId to treat numeric slugs as conflicts and add a UUID suffix:
163
+ #
164
+ # class Product < ActiveRecord::Base
165
+ # extend FriendlyId
166
+ # friendly_id :sku, use: :slugged
167
+ # friendly_id_config.treat_numeric_as_conflict = true
168
+ # end
169
+ #
170
+ # product = Product.create! sku: "123"
171
+ # product.slug #=> "123-f9f3789a-daec-4156-af1d-fab81aa16ee5"
172
+ #
173
+ # This only affects purely numeric slugs. Alphanumeric slugs like "product-123"
174
+ # or "abc123" will work normally.
175
+ #
176
+ # #### Providing Your Own Slug Processing Method
177
+ #
178
+ # You can override {FriendlyId::Slugged#normalize_friendly_id} in your model for
179
+ # total control over the slug format. It will be invoked for any generated slug,
180
+ # whether for a single slug or for slug candidates.
181
+ #
182
+ # #### Deciding When to Generate New Slugs
183
+ #
184
+ # As of FriendlyId 5.0, slugs are only generated when the `slug` field is nil. If
185
+ # you want a slug to be regenerated,set the slug field to nil:
186
+ #
187
+ # restaurant.friendly_id # joes-diner
188
+ # restaurant.name = "The Plaza Diner"
189
+ # restaurant.save!
190
+ # restaurant.friendly_id # joes-diner
191
+ # restaurant.slug = nil
192
+ # restaurant.save!
193
+ # restaurant.friendly_id # the-plaza-diner
194
+ #
195
+ # You can also override the
196
+ # {FriendlyId::Slugged#should_generate_new_friendly_id?} method, which lets you
197
+ # control exactly when new friendly ids are set:
198
+ #
199
+ # class Post < ActiveRecord::Base
200
+ # extend FriendlyId
201
+ # friendly_id :title, :use => :slugged
202
+ #
203
+ # def should_generate_new_friendly_id?
204
+ # title_changed?
205
+ # end
206
+ # end
207
+ #
208
+ # If you want to extend the default behavior but add your own conditions,
209
+ # don't forget to invoke `super` from your implementation:
210
+ #
211
+ # class Category < ActiveRecord::Base
212
+ # extend FriendlyId
213
+ # friendly_id :name, :use => :slugged
214
+ #
215
+ # def should_generate_new_friendly_id?
216
+ # name_changed? || super
217
+ # end
218
+ # end
219
+ #
220
+ # #### Locale-specific Transliterations
221
+ #
222
+ # Active Support's `parameterize` uses
223
+ # [transliterate](http://api.rubyonrails.org/classes/ActiveSupport/Inflector.html#method-i-transliterate),
224
+ # which in turn can use I18n's transliteration rules to consider the current
225
+ # locale when replacing Latin characters:
226
+ #
227
+ # # config/locales/de.yml
228
+ # de:
229
+ # i18n:
230
+ # transliterate:
231
+ # rule:
232
+ # ü: "ue"
233
+ # ö: "oe"
234
+ # etc...
235
+ #
236
+ # movie = Movie.create! :title => "Der Preis fürs Überleben"
237
+ # movie.slug #=> "der-preis-fuers-ueberleben"
238
+ #
239
+ # This functionality was in fact taken from earlier versions of FriendlyId.
240
+ #
241
+ # #### Gotchas: Common Problems
242
+ #
243
+ # FriendlyId uses a before_validation callback to generate and set the slug. This
244
+ # means that if you create two model instances before saving them, it's possible
245
+ # they will generate the same slug, and the second save will fail.
246
+ #
247
+ # This can happen in two fairly normal cases: the first, when a model using nested
248
+ # attributes creates more than one record for a model that uses friendly_id. The
249
+ # second, in concurrent code, either in threads or multiple processes.
250
+ #
251
+ # To solve the nested attributes issue, I recommend simply avoiding them when
252
+ # creating more than one nested record for a model that uses FriendlyId. See [this
253
+ # Github issue](https://github.com/norman/friendly_id/issues/185) for discussion.
254
+ #
255
+ # @guide end
256
+ module Slugged
257
+ # Sets up behavior and configuration options for FriendlyId's slugging
258
+ # feature.
259
+ def self.included(model_class)
260
+ model_class.friendly_id_config.instance_eval do
261
+ self.class.send :include, Configuration
262
+ self.slug_generator_class ||= SlugGenerator
263
+ defaults[:slug_column] ||= "slug"
264
+ defaults[:sequence_separator] ||= "-"
265
+ end
266
+ model_class.before_validation :set_slug
267
+ model_class.before_save :set_slug
268
+ model_class.after_validation :unset_slug_if_invalid
269
+ end
270
+
271
+ # Process the given value to make it suitable for use as a slug.
272
+ #
273
+ # This method is not intended to be invoked directly; FriendlyId uses it
274
+ # internally to process strings into slugs.
275
+ #
276
+ # However, if FriendlyId's default slug generation doesn't suit your needs,
277
+ # you can override this method in your model class to control exactly how
278
+ # slugs are generated.
279
+ #
280
+ # ### Example
281
+ #
282
+ # class Person < ActiveRecord::Base
283
+ # extend FriendlyId
284
+ # friendly_id :name_and_location
285
+ #
286
+ # def name_and_location
287
+ # "#{name} from #{location}"
288
+ # end
289
+ #
290
+ # # Use default slug, but upper case and with underscores
291
+ # def normalize_friendly_id(string)
292
+ # super.upcase.gsub("-", "_")
293
+ # end
294
+ # end
295
+ #
296
+ # bob = Person.create! :name => "Bob Smith", :location => "New York City"
297
+ # bob.friendly_id #=> "BOB_SMITH_FROM_NEW_YORK_CITY"
298
+ #
299
+ # ### More Resources
300
+ #
301
+ # You might want to look into Babosa[https://github.com/norman/babosa],
302
+ # which is the slugging library used by FriendlyId prior to version 4, which
303
+ # offers some specialized functionality missing from Active Support.
304
+ #
305
+ # @param [#to_s] value The value used as the basis of the slug.
306
+ # @return The candidate slug text, without a sequence.
307
+ def normalize_friendly_id(value)
308
+ value = value.to_s.parameterize
309
+ value = value[0...friendly_id_config.slug_limit] if friendly_id_config.slug_limit
310
+ value
311
+ end
312
+
313
+ # Whether to generate a new slug.
314
+ #
315
+ # You can override this method in your model if, for example, you only want
316
+ # slugs to be generated once, and then never updated.
317
+ def should_generate_new_friendly_id?
318
+ send(friendly_id_config.slug_column).nil? && !send(friendly_id_config.base).nil?
319
+ end
320
+
321
+ # Public: Resolve conflicts.
322
+ #
323
+ # This method adds UUID to first candidate and truncates (if `slug_limit` is set).
324
+ #
325
+ # Examples:
326
+ #
327
+ # resolve_friendly_id_conflict(['12345'])
328
+ # # => '12345-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
329
+ #
330
+ # FriendlyId.defaults { |config| config.slug_limit = 40 }
331
+ # resolve_friendly_id_conflict(['12345'])
332
+ # # => '123-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
333
+ #
334
+ # candidates - the Array with candidates.
335
+ #
336
+ # Returns the String with new slug.
337
+ def resolve_friendly_id_conflict(candidates)
338
+ uuid = SecureRandom.uuid
339
+ [
340
+ apply_slug_limit(candidates.first, uuid),
341
+ uuid
342
+ ].compact.join(friendly_id_config.sequence_separator)
343
+ end
344
+
345
+ # Private: Apply slug limit to candidate.
346
+ #
347
+ # candidate - the String with candidate.
348
+ # uuid - the String with UUID.
349
+ #
350
+ # Return the String with truncated candidate.
351
+ def apply_slug_limit(candidate, uuid)
352
+ return candidate unless candidate && friendly_id_config.slug_limit
353
+
354
+ candidate[0...candidate_limit(uuid)]
355
+ end
356
+ private :apply_slug_limit
357
+
358
+ # Private: Get max length of candidate.
359
+ #
360
+ # uuid - the String with UUID.
361
+ #
362
+ # Returns the Integer with max length.
363
+ def candidate_limit(uuid)
364
+ [
365
+ friendly_id_config.slug_limit - uuid.size - friendly_id_config.sequence_separator.size,
366
+ 0
367
+ ].max
368
+ end
369
+ private :candidate_limit
370
+
371
+ # Sets the slug.
372
+ def set_slug(normalized_slug = nil)
373
+ if should_generate_new_friendly_id?
374
+ candidates = FriendlyId::Candidates.new(self, normalized_slug || send(friendly_id_config.base))
375
+ slug = slug_generator.generate(candidates) || resolve_friendly_id_conflict(candidates)
376
+ send "#{friendly_id_config.slug_column}=", slug
377
+ end
378
+ end
379
+ private :set_slug
380
+
381
+ def scope_for_slug_generator
382
+ scope = self.class.base_class.unscoped
383
+ scope = scope.friendly unless scope.respond_to?(:exists_by_friendly_id?)
384
+ primary_key_name = self.class.primary_key
385
+ scope.where(self.class.base_class.arel_table[primary_key_name].not_eq(send(primary_key_name)))
386
+ end
387
+ private :scope_for_slug_generator
388
+
389
+ def slug_generator
390
+ friendly_id_config.slug_generator_class.new(scope_for_slug_generator, friendly_id_config)
391
+ end
392
+ private :slug_generator
393
+
394
+ def unset_slug_if_invalid
395
+ if errors.key?(friendly_id_config.query_field) && attribute_changed?(friendly_id_config.query_field.to_s)
396
+ diff = changes[friendly_id_config.query_field]
397
+ send "#{friendly_id_config.slug_column}=", diff.first
398
+ end
399
+ end
400
+ private :unset_slug_if_invalid
401
+
402
+ # This module adds the `:slug_column`, and `:slug_limit`, and `:sequence_separator`,
403
+ # and `:slug_generator_class` configuration options to
404
+ # {FriendlyId::Configuration FriendlyId::Configuration}.
405
+ module Configuration
406
+ attr_writer :slug_column, :slug_limit, :sequence_separator
407
+ attr_accessor :slug_generator_class, :treat_numeric_as_conflict
408
+
409
+ # Makes FriendlyId use the slug column for querying.
410
+ # @return String The slug column.
411
+ def query_field
412
+ slug_column
413
+ end
414
+
415
+ # The string used to separate a slug base from a numeric sequence.
416
+ #
417
+ # You can change the default separator by setting the
418
+ # {FriendlyId::Slugged::Configuration#sequence_separator
419
+ # sequence_separator} configuration option.
420
+ # @return String The sequence separator string. Defaults to "`-`".
421
+ def sequence_separator
422
+ @sequence_separator ||= defaults[:sequence_separator]
423
+ end
424
+
425
+ # The column that will be used to store the generated slug.
426
+ def slug_column
427
+ @slug_column ||= defaults[:slug_column]
428
+ end
429
+
430
+ # The limit that will be used for slug.
431
+ def slug_limit
432
+ @slug_limit ||= defaults[:slug_limit]
433
+ end
434
+ end
435
+ end
436
+ end
@@ -0,0 +1,3 @@
1
+ module FriendlyId
2
+ VERSION = "5.7.0".freeze
3
+ end