nateabbott-friendly_id 2.1.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. data/History.txt +133 -0
  2. data/MIT-LICENSE +19 -0
  3. data/Manifest.txt +39 -0
  4. data/README.rdoc +343 -0
  5. data/Rakefile +49 -0
  6. data/config/website.yml +2 -0
  7. data/friendly_id.gemspec +45 -0
  8. data/generators/friendly_id/friendly_id_generator.rb +12 -0
  9. data/generators/friendly_id/templates/create_slugs.rb +18 -0
  10. data/generators/friendly_id_20_upgrade/friendly_id_20_upgrade_generator.rb +11 -0
  11. data/generators/friendly_id_20_upgrade/templates/upgrade_friendly_id_to_20.rb +19 -0
  12. data/init.rb +3 -0
  13. data/lib/friendly_id/helpers.rb +15 -0
  14. data/lib/friendly_id/non_sluggable_class_methods.rb +42 -0
  15. data/lib/friendly_id/non_sluggable_instance_methods.rb +43 -0
  16. data/lib/friendly_id/slug.rb +102 -0
  17. data/lib/friendly_id/sluggable_class_methods.rb +116 -0
  18. data/lib/friendly_id/sluggable_instance_methods.rb +116 -0
  19. data/lib/friendly_id/version.rb +10 -0
  20. data/lib/friendly_id.rb +101 -0
  21. data/lib/tasks/friendly_id.rake +50 -0
  22. data/lib/tasks/friendly_id.rb +1 -0
  23. data/test/contest.rb +94 -0
  24. data/test/custom_slug_normalizer_test.rb +35 -0
  25. data/test/models/book.rb +2 -0
  26. data/test/models/country.rb +4 -0
  27. data/test/models/event.rb +3 -0
  28. data/test/models/novel.rb +3 -0
  29. data/test/models/person.rb +6 -0
  30. data/test/models/post.rb +6 -0
  31. data/test/models/thing.rb +6 -0
  32. data/test/models/user.rb +3 -0
  33. data/test/non_slugged_test.rb +98 -0
  34. data/test/schema.rb +55 -0
  35. data/test/scoped_model_test.rb +53 -0
  36. data/test/slug_test.rb +106 -0
  37. data/test/slugged_model_test.rb +284 -0
  38. data/test/sti_test.rb +48 -0
  39. data/test/test_helper.rb +30 -0
  40. metadata +155 -0
