friendly_id 3.3.3.0 → 4.0.0.beta7

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 (79) hide show
  1. data/.gitignore +11 -0
  2. data/.travis.yml +24 -0
  3. data/.yardopts +4 -0
  4. data/Changelog.md +9 -10
  5. data/README.md +39 -48
  6. data/Rakefile +56 -58
  7. data/WhatsNew.md +95 -0
  8. data/bench.rb +63 -0
  9. data/friendly_id.gemspec +40 -0
  10. data/gemfiles/Gemfile.rails-3.0.rb +18 -0
  11. data/gemfiles/Gemfile.rails-3.0.rb.lock +52 -0
  12. data/gemfiles/Gemfile.rails-3.1.rb +18 -0
  13. data/gemfiles/Gemfile.rails-3.1.rb.lock +57 -0
  14. data/lib/friendly_id.rb +126 -80
  15. data/lib/friendly_id/active_record_adapter/relation.rb +10 -2
  16. data/lib/friendly_id/active_record_adapter/slugged_model.rb +3 -9
  17. data/lib/friendly_id/base.rb +132 -0
  18. data/lib/friendly_id/configuration.rb +65 -152
  19. data/lib/friendly_id/finder_methods.rb +20 -0
  20. data/lib/friendly_id/history.rb +88 -0
  21. data/lib/friendly_id/migration.rb +18 -0
  22. data/lib/friendly_id/model.rb +22 -0
  23. data/lib/friendly_id/object_utils.rb +40 -0
  24. data/lib/friendly_id/reserved.rb +46 -0
  25. data/lib/friendly_id/scoped.rb +131 -0
  26. data/lib/friendly_id/slug.rb +9 -0
  27. data/lib/friendly_id/slug_sequencer.rb +82 -0
  28. data/lib/friendly_id/slugged.rb +191 -76
  29. data/lib/friendly_id/version.rb +2 -2
  30. data/test/base_test.rb +54 -0
  31. data/test/configuration_test.rb +27 -0
  32. data/test/core_test.rb +30 -0
  33. data/test/databases.yml +19 -0
  34. data/test/helper.rb +88 -0
  35. data/test/history_test.rb +55 -0
  36. data/test/object_utils_test.rb +26 -0
  37. data/test/reserved_test.rb +26 -0
  38. data/test/schema.rb +59 -0
  39. data/test/scoped_test.rb +57 -0
  40. data/test/shared.rb +118 -0
  41. data/test/slugged_test.rb +83 -0
  42. data/test/sti_test.rb +48 -0
  43. metadata +110 -102
  44. data/Contributors.md +0 -46
  45. data/Guide.md +0 -626
  46. data/extras/README.txt +0 -3
  47. data/extras/bench.rb +0 -40
  48. data/extras/extras.rb +0 -38
  49. data/extras/prof.rb +0 -19
  50. data/extras/template-gem.rb +0 -26
  51. data/extras/template-plugin.rb +0 -28
  52. data/generators/friendly_id/friendly_id_generator.rb +0 -30
  53. data/generators/friendly_id/templates/create_slugs.rb +0 -18
  54. data/lib/tasks/friendly_id.rake +0 -19
  55. data/rails/init.rb +0 -2
  56. data/test/active_record_adapter/ar_test_helper.rb +0 -149
  57. data/test/active_record_adapter/basic_slugged_model_test.rb +0 -14
  58. data/test/active_record_adapter/cached_slug_test.rb +0 -76
  59. data/test/active_record_adapter/core.rb +0 -138
  60. data/test/active_record_adapter/custom_normalizer_test.rb +0 -20
  61. data/test/active_record_adapter/custom_table_name_test.rb +0 -22
  62. data/test/active_record_adapter/default_scope_test.rb +0 -30
  63. data/test/active_record_adapter/optimistic_locking_test.rb +0 -18
  64. data/test/active_record_adapter/scoped_model_test.rb +0 -129
  65. data/test/active_record_adapter/simple_test.rb +0 -76
  66. data/test/active_record_adapter/slug_test.rb +0 -34
  67. data/test/active_record_adapter/slugged.rb +0 -33
  68. data/test/active_record_adapter/slugged_status_test.rb +0 -28
  69. data/test/active_record_adapter/sti_test.rb +0 -22
  70. data/test/active_record_adapter/support/database.jdbcsqlite3.yml +0 -2
  71. data/test/active_record_adapter/support/database.mysql.yml +0 -4
  72. data/test/active_record_adapter/support/database.mysql2.yml +0 -4
  73. data/test/active_record_adapter/support/database.postgres.yml +0 -6
  74. data/test/active_record_adapter/support/database.sqlite3.yml +0 -2
  75. data/test/active_record_adapter/support/models.rb +0 -104
  76. data/test/active_record_adapter/tasks_test.rb +0 -82
  77. data/test/compatibility/ancestry/Gemfile.lock +0 -34
  78. data/test/friendly_id_test.rb +0 -96
  79. data/test/test_helper.rb +0 -13
