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