friendly_id4 4.0.0.beta6 → 4.0.0.pre

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.
data/README.md CHANGED
@@ -1,95 +1,74 @@
1
- # FriendlyId
1
+ # FriendlyId 4
2
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 human-friendly
5
- strings as if they were numeric ids for Active Record models.
3
+ This is an in-progress rethink of the FriendlyId plugin. It will probably be
4
+ released some time in August or September 2011, once I've had the chance to
5
+ actually use it in a real website for a while.
6
6
 
7
- Using FriendlyId, it's easy to make your application use URL's like:
7
+ Please don't use this yet for anything real but feel free to try it and give
8
+ your feedback via the issues tracker.
8
9
 
9
- http://example.com/states/washington
10
+ ## Back to basics
10
11
 
11
- instead of:
12
+ This isn't the "big rewrite," it's the "small rewrite."
12
13
 
13
- http://example.com/states/4323454
14
+ Adding new features with each release is not sustainable. This release *removes*
15
+ features, but makes it possible to add them back as addons. We can also remove
16
+ some complexity by relying on the better default functionality provided by newer
17
+ versions of Active Support and Active Record.
14
18
 
19
+ Let's see how small we can make this!
15
20
 
16
- ## FriendlyId Features
21
+ Here's what's changed:
17
22
 
18
- FriendlyId offers many advanced features, including: slug history and
19
- versioning, scoped slugs, reserved words, and custom slug generators.
23
+ ## Active Record 3+ only
20
24
 
21
- FriendlyId is compatible with Active Record **3.0** and **3.1**.
25
+ For 2.3 support, you can use FriendlyId 3, which will continue to be maintained
26
+ until people don't want it any more.
22
27
 
23
- ## Rails Quickstart
28
+ ## Remove Babosa
24
29
 
25
- gem install friendly_id
30
+ Babosa is FriendlyId 3's slugging library.
26
31
 
27
- rails new my_app
32
+ FriendlyId 4 doesn't use it by default any more because the most important
33
+ pieces of it were already accepted into Active Support 3. You can still just
34
+ override `#normalize_friendly_id` in your model if you want to use Babosa.
28
35
 
29
- cd my_app
36
+ ## In-table slugs
30
37
 
31
- # Add to Gemfile - this will change once version 4 is no longer
32
- # in beta, but for now do this:
33
- gem "friendly_id4", "4.0.0.beta4", :require => "friendly_id"
38
+ FriendlyId no longer creates a separate slugs table - it just stores the
39
+ generated slug value in the model table, which is simpler, faster and what most
40
+ people seem to want. Keeping slugs in a separate table is an optional add-on for
41
+ FriendlyId 4 (not implemented yet).
34
42
 
43
+ ## No more finder status
35
44
 
36
- rails generate scaffold user name:string slug:string
45
+ FriendlyId 3 offered finder statuses to help you determine when an outdated
46
+ or non-friendly id was used to find the record, so that you could decide whether
47
+ to permanently redirect to the canonical URL. However, there's a simpler way to
48
+ do that, so this feature has been removed:
37
49
 
38
- # edit db/migrate/*_create_users.rb
39
- add_index :users, :slug, :unique => true
40
-
41
- rake db:migrate
42
-
43
- # edit app/models/user.rb
44
- class User < ActiveRecord::Base
45
- extend FriendlyId
46
- friendly_id :name, :use => :slugged
50
+ if request.path != person_path(@person)
51
+ return redirect_to @person, :status => :moved_permanently
47
52
  end
48
53
 