data/bench.rb ADDED
@@ -0,0 +1,63 @@
1
+ require File.expand_path("../test/helper", __FILE__)
2
+ require "ffaker"
3
+
4
+ N = 1000
5
+
6
+ def transaction
7
+ ActiveRecord::Base.transaction { yield ; raise ActiveRecord::Rollback }
8
+ end
9
+
10
+ class Array
11
+ def rand
12
+ self[Kernel.rand(length)]
13
+ end
14
+ end
15
+
16
+ Book = Class.new ActiveRecord::Base
17
+
18
+ class Journalist < ActiveRecord::Base
19
+ extend FriendlyId
20
+ include FriendlyId::Slugged
21
+ friendly_id :name
22
+ end
23
+
24
+ class Manual < ActiveRecord::Base
25
+ extend FriendlyId
26
+ include FriendlyId::History
27
+ friendly_id :name
28
+ end
29
+
30
+ BOOKS = []
31
+ JOURNALISTS = []
32
+ MANUALS = []
33
+
34
+ 100.times do
35
+ name = Faker::Name.name
36
+ BOOKS << (Book.create! :name => name).id
37
+ JOURNALISTS << (Journalist.create! :name => name).friendly_id
38
+ MANUALS << (Manual.create! :name => name).friendly_id
39
+ end
40
+
41
+ Benchmark.bmbm do |x|
42
+ x.report 'find (without FriendlyId)' do
43
+ N.times {Book.find BOOKS.rand}
44
+ end
45
+ x.report 'find (in-table slug)' do
46
+ N.times {Journalist.find JOURNALISTS.rand}
47
+ end
48
+ x.report 'find (external slug)' do
49
+ N.times {Manual.find_by_friendly_id MANUALS.rand}
50
+ end
51
+
52
+ x.report 'insert (without FriendlyId)' do
53
+ N.times {transaction {Book.create :name => Faker::Name.name}}
54
+ end
55
+
56
+ x.report 'insert (in-table-slug)' do
57
+ N.times {transaction {Journalist.create :name => Faker::Name.name}}
58
+ end
59
+
60
+ x.report 'insert (external slug)' do
61
+ N.times {transaction {Manual.create :name => Faker::Name.name}}
62
+ end
63
+ end
@@ -0,0 +1,40 @@
1
+ # encoding: utf-8
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+
4
+ require "friendly_id"
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "friendly_id"
8
+ s.version = FriendlyId::VERSION
9
+ s.authors = ["Norman Clarke"]
10
+ s.email = ["norman@njclarke.com"]
11
+ s.homepage = "http://norman.github.com/friendly_id"
12
+ s.summary = "A comprehensive slugging and pretty-URL plugin."
13
+ s.rubyforge_project = "friendly_id"
14
+ s.files = `git ls-files`.split("\n")
15
+ s.test_files = `git ls-files -- {test}/*`.split("\n")
16
+ s.require_paths = ["lib"]
17
+
18
+ s.add_development_dependency "activerecord", "~> 3.0"
19
+ s.add_development_dependency "sqlite3", "~> 1.3"
20
+ s.add_development_dependency "minitest", "~> 2.4.0"
21
+ s.add_development_dependency "mocha", "~> 0.9.12"
22
+ s.add_development_dependency "ffaker", "~> 1.8.0"
23
+ s.add_development_dependency "maruku", "~> 0.6.0"
24
+ s.add_development_dependency "yard", "~> 0.7.2"
25
+
26
+ s.description = <<-EOM
27
+ FriendlyId is the "Swiss Army bulldozer" of slugging and permalink plugins for
28
+ Ruby on Rails. It allows you to create pretty URL's and work with
29
+ human-friendly strings as if they were numeric ids for ActiveRecord models.
30
+ EOM
31
+
32
+ s.post_install_message = <<-EOM
33
+ NOTE: FriendlyId 4.x breaks compatibility with 3.x. If you're upgrading
34
+ from 3.x, please see this document:
35
+
36
+ https://github.com/norman/friendly_id/blob/4.0.0/WhatsNew.md
37
+
38
+ EOM
39
+
40
+ end
@@ -0,0 +1,18 @@
1
+ source :rubygems
2
+
3
+ platform :jruby do
4
+ gem "activerecord-jdbcmysql-adapter"
5
+ gem "activerecord-jdbcpostgresql-adapter"
6
+ gem "activerecord-jdbcsqlite3-adapter"
7
+ end
8
+
9
+ platform :ruby do
10
+ gem "mysql2", "~> 0.2.0"
11
+ gem "pg", "~> 0.11.0"
12
+ gem "sqlite3", "~> 1.3.4"
13
+ end
14
+
15
+ gem "activerecord", "3.0.9"
16
+ gem "minitest", "~> 2.4.0"
17
+ gem "mocha", "~> 0.9.12"
18
+ gem "rake"
@@ -0,0 +1,52 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ activemodel (3.0.9)
5
+ activesupport (= 3.0.9)
6
+ builder (~> 2.1.2)
7
+ i18n (~> 0.5.0)
8
+ activerecord (3.0.9)
9
+ activemodel (= 3.0.9)
10
+ activesupport (= 3.0.9)
11
+ arel (~> 2.0.10)
12
+ tzinfo (~> 0.3.23)
13
+ activerecord-jdbc-adapter (1.1.3)
14
+ activerecord-jdbcmysql-adapter (1.1.3)
15
+ activerecord-jdbc-adapter (= 1.1.3)
16
+ jdbc-mysql (~> 5.1.0)
17
+ activerecord-jdbcpostgresql-adapter (1.1.3)
18
+ activerecord-jdbc-adapter (= 1.1.3)
19
+ jdbc-postgres (~> 9.0.0)
20
+ activerecord-jdbcsqlite3-adapter (1.1.3)
21
+ activerecord-jdbc-adapter (= 1.1.3)
22
+ jdbc-sqlite3 (~> 3.7.0)
23
+ activesupport (3.0.9)
24
+ arel (2.0.10)
25
+ builder (2.1.2)
26
+ i18n (0.5.0)
27
+ jdbc-mysql (5.1.13)
28
+ jdbc-postgres (9.0.801)
29
+ jdbc-sqlite3 (3.7.2)
30
+ minitest (2.4.0)
31
+ mocha (0.9.12)
32
+ mysql2 (0.2.11)
33
+ pg (0.11.0)
34
+ rake (0.9.2)
35
+ sqlite3 (1.3.4)
36
+ tzinfo (0.3.29)
37
+
38
+ PLATFORMS
39
+ java
40
+ ruby
41
+
42
+ DEPENDENCIES
43
+ activerecord (= 3.0.9)
44
+ activerecord-jdbcmysql-adapter
45
+ activerecord-jdbcpostgresql-adapter
46
+ activerecord-jdbcsqlite3-adapter
47
+ minitest (~> 2.4.0)
48
+ mocha (~> 0.9.12)
49
+ mysql2 (~> 0.2.0)
50
+ pg (~> 0.11.0)
51
+ rake
52
+ sqlite3 (~> 1.3.4)
@@ -0,0 +1,18 @@
1
+ source :rubygems
2
+
3
+ platform :jruby do
4
+ gem "activerecord-jdbcmysql-adapter"
5
+ gem "activerecord-jdbcpostgresql-adapter"
6
+ gem "activerecord-jdbcsqlite3-adapter"
7
+ end
8
+
9
+ platform :ruby do
10
+ gem "mysql2", "~> 0.3.6"
11
+ gem "pg", "~> 0.11.0"
12
+ gem "sqlite3", "~> 1.3.4"
13
+ end
14
+
15
+ gem "activerecord", "~> 3.1.0.rc5"
16
+ gem "minitest", "~> 2.4.0"
17
+ gem "mocha", "~> 0.9.12"
18
+ gem "rake"
@@ -0,0 +1,57 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ activemodel (3.1.0.rc5)
5
+ activesupport (= 3.1.0.rc5)
6
+ bcrypt-ruby (~> 2.1.4)
7
+ builder (~> 3.0.0)
8
+ i18n (~> 0.6)
9
+ activerecord (3.1.0.rc5)
10
+ activemodel (= 3.1.0.rc5)
11
+ activesupport (= 3.1.0.rc5)
12
+ arel (~> 2.1.4)
13
+ tzinfo (~> 0.3.29)
14
+ activerecord-jdbc-adapter (1.1.3)
15
+ activerecord-jdbcmysql-adapter (1.1.3)
16
+ activerecord-jdbc-adapter (= 1.1.3)
17
+ jdbc-mysql (~> 5.1.0)
18
+ activerecord-jdbcpostgresql-adapter (1.1.3)
19
+ activerecord-jdbc-adapter (= 1.1.3)
20
+ jdbc-postgres (~> 9.0.0)
21
+ activerecord-jdbcsqlite3-adapter (1.1.3)
22
+ activerecord-jdbc-adapter (= 1.1.3)
23
+ jdbc-sqlite3 (~> 3.7.0)
24
+ activesupport (3.1.0.rc5)
25
+ multi_json (~> 1.0)
26
+ arel (2.1.4)
27
+ bcrypt-ruby (2.1.4)
28
+ bcrypt-ruby (2.1.4-java)
29
+ builder (3.0.0)
30
+ i18n (0.6.0)
31
+ jdbc-mysql (5.1.13)
32
+ jdbc-postgres (9.0.801)
33
+ jdbc-sqlite3 (3.7.2)
34
+ minitest (2.4.0)
35
+ mocha (0.9.12)
36
+ multi_json (1.0.3)
37
+ mysql2 (0.3.6)
38
+ pg (0.11.0)
39
+ rake (0.9.2)
40
+ sqlite3 (1.3.4)
41
+ tzinfo (0.3.29)
42
+
43
+ PLATFORMS
44
+ java
45
+ ruby
46
+
47
+ DEPENDENCIES
48
+ activerecord (~> 3.1.0.rc5)
49
+ activerecord-jdbcmysql-adapter
50
+ activerecord-jdbcpostgresql-adapter
51
+ activerecord-jdbcsqlite3-adapter
52
+ minitest (~> 2.4.0)
53
+ mocha (~> 0.9.12)
54
+ mysql2 (~> 0.3.6)
55
+ pg (~> 0.11.0)
56
+ rake
57
+ sqlite3 (~> 1.3.4)
data/lib/friendly_id.rb CHANGED
@@ -1,93 +1,139 @@
1
- require "babosa"
2
- require "forwardable"
3
- require "friendly_id/slug_string"
1
+ # encoding: utf-8
2
+ require "thread"
3
+ require "friendly_id/base"
4
+ require "friendly_id/model"
5
+ require "friendly_id/object_utils"
4
6
  require "friendly_id/configuration"
