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.
- checksums.yaml +7 -0
- data/friendly_id-5.7.0/Changelog.md +273 -0
- data/friendly_id-5.7.0/MIT-LICENSE +19 -0
- data/friendly_id-5.7.0/README.md +176 -0
- data/friendly_id-5.7.0/lib/friendly_id/base.rb +275 -0
- data/friendly_id-5.7.0/lib/friendly_id/candidates.rb +71 -0
- data/friendly_id-5.7.0/lib/friendly_id/configuration.rb +111 -0
- data/friendly_id-5.7.0/lib/friendly_id/finder_methods.rb +123 -0
- data/friendly_id-5.7.0/lib/friendly_id/finders.rb +92 -0
- data/friendly_id-5.7.0/lib/friendly_id/history.rb +146 -0
- data/friendly_id-5.7.0/lib/friendly_id/initializer.rb +107 -0
- data/friendly_id-5.7.0/lib/friendly_id/migration.rb +21 -0
- data/friendly_id-5.7.0/lib/friendly_id/object_utils.rb +76 -0
- data/friendly_id-5.7.0/lib/friendly_id/reserved.rb +50 -0
- data/friendly_id-5.7.0/lib/friendly_id/scoped.rb +175 -0
- data/friendly_id-5.7.0/lib/friendly_id/sequentially_slugged/calculator.rb +69 -0
- data/friendly_id-5.7.0/lib/friendly_id/sequentially_slugged.rb +40 -0
- data/friendly_id-5.7.0/lib/friendly_id/simple_i18n.rb +114 -0
- data/friendly_id-5.7.0/lib/friendly_id/slug.rb +16 -0
- data/friendly_id-5.7.0/lib/friendly_id/slug_generator.rb +38 -0
- data/friendly_id-5.7.0/lib/friendly_id/slugged.rb +436 -0
- data/friendly_id-5.7.0/lib/friendly_id/version.rb +3 -0
- data/friendly_id-5.7.0/lib/friendly_id.rb +114 -0
- data/friendly_id-5.7.0/lib/generators/friendly_id_generator.rb +26 -0
- data/ultra-clean-lib.gemspec +12 -0
- metadata +65 -0
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
module FriendlyId
|
|
2
|
+
# @guide begin
|
|
3
|
+
#
|
|
4
|
+
# ## Setting Up FriendlyId in Your Model
|
|
5
|
+
#
|
|
6
|
+
# To use FriendlyId in your ActiveRecord models, you must first either extend or
|
|
7
|
+
# include the FriendlyId module (it makes no difference), then invoke the
|
|
8
|
+
# {FriendlyId::Base#friendly_id friendly_id} method to configure your desired
|
|
9
|
+
# options:
|
|
10
|
+
#
|
|
11
|
+
# class Foo < ActiveRecord::Base
|
|
12
|
+
# include FriendlyId
|
|
13
|
+
# friendly_id :bar, :use => [:slugged, :simple_i18n]
|
|
14
|
+
# end
|
|
15
|
+
#
|
|
16
|
+
# The most important option is `:use`, which you use to tell FriendlyId which
|
|
17
|
+
# addons it should use. See the documentation for {FriendlyId::Base#friendly_id} for a list of all
|
|
18
|
+
# available addons, or skim through the rest of the docs to get a high-level
|
|
19
|
+
# overview.
|
|
20
|
+
#
|
|
21
|
+
# *A note about single table inheritance (STI): you must extend FriendlyId in
|
|
22
|
+
# all classes that participate in STI, both your parent classes and their
|
|
23
|
+
# children.*
|
|
24
|
+
#
|
|
25
|
+
# ### The Default Setup: Simple Models
|
|
26
|
+
#
|
|
27
|
+
# The simplest way to use FriendlyId is with a model that has a uniquely indexed
|
|
28
|
+
# column with no spaces or special characters, and that is seldom or never
|
|
29
|
+
# updated. The most common example of this is a user name:
|
|
30
|
+
#
|
|
31
|
+
# class User < ActiveRecord::Base
|
|
32
|
+
# extend FriendlyId
|
|
33
|
+
# friendly_id :login
|
|
34
|
+
# validates_format_of :login, :with => /\A[a-z0-9]+\z/i
|
|
35
|
+
# end
|
|
36
|
+
#
|
|
37
|
+
# @user = User.friendly.find "joe" # the old User.find(1) still works, too
|
|
38
|
+
# @user.to_param # returns "joe"
|
|
39
|
+
# redirect_to @user # the URL will be /users/joe
|
|
40
|
+
#
|
|
41
|
+
# In this case, FriendlyId assumes you want to use the column as-is; it will never
|
|
42
|
+
# modify the value of the column, and your application should ensure that the
|
|
43
|
+
# value is unique and admissible in a URL:
|
|
44
|
+
#
|
|
45
|
+
# class City < ActiveRecord::Base
|
|
46
|
+
# extend FriendlyId
|
|
47
|
+
# friendly_id :name
|
|
48
|
+
# end
|
|
49
|
+
#
|
|
50
|
+
# @city.friendly.find "Viña del Mar"
|
|
51
|
+
# redirect_to @city # the URL will be /cities/Viña%20del%20Mar
|
|
52
|
+
#
|
|
53
|
+
# Writing the code to process an arbitrary string into a good identifier for use
|
|
54
|
+
# in a URL can be repetitive and surprisingly tricky, so for this reason it's
|
|
55
|
+
# often better and easier to use {FriendlyId::Slugged slugs}.
|
|
56
|
+
#
|
|
57
|
+
# @guide end
|
|
58
|
+
module Base
|
|
59
|
+
# Configure FriendlyId's behavior in a model.
|
|
60
|
+
#
|
|
61
|
+
# class Post < ActiveRecord::Base
|
|
62
|
+
# extend FriendlyId
|
|
63
|
+
# friendly_id :title, :use => :slugged
|
|
64
|
+
# end
|
|
65
|
+
#
|
|
66
|
+
# When given the optional block, this method will yield the class's instance
|
|
67
|
+
# of {FriendlyId::Configuration} to the block before evaluating other
|
|
68
|
+
# arguments, so configuration values set in the block may be overwritten by
|
|
69
|
+
# the arguments. This order was chosen to allow passing the same proc to
|
|
70
|
+
# multiple models, while being able to override the values it sets. Here is
|
|
71
|
+
# a contrived example:
|
|
72
|
+
#
|
|
73
|
+
# $friendly_id_config_proc = Proc.new do |config|
|
|
74
|
+
# config.base = :name
|
|
75
|
+
# config.use :slugged
|
|
76
|
+
# end
|
|
77
|
+
#
|
|
78
|
+
# class Foo < ActiveRecord::Base
|
|
79
|
+
# extend FriendlyId
|
|
80
|
+
# friendly_id &$friendly_id_config_proc
|
|
81
|
+
# end
|
|
82
|
+
#
|
|
83
|
+
# class Bar < ActiveRecord::Base
|
|
84
|
+
# extend FriendlyId
|
|
85
|
+
# friendly_id :title, &$friendly_id_config_proc
|
|
86
|
+
# end
|
|
87
|
+
#
|
|
88
|
+
# However, it's usually better to use {FriendlyId.defaults} for this:
|
|
89
|
+
#
|
|
90
|
+
# FriendlyId.defaults do |config|
|
|
91
|
+
# config.base = :name
|
|
92
|
+
# config.use :slugged
|
|
93
|
+
# end
|
|
94
|
+
#
|
|
95
|
+
# class Foo < ActiveRecord::Base
|
|
96
|
+
# extend FriendlyId
|
|
97
|
+
# end
|
|
98
|
+
#
|
|
99
|
+
# class Bar < ActiveRecord::Base
|
|
100
|
+
# extend FriendlyId
|
|
101
|
+
# friendly_id :title
|
|
102
|
+
# end
|
|
103
|
+
#
|
|
104
|
+
# In general you should use the block syntax either because of your personal
|
|
105
|
+
# aesthetic preference, or because you need to share some functionality
|
|
106
|
+
# between multiple models that can't be well encapsulated by
|
|
107
|
+
# {FriendlyId.defaults}.
|
|
108
|
+
#
|
|
109
|
+
# ### Order Method Calls in a Block vs Ordering Options
|
|
110
|
+
#
|
|
111
|
+
# When calling this method without a block, you may set the hash options in
|
|
112
|
+
# any order.
|
|
113
|
+
#
|
|
114
|
+
# However, when using block-style invocation, be sure to call
|
|
115
|
+
# FriendlyId::Configuration's {FriendlyId::Configuration#use use} method
|
|
116
|
+
# *prior* to the associated configuration options, because it will include
|
|
117
|
+
# modules into your class, and these modules in turn may add required
|
|
118
|
+
# configuration options to the `@friendly_id_configuraton`'s class:
|
|
119
|
+
#
|
|
120
|
+
# class Person < ActiveRecord::Base
|
|
121
|
+
# friendly_id do |config|
|
|
122
|
+
# # This will work
|
|
123
|
+
# config.use :slugged
|
|
124
|
+
# config.sequence_separator = ":"
|
|
125
|
+
# end
|
|
126
|
+
# end
|
|
127
|
+
#
|
|
128
|
+
# class Person < ActiveRecord::Base
|
|
129
|
+
# friendly_id do |config|
|
|
130
|
+
# # This will fail
|
|
131
|
+
# config.sequence_separator = ":"
|
|
132
|
+
# config.use :slugged
|
|
133
|
+
# end
|
|
134
|
+
# end
|
|
135
|
+
#
|
|
136
|
+
# ### Including Your Own Modules
|
|
137
|
+
#
|
|
138
|
+
# Because :use can accept a name or a Module, {FriendlyId.defaults defaults}
|
|
139
|
+
# can be a convenient place to set up behavior common to all classes using
|
|
140
|
+
# FriendlyId. You can include any module, or more conveniently, define one
|
|
141
|
+
# on-the-fly. For example, let's say you want to make
|
|
142
|
+
# [Babosa](http://github.com/norman/babosa) the default slugging library in
|
|
143
|
+
# place of Active Support, and transliterate all slugs from Russian Cyrillic
|
|
144
|
+
# to ASCII:
|
|
145
|
+
#
|
|
146
|
+
# require "babosa"
|
|
147
|
+
#
|
|
148
|
+
# FriendlyId.defaults do |config|
|
|
149
|
+
# config.base = :name
|
|
150
|
+
# config.use :slugged
|
|
151
|
+
# config.use Module.new {
|
|
152
|
+
# def normalize_friendly_id(text)
|
|
153
|
+
# text.to_slug.normalize! :transliterations => [:russian, :latin]
|
|
154
|
+
# end
|
|
155
|
+
# }
|
|
156
|
+
# end
|
|
157
|
+
#
|
|
158
|
+
#
|
|
159
|
+
# @option options [Symbol,Module] :use The addon or name of an addon to use.
|
|
160
|
+
# By default, FriendlyId provides {FriendlyId::Slugged :slugged},
|
|
161
|
+
# {FriendlyId::Reserved :finders}, {FriendlyId::History :history},
|
|
162
|
+
# {FriendlyId::Reserved :reserved}, {FriendlyId::Scoped :scoped}, and
|
|
163
|
+
# {FriendlyId::SimpleI18n :simple_i18n}.
|
|
164
|
+
#
|
|
165
|
+
# @option options [Array] :reserved_words Available when using `:reserved`,
|
|
166
|
+
# which is loaded by default. Sets an array of words banned for use as
|
|
167
|
+
# the basis of a friendly_id. By default this includes "edit" and "new".
|
|
168
|
+
#
|
|
169
|
+
# @option options [Symbol] :scope Available when using `:scoped`.
|
|
170
|
+
# Sets the relation or column used to scope generated friendly ids. This
|
|
171
|
+
# option has no default value.
|
|
172
|
+
#
|
|
173
|
+
# @option options [Symbol] :sequence_separator Available when using `:slugged`.
|
|
174
|
+
# Configures the sequence of characters used to separate a slug from a
|
|
175
|
+
# sequence. Defaults to `-`.
|
|
176
|
+
#
|
|
177
|
+
# @option options [Symbol] :slug_column Available when using `:slugged`.
|
|
178
|
+
# Configures the name of the column where FriendlyId will store the slug.
|
|
179
|
+
# Defaults to `:slug`.
|
|
180
|
+
#
|
|
181
|
+
# @option options [Integer] :slug_limit Available when using `:slugged`.
|
|
182
|
+
# Configures the limit of the slug. This option has no default value.
|
|
183
|
+
#
|
|
184
|
+
# @option options [Symbol] :slug_generator_class Available when using `:slugged`.
|
|
185
|
+
# Sets the class used to generate unique slugs. You should not specify this
|
|
186
|
+
# unless you're doing some extensive hacking on FriendlyId. Defaults to
|
|
187
|
+
# {FriendlyId::SlugGenerator}.
|
|
188
|
+
#
|
|
189
|
+
# @yield Provides access to the model class's friendly_id_config, which
|
|
190
|
+
# allows an alternate configuration syntax, and conditional configuration
|
|
191
|
+
# logic.
|
|
192
|
+
#
|
|
193
|
+
# @option options [Symbol,Boolean] :dependent Available when using `:history`.
|
|
194
|
+
# Sets the value used for the slugged association's dependent option. Use
|
|
195
|
+
# `false` if you do not want to dependently destroy the associated slugged
|
|
196
|
+
# record. Defaults to `:destroy`.
|
|
197
|
+
#
|
|
198
|
+
# @option options [Symbol] :routes When set to anything other than :friendly,
|
|
199
|
+
# ensures that all routes generated by default do *not* use the slug. This
|
|
200
|
+
# allows `form_for` and `polymorphic_path` to continue to generate paths like
|
|
201
|
+
# `/team/1` instead of `/team/number-one`. You can still generate paths
|
|
202
|
+
# like the latter using: team_path(team.slug). When set to :friendly, or
|
|
203
|
+
# omitted, the default friendly_id behavior is maintained.
|
|
204
|
+
#
|
|
205
|
+
# @yieldparam config The model class's {FriendlyId::Configuration friendly_id_config}.
|
|
206
|
+
def friendly_id(base = nil, options = {}, &block)
|
|
207
|
+
yield friendly_id_config if block
|
|
208
|
+
friendly_id_config.dependent = options.delete :dependent
|
|
209
|
+
friendly_id_config.use options.delete :use
|
|
210
|
+
friendly_id_config.send :set, base ? options.merge(base: base) : options
|
|
211
|
+
include Model
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# Returns a scope that includes the friendly finders.
|
|
215
|
+
# @see FriendlyId::FinderMethods
|
|
216
|
+
def friendly
|
|
217
|
+
# Guess what? This causes Rails to invoke `extend` on the scope, which has
|
|
218
|
+
# the well-known effect of blowing away Ruby's method cache. It would be
|
|
219
|
+
# possible to make this more performant by subclassing the model's
|
|
220
|
+
# relation class, extending that, and returning an instance of it in this
|
|
221
|
+
# method. FriendlyId 4.0 did something similar. However in 5.0 I've
|
|
222
|
+
# decided to only use Rails's public API in order to improve compatibility
|
|
223
|
+
# and maintainability. If you'd like to improve the performance, your
|
|
224
|
+
# efforts would be best directed at improving it at the root cause
|
|
225
|
+
# of the problem - in Rails - because it would benefit more people.
|
|
226
|
+
all.extending(friendly_id_config.finder_methods)
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# Returns the model class's {FriendlyId::Configuration friendly_id_config}.
|
|
230
|
+
# @note In the case of Single Table Inheritance (STI), this method will
|
|
231
|
+
# duplicate the parent class's FriendlyId::Configuration and relation class
|
|
232
|
+
# on first access. If you're concerned about thread safety, then be sure
|
|
233
|
+
# to invoke {#friendly_id} in your class for each model.
|
|
234
|
+
def friendly_id_config
|
|
235
|
+
@friendly_id_config ||= base_class.friendly_id_config.dup.tap do |config|
|
|
236
|
+
config.model_class = self
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def primary_key_type
|
|
241
|
+
@primary_key_type ||= columns_hash[primary_key].type
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# Instance methods that will be added to all classes using FriendlyId.
|
|
246
|
+
module Model
|
|
247
|
+
def self.included(model_class)
|
|
248
|
+
return if model_class.respond_to?(:friendly)
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
# Convenience method for accessing the class method of the same name.
|
|
252
|
+
def friendly_id_config
|
|
253
|
+
self.class.friendly_id_config
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
# Get the instance's friendly_id.
|
|
257
|
+
def friendly_id
|
|
258
|
+
send friendly_id_config.query_field
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# Either the friendly_id, or the numeric id cast to a string.
|
|
262
|
+
def to_param
|
|
263
|
+
if friendly_id_config.routes == :friendly
|
|
264
|
+
friendly_id.presence.to_param || super
|
|
265
|
+
else
|
|
266
|
+
super
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# Clears slug on duplicate records when calling `dup`.
|
|
271
|
+
def dup
|
|
272
|
+
super.tap { |duplicate| duplicate.slug = nil if duplicate.respond_to?("slug=") }
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
require "securerandom"
|
|
2
|
+
|
|
3
|
+
module FriendlyId
|
|
4
|
+
# This class provides the slug candidate functionality.
|
|
5
|
+
# @see FriendlyId::Slugged
|
|
6
|
+
class Candidates
|
|
7
|
+
include Enumerable
|
|
8
|
+
|
|
9
|
+
def initialize(object, *array)
|
|
10
|
+
@object = object
|
|
11
|
+
@raw_candidates = to_candidate_array(object, array.flatten(1))
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def each(*args, &block)
|
|
15
|
+
return candidates unless block
|
|
16
|
+
candidates.each { |candidate| yield candidate }
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def candidates
|
|
22
|
+
@candidates ||= begin
|
|
23
|
+
candidates = normalize(@raw_candidates)
|
|
24
|
+
filter(candidates)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def normalize(candidates)
|
|
29
|
+
candidates.map do |candidate|
|
|
30
|
+
@object.normalize_friendly_id(candidate.map(&:call).join(" "))
|
|
31
|
+
end.select { |x| wanted?(x) }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def filter(candidates)
|
|
35
|
+
unless candidates.all? { |x| reserved?(x) }
|
|
36
|
+
candidates.reject! { |x| reserved?(x) }
|
|
37
|
+
end
|
|
38
|
+
candidates
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def to_candidate_array(object, array)
|
|
42
|
+
array.map do |candidate|
|
|
43
|
+
case candidate
|
|
44
|
+
when String
|
|
45
|
+
[-> { candidate }]
|
|
46
|
+
when Array
|
|
47
|
+
to_candidate_array(object, candidate).flatten
|
|
48
|
+
when Symbol
|
|
49
|
+
[object.method(candidate)]
|
|
50
|
+
else
|
|
51
|
+
if candidate.respond_to?(:call)
|
|
52
|
+
[candidate]
|
|
53
|
+
else
|
|
54
|
+
[-> { candidate.to_s }]
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def wanted?(slug)
|
|
61
|
+
slug.present?
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def reserved?(slug)
|
|
65
|
+
config = @object.friendly_id_config
|
|
66
|
+
return false unless config.uses? ::FriendlyId::Reserved
|
|
67
|
+
return false unless config.reserved_words
|
|
68
|
+
config.reserved_words.include?(slug)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
module FriendlyId
|
|
2
|
+
# The configuration parameters passed to {Base#friendly_id} will be stored in
|
|
3
|
+
# this object.
|
|
4
|
+
class Configuration
|
|
5
|
+
attr_writer :base
|
|
6
|
+
|
|
7
|
+
# The default configuration options.
|
|
8
|
+
attr_reader :defaults
|
|
9
|
+
|
|
10
|
+
# The modules in use
|
|
11
|
+
attr_reader :modules
|
|
12
|
+
|
|
13
|
+
# The model class that this configuration belongs to.
|
|
14
|
+
# @return ActiveRecord::Base
|
|
15
|
+
attr_accessor :model_class
|
|
16
|
+
|
|
17
|
+
# The module to use for finders
|
|
18
|
+
attr_accessor :finder_methods
|
|
19
|
+
|
|
20
|
+
# The value used for the slugged association's dependent option
|
|
21
|
+
attr_accessor :dependent
|
|
22
|
+
|
|
23
|
+
# Route generation preferences
|
|
24
|
+
attr_accessor :routes
|
|
25
|
+
|
|
26
|
+
def initialize(model_class, values = nil)
|
|
27
|
+
@base = nil
|
|
28
|
+
@model_class = model_class
|
|
29
|
+
@defaults = {}
|
|
30
|
+
@modules = []
|
|
31
|
+
@finder_methods = FriendlyId::FinderMethods
|
|
32
|
+
self.routes = :friendly
|
|
33
|
+
set values
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Lets you specify the addon modules to use with FriendlyId.
|
|
37
|
+
#
|
|
38
|
+
# This method is invoked by {FriendlyId::Base#friendly_id friendly_id} when
|
|
39
|
+
# passing the `:use` option, or when using {FriendlyId::Base#friendly_id
|
|
40
|
+
# friendly_id} with a block.
|
|
41
|
+
#
|
|
42
|
+
# @example
|
|
43
|
+
# class Book < ActiveRecord::Base
|
|
44
|
+
# extend FriendlyId
|
|
45
|
+
# friendly_id :name, :use => :slugged
|
|
46
|
+
# end
|
|
47
|
+
#
|
|
48
|
+
# @param [#to_s,Module] modules Arguments should be Modules, or symbols or
|
|
49
|
+
# strings that correspond with the name of an addon to use with FriendlyId.
|
|
50
|
+
# By default FriendlyId provides `:slugged`, `:finders`, `:history`,
|
|
51
|
+
# `:reserved`, `:simple_i18n`, and `:scoped`.
|
|
52
|
+
def use(*modules)
|
|
53
|
+
modules.to_a.flatten.compact.map do |object|
|
|
54
|
+
mod = get_module(object)
|
|
55
|
+
mod.setup(@model_class) if mod.respond_to?(:setup)
|
|
56
|
+
@model_class.send(:include, mod) unless uses? object
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Returns whether the given module is in use.
|
|
61
|
+
def uses?(mod)
|
|
62
|
+
@model_class < get_module(mod)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# The column that FriendlyId will use to find the record when querying by
|
|
66
|
+
# friendly id.
|
|
67
|
+
#
|
|
68
|
+
# This method is generally only used internally by FriendlyId.
|
|
69
|
+
# @return String
|
|
70
|
+
def query_field
|
|
71
|
+
base.to_s
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# The base column or method used by FriendlyId as the basis of a friendly id
|
|
75
|
+
# or slug.
|
|
76
|
+
#
|
|
77
|
+
# For models that don't use {FriendlyId::Slugged}, this is the column that
|
|
78
|
+
# is used to store the friendly id. For models using {FriendlyId::Slugged},
|
|
79
|
+
# the base is a column or method whose value is used as the basis of the
|
|
80
|
+
# slug.
|
|
81
|
+
#
|
|
82
|
+
# For example, if you have a model representing blog posts and that uses
|
|
83
|
+
# slugs, you likely will want to use the "title" attribute as the base, and
|
|
84
|
+
# FriendlyId will take care of transforming the human-readable title into
|
|
85
|
+
# something suitable for use in a URL.
|
|
86
|
+
#
|
|
87
|
+
# If you pass an argument, it will be used as the base. Otherwise the current
|
|
88
|
+
# value is returned.
|
|
89
|
+
#
|
|
90
|
+
# @param value A symbol referencing a column or method in the model. This
|
|
91
|
+
# value is usually set by passing it as the first argument to
|
|
92
|
+
# {FriendlyId::Base#friendly_id friendly_id}.
|
|
93
|
+
def base(*value)
|
|
94
|
+
if value.empty?
|
|
95
|
+
@base
|
|
96
|
+
else
|
|
97
|
+
self.base = value.first
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
private
|
|
102
|
+
|
|
103
|
+
def get_module(object)
|
|
104
|
+
Module === object ? object : FriendlyId.const_get(object.to_s.titleize.camelize.gsub(/\s+/, ""))
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def set(values)
|
|
108
|
+
values&.each { |name, value| send "#{name}=", value }
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
module FriendlyId
|
|
2
|
+
module FinderMethods
|
|
3
|
+
# Finds a record using the given id.
|
|
4
|
+
#
|
|
5
|
+
# If the id is "unfriendly", it will call the original find method.
|
|
6
|
+
# If the id is a numeric string like '123' it will first look for a friendly
|
|
7
|
+
# id matching '123' and then fall back to looking for a record with the
|
|
8
|
+
# numeric id '123'.
|
|
9
|
+
#
|
|
10
|
+
# @param [Boolean] allow_nil (default: false)
|
|
11
|
+
# Use allow_nil: true if you'd like the finder to return nil instead of
|
|
12
|
+
# raising ActivRecord::RecordNotFound
|
|
13
|
+
#
|
|
14
|
+
# ### Example
|
|
15
|
+
#
|
|
16
|
+
# MyModel.friendly.find("bad-slug")
|
|
17
|
+
# #=> raise ActiveRecord::RecordNotFound
|
|
18
|
+
#
|
|
19
|
+
# MyModel.friendly.find("bad-slug", allow_nil: true)
|
|
20
|
+
# #=> nil
|
|
21
|
+
#
|
|
22
|
+
# Since FriendlyId 5.0, if the id is a nonnumeric string like '123-foo' it
|
|
23
|
+
# will *only* search by friendly id and not fall back to the regular find
|
|
24
|
+
# method.
|
|
25
|
+
#
|
|
26
|
+
# If you want to search only by the friendly id, use {#find_by_friendly_id}.
|
|
27
|
+
# @raise ActiveRecord::RecordNotFound
|
|
28
|
+
def find(*args, allow_nil: false)
|
|
29
|
+
id = args.first
|
|
30
|
+
return super(*args) if args.count != 1 || id.unfriendly_id?
|
|
31
|
+
first_by_friendly_id(id).tap { |result| return result unless result.nil? }
|
|
32
|
+
return super(*args) if potential_primary_key?(id)
|
|
33
|
+
|
|
34
|
+
raise_not_found_exception(id) unless allow_nil
|
|
35
|
+
rescue ActiveRecord::RecordNotFound => exception
|
|
36
|
+
raise exception unless allow_nil
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Returns true if a record with the given id exists.
|
|
40
|
+
def exists?(conditions = :none)
|
|
41
|
+
return super if conditions.unfriendly_id?
|
|
42
|
+
return true if exists_by_friendly_id?(conditions)
|
|
43
|
+
super
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Finds exclusively by the friendly id, completely bypassing original
|
|
47
|
+
# `find`.
|
|
48
|
+
# @raise ActiveRecord::RecordNotFound
|
|
49
|
+
def find_by_friendly_id(id)
|
|
50
|
+
first_by_friendly_id(id) or raise_not_found_exception(id)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def exists_by_friendly_id?(id)
|
|
54
|
+
where(friendly_id_config.query_field => parse_friendly_id(id)).exists?
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
def potential_primary_key?(id)
|
|
60
|
+
key_type = primary_key_type
|
|
61
|
+
# Hook for "ActiveModel::Type::Integer" instance.
|
|
62
|
+
key_type = key_type.type if key_type.respond_to?(:type)
|
|
63
|
+
case key_type
|
|
64
|
+
when :integer
|
|
65
|
+
begin
|
|
66
|
+
Integer(id, 10)
|
|
67
|
+
rescue
|
|
68
|
+
false
|
|
69
|
+
end
|
|
70
|
+
when :uuid
|
|
71
|
+
id.match(/\A[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\z/)
|
|
72
|
+
else
|
|
73
|
+
true
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def first_by_friendly_id(id)
|
|
78
|
+
find_by(friendly_id_config.query_field => parse_friendly_id(id))
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Parse the given value to make it suitable for use as a slug according to
|
|
82
|
+
# your application's rules.
|
|
83
|
+
#
|
|
84
|
+
# This method is not intended to be invoked directly; FriendlyId uses it
|
|
85
|
+
# internally to process a slug into string to use as a finder.
|
|
86
|
+
#
|
|
87
|
+
# However, if FriendlyId's default slug parsing doesn't suit your needs,
|
|
88
|
+
# you can override this method in your model class to control exactly how
|
|
89
|
+
# slugs are generated.
|
|
90
|
+
#
|
|
91
|
+
# ### Example
|
|
92
|
+
#
|
|
93
|
+
# class Person < ActiveRecord::Base
|
|
94
|
+
# extend FriendlyId
|
|
95
|
+
# friendly_id :name_and_location
|
|
96
|
+
#
|
|
97
|
+
# def name_and_location
|
|
98
|
+
# "#{name} from #{location}"
|
|
99
|
+
# end
|
|
100
|
+
#
|
|
101
|
+
# # Use default slug, but lower case
|
|
102
|
+
# # If `id` is "Jane-Doe" or "JANE-DOE", this finds data by "jane-doe"
|
|
103
|
+
# def parse_friendly_id(slug)
|
|
104
|
+
# super.downcase
|
|
105
|
+
# end
|
|
106
|
+
# end
|
|
107
|
+
#
|
|
108
|
+
# @param [#to_s] value The slug to be parsed.
|
|
109
|
+
# @return The parsed slug, which is not modified by default.
|
|
110
|
+
def parse_friendly_id(value)
|
|
111
|
+
value
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def raise_not_found_exception(id)
|
|
115
|
+
message = "can't find record with friendly id: #{id.inspect}"
|
|
116
|
+
if ActiveRecord.version < Gem::Version.create("5.0")
|
|
117
|
+
raise ActiveRecord::RecordNotFound.new(message)
|
|
118
|
+
else
|
|
119
|
+
raise ActiveRecord::RecordNotFound.new(message, name, friendly_id_config.query_field, id)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
module FriendlyId
|
|
2
|
+
# @guide begin
|
|
3
|
+
#
|
|
4
|
+
# ## Performing Finds with FriendlyId
|
|
5
|
+
#
|
|
6
|
+
# FriendlyId offers enhanced finders which will search for your record by
|
|
7
|
+
# friendly id, and fall back to the numeric id if necessary. This makes it easy
|
|
8
|
+
# to add FriendlyId to an existing application with minimal code modification.
|
|
9
|
+
#
|
|
10
|
+
# By default, these methods are available only on the `friendly` scope:
|
|
11
|
+
#
|
|
12
|
+
# Restaurant.friendly.find('plaza-diner') #=> works
|
|
13
|
+
# Restaurant.friendly.find(23) #=> also works
|
|
14
|
+
# Restaurant.find(23) #=> still works
|
|
15
|
+
# Restaurant.find('plaza-diner') #=> will not work
|
|
16
|
+
#
|
|
17
|
+
# ### Restoring FriendlyId 4.0-style finders
|
|
18
|
+
#
|
|
19
|
+
# Prior to version 5.0, FriendlyId overrode the default finder methods to perform
|
|
20
|
+
# friendly finds all the time. This required modifying parts of Rails that did
|
|
21
|
+
# not have a public API, which was harder to maintain and at times caused
|
|
22
|
+
# compatiblity problems. In 5.0 we decided to change the library's defaults and add
|
|
23
|
+
# the friendly finder methods only to the `friendly` scope in order to boost
|
|
24
|
+
# compatiblity. However, you can still opt-in to original functionality very
|
|
25
|
+
# easily by using the `:finders` addon:
|
|
26
|
+
#
|
|
27
|
+
# class Restaurant < ActiveRecord::Base
|
|
28
|
+
# extend FriendlyId
|
|
29
|
+
#
|
|
30
|
+
# scope :active, -> {where(:active => true)}
|
|
31
|
+
#
|
|
32
|
+
# friendly_id :name, :use => [:slugged, :finders]
|
|
33
|
+
# end
|
|
34
|
+
#
|
|
35
|
+
# Restaurant.friendly.find('plaza-diner') #=> works
|
|
36
|
+
# Restaurant.find('plaza-diner') #=> now also works
|
|
37
|
+
# Restaurant.active.find('plaza-diner') #=> now also works
|
|
38
|
+
#
|
|
39
|
+
# ### Updating your application to use FriendlyId's finders
|
|
40
|
+
#
|
|
41
|
+
# Unless you've chosen to use the `:finders` addon, be sure to modify the finders
|
|
42
|
+
# in your controllers to use the `friendly` scope. For example:
|
|
43
|
+
#
|
|
44
|
+
# # before
|
|
45
|
+
# def set_restaurant
|
|
46
|
+
# @restaurant = Restaurant.find(params[:id])
|
|
47
|
+
# end
|
|
48
|
+
#
|
|
49
|
+
# # after
|
|
50
|
+
# def set_restaurant
|
|
51
|
+
# @restaurant = Restaurant.friendly.find(params[:id])
|
|
52
|
+
# end
|
|
53
|
+
#
|
|
54
|
+
# #### Active Admin
|
|
55
|
+
#
|
|
56
|
+
# Unless you use the `:finders` addon, you should modify your admin controllers
|
|
57
|
+
# for models that use FriendlyId with something similar to the following:
|
|
58
|
+
#
|
|
59
|
+
# controller do
|
|
60
|
+
# def find_resource
|
|
61
|
+
# scoped_collection.friendly.find(params[:id])
|
|
62
|
+
# end
|
|
63
|
+
# end
|
|
64
|
+
#
|
|
65
|
+
# @guide end
|
|
66
|
+
module Finders
|
|
67
|
+
module ClassMethods
|
|
68
|
+
if (ActiveRecord::VERSION::MAJOR == 4) && (ActiveRecord::VERSION::MINOR == 0)
|
|
69
|
+
def relation_delegate_class(klass)
|
|
70
|
+
relation_class_name = :"#{klass.to_s.gsub("::", "_")}_#{to_s.gsub("::", "_")}"
|
|
71
|
+
klass.const_get(relation_class_name)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def self.setup(model_class)
|
|
77
|
+
model_class.instance_eval do
|
|
78
|
+
relation.class.send(:include, friendly_id_config.finder_methods)
|
|
79
|
+
if (ActiveRecord::VERSION::MAJOR == 4 && ActiveRecord::VERSION::MINOR == 2) || ActiveRecord::VERSION::MAJOR >= 5
|
|
80
|
+
model_class.send(:extend, friendly_id_config.finder_methods)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Support for friendly finds on associations for Rails 4.0.1 and above.
|
|
85
|
+
if ::ActiveRecord.const_defined?("AssociationRelation")
|
|
86
|
+
model_class.extend(ClassMethods)
|
|
87
|
+
association_relation_delegate_class = model_class.relation_delegate_class(::ActiveRecord::AssociationRelation)
|
|
88
|
+
association_relation_delegate_class.send(:include, model_class.friendly_id_config.finder_methods)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|