@@ -0,0 +1,19 @@
1
+ class UpgradeFriendlyIdTo20 < ActiveRecord::Migration
2
+
3
+ def self.up
4
+ remove_column :slugs, :updated_at
5
+ remove_index :slugs, :column => [:name, :sluggable_type]
6
+ add_column :slugs, :sequence, :integer, :null => false, :default => 1
7
+ add_column :slugs, :scope, :string, :limit => 40
8
+ add_index :slugs, [:name, :sluggable_type, :scope, :sequence], :unique => true, :name => "index_slugs_on_n_s_s_and_s"
9
+ end
10
+
11
+ def self.down
12
+ remove_index :slugs, :name => "index_slugs_on_n_s_s_and_s"
13
+ remove_column :slugs, :scope
14
+ remove_column :slugs, :sequence
15
+ add_column :slugs, :updated_at, :datetime
16
+ add_index :slugs, [:name, :sluggable_type], :unique => true
17
+ end
18
+
19
+ end
data/init.rb ADDED
@@ -0,0 +1,3 @@
1
+ # encoding: utf-8
2
+
3
+ require 'friendly_id'
@@ -0,0 +1,15 @@
1
+ # encoding: utf-8
2
+
3
+ module FriendlyId
4
+
5
+ module Helpers
6
+ # Calculate expected result size for find_some_with_friendly (taken from
7
+ # active_record/base.rb)
8
+ def expected_size(ids_and_names, options) #:nodoc:#
9
+ size = options[:offset] ? ids_and_names.size - options[:offset] : ids_and_names.size
10
+ size = options[:limit] if options[:limit] && size > options[:limit]
11
+ size
12
+ end
13
+ end
14
+
15
+ end
@@ -0,0 +1,42 @@
1
+ # encoding: utf-8
2
+
3
+ module FriendlyId::NonSluggableClassMethods
4
+
5
+ include FriendlyId::Helpers
6
+
7
+ def self.extended(base) #:nodoc:#
8
+ class << base
9
+ alias_method_chain :find_one, :friendly
10
+ alias_method_chain :find_some, :friendly
11
+ end
12
+ end
13
+
14
+ protected
15
+
16
+ def find_one_with_friendly(id, options) #:nodoc:#
17
+ if id.is_a?(String) && result = send("find_by_#{ friendly_id_options[:column] }", id, options)
18
+ result.send(:found_using_friendly_id=, true)
19
+ else
20
+ result = find_one_without_friendly id, options
21
+ end
22
+ result
23
+ end
24
+
25
+ def find_some_with_friendly(ids_and_names, options) #:nodoc:#
26
+
27
+ results = with_scope :find => options do
28
+ find :all, :conditions => ["#{quoted_table_name}.#{primary_key} IN (?) OR #{friendly_id_options[:column].to_s} IN (?)",
29
+ ids_and_names, ids_and_names]
30
+ end
31
+
32
+ expected = expected_size(ids_and_names, options)
33
+ if results.size != expected
34
+ raise ActiveRecord::RecordNotFound, "Couldn't find all #{ name.pluralize } with IDs (#{ ids_and_names * ', ' }) AND #{ sanitize_sql options[:conditions] } (found #{ results.size } results, but was looking for #{ expected })"
35
+ end
36
+
37
+ results.each {|r| r.send(:found_using_friendly_id=, true) if ids_and_names.include?(r.friendly_id)}
38
+
39
+ results
40
+
41
+ end
42
+ end
@@ -0,0 +1,43 @@
1
+ # encoding: utf-8
2
+
3
+ module FriendlyId::NonSluggableInstanceMethods
4
+
5
+ attr :found_using_friendly_id
6
+
7
+ # Was the record found using one of its friendly ids?
8
+ def found_using_friendly_id?
9
+ @found_using_friendly_id
10
+ end
11
+
12
+ # Was the record found using its numeric id?
13
+ def found_using_numeric_id?
14
+ !@found_using_friendly_id
15
+ end
16
+ alias has_better_id? found_using_numeric_id?
17
+
18
+ # Returns the friendly_id.
19
+ def friendly_id
20
+ send friendly_id_options[:column]
21
+ end
22
+ alias best_id friendly_id
23
+
24
+ # Returns the friendly id, or if none is available, the numeric id.
25
+ def to_param
26
+ (friendly_id || id).to_s
27
+ end
28
+
29
+ private
30
+
31
+ def validate_friendly_id
32
+ if self.class.friendly_id_options[:reserved].include? friendly_id
33
+ self.errors.add(self.class.friendly_id_options[:column],
34
+ self.class.friendly_id_options[:reserved_message] % friendly_id)
35
+ return false
36
+ end
37
+ end
38
+
39
+ def found_using_friendly_id=(value) #:nodoc#
40
+ @found_using_friendly_id = value
41
+ end
42
+
43
+ end
@@ -0,0 +1,102 @@
1
+ #encoding: utf-8
2
+
3
+ # A Slug is a unique, human-friendly identifier for an ActiveRecord.
4
+ class Slug < ActiveRecord::Base
5
+
6
+ belongs_to :sluggable, :polymorphic => true
7
+ before_save :check_for_blank_name, :set_sequence
8
+
9
+
10
+ ASCII_APPROXIMATIONS = {
11
+ 198 => "AE",
12
+ 208 => "D",
13
+ 216 => "O",
14
+ 222 => "Th",
15
+ 223 => "ss",
16
+ 230 => "ae",
17
+ 240 => "d",
18
+ 248 => "o",
19
+ 254 => "th"
20
+ }.freeze
21
+
22
+ class << self
23
+
24
+ # Sanitizes and dasherizes string to make it safe for URL's.
25
+ #
26
+ # Example:
27
+ #
28
+ # slug.normalize('This... is an example!') # => "this-is-an-example"
29
+ #
30
+ # Note that the Unicode handling in ActiveSupport may fail to process some
31
+ # characters from Polish, Icelandic and other languages. If your
32
+ # application uses these languages, check {out this
33
+ # article}[http://link-coming-soon.com] for information on how to get
34
+ # better urls in your application.
35
+ def normalize(slug_text)
36
+ return "" if slug_text.nil? || slug_text == ""
37
+ ActiveSupport::Multibyte.proxy_class.new(slug_text.to_s).normalize(:kc).
38
+ gsub(/[\W]/u, ' ').
39
+ strip.
40
+ gsub(/\s+/u, '-').
41
+ gsub(/-\z/u, '').
42
+ downcase.
43
+ to_s
44
+ end
45
+
46
+ def parse(friendly_id)
47
+ name, sequence = friendly_id.split('--')
48
+ sequence ||= "1"
49
+ return name, sequence
50
+ end
51
+
52
+ # Remove diacritics (accents, umlauts, etc.) from the string. Borrowed
53
+ # from "The Ruby Way."
54
+ def strip_diacritics(string)
55
+ ActiveSupport::Multibyte.proxy_class.new(string).normalize(:kd).unpack('U*').inject([]) { |a, u|
56
+ if ASCII_APPROXIMATIONS[u]
57
+ a += ASCII_APPROXIMATIONS[u].unpack('U*')
58
+ elsif (u < 0x300 || u > 0x036F)
59
+ a << u
60
+ end
61
+ a
62
+ }.pack('U*')
63
+ end
64
+
65
+
66
+
67
+ # Remove non-ascii characters from the string.
68
+ def strip_non_ascii(string)
69
+ strip_diacritics(string).gsub(/[^a-z0-9]+/i, ' ')
70
+ end
71
+
72
+ private
73
+
74
+ end
75
+
76
+ # Whether or not this slug is the most recent of its owner's slugs.
77
+ def is_most_recent?
78
+ sluggable.slug == self
79
+ end
80
+
81
+ def to_friendly_id
82
+ sequence > 1 ? "#{name}--#{sequence}" : name
83
+ end
84
+
85
+ protected
86
+
87
+ # Raise a FriendlyId::SlugGenerationError if the slug name is blank.
88
+ def check_for_blank_name #:nodoc:#
89
+ if name.blank?
90
+ raise FriendlyId::SlugGenerationError.new("The slug text is blank.")
91
+ end
92
+ end
93
+
94
+ def set_sequence
95
+ return unless new_record?
96
+ last = Slug.find(:first, :conditions => { :name => name, :scope => scope,
97
+ :sluggable_type => sluggable_type}, :order => "sequence DESC",
98
+ :select => 'sequence')
99
+ self.sequence = last.sequence + 1 if last
100
+ end
101
+
102
+ end
@@ -0,0 +1,116 @@
1
+ # encoding: utf-8
2
+
3
+ module FriendlyId::SluggableClassMethods
4
+
5
+ include FriendlyId::Helpers
6
+
7
+ def self.extended(base) #:nodoc:#
8
+
9
+ class << base
10
+ alias_method_chain :find_one, :friendly
11
+ alias_method_chain :find_some, :friendly
12
+ alias_method_chain :validate_find_options, :friendly
13
+ end
14
+
15
+ end
16
+
17
+ # Finds a single record using the friendly id, or the record's id.
18
+ def find_one_with_friendly(id_or_name, options) #:nodoc:#
19
+
20
+ scope = options.delete(:scope)
21
+ return find_one_without_friendly(id_or_name, options) if id_or_name.is_a?(Fixnum)
22
+
23
+ find_options = {:select => "#{self.table_name}.*"}
24
+ find_options[:joins] = :slugs unless options[:include] && [*options[:include]].flatten.include?(:slugs)
25
+
26
+ name, sequence = Slug.parse(id_or_name)
27
+
28
+ find_options[:conditions] = {
29
+ "#{Slug.table_name}.name" => name,
30
+ "#{Slug.table_name}.scope" => scope,
31
+ "#{Slug.table_name}.sequence" => sequence
32
+ }
33
+
34
+ result = with_scope(:find => find_options) { find_initial(options) }
35
+
36
+ if result
37
+ result.finder_slug_name = id_or_name
38
+ else
39
+ result = find_one_without_friendly id_or_name, options
40
+ end
41
+
42
+ result
43
+ rescue ActiveRecord::RecordNotFound => e
44
+
45
+ if friendly_id_options[:scope]
46
+ if !scope
47
+ e.message << "; expected scope but got none"
48
+ else
49
+ e.message << " and scope=#{scope}"
50
+ end
51
+ end
52
+
53
+ raise e
54
+
55
+ end
56
+
57
+ # Finds multiple records using the friendly ids, or the records' ids.
58
+ def find_some_with_friendly(ids_and_names, options) #:nodoc:#
59
+
60
+ slugs, ids = get_slugs_and_ids(ids_and_names, options)
61
+ results = []
62
+
63
+ find_options = {:select => "#{self.table_name}.*"}
64
+ find_options[:joins] = :slugs unless options[:include] && [*options[:include]].flatten.include?(:slugs)
65
+ find_options[:conditions] = "#{quoted_table_name}.#{primary_key} IN (#{ids.empty? ? 'NULL' : ids.join(',')}) "
66
+ find_options[:conditions] << "OR slugs.id IN (#{slugs.to_s(:db)})"
67
+
68
+ results = with_scope(:find => find_options) { find_every(options) }.uniq
69
+
70
+ expected = expected_size(ids_and_names, options)
71
+ if results.size != expected
72
+ raise ActiveRecord::RecordNotFound, "Couldn't find all #{ name.pluralize } with IDs (#{ ids_and_names * ', ' }) AND #{ sanitize_sql options[:conditions] } (found #{ results.size } results, but was looking for #{ expected })"
73
+ end
74
+
75
+ assign_finder_slugs(slugs, results)
76
+
77
+ results
78
+ end
79
+
80
+ def validate_find_options_with_friendly(options) #:nodoc:#
81
+ options.assert_valid_keys([:conditions, :include, :joins, :limit, :offset,
82
+ :order, :select, :readonly, :group, :from, :lock, :having, :scope])
83
+ end
84
+
85
+ private
86
+
87
+ # Assign finder slugs for the results found in find_some_with_friendly
88
+ def assign_finder_slugs(slugs, results) #:nodoc:#
89
+ slugs.each do |slug|
90
+ results.select { |r| r.id == slug.sluggable_id }.each do |result|
91
+ result.send(:finder_slug=, slug)
92
+ end
93
+ end
94
+ end
95
+
96
+ # Build arrays of slugs and ids, for the find_some_with_friendly method.
97
+ def get_slugs_and_ids(ids_and_names, options) #:nodoc:#
98
+ scope = options.delete(:scope)
99
+ slugs = []
100
+ ids = []
101
+ ids_and_names.each do |id_or_name|
102
+ name, sequence = Slug.parse id_or_name.to_s
103
+ slug = Slug.find(:first, :conditions => {
104
+ :name => name,
105
+ :scope => scope,
106
+ :sequence => sequence,
107
+ :sluggable_type => base_class.name
108
+ })
109
+ # If the slug was found, add it to the array for later use. If not, and
110
+ # the id_or_name is a number, assume that it is a regular record id.
111
+ slug ? slugs << slug : (ids << id_or_name if id_or_name.to_s =~ /\A\d*\z/)
112
+ end
113
+ return slugs, ids
114
+ end
115
+
116
+ end
@@ -0,0 +1,116 @@
1
+ # encoding: utf-8
2
+
3
+ module FriendlyId::SluggableInstanceMethods
4
+
5
+ NUM_CHARS_RESERVED_FOR_FRIENDLY_ID_EXTENSION = 2
6
+
7
+ attr :finder_slug
8
+ attr_accessor :finder_slug_name
9
+
10
+ def finder_slug
11
+ @finder_slug ||= init_finder_slug or nil
12
+ end
13
+
14
+ # Was the record found using one of its friendly ids?
15
+ def found_using_friendly_id?
16
+ finder_slug
17
+ end
18
+
19
+ # Was the record found using its numeric id?
20
+ def found_using_numeric_id?
21
+ !found_using_friendly_id?
22
+ end
23
+
24
+ # Was the record found using an old friendly id?
25
+ def found_using_outdated_friendly_id?
26
+ finder_slug.id != slug.id
27
+ end
28
+
29
+ # Was the record found using an old friendly id, or its numeric id?
30
+ def has_better_id?
31
+ slug and found_using_numeric_id? || found_using_outdated_friendly_id?
32
+ end
33
+
34
+ # Returns the friendly id.
35
+ def friendly_id
36
+ slug(true).to_friendly_id
37
+ end
38
+ alias best_id friendly_id
39
+
40
+ # Has the basis of our friendly id changed, requiring the generation of a
41
+ # new slug?
42
+ def new_slug_needed?
43
+ !slug || slug_text != slug.name
44
+ end
45
+
46
+ # Returns the most recent slug, which is used to determine the friendly
47
+ # id.
48
+ def slug(reload = false)
49
+ @most_recent_slug = nil if reload
50
+ @most_recent_slug ||= slugs.first
51
+ end
52
+
53
+ # Returns the friendly id, or if none is available, the numeric id.
54
+ def to_param
55
+ slug ? slug.to_friendly_id : id.to_s
56
+ end
57
+
58
+ # Get the processed string used as the basis of the friendly id.
59
+ def slug_text
60
+ base = send friendly_id_options[:column]
61
+ if self.slug_normalizer_block
62
+ base = self.slug_normalizer_block.call(base)
63
+ else
64
+ if self.friendly_id_options[:strip_diacritics]
65
+ base = Slug::strip_diacritics(base)
66
+ end
67
+ if self.friendly_id_options[:strip_non_ascii]
68
+ base = Slug::strip_non_ascii(base)
69
+ end
70
+ base = Slug::normalize(base)
71
+ end
72
+
73
+ if base.length > friendly_id_options[:max_length]
74
+ base = base[0...friendly_id_options[:max_length]]
75
+ end
76
+ if friendly_id_options[:reserved].include?(base)
77
+ raise FriendlyId::SlugGenerationError.new("The slug text is a reserved value")
78
+ end
79
+ return base
80
+ end
81
+
82
+ private
83
+
84
+ def finder_slug=(finder_slug)
85
+ @finder_slug_name = finder_slug.name
86
+ slug = finder_slug
87
+ slug.sluggable = self
88
+ slug
89
+ end
90
+
91
+ def init_finder_slug
92
+ return false if !@finder_slug_name
93
+ name, sequence = Slug.parse(@finder_slug_name)
94
+ slug = Slug.find(:first, :conditions => {:sluggable_id => id, :name => name, :sequence => sequence, :sluggable_type => self.class.base_class.name })
95
+ finder_slug = slug
96
+ end
97
+
98
+ # Set the slug using the generated friendly id.
99
+ def set_slug
100
+ if self.class.friendly_id_options[:use_slug] && new_slug_needed?
101
+ @most_recent_slug = nil
102
+ slug_attributes = {:name => slug_text}
103
+ if friendly_id_options[:scope]
104
+ scope = self.class.friendly_id_options[:scope] # originally: scope = send(friendly_id_options[:scope])
105
+ slug_attributes[:scope] = scope.respond_to?(:to_param) ? scope.to_param : scope.to_s
106
+ end
107
+ # If we're renaming back to a previously used friendly_id, delete the
108
+ # slug so that we can recycle the name without having to use a sequence.
109
+ # ORIG: slugs.find(:all, :conditions => {:name => slug_text, :scope => scope}).each { |s| s.destroy }
110
+ slugs.find(:all, :conditions => {:name => slug_text, :scope => slug_attributes[:scope]}).each
111
+ slug = slugs.build slug_attributes
112
+ slug
113
+ end
114
+ end
115
+
116
+ end
@@ -0,0 +1,10 @@
1
+ # encoding: utf-8
2
+
3
+ module FriendlyId #:nodoc:
4
+ module Version #:nodoc:
5
+ MAJOR = 2
6
+ MINOR = 1
7
+ TINY = 3
8
+ STRING = [MAJOR, MINOR, TINY].join('.')
9
+ end
10
+ end
@@ -0,0 +1,101 @@
1
+ # encoding: utf-8
2
+
3
+ require 'friendly_id/helpers'
4
+ require 'friendly_id/slug'
5
+
6
+ # FriendlyId is a comprehensize Rails plugin/gem for slugging and permalinks.
7
+ module FriendlyId
8
+
9
+ # Default options for has_friendly_id.
10
+ DEFAULT_FRIENDLY_ID_OPTIONS = {
11
+ :max_length => 255,
12
+ :method => nil,
13
+ :reserved => ["new", "index"],
14
+ :reserved_message => 'can not be "%s"',
15
+ :scope => nil,
16
+ :strip_diacritics => false,
17
+ :strip_non_ascii => false,
18
+ :use_slug => false }.freeze
19
+
20
+ # Valid keys for has_friendly_id options.
21
+ VALID_FRIENDLY_ID_KEYS = [
22
+ :max_length,
23
+ :reserved,
24
+ :reserved_message,
25
+ :scope,
26
+ :strip_diacritics,
27
+ :strip_non_ascii,
28
+ :use_slug ].freeze
29
+
30
+ # This error is raised when it's not possible to generate a unique slug.
31
+ class SlugGenerationError < StandardError ; end
32
+
33
+ module ClassMethods
34
+
35
+ # Set up an ActiveRecord model to use a friendly_id.
36
+ #
37
+ # The column argument can be one of your model's columns, or a method
38
+ # you use to generate the slug.
39
+ #
40
+ # Options:
41
+ # * <tt>:use_slug</tt> - Defaults to false. Use slugs when you want to use a non-unique text field for friendly ids.
42
+ # * <tt>:max_length</tt> - Defaults to 255. The maximum allowed length for a slug.
43
+ # * <tt>:strip_diacritics</tt> - Defaults to false. If true, it will remove accents, umlauts, etc. from western characters.
44
+ # * <tt>:strip_non_ascii</tt> - Defaults to false. If true, it will all non-ascii ([^a-z0-9]) characters.
45
+ # * <tt>:reserved</tt> - Array of words that are reserved and can't be used as friendly_id's. For sluggable models, if such a word is used, it will be treated the same as if that slug was already taken (numeric extension will be appended). Defaults to ["new", "index"].
46
+ # * <tt>:reserved_message</tt> - The validation message that will be shown when a reserved word is used as a frindly_id. Defaults to '"%s" is reserved'.
47
+ #
48
+ # You can also optionally pass a block if you want to use your own custom
49
+ # slugnormalization routines rather than the default ones that come with
50
+ # friendly_id:
51
+ #
52
+ # require 'stringex'
53
+ # class Post < ActiveRecord::Base
54
+ # has_friendly_id :title, :use_slug => true do |text|
55
+ # # Use stringex to generate the friendly_id rather than the baked-in methods
56
+ # text.to_url
57
+ # end
58
+ # end
59
+ def has_friendly_id(column, options = {}, &block)
60
+ options.assert_valid_keys VALID_FRIENDLY_ID_KEYS
61
+ options = DEFAULT_FRIENDLY_ID_OPTIONS.merge(options).merge(:column => column)
62
+ write_inheritable_attribute :friendly_id_options, options
63
+ class_inheritable_accessor :friendly_id_options
64
+ class_inheritable_reader :slug_normalizer_block
65
+
66
+ if options[:use_slug]
67
+ has_many :slugs, :order => 'id DESC', :as => :sluggable, :dependent => :destroy
68
+ require 'friendly_id/sluggable_class_methods'
69
+ require 'friendly_id/sluggable_instance_methods'
70
+ extend SluggableClassMethods
71
+ include SluggableInstanceMethods
72
+ before_save :set_slug
73
+ if block_given?
74
+ write_inheritable_attribute :slug_normalizer_block, block
75
+ end
76
+ else
77
+ require 'friendly_id/non_sluggable_class_methods'
78
+ require 'friendly_id/non_sluggable_instance_methods'
79
+ extend NonSluggableClassMethods
80
+ include NonSluggableInstanceMethods
81
+ validate :validate_friendly_id
82
+ end
83
+ end
84
+
85
+ end
86
+
87
+ class << self
88
+
89
+ # Load FriendlyId if the gem is included in a Rails app.
90
+ def enable
91
+ return if ActiveRecord::Base.methods.include? 'has_friendly_id'
92
+ ActiveRecord::Base.class_eval { extend FriendlyId::ClassMethods }
93
+ end
94
+
95
+ end
96
+
97
+ end
98
+
99
+ if defined?(ActiveRecord)
100
+ FriendlyId::enable
101
+ end
@@ -0,0 +1,50 @@
1
+ # encoding: utf-8
2
+
3
+ namespace :friendly_id do
4
+ desc "Make slugs for a model."
5
+ task :make_slugs => :environment do
6
+ raise 'USAGE: rake friendly_id:make_slugs MODEL=MyModelName' if ENV["MODEL"].nil?
7
+ if !sluggable_class.friendly_id_options[:use_slug]
8
+ raise "Class \"#{sluggable_class.to_s}\" doesn't appear to be using slugs"
9
+ end
10
+ while records = sluggable_class.find(:all, :include => :slugs, :conditions => "slugs.id IS NULL", :limit => 1000) do
11
+ break if records.size == 0
12
+ records.each do |r|
13
+ r.send(:set_slug)
14
+ r.save!
15
+ puts "#{sluggable_class.to_s}(#{r.id}) friendly_id set to \"#{r.slug.name}\""
16
+ end
17
+ end
18
+ end
19
+
20
+ desc "Regenereate slugs for a model."
21
+ task :redo_slugs => :environment do
22
+ raise 'USAGE: rake friendly_id:redo_slugs MODEL=MyModelName' if ENV["MODEL"].nil?
23
+ if !sluggable_class.friendly_id_options[:use_slug]
24
+ raise "Class \"#{sluggable_class.to_s}\" doesn't appear to be using slugs"
25
+ end
26
+ Slug.destroy_all(["sluggable_type = ?", sluggable_class.to_s])
27
+ Rake::Task["friendly_id:make_slugs"].invoke
28
+ end
29
+
30
+ desc "Kill obsolete slugs older than 45 days."
31
+ task :remove_old_slugs => :environment do
32
+ if ENV["DAYS"].nil?
33
+ @days = 45
34
+ else
35
+ @days = ENV["DAYS"].to_i
36
+ end
37
+ slugs = Slug.find(:all, :conditions => ["created_at < ?", DateTime.now - @days.days])
38
+ slugs.each do |s|
39
+ s.destroy if !s.is_most_recent?
40
+ end
41
+ end
42
+ end
43
+
44
+ def sluggable_class
45
+ if (ENV["MODEL"].split('::').size > 1)
46
+ ENV["MODEL"].split('::').inject(Kernel) {|scope, const_name| scope.const_get(const_name)}
47
+ else
48
+ Object.const_get(ENV["MODEL"])
49
+ end
50
+ end
@@ -0,0 +1 @@
1
+ load 'tasks/friendly_id.rake'