5
- require "friendly_id/status"
6
- require "friendly_id/slugged"
7
-
8
- # FriendlyId is a comprehensive Ruby library for slugging and permalinks with
9
- # ActiveRecord.
10
- # @author Norman Clarke
11
- # @author Emilio Tagua
12
- # @author Adrian Mugnolo
13
- module FriendlyId
7
+ require "friendly_id/finder_methods"
8
+
9
+ =begin
10
+
11
+ == About FriendlyId
12
+
13
+ FriendlyId is an add-on to Ruby's Active Record that allows you to replace ids
14
+ in your URLs with strings:
15
+
16
+ # without FriendlyId
17
+ http://example.com/states/4323454
18
+
19
+ # with FriendlyId
20
+ http://example.com/states/washington
21
+
22
+ It requires few changes to your application code and offers flexibility,
23
+ performance and a well-documented codebase.
24
+
25
+ === Concepts
14
26
 
15
- # An error based on this class is raised when slug generation fails
16
- class SlugGenerationError < StandardError ; end
17
-
18
- # Raised when the slug text is blank.
19
- class BlankError < SlugGenerationError ; end
20
-
21
- # Raised when the slug text is reserved.
22
- class ReservedError < SlugGenerationError ; end
23
-
24
- module Base
25
- # Set up a model to use a friendly_id. This method accepts a hash with
26
- # {FriendlyId::Configuration several possible options}.
27
- #
28
- # @param [#to_sym] method The column or method that should be used as the
29
- # basis of the friendly_id string.
30
- #
31
- # @param [Hash] options For valid configuration options, see
32
- # {FriendlyId::Configuration}.
33
- #
34
- # @example
35
- #
36
- # class User < ActiveRecord::Base
37
- # has_friendly_id :user_name
38
- # end
39
- #
40
- # class Post < ActiveRecord::Base
41
- # has_friendly_id :title, :use_slug => true, :approximate_ascii => true
42
- # end
43
- #
44
- # @see FriendlyId::Configuration
45
- def has_friendly_id(method, options = {})
46
- raise NotImplementedError
27
+ Although FriendlyId helps with URLs, it does all of its work inside your models,
28
+ not your routes.
29
+
30
+ === Simple Models
31
+
32
+ The simplest way to use FriendlyId is with a model that has a uniquely indexed
33
+ column with no spaces or special characters, and that is seldom or never
34
+ updated. The most common example of this is a user name:
35
+
36
+ class User < ActiveRecord::Base
37
+ extend FriendlyId
38
+ friendly_id :login
39
+ validates_format_of :login, :with => /\A[a-z0-9]+\z/i
47
40
  end
