dougcole-friendly_id 2.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. data/History.txt +88 -0
  2. data/MIT-LICENSE +19 -0
  3. data/Manifest.txt +45 -0
  4. data/README.rdoc +318 -0
  5. data/Rakefile +67 -0
  6. data/generators/friendly_id/friendly_id_generator.rb +12 -0
  7. data/generators/friendly_id/templates/create_slugs.rb +18 -0
  8. data/generators/friendly_id_20_upgrade/friendly_id_20_upgrade_generator.rb +11 -0
  9. data/generators/friendly_id_20_upgrade/templates/upgrade_friendly_id_to_20.rb +19 -0
  10. data/init.rb +1 -0
  11. data/lib/friendly_id/helpers.rb +13 -0
  12. data/lib/friendly_id/non_sluggable_class_methods.rb +40 -0
  13. data/lib/friendly_id/non_sluggable_instance_methods.rb +33 -0
  14. data/lib/friendly_id/shoulda_macros.rb +36 -0
  15. data/lib/friendly_id/slug.rb +89 -0
  16. data/lib/friendly_id/sluggable_class_methods.rb +103 -0
  17. data/lib/friendly_id/sluggable_instance_methods.rb +106 -0
  18. data/lib/friendly_id/version.rb +8 -0
  19. data/lib/friendly_id.rb +67 -0
  20. data/lib/tasks/friendly_id.rake +48 -0
  21. data/lib/tasks/friendly_id.rb +1 -0
  22. data/test/database.yml +3 -0
  23. data/test/fixtures/countries.yml +4 -0
  24. data/test/fixtures/country.rb +4 -0
  25. data/test/fixtures/people.yml +7 -0
  26. data/test/fixtures/person.rb +6 -0
  27. data/test/fixtures/post.rb +3 -0
  28. data/test/fixtures/posts.yml +23 -0
  29. data/test/fixtures/slugs.yml +53 -0
  30. data/test/fixtures/user.rb +3 -0
  31. data/test/fixtures/users.yml +7 -0
  32. data/test/non_slugged_test.rb +85 -0
  33. data/test/rails/2.x/app/controllers/application.rb +0 -0
  34. data/test/rails/2.x/config/boot.rb +109 -0
  35. data/test/rails/2.x/config/database.yml +3 -0
  36. data/test/rails/2.x/config/environment.rb +7 -0
  37. data/test/rails/2.x/config/environments/test.rb +6 -0
  38. data/test/rails/2.x/config/routes.rb +0 -0
  39. data/test/schema.rb +38 -0
  40. data/test/scoped_model_test.rb +21 -0
  41. data/test/slug_test.rb +87 -0
  42. data/test/sluggable_test.rb +185 -0
  43. data/test/test_helper.rb +35 -0
  44. metadata +157 -0
