# FriendlyId is a comprehensive Ruby library for ActiveRecord permalinks and # slugs. # @author Norman Clarke module FriendlyId autoload :Slugged, "friendly_id/slugged" autoload :Scoped, "friendly_id/scoped" # Class methods that will be added to ActiveRecord::Base. module Base extend self 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 def friendly_id_config @friendly_id_config ||= Configuration.new(self) end def uses_friendly_id? !! @friendly_id_config end end # Instance methods that will be added to all classes using FriendlyId. module Model # 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 (friendly_id or id).to_s end end # The configuration paramters passed to +has_friendly_id+ will be stored # in this object. class Configuration attr_accessor :base attr_reader :klass DEFAULTS = { :config_error_message => 'FriendlyId has no such config option "%s"', :reserved_words => ["new", "edit"] } def initialize(klass, values = nil) @klass = klass set values end def method_missing(symbol, *args, &block) option = symbol.to_s.gsub(/=\z/, '') raise ArgumentError, DEFAULTS[:config_error_message] % option end def set(values) values and values.each {|name, value| self.send "#{name}=", value} end def query_field base end end # 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 # 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 # 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 # These methods will override the finder methods in ActiveRecord::Relation. module FinderMethods protected # @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 end end ActiveRecord::Base.extend FriendlyId::Base ActiveRecord::Relation.send :include, FriendlyId::FinderMethods Object.send :include, FriendlyId::ObjectUtils