begin require 'iconv' rescue Object puts "no iconv, you might want to look into it." end require 'digest/sha1' module PermalinkFu class << self attr_accessor :translation_to attr_accessor :translation_from # This method does the actual permalink escaping. def escape(string) begin result = ((translation_to && translation_from) ? Iconv.iconv(translation_to, translation_from, string) : string).to_s rescue result = Digest::SHA1.hexdigest( Time.now.to_s.split(//).sort_by {rand}.join ) end result.gsub!(/[^\x00-\x7F]+/, '') # Remove anything non-ASCII entirely (e.g. diacritics). result.gsub!(/[^\w_ \-]+/i, '') # Remove unwanted chars. result.gsub!(/[ \-]+/i, '-') # No more than one of the separator in a row. result.gsub!(/^\-|\-$/i, '') # Remove leading/trailing separator. result.downcase! result end end # This is the plugin method available on all ActiveRecord models. module PluginMethods # Specifies the given field(s) as a permalink, meaning it is passed through PermalinkFu.escape and set to the permalink_field. This # is done # # class Foo < ActiveRecord::Base # # stores permalink form of #title to the #permalink attribute # has_permalink :title # # # stores a permalink form of "#{category}-#{title}" to the #permalink attribute # # has_permalink [:category, :title] # # # stores permalink form of #title to the #category_permalink attribute # has_permalink [:category, :title], :category_permalink # # # add a scope # has_permalink :title, :scope => :blog_id # # # add a scope and specify the permalink field name # has_permalink :title, :slug, :scope => :blog_id # # # do not bother checking for a unique scope # has_permalink :title, :unique => false # end # def has_permalink(attr_names = [], permalink_field = nil, options = {}) if permalink_field.is_a?(Hash) options = permalink_field permalink_field = nil end ClassMethods.setup_permalink_fu_on self do self.permalink_attributes = Array(attr_names) self.permalink_field = (permalink_field || 'permalink').to_s self.permalink_options = {:unique => true}.update(options) end end end # Contains class methods for ActiveRecord models that have permalinks module ClassMethods def self.setup_permalink_fu_on(base) base.extend self class << base attr_accessor :permalink_options attr_accessor :permalink_attributes attr_accessor :permalink_field end base.send :include, InstanceMethods yield if base.permalink_options[:unique] base.before_validation :create_unique_permalink else base.before_validation :create_common_permalink end class << base alias_method :define_attribute_methods_without_permalinks, :define_attribute_methods alias_method :define_attribute_methods, :define_attribute_methods_with_permalinks end end def define_attribute_methods_with_permalinks if value = define_attribute_methods_without_permalinks evaluate_attribute_method permalink_field, "def #{self.permalink_field}=(new_value);write_attribute(:#{self.permalink_field}, PermalinkFu.escape(new_value));end", "#{self.permalink_field}=" end value end end # This contains instance methods for ActiveRecord models that have permalinks. module InstanceMethods protected def create_common_permalink return unless should_create_permalink? if read_attribute(self.class.permalink_field).to_s.empty? send("#{self.class.permalink_field}=", create_permalink_for(self.class.permalink_attributes)) end limit = self.class.columns_hash[self.class.permalink_field].limit base = send("#{self.class.permalink_field}=", read_attribute(self.class.permalink_field)[0..limit - 1]) [limit, base] end def create_unique_permalink limit, base = create_common_permalink return if limit.nil? counter = 1 # oh how i wish i could use a hash for conditions conditions = ["#{self.class.permalink_field} = ?", base] unless new_record? conditions.first << " and id != ?" conditions << id end if self.class.permalink_options[:scope] [self.class.permalink_options[:scope]].flatten.each do |scope| value = send(scope) if value conditions.first << " and #{scope} = ?" conditions << send(scope) else conditions.first << " and #{scope} IS NULL" end end end while self.class.exists?(conditions) suffix = "-#{counter += 1}" conditions[1] = "#{base[0..limit-suffix.size-1]}#{suffix}" send("#{self.class.permalink_field}=", conditions[1]) end end def create_permalink_for(attr_names) attr_names.collect { |attr_name| send(attr_name).to_s } * " " end private def should_create_permalink? return false unless permalink_fields_changed? if self.class.permalink_options[:if] evaluate_method(self.class.permalink_options[:if]) elsif self.class.permalink_options[:unless] !evaluate_method(self.class.permalink_options[:unless]) else true end end def permalink_fields_changed? self.class.permalink_attributes.any? do |attribute| changed_method = "#{attribute}_changed?" respond_to?(changed_method) ? send(changed_method) : true end end def evaluate_method(method) case method when Symbol send(method) when String eval(method, instance_eval { binding }) when Proc, Method method.call(self) end end end end if Object.const_defined?(:Iconv) PermalinkFu.translation_to = 'ascii//translit//IGNORE' PermalinkFu.translation_from = 'utf-8' end