@@ -0,0 +1,33 @@
1
+ module FriendlyId::NonSluggableInstanceMethods
2
+
3
+ attr :found_using_friendly_id
4
+
5
+ # Was the record found using one of its friendly ids?
6
+ def found_using_friendly_id?
7
+ @found_using_friendly_id
8
+ end
9
+
10
+ # Was the record found using its numeric id?
11
+ def found_using_numeric_id?
12
+ !@found_using_friendly_id
13
+ end
14
+ alias has_better_id? found_using_numeric_id?
15
+
16
+ # Returns the friendly_id.
17
+ def friendly_id
18
+ send friendly_id_options[:column]
19
+ end
20
+ alias best_id friendly_id
21
+
22
+ # Returns the friendly id, or if none is available, the numeric id.
23
+ def to_param
24
+ friendly_id.to_s || id.to_s
25
+ end
26
+
27
+ private
28
+
29
+ def found_using_friendly_id=(value) #:nodoc#
30
+ @found_using_friendly_id = value
31
+ end
32
+
33
+ end
@@ -0,0 +1,36 @@
1
+ module FriendlyId
2
+
3
+ # A Shoulda[http://www.thoughtbot.com/projects/shoulda/] macros for testing
4
+ # models using FriendlyId.
5
+ module ShouldaMacros
6
+
7
+ # Ensure that a model is using FriendlyId.
8
+ def self.should_have_friendly_id(column, options = {})
9
+
10
+ options.assert_valid_keys(:use_slug)
11
+ klass = self.model_class
12
+
13
+ should "have friendly id for #{method}" do
14
+ assert_respond_to klass, :friendly_id_options,
15
+ "#{klass} does not respond to friendly_id_options"
16
+ assert_equal column, klass.friendly_id_options[:method]
17
+ end
18
+
19
+ if options[:use_slug]
20
+ should "include/extend friendly_id's sluggable modules" do
21
+ assert klass.extended_by.include?(FriendlyId::SluggableClassMethods),
22
+ "#{klass} does not extend FriendlyId::SluggableClassMethods"
23
+ assert klass.include?(FriendlyId::SluggableInstanceMethods),
24
+ "#{klass} not include FriendlyId::SluggableInstanceMethods"
25
+ end
26
+ else
27
+ should "include/extend friendly_id's non-sluggable modules" do
28
+ assert klass.extended_by.include?(FriendlyId::NonSluggableClassMethods),
29
+ "#{klass} does not extend FriendlyId::NonSluggableClassMethods"
30
+ assert klass.include?(FriendlyId::NonSluggableInstanceMethods),
31
+ "#{klass} not include FriendlyId::NonSluggableInstanceMethods"
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,89 @@
1
+ # A Slug is a unique, human-friendly identifier for an ActiveRecord.
2
+ class Slug < ActiveRecord::Base
3
+
4
+ belongs_to :sluggable, :polymorphic => true
5
+ before_save :check_for_blank_name, :set_sequence
6
+
7
+ class << self
8
+
9
+ # Sanitizes and dasherizes string to make it safe for URL's.
10
+ #
11
+ # Example:
12
+ #
13
+ # slug.normalize('This... is an example!') # => "this-is-an-example"
14
+ #
15
+ # Note that Rails 2.2.x offers a parameterize method for this. It's not
16
+ # used here because it assumes you want to strip away accented characters,
17
+ # and this may not always be your desire.
18
+ #
19
+ # At the time of writing, it also handles several characters incorrectly,
20
+ # for instance replacing Icelandic's "thorn" character with "y" rather
21
+ # than "d." This might be pedantic, but I don't want to piss off the
22
+ # Vikings. The last time anyone pissed them off, they uleashed a wave of
23
+ # terror in Europe unlike anything ever seen before or after. I'm not
24
+ # taking any chances.
25
+ def normalize(slug_text)
26
+ return "" if slug_text.blank?
27
+ slug_text.
28
+ send(chars_func).
29
+ # For some reason Spanish ¡ and ¿ are not detected as non-word
30
+ # characters. Bug in Ruby?
31
+ normalize.gsub(/[\W|¡|¿]/u, ' ').
32
+ strip.
33
+ gsub(/\s+/u, '-').
34
+ gsub(/-\z/u, '').
35
+ downcase.
36
+ to_s
37
+ end
38
+
39
+ def parse(friendly_id)
40
+ name, sequence = friendly_id.split('--')
41
+ sequence ||= "1"
42
+ return name, sequence
43
+ end
44
+
45
+ # Remove diacritics (accents, umlauts, etc.) from the string.
46
+ def strip_diacritics(string)
47
+ require 'unicode'
48
+ Unicode::normalize_KD(string).unpack('U*').select { |cp|
49
+ cp < 0x300 || cp > 0x036F
50
+ }.pack('U*')
51
+ end
52
+
53
+ private
54
+
55
+ def chars_func
56
+ Rails.version =~ /2.2.[\d]*/ ? :mb_chars : :chars
57
+ rescue NoMethodError
58
+ :chars
59
+ end
60
+
61
+ end
62
+
63
+ # Whether or not this slug is the most recent of its owner's slugs.
64
+ def is_most_recent?
65
+ sluggable.slug == self
66
+ end
67
+
68
+ def to_friendly_id
69
+ sequence > 1 ? "#{name}--#{sequence}" : name
70
+ end
71
+
72
+ protected
73
+
74
+ # Raise a FriendlyId::SlugGenerationError if the slug name is blank.
75
+ def check_for_blank_name #:nodoc:#
76
+ if name.blank?
77
+ raise FriendlyId::SlugGenerationError.new("The slug text is blank.")
78
+ end
79
+ end
80
+
81
+ def set_sequence
82
+ return unless new_record?
83
+ last = Slug.find(:first, :conditions => { :name => name, :scope => scope,
84
+ :sluggable_type => sluggable_type}, :order => "sequence DESC",
85
+ :select => 'sequence')
86
+ self.sequence = last.sequence + 1 if last
87
+ end
88
+
89
+ end
@@ -0,0 +1,103 @@
1
+ module FriendlyId::SluggableClassMethods
2
+
3
+ include FriendlyId::Helpers
4
+
5
+ def self.extended(base) #:nodoc:#
6
+
7
+ class << base
8
+ alias_method_chain :find_one, :friendly
9
+ alias_method_chain :find_some, :friendly
10
+ alias_method_chain :validate_find_options, :friendly
11
+ end
12
+
13
+ end
14
+
15
+ # Finds a single record using the friendly id, or the record's id.
16
+ def find_one_with_friendly(id_or_name, options) #:nodoc:#
17
+
18
+ scope = options.delete(:scope)
19
+ return find_one_without_friendly(id_or_name, options) if id_or_name.is_a?(Fixnum)
20
+
21
+ find_options = {:select => "#{self.table_name}.*"}
22
+ find_options[:joins] = :slugs unless options[:include] && [*options[:include]].flatten.include?(:slugs)
23
+
24
+ name, sequence = Slug.parse(id_or_name)
25
+
26
+ find_options[:conditions] = {
27
+ "#{Slug.table_name}.name" => name,
28
+ "#{Slug.table_name}.scope" => scope,
29
+ "#{Slug.table_name}.sequence" => sequence
30
+ }
31
+
32
+ result = with_scope(:find => find_options) { find_initial(options) }
33
+
34
+ if result
35
+ result.finder_slug_name = id_or_name
36
+ else
37
+ result = find_one_without_friendly id_or_name, options
38
+ end
39
+
40
+ result
41
+
42
+ end
43
+
44
+ # Finds multiple records using the friendly ids, or the records' ids.
45
+ def find_some_with_friendly(ids_and_names, options) #:nodoc:#
46
+
47
+ slugs, ids = get_slugs_and_ids(ids_and_names, options)
48
+ results = []
49
+
50
+ find_options = {:select => "#{self.table_name}.*"}
51
+ find_options[:joins] = :slugs unless options[:include] && [*options[:include]].flatten.include?(:slugs)
52
+ find_options[:conditions] = "#{quoted_table_name}.#{primary_key} IN (#{ids.empty? ? 'NULL' : ids.join(',')}) "
53
+ find_options[:conditions] << "OR slugs.id IN (#{slugs.to_s(:db)})"
54
+
55
+ results = with_scope(:find => find_options) { find_every(options) }
56
+
57
+ expected = expected_size(ids_and_names, options)
58
+ if results.size != expected
59
+ 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 })"
60
+ end
61
+
62
+ assign_finder_slugs(slugs, results)
63
+
64
+ results
65
+ end
66
+
67
+ def validate_find_options_with_friendly(options) #:nodoc:#
68
+ options.assert_valid_keys([:conditions, :include, :joins, :limit, :offset,
69
+ :order, :select, :readonly, :group, :from, :lock, :having, :scope])
70
+ end
71
+
72
+ private
73
+
74
+ # Assign finder slugs for the results found in find_some_with_friendly
75
+ def assign_finder_slugs(slugs, results) #:nodoc:#
76
+ slugs.each do |slug|
77
+ results.select { |r| r.id == slug.sluggable_id }.each do |result|
78
+ result.send(:finder_slug=, slug)
79
+ end
80
+ end
81
+ end
82
+
83
+ # Build arrays of slugs and ids, for the find_some_with_friendly method.
84
+ def get_slugs_and_ids(ids_and_names, options) #:nodoc:#
85
+ scope = options.delete(:scope)
86
+ slugs = []
87
+ ids = []
88
+ ids_and_names.each do |id_or_name|
89
+ name, sequence = Slug.parse id_or_name
90
+ slug = Slug.find(:first, :readonly => true, :conditions => {
91
+ :name => name,
92
+ :scope => scope,
93
+ :sequence => sequence,
94
+ :sluggable_type => base_class.name
95
+ })
96
+ # If the slug was found, add it to the array for later use. If not, and
97
+ # the id_or_name is a number, assume that it is a regular record id.
98
+ slug ? slugs << slug : (ids << id_or_name if id_or_name =~ /\A\d*\z/)
99
+ end
100
+ return slugs, ids
101
+ end
102
+
103
+ end
@@ -0,0 +1,106 @@
1
+ module FriendlyId::SluggableInstanceMethods
2
+
3
+ NUM_CHARS_RESERVED_FOR_FRIENDLY_ID_EXTENSION = 2
4
+
5
+ attr :finder_slug
6
+ attr_accessor :finder_slug_name
7
+
8
+ def finder_slug
9
+ @finder_slug ||= init_finder_slug
10
+ end
11
+
12
+ # Was the record found using one of its friendly ids?
13
+ def found_using_friendly_id?
14
+ finder_slug
15
+ end
16
+
17
+ # Was the record found using its numeric id?
18
+ def found_using_numeric_id?
19
+ !found_using_friendly_id?
20
+ end
21
+
22
+ # Was the record found using an old friendly id?
23
+ def found_using_outdated_friendly_id?
24
+ finder_slug.id != slug.id
25
+ end
26
+
27
+ # Was the record found using an old friendly id, or its numeric id?
28
+ def has_better_id?
29
+ slug and found_using_numeric_id? || found_using_outdated_friendly_id?
30
+ end
31
+
32
+ # Returns the friendly id.
33
+ def friendly_id
34
+ slug(true).to_friendly_id
35
+ end
36
+ alias best_id friendly_id
37
+
38
+ # Has the basis of our friendly id changed, requiring the generation of a
39
+ # new slug?
40
+ def new_slug_needed?
41
+ !slug || slug_text != slug.name
42
+ end
43
+
44
+ # Returns the most recent slug, which is used to determine the friendly
45
+ # id.
46
+ def slug(reload = false)
47
+ @most_recent_slug = nil if reload
48
+ @most_recent_slug ||= slugs.first
49
+ end
50
+
51
+ # Returns the friendly id, or if none is available, the numeric id.
52
+ def to_param
53
+ slug ? slug.to_friendly_id : id.to_s
54
+ end
55
+
56
+ # Get the processed string used as the basis of the friendly id.
57
+ def slug_text
58
+ base = send friendly_id_options[:column]
59
+ if self.friendly_id_options[:strip_diacritics]
60
+ base = Slug::normalize(Slug::strip_diacritics(base))
61
+ else
62
+ base = Slug::normalize(base)
63
+ end
64
+ if base.length > friendly_id_options[:max_length]
65
+ base = base[0...friendly_id_options[:max_length]]
66
+ end
67
+ if friendly_id_options[:reserved].include?(base)
68
+ raise FriendlyId::SlugGenerationError.new("The slug text is a reserved value")
69
+ end
70
+ return base
71
+ end
72
+
73
+ private
74
+
75
+ def finder_slug=(finder_slug)
76
+ @finder_slug_name = finder_slug.name
77
+ slug = finder_slug
78
+ slug.sluggable = self
79
+ slug
80
+ end
81
+
82
+ def init_finder_slug
83
+ return false if !@finder_slug_name
84
+ name, sequence = Slug.parse(@finder_slug_name)
85
+ slug = Slug.find(:first, :conditions => {:sluggable_id => id, :name => name, :sequence => sequence, :sluggable_type => self.class.name })
86
+ finder_slug = slug
87
+ end
88
+
89
+ # Set the slug using the generated friendly id.
90
+ def set_slug
91
+ if self.class.friendly_id_options[:use_slug] && new_slug_needed?
92
+ @most_recent_slug = nil
93
+ slug_attributes = {:name => slug_text}
94
+ if friendly_id_options[:scope]
95
+ scope = send(friendly_id_options[:scope])
96
+ slug_attributes[:scope] = scope.respond_to?(:to_param) ? scope.to_param : scope.to_s
97
+ end
98
+ # If we're renaming back to a previously used friendly_id, delete the
99
+ # slug so that we can recycle the name without having to use a sequence.
100
+ slugs.find(:all, :conditions => {:name => slug_text, :scope => scope}).each { |s| s.destroy }
101
+ slug = slugs.build slug_attributes
102
+ slug
103
+ end
104
+ end
105
+
106
+ end
@@ -0,0 +1,8 @@
1
+ module FriendlyId #:nodoc:
2
+ module Version #:nodoc:
3
+ MAJOR = 2
4
+ MINOR = 0
5
+ TINY = 1
6
+ STRING = [MAJOR, MINOR, TINY].join('.')
7
+ end
8
+ end
@@ -0,0 +1,67 @@
1
+ require 'unicode'
2
+ require 'friendly_id/helpers'
3
+ require 'friendly_id/slug'
4
+ require 'friendly_id/shoulda_macros'
5
+
6
+
7
+ # FriendlyId is a comprehensize Rails plugin/gem for slugging and permalinks.
8
+ module FriendlyId
9
+
10
+ # This error is raised when it's not possible to generate a unique slug.
11
+ class SlugGenerationError < StandardError ; end
12
+
13
+ module ClassMethods
14
+
15
+ # Default options for friendly_id.
16
+ DEFAULT_FRIENDLY_ID_OPTIONS = {:method => nil, :use_slug => false, :max_length => 255, :reserved => [], :strip_diacritics => false, :scope => nil}.freeze
17
+ VALID_FRIENDLY_ID_KEYS = [:use_slug, :max_length, :reserved, :strip_diacritics, :scope].freeze
18
+
19
+ # Set up an ActiveRecord model to use a friendly_id.
20
+ #
21
+ # The column argument can be one of your model's columns, or a method
22
+ # you use to generate the slug.
23
+ #
24
+ # Options:
25
+ # * <tt>:use_slug</tt> - Defaults to false. Use slugs when you want to use a non-unique text field for friendly ids.
26
+ # * <tt>:max_length</tt> - Defaults to 255. The maximum allowed length for a slug.
27
+ # * <tt>:strip_diacritics</tt> - Defaults to false. If true, it will remove accents, umlauts, etc. from western characters.
28
+ # * <tt>:reseved</tt> - Array of words that are reserved and can't be used as slugs. 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 [].
29
+ def has_friendly_id(column, options = {})
30
+ options.assert_valid_keys VALID_FRIENDLY_ID_KEYS
31
+ options = DEFAULT_FRIENDLY_ID_OPTIONS.merge(options).merge(:column => column)
32
+ write_inheritable_attribute :friendly_id_options, options
33
+ class_inheritable_reader :friendly_id_options
34
+
35
+ if options[:use_slug]
36
+ has_many :slugs, :order => 'id DESC', :as => :sluggable, :dependent => :destroy, :readonly => true
37
+ require 'friendly_id/sluggable_class_methods'
38
+ require 'friendly_id/sluggable_instance_methods'
39
+ extend SluggableClassMethods
40
+ include SluggableInstanceMethods
41
+ before_save :set_slug
42
+ else
43
+ require 'friendly_id/non_sluggable_class_methods'
44
+ require 'friendly_id/non_sluggable_instance_methods'
45
+ extend NonSluggableClassMethods
46
+ include NonSluggableInstanceMethods
47
+ end
48
+ end
49
+
50
+ end
51
+
52
+ class << self
53
+
54
+ # Load FriendlyId if the gem is included in a Rails app.
55
+ def enable
56
+ return if ActiveRecord::Base.methods.include? 'has_friendly_id'
57
+ ActiveRecord::Base.class_eval { extend FriendlyId::ClassMethods }
58
+ Test::Unit::TestCase.class_eval { include FriendlyId::ShouldaMacros }
59
+ end
60
+
61
+ end
62
+
63
+ end
64
+
65
+ if defined?(ActiveRecord)
66
+ FriendlyId::enable
67
+ end
@@ -0,0 +1,48 @@
1
+ namespace :friendly_id do
2
+ desc "Make slugs for a model."
3
+ task :make_slugs => :environment do
4
+ raise 'USAGE: rake friendly_id:make_slugs MODEL=MyModelName' if ENV["MODEL"].nil?
5
+ if !sluggable_class.friendly_id_options[:use_slug]
6
+ raise "Class \"#{sluggable_class.to_s}\" doesn't appear to be using slugs"
7
+ end
8
+ while records = sluggable_class.find(:all, :include => :slugs, :conditions => "slugs.id IS NULL", :limit => 1000) do
9
+ break if records.size == 0
10
+ records.each do |r|
11
+ r.send(:set_slug)
12
+ r.save!
13
+ puts "#{sluggable_class.to_s}(#{r.id}) friendly_id set to \"#{r.slug.name}\""
14
+ end
15
+ end
16
+ end
17
+
18
+ desc "Regenereate slugs for a model."
19
+ task :redo_slugs => :environment do
20
+ raise 'USAGE: rake friendly_id:redo_slugs MODEL=MyModelName' if ENV["MODEL"].nil?
21
+ if !sluggable_class.friendly_id_options[:use_slug]
22
+ raise "Class \"#{sluggable_class.to_s}\" doesn't appear to be using slugs"
23
+ end
24
+ Slug.destroy_all(["sluggable_type = ?", sluggable_class.to_s])
25
+ Rake::Task["friendly_id:make_slugs"].invoke
26
+ end
27
+
28
+ desc "Kill obsolete slugs older than 45 days."
29
+ task :remove_old_slugs => :environment do
30
+ if ENV["DAYS"].nil?
31
+ @days = 45
32
+ else
33
+ @days = ENV["DAYS"].to_i
34
+ end
35
+ slugs = Slug.find(:all, :conditions => ["created_at < ?", DateTime.now - @days.days])
36
+ slugs.each do |s|
37
+ s.destroy if !s.is_most_recent?
38
+ end
39
+ end
40
+ end
41
+
42
+ def sluggable_class
43
+ if (ENV["MODEL"].split('::').size > 1)
44
+ ENV["MODEL"].split('::').inject(Kernel) {|scope, const_name| scope.const_get(const_name)}
45
+ else
46
+ Object.const_get(ENV["MODEL"])
47
+ end
48
+ end
@@ -0,0 +1 @@
1
+ load 'tasks/friendly_id.rake'
data/test/database.yml ADDED
@@ -0,0 +1,3 @@
1
+ sqlite3:
2
+ adapter: sqlite3
3
+ database: ":memory:"
@@ -0,0 +1,4 @@
1
+ argentina:
2
+ name: Argentina
3
+ usa:
4
+ name: USA
@@ -0,0 +1,4 @@
1
+ class Country < ActiveRecord::Base
2
+ has_many :people
3
+ has_friendly_id :name, :use_slug => true
4
+ end
@@ -0,0 +1,7 @@
1
+ john_smith:
2
+ name: John Smith
3
+ country: argentina
4
+
5
+ john_smith2:
6
+ name: John Smith
7
+ country: usa
@@ -0,0 +1,6 @@
1
+ class Person < ActiveRecord::Base
2
+
3
+ belongs_to :country
4
+ has_friendly_id :name, :use_slug => true, :scope => :country
5
+
6
+ end
@@ -0,0 +1,3 @@
1
+ class Post < ActiveRecord::Base
2
+ has_friendly_id :name, :use_slug => true, :reserved => ['new', 'edit']
3
+ end
@@ -0,0 +1,23 @@
1
+ without_slug:
2
+ name: Without a slug
3
+ content: Content without a slug
4
+
5
+ with_one_slug:
6
+ name: With one slug
7
+ content: Content with one slug
8
+
9
+ with_two_slugs:
10
+ name: With two slugs
11
+ content: Content with two slugs
12
+
13
+ common_title:
14
+ name: Common Title
15
+ content: A post with a very common title
16
+
17
+ common_title2:
18
+ name: Common Title
19
+ content: A second post with a very common title
20
+
21
+ john_smith:
22
+ name: John Smith
23
+ content: Should allow for identical slug names between sluggable types
@@ -0,0 +1,53 @@
1
+ post_with_same_friendly_id_as_person:
2
+ name: john-smith
3
+ sluggable: john_smith (Post)
4
+
5
+ person_with_same_friendly_id_as_post:
6
+ name: john-smith
7
+ sluggable: john_smith (Person)
8
+
9
+ one:
10
+ name: with-one-slug
11
+ sluggable: with_one_slug
12
+ sluggable_type: Post
13
+ sequence: 1
14
+
15
+ two_old:
16
+ name: with-two-slugs
17
+ sluggable: with_two_slugs (Post)
18
+ sequence: 1
19
+
20
+ two_new:
21
+ name: with-two-slugs-new
22
+ sluggable: with_two_slugs (Post)
23
+ sequence: 1
24
+
25
+ common_title:
26
+ name: common-title
27
+ sluggable: common_title (Post)
28
+ sequence: 1
29
+
30
+ common_title2:
31
+ name: common-title
32
+ sluggable: common_title2 (Post)
33
+ sequence: 2
34
+
35
+ john_smith:
36
+ name: john-smith
37
+ sluggable: john_smith (Person)
38
+ sequence: 1
39
+ scope: argentina
40
+
41
+ john_smith2:
42
+ name: john-smith
43
+ sluggable: john_smith2 (Person)
44
+ sequence: 1
45
+ scope: usa
46
+
47
+ argentina:
48
+ name: argentina
49
+ sluggable: argentina (Country)
50
+
51
+ usa:
52
+ name: usa
53
+ sluggable: usa (Country)
@@ -0,0 +1,3 @@
1
+ class User < ActiveRecord::Base
2
+ has_friendly_id :login
3
+ end
@@ -0,0 +1,7 @@
1
+ joe:
2
+ login: joe
3
+ email: joe@example.org
4
+
5
+ jane:
6
+ login: jane
7
+ email: jane@example.org