friendly_id 2.2.7 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
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