Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
tree: 3d90ea53fb
Fetching contributors…

Cannot retrieve contributors at this time

file 113 lines (106 sloc) 4.788 kb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
# encoding: UTF-8
module Stringex
  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 <tt>attribute</tt> argument. Example:
      #
      # act_as_url :title
      #
      # will use the string contents of the <tt>title</tt> attribute
      # to create the permalink. <strong>Note:</strong> 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 <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>:url_attribute</tt>:: The name of the attribute to use for storing the generated url string.
      # Default is <tt>:url</tt>
      # <tt>:scope</tt>:: The name of model attribute to scope unique urls to. There is no default here.
      # <tt>:only_when_blank</tt>:: If true, the url generation will only happen when <tt>:url_attribute</tt> is
      # blank. Default is false (meaning url generation will happen always)
      # <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.
      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
        cattr_accessor :allow_slash
        cattr_accessor :allow_duplicates
        cattr_accessor :url_limit

        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] || "-"
        self.allow_slash = options[:allow_slash] || false
        self.allow_duplicates = options[:allow_duplicates] || false
        self.url_limit = options[:limit] || nil

        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
      # <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(: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(:allow_slash => self.allow_slash, :limit => self.url_limit) 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
      unless self.class.allow_duplicates
        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
end
Something went wrong with that request. Please try again.