nateabbott-friendly_id 2.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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'