lib/stringex/acts_as_url.rb in stringex-1.5.1 vs lib/stringex/acts_as_url.rb in stringex-2.0.0

- old
+ new

@@ -1,68 +1,19 @@ # encoding: UTF-8 +require "stringex/acts_as_url/adapter" + module Stringex module ActsAsUrl # :nodoc: - def self.included(base) - base.extend ClassMethods + def self.configure(&block) + Stringex::Configuration::ActsAsUrl.configure &block end - class Configuration - attr_accessor :allow_slash, :allow_duplicates, :attribute_to_urlify, :duplicate_count_separator, - :exclude, :only_when_blank, :scope_for_url, :sync_url, :url_attribute, :url_limit - - def initialize(klass, options = {}) - self.allow_slash = options[:allow_slash] - self.allow_duplicates = options[:allow_duplicates] - self.attribute_to_urlify = options[:attribute] - self.duplicate_count_separator = options[:duplicate_count_separator] || "-" - self.exclude = options[:exclude] || [] - self.only_when_blank = options[:only_when_blank] - self.scope_for_url = options[:scope] - self.sync_url = options[:sync_url] - self.url_attribute = options[:url_attribute] || "url" - self.url_limit = options[:limit] - end - - def get_base_url!(instance) - base_url = instance.send(url_attribute) - if base_url.blank? || !only_when_blank - root = instance.send(attribute_to_urlify).to_s - base_url = root.to_url(:allow_slash => allow_slash, :limit => url_limit, :exclude => exclude) - end - instance.instance_variable_set "@acts_as_url_base_url", base_url - end - - def get_conditions!(instance) - conditions = ["#{url_attribute} LIKE ?", instance.instance_variable_get("@acts_as_url_base_url") + '%'] - unless instance.new_record? - conditions.first << " and id != ?" - conditions << instance.id - end - if scope_for_url - conditions.first << " and #{scope_for_url} = ?" - conditions << instance.send(scope_for_url) - end - conditions - end - - def handle_duplicate_urls!(instance) - return if allow_duplicates - - base_url = instance.instance_variable_get("@acts_as_url_base_url") - url_owners = instance.class.unscoped.find(:all, :conditions => get_conditions!(instance)) - if url_owners.any?{|owner| owner.send(url_attribute) == base_url} - separator = duplicate_count_separator - n = 1 - while url_owners.any?{|owner| owner.send(url_attribute) == "#{base_url}#{separator}#{n}"} - n = n.succ - end - instance.send :write_attribute, url_attribute, "#{base_url}#{separator}#{n}" - end - end + def self.unconfigure! + Stringex::Configuration::ActsAsUrl.unconfigure! end - module ClassMethods # :doc: + module ActsAsUrlClassMethods # :doc: # Creates a callback to automatically create an url-friendly representation # of the <tt>attribute</tt> argument. Example: # # acts_as_url :title # @@ -72,77 +23,78 @@ # as the argument as you would an attribute. # # The default attribute <tt>acts_as_url</tt> uses to save the permalink is <tt>url</tt> # but this can be changed in the options hash. Available options are: # - # <tt>:allow_slash</tt>:: If true, allow the generated url to contain slashes. Default is false[y]. - # <tt>:allow_duplicates</tt>:: If true, allow duplicate urls instead of appending numbers to - # differentiate between urls. Default is false[y]. + # <tt>:adapter</tt>:: If specified, will indicate what ORM adapter to use. Default functionality + # is to use the first available adapter. This should work for most cases + # unless you are using multiple ORMs in a single project. + # <tt>:allow_slash</tt>:: If true, allows the generated url to contain slashes. Default is false[y]. + # <tt>:allow_duplicates</tt>:: If true, allows duplicate urls instead of appending numbers to + # differentiate between urls. Default is false[y]. See note on <tt>:scope</tt>. # <tt>:duplicate_count_separator</tt>:: String to use when forcing unique urls from non-unique strings. # Default is "-". + # <tt>:force_downcase</tt>:: If false, allows generated url to contain uppercased letters. Default is false. # <tt>:exclude_list</tt>:: List of complete strings that should not be transformed by <tt>acts_as_url</tt>. # Default is empty. # <tt>:only_when_blank</tt>:: If true, the url generation will only happen when <tt>:url_attribute</tt> is # blank. Default is false[y] (meaning url generation will happen always). # <tt>:scope</tt>:: The name of model attribute to scope unique urls to. There is no default here. + # <strong>Note:</strong> this will automatically act as if <tt>:allow_duplicates</tt> + # is set to true. # <tt>:sync_url</tt>:: If set to true, the url field will be updated when changes are made to the - # attribute it is based on. Default is false[y]. + # attribute it is based on. Default is false. # <tt>:url_attribute</tt>:: The name of the attribute to use for storing the generated url string. # Default is <tt>:url</tt>. - # <tt>:url_limit</tt>:: The maximum size a generated url should be. <strong>Note:</strong> this does not - # include the characters needed to enforce uniqueness on duplicate urls. - # Default is nil. + # <tt>:limit</tt>:: The maximum size a generated url should be. <strong>Note:</strong> this does not + # include the characters needed to enforce uniqueness on duplicate urls. + # Default is nil. def acts_as_url(attribute, options = {}) - cattr_accessor :acts_as_url_configuration - - options[:attribute] = attribute - self.acts_as_url_configuration = ActsAsUrl::Configuration.new(self, options) - - if acts_as_url_configuration.sync_url - before_validation(:ensure_unique_url) - else - if defined?(ActiveModel::Callbacks) - before_validation(:ensure_unique_url, :on => :create) - else - before_validation_on_create(:ensure_unique_url) + class_eval do + class << self + attr_accessor :acts_as_url_configuration end - end - class_eval <<-"END" - def #{acts_as_url_configuration.url_attribute} - if !new_record? && errors[acts_as_url_configuration.attribute_to_urlify].present? - self.class.find(id).send(acts_as_url_configuration.url_attribute) - else - read_attribute(acts_as_url_configuration.url_attribute) + define_method :acts_as_url_configuration do + klass = self.class + while klass.acts_as_url_configuration.nil? + klass = klass.superclass end + klass.acts_as_url_configuration end - END + end + + options[:attribute_to_urlify] = attribute + self.acts_as_url_configuration = Stringex::Configuration::ActsAsUrl.new(options) + + acts_as_url_configuration.adapter.create_callbacks! self end + + # Some ORMs function as mixins not base classes and need to have a hook to reinclude + # and re-extend ActsAsUrl methods + def included(base) + super + + base.send :include, Stringex::ActsAsUrl::ActsAsUrlInstanceMethods + base.send :extend, Stringex::ActsAsUrl::ActsAsUrlClassMethods + end + # Initialize the url fields for the records that need it. Designed for people who add # <tt>acts_as_url</tt> support once there's already development/production data they'd # like to keep around. # # Note: This method can get very expensive, very fast. If you're planning on using this # on a large selection, you will get much better results writing your own version with # using pagination. def initialize_urls - find_each(:conditions => {acts_as_url_configuration.url_attribute => nil}) do |instance| - instance.send :ensure_unique_url - instance.save - end + acts_as_url_configuration.adapter.initialize_urls! self end end - private - - def ensure_unique_url - # Just to save some typing - config = acts_as_url_configuration - url_attribute = config.url_attribute - - config.get_base_url! self - write_attribute url_attribute, @acts_as_url_base_url - config.handle_duplicate_urls!(self) unless config.allow_duplicates + module ActsAsUrlInstanceMethods + def ensure_unique_url + acts_as_url_configuration.adapter.ensure_unique_url! self + end end end end