friendly_id4 4.0.0.beta4 → 4.0.0.beta5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,4 @@
1
+ --files=*.md
2
+ --protected
3
+ --list-undoc
4
+ --exclude lib/friendly_id/migration
data/README.md CHANGED
@@ -1,17 +1,8 @@
1
- <hr>
2
- **NOTE** This is FriendlyId4 - a rewrite of FriendlyId. For more info about this
3
- rewrite, and the changes it brings, read [this
4
- document](https://github.com/norman/friendly_id_4/blob/master/ABOUT.md).
5
-
6
- For the current stable FriendlyId, please see:
7
-
8
- [https://github.com/norman/friendly_id](https://github.com/norman/friendly_id_4)
9
- <hr>
10
1
  # FriendlyId
11
2
 
12
3
  FriendlyId is the "Swiss Army bulldozer" of slugging and permalink plugins for
13
- Ruby on Rails. It allows you to create pretty URL's and work with
14
- human-friendly strings as if they were numeric ids for Active Record models.
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.
15
6
 
16
7
  Using FriendlyId, it's easy to make your application use URL's like:
17
8
 
@@ -24,20 +15,10 @@ instead of:
24
15
  ## FriendlyId Features
25
16
 
26
17
  FriendlyId offers many advanced features, including: slug history and
27
- versioning, scoped slugs, reserved words, custom slug generators, and
28
- excellent Unicode support. For complete information on using FriendlyId,
29
- please see the [FriendlyId Guide](http://norman.github.com/friendly_id/file.Guide.html).
18
+ versioning, scoped slugs, reserved words, and custom slug generators.
30
19
 
31
20
  FriendlyId is compatible with Active Record **3.0** and **3.1**.
32
21
 
33
- ## Docs, Info and Support
34
-
35
- * [FriendlyId Guide](http://norman.github.com/friendly_id/file.Guide.html)
36
- * [API Docs](http://norman.github.com/friendly_id)
37
- * [Google Group](http://groups.google.com/group/friendly_id)
38
- * [Source Code](http://github.com/norman/friendly_id/)
39
- * [Issue Tracker](http://github.com/norman/friendly_id/issues)
40
-
41
22
  ## Rails Quickstart
42
23
 
43
24
  gem install friendly_id
@@ -46,8 +27,10 @@ FriendlyId is compatible with Active Record **3.0** and **3.1**.
46
27
 
47
28
  cd my_app
48
29
 
49
- # add to Gemfile
50
- gem "friendly_id", "~> 4.0.0"
30
+ # Add to Gemfile - this will change once version 4 is no longer
31
+ # in beta, but for now do this:
32
+ gem "friendly_id4", "4.0.0.beta4", :require => "friendly_id"
33
+
51
34
 
52
35
  rails generate scaffold user name:string slug:string
53
36
 
@@ -68,6 +51,20 @@ FriendlyId is compatible with Active Record **3.0** and **3.1**.
68
51
 
69
52
  GET http://localhost:3000/users/joe-schmoe
70
53
 
54
+
55
+ ### Future Compatibility
56
+
57
+ FriendlyId will always remain compatible with the current release of Rails, and
58
+ at least one stable release behind. That means that support for 3.0.x will not be
59
+ dropped until a stable release of 3.2 is out, or possibly longer.
60
+
61
+
62
+ ## Benchmarks
63
+
64
+ The latest benchmarks for FriendlyId are maintained
65
+ [here](https://gist.github.com/1129745).
66
+
67
+
71
68
  ## Bugs
72
69
 
73
70
  Please report them on the [Github issue
data/Rakefile CHANGED
@@ -45,7 +45,7 @@ task :gem do
45
45
  end
46
46
 
47
47
  task :yard do
48
- puts %x{bundle exec yard doc --files=*.md}
48
+ puts %x{bundle exec yard}
49
49
  end
50
50
 
51
51
  task :bench do
@@ -1,11 +1,11 @@
1
1
  # encoding: utf-8
2
2
  $:.push File.expand_path("../lib", __FILE__)
3
3
 
4
- require "friendly_id/version"
4
+ require "friendly_id"
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "friendly_id4"
8
- s.version = FriendlyId::Version::STRING
8
+ s.version = FriendlyId::VERSION
9
9
  s.authors = ["Norman Clarke"]
10
10
  s.email = ["norman@njclarke.com"]
11
11
  s.homepage = "http://norman.github.com/friendly_id"
@@ -1,17 +1,94 @@
1
+ # encoding: utf-8
2
+ require "thread"
1
3
  require "friendly_id/base"
2
4
  require "friendly_id/model"
3
5
  require "friendly_id/object_utils"
4
6
  require "friendly_id/configuration"
5
7
  require "friendly_id/finder_methods"
6
8
 
7
- # FriendlyId is a comprehensive Ruby library for ActiveRecord permalinks and
8
- # slugs.
9
- #
10
- # @author Norman Clarke
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
26
+
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
40
+ end
41
+
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
53
+ end
54
+
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.
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.
67
+
68
+ class Post < ActiveRecord::Base
69
+ extend FriendlyId
70
+ friendly_id :title, :use => :slugged
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
11
81
  module FriendlyId
12
- autoload :Slugged, "friendly_id/slugged"
13
- autoload :Scoped, "friendly_id/scoped"
82
+
83
+ # The current version.
84
+ VERSION = "4.0.0.beta5"
85
+
86
+ @mutex = Mutex.new
87
+
14
88
  autoload :History, "friendly_id/history"
89
+ autoload :Reserved, "friendly_id/reserved"
90
+ autoload :Scoped, "friendly_id/scoped"
91
+ autoload :Slugged, "friendly_id/slugged"
15
92
 
16
93
  # FriendlyId takes advantage of `extended` to do basic model setup, primarily
17
94
  # extending {FriendlyId::Base} to add {FriendlyId::Base#friendly_id
@@ -20,7 +97,7 @@ module FriendlyId
20
97
  # Previous versions of FriendlyId simply patched ActiveRecord::Base, but this
21
98
  # version tries to be less invasive.
22
99
  #
23
- # In addition to adding {FriendlyId::Base.friendly_id friendly_id}, the class
100
+ # In addition to adding {FriendlyId::Base#friendly_id friendly_id}, the class
24
101
  # instance variable +@friendly_id_config+ is added. This variable is an
25
102
  # instance of an anonymous subclass of {FriendlyId::Configuration}. This
26
103
  # allows subsequently loaded modules like {FriendlyId::Slugged} and
@@ -39,7 +116,24 @@ module FriendlyId
39
116
  base.instance_eval do
40
117
  extend FriendlyId::Base
41
118
  @friendly_id_config = Class.new(FriendlyId::Configuration).new(base)
119
+ if defaults = FriendlyId.defaults
120
+ defaults.yield @friendly_id_config
121
+ end
42
122
  end
43
123
  ActiveRecord::Relation.send :include, FriendlyId::FinderMethods
44
124
  end
45
- end
125
+
126
+ # Set global defaults for all models using FriendlyId.
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
+ end
137
+ @defaults
138
+ end
139
+ end
@@ -1,31 +1,99 @@
1
1
  module FriendlyId
2
- # Class methods that will be added to ActiveRecord::Base.
2
+ # Class methods that will be added to model classes that extend {FriendlyId}.
3
3
  module Base
4
4
 
5
- # Configure FriendlyId for a model. Use this method to configure FriendlyId
6
- # for your model.
5
+ # Configure FriendlyId's behavior in a model.
7
6
  #
8
7
  # class Post < ActiveRecord::Base
9
8
  # extend FriendlyId
10
9
  # friendly_id :title, :use => :slugged
11
10
  # end
12
11
  #
13
- # @option options [Symbol] :use The name of an addon to use. By default, FriendlyId
14
- # provides {FriendlyId::Slugged :slugged}, {FriendlyId::History :history}
15
- # and {FriendlyId::Scoped :scoped}.
12
+ # When given the optional block, this method will yield the class's instance
13
+ # of {FriendlyId::Configuration} to the block before evaluating other
14
+ # arguments, so configuration values set in the block may be overwritten by
15
+ # the arguments. This order was chosen to allow passing the same proc to
16
+ # multiple models, while being able to override the values it sets. Here
17
+ # is a contrived example:
18
+ #
19
+ # $friendly_id_config_proc = Proc.new do |config|
20
+ # config.base = :name
21
+ # config.use :slugged
22
+ # end
23
+ #
24
+ # class Foo < ActiveRecord::Base
25
+ # extend FriendlyId
26
+ # friendly_id &$friendly_id_config_proc
27
+ # end
28
+ #
29
+ # class Bar < ActiveRecord::Base
30
+ # extend FriendlyId
31
+ # friendly_id :title, &$friendly_id_config_proc
32
+ # end
33
+ #
34
+ # However, it's usually better to use {FriendlyId.defaults} for this:
35
+ #
36
+ # FriendlyId.defaults do |config|
37
+ # config.base = :name
38
+ # config.use :slugged
39
+ # end
40
+ #
41
+ # class Foo < ActiveRecord::Base
42
+ # extend FriendlyId
43
+ # end
44
+ #
45
+ # class Bar < ActiveRecord::Base
46
+ # extend FriendlyId
47
+ # friendly_id :title
48
+ # end
49
+ #
50
+ # In general you should use the block syntax either because of your personal
51
+ # aesthetic preference, or because you need to share some functionality
52
+ # between multiple models that can't be well encapsulated by
53
+ # {FriendlyId.defaults}.
54
+ #
55
+ # @option options [Symbol] :use The name of an addon to use. By default,
56
+ # FriendlyId provides {FriendlyId::Slugged :slugged},
57
+ # {FriendlyId::History :history}, {FriendlyId::Reserved :reserved}, and
58
+ # {FriendlyId::Scoped :scoped}.
59
+ #
60
+ # @option options [Array] :reserved_words Available when using +:reserved+,
61
+ # which is loaded by default. Sets an array of words banned for use as
62
+ # the basis of a friendly_id. By default this includes "edit" and "new".
63
+ #
64
+ # @option options [Symbol] :scope Available when using +:scoped+.
65
+ # Sets the relation or column used to scope generated friendly ids. This
66
+ # option has no default value.
67
+ #
68
+ # @option options [Symbol] :sequence_separator Available when using +:slugged+.
69
+ # Configures the sequence of characters used to separate a slug from a
70
+ # sequence. Defaults to +--+.
71
+ #
16
72
  # @option options [Symbol] :slug_column Available when using +:slugged+.
17
73
  # Configures the name of the column where FriendlyId will store the slug.
18
74
  # Defaults to +:slug+.
75
+ #
76
+ # @option options [Symbol] :slug_sequencer_class Available when using +:slugged+.
77
+ # Sets the class used to generate unique slugs. You should not specify this
78
+ # unless you're doing some extensive hacking on FriendlyId. Defaults to
79
+ # {FriendlyId::SlugSequencer}.
80
+ #
81
+ # @yield Provides access to the model class's friendly_id_config, which
82
+ # allows an alternate configuration syntax, and conditional configuration
83
+ # logic.
84
+ #
85
+ # @yieldparam config The model class's {FriendlyId::Configuration friendly_id_config}.
19
86
  def friendly_id(base = nil, options = {}, &block)
20
- @friendly_id_config.use options.delete :use
21
- @friendly_id_config.send :set, options.merge(:base => base)
22
87
  yield @friendly_id_config if block_given?
88
+ @friendly_id_config.use options.delete :use
89
+ @friendly_id_config.send :set, base ? options.merge(:base => base) : options
23
90
  before_save do |record|
24
91
  record.instance_eval {@current_friendly_id = friendly_id}
25
92
  end
26
93
  include Model
27
94
  end
28
95
 
96
+ # Returns the model class's {FriendlyId::Configuration friendly_id_config}.
29
97
  def friendly_id_config
30
98
  @friendly_id_config
31
99
  end
@@ -25,39 +25,21 @@ module FriendlyId
25
25
  # extend FriendlyId
26
26
  # friendly_id :name
27
27
  # end
28
- attr_reader :base
28
+ attr_accessor :base
29
29
 
30
- # The model class that this configuration belongs to.
31
- # @return ActiveRecord::Base
32
- attr_reader :klass
33
-
34
- # The configuration parameters for the {#klass model class} using FriendlyId.
35
- # @return Hash
30
+ # The default configuration options.
36
31
  attr_reader :defaults
37
32
 
38
- @@defaults = {
39
- :reserved_words => ["new", "edit"]
40
- }
41
-
42
- # The default configuration parameters for models using FriendlyId.
43
- # @return Hash
44
- def self.defaults
45
- @@defaults
46
- end
33
+ # The model class that this configuration belongs to.
34
+ # @return ActiveRecord::Base
35
+ attr_reader :model_class
47
36
 
48
- def initialize(klass, values = nil)
49
- @klass = klass
50
- @defaults = self.class.defaults.dup
37
+ def initialize(model_class, values = nil)
38
+ @model_class = model_class
39
+ @defaults = {}
51
40
  set values
52
41
  end
53
42
 
54
- def base=(base)
55
- @base = base
56
- if @base.respond_to?(:to_s)
57
- @klass.validates_exclusion_of @base, :in => defaults[:reserved_words]
58
- end
59
- end
60
-
61
43
  # Lets you specify the modules to use with FriendlyId.
62
44
  #
63
45
  # This method is invoked by {FriendlyId::Base#friendly_id friendly_id} when
@@ -74,7 +56,7 @@ module FriendlyId
74
56
  # default FriendlyId provides +:slugged+, +:history+ and +:scoped+.
75
57
  def use(*modules)
76
58
  modules.to_a.flatten.compact.map do |name|
77
- klass.send :include, FriendlyId.const_get(name.to_s.classify)
59
+ model_class.send :include, FriendlyId.const_get(name.to_s.classify)
78
60
  end
79
61
  end
80
62
 
@@ -4,6 +4,14 @@ module FriendlyId
4
4
 
5
5
  protected
6
6
 
7
+ # FriendlyId overrides this method to make it possible to use friendly id's
8
+ # identically to numeric ids in finders.
9
+ #
10
+ # @example
11
+ # person = Person.find(123)
12
+ # person = Person.find("joe")
13
+ #
14
+ # @see FriendlyId::ObjectUtils
7
15
  def find_one(id)
8
16
  return super if !@klass.respond_to?(:friendly_id) || id.unfriendly_id?
9
17
  where(@klass.friendly_id_config.query_field => id).first or super
@@ -1,8 +1,40 @@
1
1
  require "friendly_id/slug"
2
2
 
3
3
  module FriendlyId
4
+
5
+ =begin
6
+ FriendlyId can maintain a history of your record's older slugs, so if your
7
+ record's friendly_id changes, your URL's won't break.
8
+
9
+ class Post < ActiveRecord::Base
10
+ extend FriendlyId
11
+ friendly_id :title, :use => :history
12
+ end
13
+
14
+ class PostsController < ApplicationController
15
+
16
+ before_filter :find_post
17
+
18
+ ...
19
+ def find_post
20
+ return unless params[:id]
21
+ @post = begin
22
+ Post.find params[:id]
23
+ rescue ActiveRecord::RecordNotFound
24
+ Post.find_by_friendly_id params[:id]
25
+ end
26
+ # If an old id or a numeric id was used to find the record, then
27
+ # the request path will not match the post_path, and we should do
28
+ # a 301 redirect that uses the current friendly_id
29
+ if request.path != post_path(@post)
30
+ return redirect_to @post, :status => :moved_permanently
31
+ end
32
+ end
33
+ end
34
+ =end
4
35
  module History
5
36
 
37
+ # Configures the model instance to use the History add-on.
6
38
  def self.included(klass)
7
39
  klass.instance_eval do
8
40
  raise "FriendlyId::History is incompatibe with FriendlyId::Scoped" if self < Scoped
@@ -21,7 +53,10 @@ module FriendlyId
21
53
  end
22
54
  end
23
55
 
56
+ # Adds a finder that explictly uses slugs from the slug table.
24
57
  module Finder
58
+
59
+ # Search for a record in the slugs table using the specified slug.
25
60
  def find_by_friendly_id(*args)
26
61
  with_friendly_id(args.shift).first(*args)
27
62
  end