48
41
 
49
- # Does the model class use the FriendlyId plugin?
50
- def uses_friendly_id?
51
- respond_to? :friendly_id_config
42
+ @user = User.find "joe" # the old User.find(1) still works, too
43
+ @user.to_param # returns "joe"
44
+ redirect_to @user # the URL will be /users/joe
45
+
46
+ In this case, FriendlyId assumes you want to use the column as-is; it will never
47
+ modify the value of the column, and your application should ensure that the
48
+ value is admissible in a URL:
49
+
50
+ class City < ActiveRecord::Base
51
+ extend FriendlyId
52
+ friendly_id :name
52
53
  end
53
- end
54
54
 
55
- end
55
+ @city.find "Viña del Mar"
56
+ redirect_to @city # the URL will be /cities/Viña%20del%20Mar
56
57
 
57
- class String
58
- def parse_friendly_id(separator = nil)
59
- separator ||= FriendlyId::Configuration::DEFAULTS[:sequence_separator]
60
- name, sequence = split(/#{Regexp.escape(separator)}(\d+)?\z/)
61
- return name, (sequence ||= 1).to_i
62
- end
63
- end
58
+ For this reason, it is often more convenient to use "slugs" rather than a single
59
+ column.
60
+
61
+ === Slugged Models
62
+
63
+ FriendlyId can uses a separate column to store slugs for models which require
64
+ some processing of the friendly_id text. The most common example is a blog
65
+ post's title, which may have spaces, uppercase characters, or other attributes
66
+ you wish to modify to make them more suitable for use in URL's.
64
67
 
65
- class Object
66
-
67
- # Is the object a friendly id? Note that the return value here is
68
- # +false+ if the +id+ is definitely not friendly, and +nil+ if it can
69
- # not be determined.
70
- # The return value will be:
71
- # * +true+ - if the id is definitely friendly (i.e., a string with non-numeric characters)
72
- # * +false+ - if the id is definitely unfriendly (i.e., an Integer, a model instance, etc.)
73
- # * +nil+ - if it can not be determined (i.e., a numeric string like "206".)
74
- # @return [true, false, nil]
75
- # @see #unfriendly?
76
- def friendly_id?
77
- if kind_of?(Integer) or kind_of?(Symbol) or self.class.respond_to? :friendly_id_config
78
- false
79
- elsif to_i.to_s != to_s
80
- true
68
+ class Post < ActiveRecord::Base
69
+ extend FriendlyId
70
+ friendly_id :title, :use => :slugged
81
71
  end
72
+
73
+ @post = Post.create(:title => "This is the first post!")
74
+ @post.friendly_id # returns "this-is-the-first-post"
75
+ redirect_to @post # the URL will be /posts/this-is-the-first-post
76
+
77
+ In general, use slugs by default unless you know for sure you don't need them.
78
+
79
+ @author Norman Clarke
80
+ =end
81
+ module FriendlyId
82
+
83
+ # The current version.
84
+ VERSION = "4.0.0.beta7"
85
+
86
+ @mutex = Mutex.new
87
+
88
+ autoload :History, "friendly_id/history"
89
+ autoload :Reserved, "friendly_id/reserved"
90
+ autoload :Scoped, "friendly_id/scoped"
91
+ autoload :Slugged, "friendly_id/slugged"
92
+
93
+ # FriendlyId takes advantage of `extended` to do basic model setup, primarily
94
+ # extending {FriendlyId::Base} to add {FriendlyId::Base#friendly_id
95
+ # friendly_id} as a class method.
96
+ #
97
+ # Previous versions of FriendlyId simply patched ActiveRecord::Base, but this
98
+ # version tries to be less invasive.
99
+ #
100
+ # In addition to adding {FriendlyId::Base#friendly_id friendly_id}, the class
101
+ # instance variable +@friendly_id_config+ is added. This variable is an
102
+ # instance of an anonymous subclass of {FriendlyId::Configuration}. This
103
+ # allows subsequently loaded modules like {FriendlyId::Slugged} and
104
+ # {FriendlyId::Scoped} to add functionality to the configuration class only
105
+ # for the current class, rather than monkey patching
106
+ # {FriendlyId::Configuration} directly. This isolates other models from large
107
+ # feature changes an addon to FriendlyId could potentially introduce.
108
+ #
109
+ # The upshot of this is, you can htwo Active Record models that both have a
110
+ # @friendly_id_config, but each config object can have different methods and
111
+ # behaviors depending on what modules have been loaded, without conflicts.
112
+ # Keep this in mind if you're hacking on FriendlyId.
113
+ #
114
+ # For examples of this, see the source for {Scoped.included}.
115
+ def self.extended(model_class)
116
+ model_class.instance_eval do
117
+ extend Base
118
+ @friendly_id_config = Class.new(Configuration).new(self)
119
+ FriendlyId.defaults.call @friendly_id_config
120
+ end
121
+ ActiveRecord::Relation.send :include, FinderMethods
82
122
  end
83
123
 
84
- # Is the object a numeric id?
85
- # @return [true, false, nil] +true+ if definitely unfriendly, +false+ if
86
- # definitely friendly, else +nil+.
87
- # @see #friendly?
88
- def unfriendly_id?
89
- val = friendly_id? ; !val unless val.nil?
124
+ # Set global defaults for all models using FriendlyId.
125
+ #
126
+ # The default defaults are to use the +:reserved+ module and nothing else.
127
+ #
128
+ # @example
129
+ # FriendlyId.defaults do |config|
130
+ # config.base = :name
131
+ # config.use :slugged
132
+ # end
133
+ def self.defaults(&block)
134
+ @mutex.synchronize do
135
+ @defaults = block if block_given?
136
+ @defaults ||= lambda {|config| config.use :reserved}
137
+ end
90
138
  end
91
139
  end
92
-
93
- require "friendly_id/railtie" if defined?(Rails) && Rails.version >= "3"