# encoding: UTF-8 module LuckySneaks module ActsAsUrl # :nodoc: def self.included(base) base.extend ClassMethods end module ClassMethods # :doc: # Creates a callback to automatically create an url-friendly representation # of the attribute argument. Example: # # act_as_url :title # # will use the string contents of the title attribute # to create the permalink. Note: you can also use a non-database-backed # method to supply the string contents for the permalink. Just use that method's name # as the argument as you would an attribute. # # The default attribute acts_as_url uses to save the permalink is url # but this can be changed in the options hash. Available options are: # # :url_attribute:: The name of the attribute to use for storing the generated url string. # Default is :url # :scope:: The name of model attribute to scope unique urls to. There is no default here. # :only_when_blank:: If true, the url generation will only happen when :url_attribute is # blank. Default is false (meaning url generation will happen always) # :sync_url:: If set to true, the url field will be updated when changes are made to the # attribute it is based on. Default is false. def acts_as_url(attribute, options = {}) cattr_accessor :attribute_to_urlify cattr_accessor :scope_for_url cattr_accessor :url_attribute # The attribute on the DB cattr_accessor :only_when_blank cattr_accessor :duplicate_count_separator if options[: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) end end self.attribute_to_urlify = attribute self.scope_for_url = options[:scope] self.url_attribute = options[:url_attribute] || "url" self.only_when_blank = options[:only_when_blank] || false self.duplicate_count_separator = options[:duplicate_count_separator] || "-" class_eval <<-"END" def #{url_attribute} if !new_record? && errors[attribute_to_urlify].present? self.class.find(id).send(url_attribute) else read_attribute(url_attribute) end end END end # Initialize the url fields for the records that need it. Designed for people who add # acts_as_url 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(:all, :conditions => {self.url_attribute => nil}).each do |instance| instance.send :ensure_unique_url instance.save end end end private def ensure_unique_url url_attribute = self.class.url_attribute separator = self.class.duplicate_count_separator base_url = self.send(url_attribute) base_url = self.send(self.class.attribute_to_urlify).to_s.to_url if base_url.blank? || !self.only_when_blank conditions = ["#{url_attribute} LIKE ?", base_url+'%'] unless new_record? conditions.first << " and id != ?" conditions << id end if self.class.scope_for_url conditions.first << " and #{self.class.scope_for_url} = ?" conditions << send(self.class.scope_for_url) end url_owners = self.class.find(:all, :conditions => conditions) write_attribute url_attribute, base_url if url_owners.any?{|owner| owner.send(url_attribute) == base_url} n = 1 while url_owners.any?{|owner| owner.send(url_attribute) == "#{base_url}#{separator}#{n}"} n = n.succ end write_attribute url_attribute, "#{base_url}#{separator}#{n}" end end end end