friendly_id 2.2.7 → 2.3.0

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 (73) hide show
  1. data/Changelog.md +225 -0
  2. data/Contributors.md +28 -0
  3. data/Guide.md +509 -0
  4. data/LICENSE +1 -1
  5. data/README.md +76 -0
  6. data/Rakefile +48 -15
  7. data/extras/bench.rb +59 -0
  8. data/extras/extras.rb +31 -0
  9. data/extras/prof.rb +14 -0
  10. data/extras/template-gem.rb +1 -1
  11. data/extras/template-plugin.rb +1 -1
  12. data/generators/friendly_id/friendly_id_generator.rb +1 -1
  13. data/generators/friendly_id/templates/create_slugs.rb +2 -2
  14. data/lib/friendly_id.rb +54 -63
  15. data/lib/friendly_id/active_record2.rb +47 -0
  16. data/lib/friendly_id/active_record2/configuration.rb +66 -0
  17. data/lib/friendly_id/active_record2/finders.rb +140 -0
  18. data/lib/friendly_id/active_record2/simple_model.rb +162 -0
  19. data/lib/friendly_id/active_record2/slug.rb +111 -0
  20. data/lib/friendly_id/active_record2/slugged_model.rb +317 -0
  21. data/lib/friendly_id/active_record2/tasks.rb +66 -0
  22. data/lib/friendly_id/active_record2/tasks/friendly_id.rake +19 -0
  23. data/lib/friendly_id/configuration.rb +132 -0
  24. data/lib/friendly_id/finders.rb +106 -0
  25. data/lib/friendly_id/slug_string.rb +292 -0
  26. data/lib/friendly_id/slugged.rb +91 -0
  27. data/lib/friendly_id/status.rb +35 -0
  28. data/lib/friendly_id/test.rb +168 -0
  29. data/lib/friendly_id/version.rb +5 -5
  30. data/rails/init.rb +2 -0
  31. data/test/active_record2/basic_slugged_model_test.rb +14 -0
  32. data/test/active_record2/cached_slug_test.rb +61 -0
  33. data/test/active_record2/core.rb +93 -0
  34. data/test/active_record2/custom_normalizer_test.rb +20 -0
  35. data/test/active_record2/custom_table_name_test.rb +22 -0
  36. data/test/active_record2/scoped_model_test.rb +111 -0
  37. data/test/active_record2/simple_test.rb +59 -0
  38. data/test/active_record2/slug_test.rb +34 -0
  39. data/test/active_record2/slugged.rb +30 -0
  40. data/test/active_record2/slugged_status_test.rb +61 -0
  41. data/test/active_record2/sti_test.rb +22 -0
  42. data/test/active_record2/support/database.mysql.yml +4 -0
  43. data/test/{support/database.yml.postgres → active_record2/support/database.postgres.yml} +0 -0
  44. data/test/{support/database.yml.sqlite3 → active_record2/support/database.sqlite3.yml} +0 -0
  45. data/test/{support → active_record2/support}/models.rb +28 -0
  46. data/test/active_record2/tasks_test.rb +82 -0
  47. data/test/active_record2/test_helper.rb +107 -0
  48. data/test/friendly_id_test.rb +23 -0
  49. data/test/slug_string_test.rb +74 -0
  50. data/test/test_helper.rb +7 -102
  51. metadata +64 -56
  52. data/History.txt +0 -194
  53. data/README.rdoc +0 -385
  54. data/generators/friendly_id_20_upgrade/friendly_id_20_upgrade_generator.rb +0 -12
  55. data/generators/friendly_id_20_upgrade/templates/upgrade_friendly_id_to_20.rb +0 -19
  56. data/init.rb +0 -1
  57. data/lib/friendly_id/helpers.rb +0 -12
  58. data/lib/friendly_id/non_sluggable_class_methods.rb +0 -34
  59. data/lib/friendly_id/non_sluggable_instance_methods.rb +0 -45
  60. data/lib/friendly_id/slug.rb +0 -98
  61. data/lib/friendly_id/sluggable_class_methods.rb +0 -110
  62. data/lib/friendly_id/sluggable_instance_methods.rb +0 -161
  63. data/lib/friendly_id/tasks.rb +0 -56
  64. data/lib/tasks/friendly_id.rake +0 -25
  65. data/lib/tasks/friendly_id.rb +0 -1
  66. data/test/cached_slug_test.rb +0 -109
  67. data/test/custom_slug_normalizer_test.rb +0 -36
  68. data/test/non_slugged_test.rb +0 -99
  69. data/test/scoped_model_test.rb +0 -64
  70. data/test/slug_test.rb +0 -105
  71. data/test/slugged_model_test.rb +0 -348
  72. data/test/sti_test.rb +0 -49
  73. data/test/tasks_test.rb +0 -105
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2008 Norman Clarke, Adrian Mugnolo and Emilio Tagua.
1
+ Copyright (c) 2008-2010 Norman Clarke, Adrian Mugnolo and Emilio Tagua.
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  of this software and associated documentation files (the "Software"), to deal
data/README.md ADDED
@@ -0,0 +1,76 @@
1
+ # FriendlyId
2
+
3
+ FriendlyId is the "Swiss Army bulldozer" of slugging and permalink plugins for
4
+ Ruby on Rails. It allows you to create pretty URL's and work with
5
+ human-friendly strings as if they were numeric ids for ActiveRecord models.
6
+
7
+ Using FriendlyId, it's easy to make your application use URL's like:
8
+
9
+ http://example.com/states/washington
10
+
11
+ instead of:
12
+
13
+ http://example.com/states/4323454
14
+
15
+ ## FriendlyId Features
16
+
17
+ FriendlyId offers many advanced features, including: slug history and
18
+ versioning, scoped slugs, reserved words, custom slug generators, and
19
+ excellent Unicode support. For complete information on using FriendlyId,
20
+ please see the {file:GUIDE.md FriendlyId Guide}.
21
+
22
+ ## Rails Quickstart
23
+
24
+ gem install friendly_id
25
+
26
+ rails my_app
27
+
28
+ cd my_app
29
+
30
+ # add to config/environment.rb
31
+ config.gem "friendly_id", :version => ">= 2.3.0"
32
+
33
+ ./script/generate friendly_id
34
+ ./script/generate scaffold user name:string cached_slug:string
35
+
36
+ rake db:migrate
37
+
38
+ # edit app/models/user.rb
39
+ class User < ActiveRecord::Base
40
+ has_friendly_id :name, :use_slug => true
41
+ end
42
+
43
+ User.create! :name => "Joe Schmoe"
44
+
45
+ ./script/server
46
+
47
+ GET http://0.0.0.0:3000/users/joe-schmoe
48
+
49
+ ## Docs, Info and Support
50
+
51
+ * [FriendlyId Guide](http://norman.github.com/friendly_id/file.Guide.html)
52
+ * [API Docs](http://norman.github.com/friendly_id)
53
+ * [Google Group](http://groups.google.com/group/friendly_id)
54
+ * [Source Code](http://github.com/norman/friendly_id/)
55
+ * [Issue Tracker](http://github.com/norman/friendly_id/issues)
56
+
57
+ ## Bugs:
58
+
59
+ Please report them on the [Github issue tracker](http://github.com/norman/friendly_id/issues)
60
+ for this project.
61
+
62
+ If you have a bug to report, please include the following information:
63
+
64
+ * Stack trace and error message.
65
+ * Version information for FriendlyId, Rails and Ruby.
66
+ * Any snippets of relevant model, view or controller code that shows how your
67
+ are using FriendlyId.
68
+
69
+ If you are able to, it helps even more if you can fork FriendlyId on Github,
70
+ and add a test that reproduces the error you are experiencing.
71
+
72
+ ## Credits:
73
+
74
+ FriendlyId was created by Norman Clarke, Adrian Mugnolo, and Emilio Tagua.
75
+
76
+ Copyright (c) 2008-2010, released under the MIT license.
data/Rakefile CHANGED
@@ -1,35 +1,68 @@
1
- require 'rake'
2
- require 'rake/testtask'
3
- require 'rake/gempackagetask'
4
- require 'rake/rdoctask'
5
- require 'rake/clean'
1
+ require "rake"
2
+ require "rake/testtask"
3
+ require "rake/gempackagetask"
4
+ require "rake/rdoctask"
5
+ require "rake/clean"
6
6
 
7
- CLEAN << "pkg" << "docs" << "coverage"
7
+ CLEAN << "pkg" << "doc" << "coverage" << ".yardoc"
8
8
 
9
- task :default => :test
10
-
11
- Rake::TestTask.new(:test) { |t| t.pattern = 'test/**/*_test.rb' }
12
9
  Rake::GemPackageTask.new(eval(File.read("friendly_id.gemspec"))) { |pkg| }
13
10
  Rake::RDocTask.new do |r|
14
- r.rdoc_dir = "docs"
15
- r.main = "README.rdoc"
16
- r.rdoc_files.include "README.rdoc", "History.txt", "lib/**/*.rb"
11
+ r.rdoc_dir = "doc"
12
+ r.rdoc_files.include "lib/**/*.rb"
17
13
  end
18
14
 
19
15
  begin
20
16
  require "yard"
21
17
  YARD::Rake::YardocTask.new do |t|
22
- t.options = ["--output-dir=docs"]
18
+ t.options = ["--output-dir=doc"]
19
+ t.options << "--files" << ["Guide.md", "Contributors.md", "Changelog.md"].join(",")
23
20
  end
24
21
  rescue LoadError
25
22
  end
26
23
 
27
24
  begin
28
- require 'rcov/rcovtask'
25
+ require "rcov/rcovtask"
29
26
  Rcov::RcovTask.new do |r|
30
- r.test_files = FileList['test/*_test.rb']
27
+ r.test_files = FileList["test/**/*_test.rb"]
31
28
  r.verbose = true
32
29
  r.rcov_opts << "--exclude gems/*"
33
30
  end
34
31
  rescue LoadError
35
32
  end
33
+
34
+
35
+ task :test do
36
+ Rake::Task["test:friendly_id"].invoke
37
+ Rake::Task["test:ar"].invoke
38
+ end
39
+
40
+ namespace :test do
41
+ task :rails do
42
+ rm_rf "fid"
43
+ sh "rails --template extras/template-gem.rb fid"
44
+ sh "cd fid; rake test"
45
+ end
46
+ Rake::TestTask.new(:friendly_id) { |t| t.pattern = "test/*_test.rb" }
47
+ Rake::TestTask.new(:ar) { |t| t.pattern = "test/active_record2/*_test.rb" }
48
+
49
+ namespace :rails do
50
+ task :plugin do
51
+ rm_rf "fid"
52
+ sh "rails --template extras/template-plugin.rb fid"
53
+ sh "cd fid; rake test"
54
+ end
55
+ end
56
+
57
+ end
58
+
59
+ task :pushdocs do
60
+ branch = `git branch | grep "*"`.chomp.gsub("* ", "")
61
+ sh "git stash"
62
+ sh "git checkout gh-pages"
63
+ sh "cp -rp doc/* ."
64
+ sh 'git commit -a -m "Regenerated docs"'
65
+ sh "git push origin gh-pages"
66
+ sh "git checkout #{branch}"
67
+ sh "git stash apply"
68
+ end
data/extras/bench.rb ADDED
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env ruby -KU
2
+ require File.dirname(__FILE__) + '/extras'
3
+ require 'rbench'
4
+ FACTOR = 100
5
+ RBench.run(TIMES) do
6
+
7
+ column :times
8
+ column :ar
9
+
10
+ report 'find model using id', (TIMES * FACTOR).ceil do
11
+ ar { User.find(get_id) }
12
+ end
13
+
14
+ report 'find model using array of ids', (TIMES * FACTOR).ceil do
15
+ ar { User.find([get_id, get_id]) }
16
+ end
17
+
18
+ report 'find unslugged model using friendly id', (TIMES * FACTOR).ceil do
19
+ ar { User.find(USERS.rand) }
20
+ end
21
+
22
+ report 'find unslugged model using array of friendly ids', (TIMES * FACTOR).ceil do
23
+ ar { User.find([USERS.rand, USERS.rand]) }
24
+ end
25
+
26
+ report 'find slugged model using friendly id', (TIMES * FACTOR).ceil do
27
+ ar { Post.find(POSTS.rand) }
28
+ end
29
+
30
+ report 'find slugged model using array of friendly ids', (TIMES * FACTOR).ceil do
31
+ ar { Post.find([POSTS.rand, POSTS.rand]) }
32
+ end
33
+
34
+ report 'find cached slugged model using friendly id', (TIMES * FACTOR).ceil do
35
+ ar { District.find(DISTRICTS.rand) }
36
+ end
37
+
38
+ report 'find cached slugged model using array of friendly ids', (TIMES * FACTOR).ceil do
39
+ ar { District.find([DISTRICTS.rand, DISTRICTS.rand]) }
40
+ end
41
+
42
+ report 'find model using id, then to_param', (TIMES * FACTOR).ceil do
43
+ ar { User.find(get_id).to_param }
44
+ end
45
+
46
+ report 'find unslugged model using friendly id, then to_param', (TIMES * FACTOR).ceil do
47
+ ar { User.find(USERS.rand).to_param }
48
+ end
49
+
50
+ report 'find slugged model using friendly id, then to_param', (TIMES * FACTOR).ceil do
51
+ ar { Post.find(POSTS.rand).to_param }
52
+ end
53
+
54
+ report 'find cached slugged model using friendly id, then to_param', (TIMES * FACTOR).ceil do
55
+ ar { District.find(DISTRICTS.rand).to_param }
56
+ end
57
+
58
+ summary 'Total'
59
+ end
data/extras/extras.rb ADDED
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env ruby -KU
2
+ require File.dirname(__FILE__) + '/../test/test_helper'
3
+ require File.dirname(__FILE__) + '/../test/active_record2/test_helper'
4
+ require 'ffaker'
5
+
6
+ TIMES = (ENV['N'] || 100).to_i
7
+ POSTS = []
8
+ DISTRICTS = []
9
+ USERS = []
10
+
11
+ User.delete_all
12
+ Post.delete_all
13
+ District.delete_all
14
+ (Slug or Slug).delete_all
15
+
16
+ 100.times do
17
+ name = Faker::Name.name
18
+ USERS << (User.create! :name => name).friendly_id
19
+ POSTS << (Post.create! :name => name).friendly_id
20
+ DISTRICTS << (District.create! :name => name).friendly_id
21
+ end
22
+
23
+ def get_id
24
+ rand(99) + 1
25
+ end
26
+
27
+ class Array
28
+ def rand
29
+ self[Kernel.rand(length)]
30
+ end
31
+ end
data/extras/prof.rb ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby -KU
2
+ require File.dirname(__FILE__) + '/extras'
3
+ require 'ruby-prof'
4
+
5
+ # RubyProf.measure_mode = RubyProf::MEMORY
6
+ GC.disable
7
+ RubyProf.start
8
+ 100.times do
9
+ Post.find(slug = POSTS.rand)
10
+ end
11
+ result = RubyProf.stop
12
+ GC.enable
13
+ printer = RubyProf::CallTreePrinter.new(result)
14
+ printer.print(File.new("prof.txt", "w"))
@@ -21,6 +21,6 @@ one:
21
21
 
22
22
  two:
23
23
  name: mystring
24
- sequence: 2
24
+ sequence: 1
25
25
  sluggable: two (Post)
26
26
  }
@@ -1,6 +1,6 @@
1
1
  run "rm public/index.html"
2
2
  inside 'vendor/plugins' do
3
- run "git clone ../../../../ friendly_id"
3
+ run "git clone ../../../ friendly_id"
4
4
  end
5
5
  gem "haml"
6
6
  gem "will_paginate"
@@ -7,7 +7,7 @@ class FriendlyIdGenerator < Rails::Generator::Base
7
7
  end
8
8
  unless options[:skip_tasks]
9
9
  m.directory "lib/tasks"
10
- m.file "/../../../lib/tasks/friendly_id.rake", "lib/tasks/friendly_id.rake"
10
+ m.file "/../../../lib/friendly_id/active_record2/tasks/friendly_id.rake", "lib/tasks/friendly_id.rake"
11
11
  end
12
12
  end
13
13
  end
@@ -5,11 +5,11 @@ class CreateSlugs < ActiveRecord::Migration
5
5
  t.integer :sluggable_id
6
6
  t.integer :sequence, :null => false, :default => 1
7
7
  t.string :sluggable_type, :limit => 40
8
- t.string :scope, :limit => 40
8
+ t.string :scope
9
9
  t.datetime :created_at
10
10
  end
11
- add_index :slugs, [:name, :sluggable_type, :scope, :sequence], :name => "index_slugs_on_n_s_s_and_s", :unique => true
12
11
  add_index :slugs, :sluggable_id
12
+ add_index :slugs, [:name, :sluggable_type, :sequence, :scope], :name => "index_slugs_on_n_s_s_and_s", :unique => true
13
13
  end
14
14
 
15
15
  def self.down
data/lib/friendly_id.rb CHANGED
@@ -1,75 +1,66 @@
1
- require "friendly_id/helpers"
2
- require "friendly_id/slug"
3
- require "friendly_id/sluggable_class_methods"
4
- require "friendly_id/sluggable_instance_methods"
5
- require "friendly_id/non_sluggable_class_methods"
6
- require "friendly_id/non_sluggable_instance_methods"
1
+ require File.join(File.dirname(__FILE__), "friendly_id", "slug_string")
2
+ require File.join(File.dirname(__FILE__), "friendly_id", "configuration")
3
+ require File.join(File.dirname(__FILE__), "friendly_id", "status")
4
+ require File.join(File.dirname(__FILE__), "friendly_id", "finders")
5
+ require File.join(File.dirname(__FILE__), "friendly_id", "slugged")
7
6
 
8
7
  # FriendlyId is a comprehensive Ruby library for slugging and permalinks with
9
8
  # ActiveRecord.
9
+ # @author Norman Clarke
10
+ # @author Emilio Tagua
11
+ # @author Adrian Mugnolo
10
12
  module FriendlyId
11
13
 
12
- # Default options for has_friendly_id.
13
- DEFAULT_OPTIONS = {
14
- :max_length => 255,
15
- :reserved => ["new", "index"],
16
- :reserved_message => 'can not be "%s"'
17
- }.freeze
14
+ # An error based on this class is raised when slug generation fails
15
+ class SlugGenerationError < StandardError ; end
18
16
 
19
- # The names of all valid configuration options.
20
- VALID_OPTIONS = (DEFAULT_OPTIONS.keys + [
21
- :cache_column,
22
- :scope,
23
- :strip_diacritics,
24
- :strip_non_ascii,
25
- :use_slug
26
- ]).freeze
17
+ # Raised when the slug text is blank.
18
+ class BlankError < SlugGenerationError ; end
27
19
 
28
- # This error is raised when it's not possible to generate a unique slug.
29
- class SlugGenerationError < StandardError ; end
20
+ # Raised when the slug text is reserved.
21
+ class ReservedError < SlugGenerationError ; end
22
+
23
+ module Base
24
+ # Set up a model to use a friendly_id. This method accepts a hash with
25
+ # {FriendlyId::Configuration several possible options}.
26
+ #
27
+ # @param [#to_sym] method The column or method that should be used as the
28
+ # basis of the friendly_id string.
29
+ #
30
+ # @param [Hash] options For valid configuration options, see
31
+ # {FriendlyId::Configuration}.
32
+ #
33
+ # @param [block] block An optional block through which to filter the
34
+ # friendly_id text; see {FriendlyId::Configuration#normalizer}. Note that
35
+ # passing a block parameter is now deprecated and will be removed
36
+ # from FriendlyId 3.0.
37
+ #
38
+ # @example
39
+ #
40
+ # class User < ActiveRecord::Base
41
+ # has_friendly_id :user_name
42
+ # end
43
+ #
44
+ # class Post < ActiveRecord::Base
45
+ # has_friendly_id :title, :use_slug => true, :approximate_ascii => true
46
+ # end
47
+ #
48
+ # @see FriendlyId::Configuration
49
+ def has_friendly_id(method, options = {}, &block)
50
+ raise NotImplementedError
51
+ end
30
52
 
31
- # Set up an ActiveRecord model to use a friendly_id.
32
- #
33
- # The column argument can be one of your model's columns, or a method
34
- # you use to generate the slug.
35
- #
36
- # Options:
37
- # * <tt>:use_slug</tt> - Defaults to nil. Use slugs when you want to use a non-unique text field for friendly ids.
38
- # * <tt>:max_length</tt> - Defaults to 255. The maximum allowed length for a slug.
39
- # * <tt>:cache_column</tt> - Defaults to nil. Use this column as a cache for generating to_param (experimental) Note that if you use this option, any calls to +attr_accessible+ must be made BEFORE any calls to has_friendly_id in your class.
40
- # * <tt>:strip_diacritics</tt> - Defaults to nil. If true, it will remove accents, umlauts, etc. from western characters.
41
- # * <tt>:strip_non_ascii</tt> - Defaults to nil. If true, it will remove all non-ASCII characters.
42
- # * <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 raise a FriendlyId::SlugGenerationError. Defaults to ["new", "index"].
43
- # * <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'.
44
- #
45
- # You can also optionally pass a block if you want to use your own custom
46
- # slug normalization routines rather than the default ones that come with
47
- # friendly_id:
48
- #
49
- # require "stringex"
50
- # class Post < ActiveRecord::Base
51
- # has_friendly_id :title, :use_slug => true do |text|
52
- # # Use stringex to generate the friendly_id rather than the baked-in methods
53
- # text.to_url
54
- # end
55
- # end
56
- def has_friendly_id(method, options = {}, &block)
57
- options.assert_valid_keys VALID_OPTIONS
58
- options = DEFAULT_OPTIONS.merge(options).merge(:method => method)
59
- write_inheritable_attribute :friendly_id_options, options
60
- class_inheritable_accessor :friendly_id_options
61
- class_inheritable_reader :slug_normalizer_block
62
- write_inheritable_attribute(:slug_normalizer_block, block) if block_given?
63
- if friendly_id_options[:use_slug]
64
- extend SluggableClassMethods
65
- include SluggableInstanceMethods
66
- else
67
- extend NonSluggableClassMethods
68
- include NonSluggableInstanceMethods
53
+ # Does the model class use the FriendlyId plugin?
54
+ def uses_friendly_id?
55
+ respond_to? :friendly_id_config
69
56
  end
70
57
  end
71
- end
72
58
 
73
- class ActiveRecord::Base #:nodoc:#
74
- extend FriendlyId #:nodoc:#
75
59
  end
60
+
61
+ class String
62
+ def parse_friendly_id(separator = nil)
63
+ name, sequence = split(separator || FriendlyId::Configuration::DEFAULTS[:sequence_separator])
64
+ return name, sequence ||= "1"
65
+ end
66
+ end