49
- User.create! :name => "Joe Schmoe"
50
-
51
- rails server
52
-
53
- GET http://localhost:3000/users/joe-schmoe
54
-
55
- ## Docs
56
-
57
- The current docs can be found
58
- [here](http://rdoc.info/github/norman/friendly_id/a4128af31d85ee29ad8f/frames).
59
-
60
- ## Future Compatibility
61
-
62
- FriendlyId will always remain compatible with the current release of Rails, and
63
- at least one stable release behind. That means that support for 3.0.x will not be
64
- dropped until a stable release of 3.2 is out, or possibly longer.
65
-
66
-
67
- ## Benchmarks
68
-
69
- The latest benchmarks for FriendlyId are maintained
70
- [here](https://gist.github.com/1129745).
71
-
54
+ ## No more multiple finds
72
55
 
73
- ## Bugs
56
+ Person.find "joe-schmoe" # Supported
57
+ Person.find ["joe-schmoe", "john-doe"] # No longer supported
74
58
 
75
- Please report them on the [Github issue
76
- tracker](http://github.com/norman/friendly_id/issues) for this project.
59
+ If you want find by more than one friendly id, build your own query:
77
60
 
78
- If you have a bug to report, please include the following information:
61
+ Person.where(:slug => ["joe-schmoe", "john-doe"])
79
62
 
80
- * **Version information for FriendlyId, Rails and Ruby.**
81
- * Stack trace and error message.
82
- * Any snippets of relevant model, view or controller code that shows how you
83
- are using FriendlyId.
63
+ This lets us do *far* less monkeypatching in Active Record.
84
64
 
85
- If you are able to, it helps even more if you can fork FriendlyId on Github,
86
- and add a test that reproduces the error you are experiencing.
65
+ ## No more reserved words
87
66
 
88
- ## Credits
67
+ Rather than use a custom reserved words validator, use the validations provided
68
+ by Active Record. FriendlyId still reserves "new" and "edit" by default to avoid
69
+ routing problems.
89
70
 
90
- FriendlyId was originally created by Norman Clarke and Adrian Mugnolo, with
91
- significant help early in its life by Emilio Tagua. I'm deeply gratful for the
92
- generous contributions over the years from [many
93
- volunteers](https://github.com/norman/friendly_id/contributors).
71
+ validates_exclusion_of :name, :in => ["bad", "word"]
94
72
 
95
- Copyright (c) 2008-2011 Norman Clarke, released under the MIT license.
73
+ You can configure the default words reserved by FriendlyId in
74
+ `FriendlyId::Configuration::DEFAULTS[:reserved_words]`.
data/Rakefile CHANGED
@@ -1,119 +1,24 @@
1
- require "rubygems"
1
+ require "rake"
2
2
  require "rake/testtask"
3
-
4
- def rubies(&block)
5
- ["ruby-1.9.2-p180", "ree-1.8.7-2011.03", "jruby-1.6.2", "rbx-2.0.0pre"].each do |ruby|
6
- old = ENV["RB"]
7
- ENV["RB"] = ruby
8
- yield
9
- ENV["RB"] = old
10
- end
11
- end
12
-
13
- def versions(&block)
14
- ["3.1.0.rc5", "3.0.9"].each do |version|
15
- old = ENV["AR"]
16
- ENV["AR"] = version
17
- yield
18
- ENV["AR"] = old
19
- end
20
- end
21
-
22
- def adapters(&block)
23
- ["mysql", "mysql2", "postgres", "sqlite3"].each do |adapter|
24
- old = ENV["DB"]
25
- ENV["DB"] = adapter
26
- ENV["DB_VERSION"] = "~> 0.3.6" if adapter == "mysql2" && ENV["AR"] && ENV["AR"][0..2] >= "3.1"
27
- yield
28
- ENV["DB"] = old
29
- end
30
- end
3
+ require "rake/gempackagetask"
4
+ require "rake/clean"
31
5
 
32
6
  task :default => :test
33
7
 
34
- Rake::TestTask.new do |t|
35
- t.test_files = FileList['test/*_test.rb']
36
- t.verbose = true
37
- end
38
-
39
- task :clean do
40
- %x{rm -rf *.gem doc pkg coverage}
41
- %x{rm -f `find . -name '*.rbc'`}
42
- end
43
-
44
- task :gem do
45
- %x{gem build friendly_id.gemspec}
46
- end
47
-
48
- task :yard do
49
- puts %x{bundle exec yard}
50
- end
51
-
52
- task :bench do
53
- require File.expand_path("../bench", __FILE__)
54
- end
55
-
56
- desc "Bundle for all supported Ruby/AR versions"
57
- task :bundle do
58
- rubies do
59
- versions do
60
- command = "#{ENV["RB"]} -S bundle"
61
- puts "#{command} (with #{ENV['AR']})"
62
- `#{command}`
63
- end
64
- end
65
- end
66
-
67
- namespace :test do
68
-
69
- desc "Test with all configured adapters"
70
- task :adapters do
71
- adapters {|a| puts %x{rake test}}
72
- end
73
-
74
- desc "Test with all configured Rubies"
75
- task :rubies do
76
- rubies {|r| puts %x{rake-#{ENV["RB"]} test}}
77
- end
78
-
79
- desc "Test with all configured versions"
80
- task :versions do
81
- versions {|v| puts %x{rake test}}
82
- end
8
+ CLEAN << "pkg" << "doc" << "coverage" << ".yardoc"
83
9
 
84
- desc "Test all rubies, versions and adapters"
85
- task :prerelease do
86
- rubies do
87
- versions do
88
- adapters do
89
- puts %x{rake test}
90
- end
91
- end
92
- end
93
- end
10
+ Rake::TestTask.new(:test) { |t| t.pattern = "test/**/*test.rb" }
94
11
 
95
- desc "Run each test class in a separate process"
96
- task :isolated do
97
- dir = File.expand_path("../test", __FILE__)
98
- Dir["#{dir}/*_test.rb"].each do |test|
99
- puts "Running #{test}:"
100
- puts %x{ruby #{test}}
101
- end
12
+ begin
13
+ require 'reek/rake/task'
14
+ Reek::Rake::Task.new do |t|
15
+ t.fail_on_error = false
102
16
  end
17
+ rescue LoadError
103
18
  end
104
19
 
105
- namespace :db do
106
- desc "Set up the database schema"
107
- task :up do
108
- require File.expand_path("../test/helper", __FILE__)
109
- FriendlyId::Test::Schema.up
110
- end
111
-
112
- desc "Destroy the database schema"
113
- task :down do
114
- require File.expand_path("../test/helper", __FILE__)
115
- FriendlyId::Test::Schema.down
116
- end
20
+ gemspec = File.expand_path("../friendly_id.gemspec", __FILE__)
21
+ if File.exists? gemspec
22
+ Rake::GemPackageTask.new(eval(File.read("friendly_id.gemspec"))) { |pkg| }
117
23
  end
118
24
 
119
- task :doc => :yard
@@ -1,141 +1,121 @@
1
- # encoding: utf-8
2
- require "thread"
3
- require "friendly_id/base"
4
- require "friendly_id/model"
5
- require "friendly_id/object_utils"
6
- require "friendly_id/configuration"
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
1
+ # FriendlyId is a comprehensive Ruby library for ActiveRecord permalinks and
2
+ # slugs.
3
+ # @author Norman Clarke
4
+ module FriendlyId
18
5
 
19
- # with FriendlyId
20
- http://example.com/states/washington
6
+ autoload :Slugged, "friendly_id/slugged"
7
+ autoload :Scoped, "friendly_id/scoped"
8
+
9
+ # Class methods that will be added to ActiveRecord::Base.
10
+ module Base
11
+ extend self
12
+
13
+ def has_friendly_id(*args)
14
+ options = args.extract_options!
15
+ base = args.shift
16
+ friendly_id_config.set options.merge(:base => base)
17
+ include Model
18
+ # @NOTE: AR-specific code here
19
+ validates_exclusion_of base, :in => Configuration::DEFAULTS[:reserved_words]
20
+ before_save do |record|
21
+ record.instance_eval {@_current_friendly_id = friendly_id}
22
+ end
23
+ end
21
24
 
22
- It requires few changes to your application code and offers flexibility,
23
- performance and a well-documented codebase.
25
+ def friendly_id_config
26
+ @friendly_id_config ||= Configuration.new(self)
27
+ end
24
28
 
25
- === Concepts
29
+ def uses_friendly_id?
30
+ !! @friendly_id_config
31
+ end
32
+ end
26
33
 
27
- Although FriendlyId helps with URLs, it does all of its work inside your models,
28
- not your routes.
34
+ # Instance methods that will be added to all classes using FriendlyId.
35
+ module Model
29
36
 
30
- === Simple Models
37
+ # Convenience method for accessing the class method of the same name.
38
+ def friendly_id_config
39
+ self.class.friendly_id_config
40
+ end
31
41
 
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:
42
+ # Get the instance's friendly_id.
43
+ def friendly_id
44
+ send friendly_id_config.query_field
45
+ end
35
46
 
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
+ # Either the friendly_id, or the numeric id cast to a string.
48
+ def to_param
49
+ (friendly_id or id).to_s
40
50
  end
51
+ end
41
52
 
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
53
+ # The configuration paramters passed to +has_friendly_id+ will be stored
54
+ # in this object.
55
+ class Configuration
56
+ attr_accessor :base
57
+ attr_reader :klass
45
58
 
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:
59
+ DEFAULTS = {
60
+ :config_error_message => 'FriendlyId has no such config option "%s"',
61
+ :reserved_words => ["new", "edit"]
62
+ }
49
63
 
50
- class City < ActiveRecord::Base
51
- extend FriendlyId
52
- friendly_id :name
64
+ def initialize(klass, values = nil)
65
+ @klass = klass
66
+ set values
53
67
  end
54
68
 
55
- @city.find "Viña del Mar"
56
- redirect_to @city # the URL will be /cities/Viña%20del%20Mar
57
-
58
- For this reason, it is often more convenient to use "slugs" rather than a single
59
- column.
69
+ def method_missing(symbol, *args, &block)
70
+ option = symbol.to_s.gsub(/=\z/, '')
71
+ raise ArgumentError, DEFAULTS[:config_error_message] % option
72
+ end
60
73
 
61
- === Slugged Models
74
+ def set(values)
75
+ values and values.each {|name, value| self.send "#{name}=", value}
76
+ end
62
77
 
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.
78
+ def query_field
79
+ base
80
+ end
81
+ end
67
82
 
68
- class Post < ActiveRecord::Base
69
- extend FriendlyId
70
- friendly_id :title, :use => :slugged
83
+ # Utility methods that are in Object because it's impossible to predict what
84
+ # kinds of objects get passed into FinderMethods#find_one and
85
+ # Model#normalize_friendly_id.
86
+ module ObjectUtils
87
+
88
+ # True is the id is definitely friendly, false if definitely unfriendly,
89
+ # else nil.
90
+ def friendly_id?
91
+ if kind_of?(Integer) or kind_of?(Symbol) or self.class.respond_to? :friendly_id_config
92
+ false
93
+ elsif to_i.to_s != to_s
94
+ true
95
+ end
71
96
  end
72
97
 
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
98
+ # True if the id is definitely unfriendly, false if definitely friendly,
99
+ # else nil.
100
+ def unfriendly_id?
101
+ val = friendly_id? ; !val unless val.nil?
102
+ end
103
+ end
76
104
 
77
- In general, use slugs by default unless you know for sure you don't need them.
105
+ # These methods will override the finder methods in ActiveRecord::Relation.
106
+ module FinderMethods
78
107
 
79
- @author Norman Clarke
80
- =end
81
- module FriendlyId
108
+ protected
82
109
 
83
- # The current version.
84
- VERSION = "4.0.0.beta6"
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(base)
116
- base.instance_eval do
117
- extend FriendlyId::Base
118
- @friendly_id_config = Class.new(FriendlyId::Configuration).new(base)
119
- if defaults = FriendlyId.defaults
120
- defaults.yield @friendly_id_config
121
- end
110
+ # @NOTE AR-specific code here
111
+ def find_one(id)
112
+ return super if !@klass.uses_friendly_id? or id.unfriendly_id?
113
+ where(@klass.friendly_id_config.query_field => id).first or super
122
114
  end
123
- ActiveRecord::Relation.send :include, FriendlyId::FinderMethods
124
- end
125
115
 
126
- # Set global defaults for all models using FriendlyId.
127
- #
128
- # The default defaults are to use the +:reserved+ module and nothing else.
129
- #
130
- # @example
131
- # FriendlyId.defaults do |config|
132
- # config.base = :name
133
- # config.use :slugged
134
- # end
135
- def self.defaults(&block)
136
- @mutex.synchronize do
137
- @defaults = block if block_given?
138
- @defaults ||= lambda {|config| config.use :reserved}
139
- end
140
116
  end
141
- end
117
+ end
118
+
119
+ ActiveRecord::Base.extend FriendlyId::Base
120
+ ActiveRecord::Relation.send :include, FriendlyId::FinderMethods
121
+ Object.send :include, FriendlyId::ObjectUtils