lib/friendly_id.rb in friendly_id4-4.0.0.beta6 vs lib/friendly_id.rb in friendly_id4-4.0.0.pre
- old
+ new
@@ -1,141 +1,121 @@
-# encoding: utf-8
-require "thread"
-require "friendly_id/base"
-require "friendly_id/model"
-require "friendly_id/object_utils"
-require "friendly_id/configuration"
-require "friendly_id/finder_methods"
+# FriendlyId is a comprehensive Ruby library for ActiveRecord permalinks and
+# slugs.
+# @author Norman Clarke
+module FriendlyId
-=begin
+ autoload :Slugged, "friendly_id/slugged"
+ autoload :Scoped, "friendly_id/scoped"
-== About FriendlyId
+ # Class methods that will be added to ActiveRecord::Base.
+ module Base
+ extend self
-FriendlyId is an add-on to Ruby's Active Record that allows you to replace ids
-in your URLs with strings:
+ def has_friendly_id(*args)
+ options = args.extract_options!
+ base = args.shift
+ friendly_id_config.set options.merge(:base => base)
+ include Model
+ # @NOTE: AR-specific code here
+ validates_exclusion_of base, :in => Configuration::DEFAULTS[:reserved_words]
+ before_save do |record|
+ record.instance_eval {@_current_friendly_id = friendly_id}
+ end
+ end
- # without FriendlyId
- http://example.com/states/4323454
+ def friendly_id_config
+ @friendly_id_config ||= Configuration.new(self)
+ end
- # with FriendlyId
- http://example.com/states/washington
+ def uses_friendly_id?
+ !! @friendly_id_config
+ end
+ end
-It requires few changes to your application code and offers flexibility,
-performance and a well-documented codebase.
+ # Instance methods that will be added to all classes using FriendlyId.
+ module Model
-=== Concepts
+ # Convenience method for accessing the class method of the same name.
+ def friendly_id_config
+ self.class.friendly_id_config
+ end
-Although FriendlyId helps with URLs, it does all of its work inside your models,
-not your routes.
+ # Get the instance's friendly_id.
+ def friendly_id
+ send friendly_id_config.query_field
+ end
-=== 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
+ # Either the friendly_id, or the numeric id cast to a string.
+ def to_param
+ (friendly_id or id).to_s
end
+ end
- @user = User.find "joe" # the old User.find(1) still works, too
- @user.to_param # returns "joe"
- redirect_to @user # the URL will be /users/joe
+ # The configuration paramters passed to +has_friendly_id+ will be stored
+ # in this object.
+ class Configuration
+ attr_accessor :base
+ attr_reader :klass
-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 admissible in a URL:
+ DEFAULTS = {
+ :config_error_message => 'FriendlyId has no such config option "%s"',
+ :reserved_words => ["new", "edit"]
+ }
- class City < ActiveRecord::Base
- extend FriendlyId
- friendly_id :name
+ def initialize(klass, values = nil)
+ @klass = klass
+ set values
end
- @city.find "Viña del Mar"
- redirect_to @city # the URL will be /cities/Viña%20del%20Mar
+ def method_missing(symbol, *args, &block)
+ option = symbol.to_s.gsub(/=\z/, '')
+ raise ArgumentError, DEFAULTS[:config_error_message] % option
+ end
-For this reason, it is often more convenient to use "slugs" rather than a single
-column.
+ def set(values)
+ values and values.each {|name, value| self.send "#{name}=", value}
+ end
-=== Slugged Models
+ def query_field
+ base
+ end
+ end
-FriendlyId can uses a separate column to store slugs for models which require
-some processing of the friendly_id text. The most common example is a blog
-post's title, which may have spaces, uppercase characters, or other attributes
-you wish to modify to make them more suitable for use in URL's.
+ # Utility methods that are in Object because it's impossible to predict what
+ # kinds of objects get passed into FinderMethods#find_one and
+ # Model#normalize_friendly_id.
+ module ObjectUtils
- class Post < ActiveRecord::Base
- extend FriendlyId
- friendly_id :title, :use => :slugged
+ # True is the id is definitely friendly, false if definitely unfriendly,
+ # else nil.
+ def friendly_id?
+ if kind_of?(Integer) or kind_of?(Symbol) or self.class.respond_to? :friendly_id_config
+ false
+ elsif to_i.to_s != to_s
+ true
+ end
end
- @post = Post.create(:title => "This is the first post!")
- @post.friendly_id # returns "this-is-the-first-post"
- redirect_to @post # the URL will be /posts/this-is-the-first-post
+ # True if the id is definitely unfriendly, false if definitely friendly,
+ # else nil.
+ def unfriendly_id?
+ val = friendly_id? ; !val unless val.nil?
+ end
+ end
-In general, use slugs by default unless you know for sure you don't need them.
+ # These methods will override the finder methods in ActiveRecord::Relation.
+ module FinderMethods
-@author Norman Clarke
-=end
-module FriendlyId
+ protected
- # The current version.
- VERSION = "4.0.0.beta6"
-
- @mutex = Mutex.new
-
- autoload :History, "friendly_id/history"
- autoload :Reserved, "friendly_id/reserved"
- autoload :Scoped, "friendly_id/scoped"
- autoload :Slugged, "friendly_id/slugged"
-
- # FriendlyId takes advantage of `extended` to do basic model setup, primarily
- # extending {FriendlyId::Base} to add {FriendlyId::Base#friendly_id
- # friendly_id} as a class method.
- #
- # Previous versions of FriendlyId simply patched ActiveRecord::Base, but this
- # version tries to be less invasive.
- #
- # In addition to adding {FriendlyId::Base#friendly_id friendly_id}, the class
- # instance variable +@friendly_id_config+ is added. This variable is an
- # instance of an anonymous subclass of {FriendlyId::Configuration}. This
- # allows subsequently loaded modules like {FriendlyId::Slugged} and
- # {FriendlyId::Scoped} to add functionality to the configuration class only
- # for the current class, rather than monkey patching
- # {FriendlyId::Configuration} directly. This isolates other models from large
- # feature changes an addon to FriendlyId could potentially introduce.
- #
- # The upshot of this is, you can htwo Active Record models that both have a
- # @friendly_id_config, but each config object can have different methods and
- # behaviors depending on what modules have been loaded, without conflicts.
- # Keep this in mind if you're hacking on FriendlyId.
- #
- # For examples of this, see the source for {Scoped.included}.
- def self.extended(base)
- base.instance_eval do
- extend FriendlyId::Base
- @friendly_id_config = Class.new(FriendlyId::Configuration).new(base)
- if defaults = FriendlyId.defaults
- defaults.yield @friendly_id_config
- end
+ # @NOTE AR-specific code here
+ def find_one(id)
+ return super if !@klass.uses_friendly_id? or id.unfriendly_id?
+ where(@klass.friendly_id_config.query_field => id).first or super
end
- ActiveRecord::Relation.send :include, FriendlyId::FinderMethods
- end
- # Set global defaults for all models using FriendlyId.
- #
- # The default defaults are to use the +:reserved+ module and nothing else.
- #
- # @example
- # FriendlyId.defaults do |config|
- # config.base = :name
- # config.use :slugged
- # end
- def self.defaults(&block)
- @mutex.synchronize do
- @defaults = block if block_given?
- @defaults ||= lambda {|config| config.use :reserved}
- end
end
-end
\ No newline at end of file
+end
+
+ActiveRecord::Base.extend FriendlyId::Base
+ActiveRecord::Relation.send :include, FriendlyId::FinderMethods
+Object.send :include, FriendlyId::ObjectUtils