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