1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275
|
module FriendlyId
# @guide begin
#
# ## Setting Up FriendlyId in Your Model
#
# To use FriendlyId in your ActiveRecord models, you must first either extend or
# include the FriendlyId module (it makes no difference), then invoke the
# {FriendlyId::Base#friendly_id friendly_id} method to configure your desired
# options:
#
# class Foo < ActiveRecord::Base
# include FriendlyId
# friendly_id :bar, :use => [:slugged, :simple_i18n]
# end
#
# The most important option is `:use`, which you use to tell FriendlyId which
# addons it should use. See the documentation for {FriendlyId::Base#friendly_id} for a list of all
# available addons, or skim through the rest of the docs to get a high-level
# overview.
#
# *A note about single table inheritance (STI): you must extend FriendlyId in
# all classes that participate in STI, both your parent classes and their
# children.*
#
# ### The Default Setup: Simple Models
#
# The simplest way to use FriendlyId is with a model that has a uniquely indexed
# column with no spaces or special characters, and that is seldom or never
# updated. The most common example of this is a user name:
#
# class User < ActiveRecord::Base
# extend FriendlyId
# friendly_id :login
# validates_format_of :login, :with => /\A[a-z0-9]+\z/i
# end
#
# @user = User.friendly.find "joe" # the old User.find(1) still works, too
# @user.to_param # returns "joe"
# redirect_to @user # the URL will be /users/joe
#
# In this case, FriendlyId assumes you want to use the column as-is; it will never
# modify the value of the column, and your application should ensure that the
# value is unique and admissible in a URL:
#
# class City < ActiveRecord::Base
# extend FriendlyId
# friendly_id :name
# end
#
# @city.friendly.find "Viña del Mar"
# redirect_to @city # the URL will be /cities/Viña%20del%20Mar
#
# Writing the code to process an arbitrary string into a good identifier for use
# in a URL can be repetitive and surprisingly tricky, so for this reason it's
# often better and easier to use {FriendlyId::Slugged slugs}.
#
# @guide end
module Base
# Configure FriendlyId's behavior in a model.
#
# class Post < ActiveRecord::Base
# extend FriendlyId
# friendly_id :title, :use => :slugged
# end
#
# When given the optional block, this method will yield the class's instance
# of {FriendlyId::Configuration} to the block before evaluating other
# arguments, so configuration values set in the block may be overwritten by
# the arguments. This order was chosen to allow passing the same proc to
# multiple models, while being able to override the values it sets. Here is
# a contrived example:
#
# $friendly_id_config_proc = Proc.new do |config|
# config.base = :name
# config.use :slugged
# end
#
# class Foo < ActiveRecord::Base
# extend FriendlyId
# friendly_id &$friendly_id_config_proc
# end
#
# class Bar < ActiveRecord::Base
# extend FriendlyId
# friendly_id :title, &$friendly_id_config_proc
# end
#
# However, it's usually better to use {FriendlyId.defaults} for this:
#
# FriendlyId.defaults do |config|
# config.base = :name
# config.use :slugged
# end
#
# class Foo < ActiveRecord::Base
# extend FriendlyId
# end
#
# class Bar < ActiveRecord::Base
# extend FriendlyId
# friendly_id :title
# end
#
# In general you should use the block syntax either because of your personal
# aesthetic preference, or because you need to share some functionality
# between multiple models that can't be well encapsulated by
# {FriendlyId.defaults}.
#
# ### Order Method Calls in a Block vs Ordering Options
#
# When calling this method without a block, you may set the hash options in
# any order.
#
# However, when using block-style invocation, be sure to call
# FriendlyId::Configuration's {FriendlyId::Configuration#use use} method
# *prior* to the associated configuration options, because it will include
# modules into your class, and these modules in turn may add required
# configuration options to the `@friendly_id_configuraton`'s class:
#
# class Person < ActiveRecord::Base
# friendly_id do |config|
# # This will work
# config.use :slugged
# config.sequence_separator = ":"
# end
# end
#
# class Person < ActiveRecord::Base
# friendly_id do |config|
# # This will fail
# config.sequence_separator = ":"
# config.use :slugged
# end
# end
#
# ### Including Your Own Modules
#
# Because :use can accept a name or a Module, {FriendlyId.defaults defaults}
# can be a convenient place to set up behavior common to all classes using
# FriendlyId. You can include any module, or more conveniently, define one
# on-the-fly. For example, let's say you want to make
# [Babosa](http://github.com/norman/babosa) the default slugging library in
# place of Active Support, and transliterate all slugs from Russian Cyrillic
# to ASCII:
#
# require "babosa"
#
# FriendlyId.defaults do |config|
# config.base = :name
# config.use :slugged
# config.use Module.new {
# def normalize_friendly_id(text)
# text.to_slug.normalize! :transliterations => [:russian, :latin]
# end
# }
# end
#
#
# @option options [Symbol,Module] :use The addon or name of an addon to use.
# By default, FriendlyId provides {FriendlyId::Slugged :slugged},
# {FriendlyId::Reserved :finders}, {FriendlyId::History :history},
# {FriendlyId::Reserved :reserved}, {FriendlyId::Scoped :scoped}, and
# {FriendlyId::SimpleI18n :simple_i18n}.
#
# @option options [Array] :reserved_words Available when using `:reserved`,
# which is loaded by default. Sets an array of words banned for use as
# the basis of a friendly_id. By default this includes "edit" and "new".
#
# @option options [Symbol] :scope Available when using `:scoped`.
# Sets the relation or column used to scope generated friendly ids. This
# option has no default value.
#
# @option options [Symbol] :sequence_separator Available when using `:slugged`.
# Configures the sequence of characters used to separate a slug from a
# sequence. Defaults to `-`.
#
# @option options [Symbol] :slug_column Available when using `:slugged`.
# Configures the name of the column where FriendlyId will store the slug.
# Defaults to `:slug`.
#
# @option options [Integer] :slug_limit Available when using `:slugged`.
# Configures the limit of the slug. This option has no default value.
#
# @option options [Symbol] :slug_generator_class Available when using `:slugged`.
# Sets the class used to generate unique slugs. You should not specify this
# unless you're doing some extensive hacking on FriendlyId. Defaults to
# {FriendlyId::SlugGenerator}.
#
# @yield Provides access to the model class's friendly_id_config, which
# allows an alternate configuration syntax, and conditional configuration
# logic.
#
# @option options [Symbol,Boolean] :dependent Available when using `:history`.
# Sets the value used for the slugged association's dependent option. Use
# `false` if you do not want to dependently destroy the associated slugged
# record. Defaults to `:destroy`.
#
# @option options [Symbol] :routes When set to anything other than :friendly,
# ensures that all routes generated by default do *not* use the slug. This
# allows `form_for` and `polymorphic_path` to continue to generate paths like
# `/team/1` instead of `/team/number-one`. You can still generate paths
# like the latter using: team_path(team.slug). When set to :friendly, or
# omitted, the default friendly_id behavior is maintained.
#
# @yieldparam config The model class's {FriendlyId::Configuration friendly_id_config}.
def friendly_id(base = nil, options = {}, &block)
yield friendly_id_config if block
friendly_id_config.dependent = options.delete :dependent
friendly_id_config.use options.delete :use
friendly_id_config.send :set, base ? options.merge(base: base) : options
include Model
end
# Returns a scope that includes the friendly finders.
# @see FriendlyId::FinderMethods
def friendly
# Guess what? This causes Rails to invoke `extend` on the scope, which has
# the well-known effect of blowing away Ruby's method cache. It would be
# possible to make this more performant by subclassing the model's
# relation class, extending that, and returning an instance of it in this
# method. FriendlyId 4.0 did something similar. However in 5.0 I've
# decided to only use Rails's public API in order to improve compatibility
# and maintainability. If you'd like to improve the performance, your
# efforts would be best directed at improving it at the root cause
# of the problem - in Rails - because it would benefit more people.
all.extending(friendly_id_config.finder_methods)
end
# Returns the model class's {FriendlyId::Configuration friendly_id_config}.
# @note In the case of Single Table Inheritance (STI), this method will
# duplicate the parent class's FriendlyId::Configuration and relation class
# on first access. If you're concerned about thread safety, then be sure
# to invoke {#friendly_id} in your class for each model.
def friendly_id_config
@friendly_id_config ||= base_class.friendly_id_config.dup.tap do |config|
config.model_class = self
end
end
def primary_key_type
@primary_key_type ||= columns_hash[primary_key].type
end
end
# Instance methods that will be added to all classes using FriendlyId.
module Model
def self.included(model_class)
return if model_class.respond_to?(:friendly)
end
# Convenience method for accessing the class method of the same name.
def friendly_id_config
self.class.friendly_id_config
end
# Get the instance's friendly_id.
def friendly_id
send friendly_id_config.query_field
end
# Either the friendly_id, or the numeric id cast to a string.
def to_param
if friendly_id_config.routes == :friendly
friendly_id.presence.to_param || super
else
super
end
end
# Clears slug on duplicate records when calling `dup`.
def dup
super.tap { |duplicate| duplicate.slug = nil if duplicate.respond_to?("slug=") }
end
end
end
|