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