friendly_id 5.0.0.alpha.1 → 5.0.0.beta1

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
@@ -4,14 +4,14 @@
4
4
 
5
5
  **Rails 4**:
6
6
  Master branch of this repository contains FriendlyId 5 which is compatible with Rails 4.
7
- This version is under active development and not yet fully stable.
7
+ This version is in beta and will be released soon.
8
8
 
9
9
  **Rails 3**:
10
10
  If you wish to use this gem with Rails 3.1 or 3.2 you need to use FriendlyId version 4, which is the current stable release.
11
11
  Please see [4.0-stable
12
- branch](https://github.com/FriendlyId/friendly_id/tree/4.0-stable).
12
+ branch](https://github.com/norman/friendly_id/tree/4.0-stable).
13
13
 
14
- [![Build Status](https://travis-ci.org/FriendlyId/friendly_id.png)](https://travis-ci.org/FriendlyId/friendly_id)
14
+ [![Build Status](https://travis-ci.org/norman/friendly_id.png)](https://travis-ci.org/norman/friendly_id)
15
15
 
16
16
  FriendlyId is the "Swiss Army bulldozer" of slugging and permalink plugins for
17
17
  Ruby on Rails. It allows you to create pretty URLs and work with human-friendly
@@ -96,10 +96,10 @@ restaurant.friendly_id # the-plaza-diner
96
96
  ## Docs
97
97
 
98
98
  The current docs can always be found
99
- [here](http://rubydoc.info/github/FriendlyId/friendly_id/master/frames).
99
+ [here](http://rubydoc.info/github/norman/friendly_id/master/frames).
100
100
 
101
101
  The best place to start is with the
102
- [Guide](http://rubydoc.info/github/FriendlyId/friendly_id/master/file/Guide.rdoc),
102
+ [Guide](http://rubydoc.info/github/norman/friendly_id/master/file/Guide.md),
103
103
  which compiles the top-level RDocs into one outlined document.
104
104
 
105
105
  You might also want to watch Ryan Bates's [Railscast on FriendlyId](http://railscasts.com/episodes/314-pretty-urls-with-friendlyid),
@@ -113,9 +113,10 @@ cd my_app
113
113
  ```
114
114
  ```ruby
115
115
  # Gemfile
116
- gem 'friendly_id', github: 'FriendlyId/friendly_id', branch: 'master' # Note: You MUST use 5.0.0 or greater for Rails 4.0+
116
+ gem 'friendly_id', '5.0.0.beta1' # Note: You MUST use 5.0.0 or greater for Rails 4.0+
117
117
  ```
118
118
  ```shell
119
+ rails generate friendly_id
119
120
  rails generate scaffold user name:string slug:string
120
121
  ```
121
122
  ```ruby
@@ -158,7 +159,7 @@ The latest benchmarks for FriendlyId are maintained
158
159
  ## Bugs
159
160
 
160
161
  Please report them on the [Github issue
161
- tracker](http://github.com/FriendlyId/friendly_id/issues) for this project.
162
+ tracker](http://github.com/norman/friendly_id/issues) for this project.
162
163
 
163
164
  If you have a bug to report, please include the following information:
164
165
 
@@ -180,7 +181,7 @@ significant help early in its life by Emilio Tagua. It is now maintained by
180
181
  Norman Clarke and Philip Arndt.
181
182
 
182
183
  We're deeply grateful for the generous contributions over the years from [many
183
- volunteers](https://github.com/FriendlyId/friendly_id/contributors).
184
+ volunteers](https://github.com/norman/friendly_id/contributors).
184
185
 
185
186
  ## License
186
187
 
data/Rakefile CHANGED
@@ -36,7 +36,7 @@ task :bench => :load_path do
36
36
  require File.expand_path("../bench", __FILE__)
37
37
  end
38
38
 
39
- desc "Generate Guide.rdoc"
39
+ desc "Generate Guide.md"
40
40
  task :guide do
41
41
  def read_comments(path)
42
42
  path = File.expand_path("../#{path}", __FILE__)
@@ -54,8 +54,7 @@ task :guide do
54
54
  buffer << read_comments("lib/friendly_id/simple_i18n.rb")
55
55
  buffer << read_comments("lib/friendly_id/reserved.rb")
56
56
 
57
- File.open("Guide.rdoc", "w") do |file|
58
- file.write("#encoding: utf-8\n")
57
+ File.open("Guide.md", "w") do |file|
59
58
  file.write(buffer.join("\n"))
60
59
  end
61
60
  end
data/bench.rb CHANGED
@@ -42,11 +42,13 @@ Benchmark.bmbm do |x|
42
42
  x.report 'find (without FriendlyId)' do
43
43
  N.times {Book.find BOOKS.rand}
44
44
  end
45
+
45
46
  x.report 'find (in-table slug)' do
46
- N.times {Journalist.find JOURNALISTS.rand}
47
+ N.times {Journalist.friendly.find JOURNALISTS.rand}
47
48
  end
49
+
48
50
  x.report 'find (external slug)' do
49
- N.times {Manual.find MANUALS.rand}
51
+ N.times {Manual.friendly.find MANUALS.rand}
50
52
  end
51
53
 
52
54
  x.report 'insert (without FriendlyId)' do
@@ -60,4 +62,4 @@ Benchmark.bmbm do |x|
60
62
  x.report 'insert (external slug)' do
61
63
  N.times {transaction {Manual.create :name => Faker::Name.name}}
62
64
  end
63
- end
65
+ end
data/friendly_id.gemspec CHANGED
@@ -16,14 +16,15 @@ Gem::Specification.new do |s|
16
16
 
17
17
  s.required_ruby_version = '>= 1.9.3'
18
18
 
19
- s.add_development_dependency "railties", "~> 4.0.0"
20
- s.add_development_dependency "activerecord", "~> 4.0.0"
21
- s.add_development_dependency "minitest", ">= 4.4.0"
22
- s.add_development_dependency "mocha", "~> 0.13.3"
23
- s.add_development_dependency "yard"
24
- s.add_development_dependency "i18n"
25
- s.add_development_dependency "ffaker"
26
- s.add_development_dependency "simplecov"
19
+ s.add_development_dependency 'railties', '~> 4.0.0'
20
+ s.add_development_dependency 'activerecord', '~> 4.0.0'
21
+ s.add_development_dependency 'minitest', '>= 4.4.0'
22
+ s.add_development_dependency 'mocha', '~> 0.13.3'
23
+ s.add_development_dependency 'yard'
24
+ s.add_development_dependency 'i18n'
25
+ s.add_development_dependency 'ffaker'
26
+ s.add_development_dependency 'simplecov'
27
+ s.add_development_dependency 'redcarpet'
27
28
 
28
29
  s.description = <<-EOM
29
30
  FriendlyId is the "Swiss Army bulldozer" of slugging and permalink plugins for
data/lib/friendly_id.rb CHANGED
@@ -3,11 +3,11 @@ require "thread"
3
3
  require "friendly_id/base"
4
4
  require "friendly_id/object_utils"
5
5
  require "friendly_id/configuration"
6
- require "friendly_id/scopes"
6
+ require "friendly_id/finders"
7
7
 
8
8
  =begin
9
9
 
10
- == About FriendlyId
10
+ ## About FriendlyId
11
11
 
12
12
  FriendlyId is an add-on to Ruby's Active Record that allows you to replace ids
13
13
  in your URLs with strings:
@@ -21,25 +21,24 @@ in your URLs with strings:
21
21
  It requires few changes to your application code and offers flexibility,
22
22
  performance and a well-documented codebase.
23
23
 
24
- === Core Concepts
24
+ ### Core Concepts
25
25
 
26
- ==== Slugs
26
+ #### Slugs
27
27
 
28
- The concept of "slugs[http://en.wikipedia.org/wiki/Slug_(web_publishing)]" is at
29
- the heart of FriendlyId.
28
+ The concept of *slugs* is at the heart of FriendlyId.
30
29
 
31
30
  A slug is the part of a URL which identifies a page using human-readable
32
31
  keywords, rather than an opaque identifier such as a numeric id. This can make
33
32
  your application more friendly both for users and search engine.
34
33
 
35
- ==== Finders: Slugs Act Like Numeric IDs
34
+ #### Finders: Slugs Act Like Numeric IDs
36
35
 
37
36
  To the extent possible, FriendlyId lets you treat text-based identifiers like
38
37
  normal IDs. This means that you can perform finds with slugs just like you do
39
38
  with numeric ids:
40
39
 
41
40
  Person.find(82542335)
42
- Person.find("joe")
41
+ Person.friendly.find("joe")
43
42
 
44
43
  =end
45
44
  module FriendlyId
@@ -1,7 +1,7 @@
1
1
  module FriendlyId
2
2
  =begin
3
3
 
4
- == Setting Up FriendlyId in Your Model
4
+ ## Setting Up FriendlyId in Your Model
5
5
 
6
6
  To use FriendlyId in your ActiveRecord models, you must first either extend or
7
7
  include the FriendlyId module (it makes no difference), then invoke the
@@ -18,7 +18,7 @@ addons it should use. See the documentation for this method for a list of all
18
18
  available addons, or skim through the rest of the docs to get a high-level
19
19
  overview.
20
20
 
21
- === The Default Setup: Simple Models
21
+ ### The Default Setup: Simple Models
22
22
 
23
23
  The simplest way to use FriendlyId is with a model that has a uniquely indexed
24
24
  column with no spaces or special characters, and that is seldom or never
@@ -30,9 +30,9 @@ updated. The most common example of this is a user name:
30
30
  validates_format_of :login, :with => /\A[a-z0-9]+\z/i
31
31
  end
32
32
 
33
- @user = User.find "joe" # the old User.find(1) still works, too
34
- @user.to_param # returns "joe"
35
- redirect_to @user # the URL will be /users/joe
33
+ @user = User.friendly.find "joe" # the old User.find(1) still works, too
34
+ @user.to_param # returns "joe"
35
+ redirect_to @user # the URL will be /users/joe
36
36
 
37
37
  In this case, FriendlyId assumes you want to use the column as-is; it will never
38
38
  modify the value of the column, and your application should ensure that the
@@ -43,7 +43,7 @@ value is unique and admissible in a URL:
43
43
  friendly_id :name
44
44
  end
45
45
 
46
- @city.find "Viña del Mar"
46
+ @city.friendly.find "Viña del Mar"
47
47
  redirect_to @city # the URL will be /cities/Viña%20del%20Mar
48
48
 
49
49
  Writing the code to process an arbitrary string into a good identifier for use
@@ -103,7 +103,7 @@ often better and easier to use {FriendlyId::Slugged slugs}.
103
103
  # between multiple models that can't be well encapsulated by
104
104
  # {FriendlyId.defaults}.
105
105
  #
106
- # === Order Method Calls in a Block vs Ordering Options
106
+ # ### Order Method Calls in a Block vs Ordering Options
107
107
  #
108
108
  # When calling this method without a block, you may set the hash options in
109
109
  # any order.
@@ -112,7 +112,7 @@ often better and easier to use {FriendlyId::Slugged slugs}.
112
112
  # FriendlyId::Configuration's {FriendlyId::Configuration#use use} method
113
113
  # *prior* to the associated configuration options, because it will include
114
114
  # modules into your class, and these modules in turn may add required
115
- # configuration options to the +@friendly_id_configuraton+'s class:
115
+ # configuration options to the `@friendly_id_configuraton`'s class:
116
116
  #
117
117
  # class Person < ActiveRecord::Base
118
118
  # friendly_id do |config|
@@ -130,7 +130,7 @@ often better and easier to use {FriendlyId::Slugged slugs}.
130
130
  # end
131
131
  # end
132
132
  #
133
- # === Including Your Own Modules
133
+ # ### Including Your Own Modules
134
134
  #
135
135
  # Because :use can accept a name or a Module, {FriendlyId.defaults defaults}
136
136
  # can be a convenient place to set up behavior common to all classes using
@@ -158,23 +158,23 @@ often better and easier to use {FriendlyId::Slugged slugs}.
158
158
  # {FriendlyId::History :history}, {FriendlyId::Reserved :reserved}, and
159
159
  # {FriendlyId::Scoped :scoped}, and {FriendlyId::SimpleI18n :simple_i18n}.
160
160
  #
161
- # @option options [Array] :reserved_words Available when using +:reserved+,
161
+ # @option options [Array] :reserved_words Available when using `:reserved`,
162
162
  # which is loaded by default. Sets an array of words banned for use as
163
163
  # the basis of a friendly_id. By default this includes "edit" and "new".
164
164
  #
165
- # @option options [Symbol] :scope Available when using +:scoped+.
165
+ # @option options [Symbol] :scope Available when using `:scoped`.
166
166
  # Sets the relation or column used to scope generated friendly ids. This
167
167
  # option has no default value.
168
168
  #
169
- # @option options [Symbol] :sequence_separator Available when using +:slugged+.
169
+ # @option options [Symbol] :sequence_separator Available when using `:slugged`.
170
170
  # Configures the sequence of characters used to separate a slug from a
171
- # sequence. Defaults to +-+.
171
+ # sequence. Defaults to `-`.
172
172
  #
173
- # @option options [Symbol] :slug_column Available when using +:slugged+.
173
+ # @option options [Symbol] :slug_column Available when using `:slugged`.
174
174
  # Configures the name of the column where FriendlyId will store the slug.
175
- # Defaults to +:slug+.
175
+ # Defaults to `:slug`.
176
176
  #
177
- # @option options [Symbol] :slug_generator_class Available when using +:slugged+.
177
+ # @option options [Symbol] :slug_generator_class Available when using `:slugged`.
178
178
  # Sets the class used to generate unique slugs. You should not specify this
179
179
  # unless you're doing some extensive hacking on FriendlyId. Defaults to
180
180
  # {FriendlyId::SlugGenerator}.
@@ -189,7 +189,21 @@ often better and easier to use {FriendlyId::Slugged slugs}.
189
189
  friendly_id_config.use options.delete :use
190
190
  friendly_id_config.send :set, base ? options.merge(:base => base) : options
191
191
  include Model
192
- extend Scopes
192
+ end
193
+
194
+ # Return a scope that includes the friendly finders.
195
+ # @see FriendlyId::Finders
196
+ def friendly
197
+ # Guess what? This causes Rails to invoke `extend` on the scope, which has
198
+ # the well-known effect of blowing away Ruby's method cache. It would be
199
+ # possible to make this more performant by subclassing the model's
200
+ # relation class, extending that, and returning an instance of it in this
201
+ # method. FriendlyId 4.0 did something similar. However in 5.0 I've
202
+ # decided to only use Rails's public API in order to improve compatibility
203
+ # and maintainability. If you'd like to improve the performance, your
204
+ # efforts would be best directed at improving it at the root cause
205
+ # of the problem - in Rails - because it would benefit more people.
206
+ all.extending(Finders)
193
207
  end
194
208
 
195
209
  # Returns the model class's {FriendlyId::Configuration friendly_id_config}.
@@ -2,6 +2,8 @@ require 'securerandom'
2
2
 
3
3
  module FriendlyId
4
4
 
5
+ # This class provides the slug candidate functionality.
6
+ # @see FriendlyId::Slugged
5
7
  class Candidates
6
8
 
7
9
  include Enumerable
@@ -11,6 +13,8 @@ module FriendlyId
11
13
  @candidates = to_candidate_array(object, array.flatten(1))
12
14
  end
13
15
 
16
+
17
+ # Visits each slug candidate, calls it, passes it to `normalize_friendly_id` and yields the result.
14
18
  def each(*args, &block)
15
19
  @candidates.each(*args) do |candidate|
16
20
  yield @object.normalize_friendly_id(candidate.map(&:call).join(' '))
@@ -16,9 +16,9 @@ module FriendlyId
16
16
  # FriendlyId will take care of transforming the human-readable title into
17
17
  # something suitable for use in a URL.
18
18
  #
19
- # @param [Symbol] A symbol referencing a column or method in the model. This
20
- # value is usually set by passing it as the first argument to
21
- # {FriendlyId::Base#friendly_id friendly_id}:
19
+ # A symbol referencing a column or method in the model. This
20
+ # value is usually set by passing it as the first argument to
21
+ # {FriendlyId::Base#friendly_id friendly_id}:
22
22
  #
23
23
  # @example
24
24
  # class Book < ActiveRecord::Base
@@ -55,7 +55,7 @@ module FriendlyId
55
55
  # extend FriendlyId
56
56
  # friendly_id :name, :use => :slugged
57
57
  # end
58
- # @param [#to_s,Module] *modules Arguments should be Modules, or symbols or
58
+ # @param [#to_s,Module] modules Arguments should be Modules, or symbols or
59
59
  # strings that correspond with the name of a module inside the FriendlyId
60
60
  # namespace. By default FriendlyId provides +:slugged+, +:history+,
61
61
  # +:simple_i18n+, and +:scoped+.
@@ -0,0 +1,51 @@
1
+ module FriendlyId
2
+
3
+ # These are the finder methods that are added to the +friendly+ scope.
4
+ module Finders
5
+
6
+ # Finds a record using the given id.
7
+ #
8
+ # If the id is "unfriendly", it will call the original find method.
9
+ # If the id is a numeric string like '123' it will first look for a friendly
10
+ # id matching '123' and then fall back to looking for a record with the
11
+ # numeric id '123'.
12
+ #
13
+ # Since FriendlyId 5.0, if the id is a numeric string like '123-foo' it
14
+ # will *only* search by friendly id and not fall back to the regular find
15
+ # method.
16
+ #
17
+ # If you want to search only by the friendly id, use {#find_by_friendly_id}.
18
+ # @raise ActiveRecord::RecordNotFound
19
+ def find(*args)
20
+ id = args.first
21
+ return super if args.count != 1 || id.unfriendly_id?
22
+ first_by_friendly_id(id).tap {|result| return result unless result.nil?}
23
+ return super if Integer(id, 10) rescue nil
24
+ raise ActiveRecord::RecordNotFound
25
+ end
26
+
27
+ # Returns true if a record with the given id exists.
28
+ def exists?(conditions = :none)
29
+ return super unless conditions.friendly_id?
30
+ exists_by_friendly_id?(conditions)
31
+ end
32
+
33
+ # Finds exclusively by the friendly id, completely bypassing original
34
+ # +find+.
35
+ # @raise ActiveRecord::RecordNotFound
36
+ def find_by_friendly_id(id)
37
+ first_by_friendly_id(id) or raise ActiveRecord::RecordNotFound
38
+ end
39
+
40
+ private
41
+
42
+ def first_by_friendly_id(id)
43
+ where(friendly_id_config.query_field => id).first
44
+ end
45
+
46
+ def exists_by_friendly_id?(id)
47
+ where(friendly_id_config.query_field => id).exists?
48
+ end
49
+
50
+ end
51
+ end
@@ -2,7 +2,7 @@ module FriendlyId
2
2
 
3
3
  =begin
4
4
 
5
- == History: Avoiding 404's When Slugs Change
5
+ ## History: Avoiding 404's When Slugs Change
6
6
 
7
7
  FriendlyId's {FriendlyId::History History} module adds the ability to store a
8
8
  log of a model's slugs, so that when its friendly id changes, it's still
@@ -10,26 +10,24 @@ possible to perform finds by the old id.
10
10
 
11
11
  The primary use case for this is avoiding broken URLs.
12
12
 
13
- === Setup
13
+ ### Setup
14
14
 
15
15
  In order to use this module, you must add a table to your database schema to
16
16
  store the slug records. FriendlyId provides a generator for this purpose:
17
17
 
18
- rails generate friendly_id
19
- rake db:migrate
18
+ rails generate friendly_id
19
+ rake db:migrate
20
20
 
21
- This will add a table named +friendly_id_slugs+, used by the {FriendlyId::Slug}
21
+ This will add a table named `friendly_id_slugs`, used by the {FriendlyId::Slug}
22
22
  model.
23
23
 
24
- === Considerations
25
-
26
- This module is incompatible with the +:scoped+ module.
24
+ ### Considerations
27
25
 
28
26
  Because recording slug history requires creating additional database records,
29
- this module has an impact on the performance of the associated model's +create+
27
+ this module has an impact on the performance of the associated model's `create`
30
28
  method.
31
29
 
32
- === Example
30
+ ### Example
33
31
 
34
32
  class Post < ActiveRecord::Base
35
33
  extend FriendlyId
@@ -66,18 +64,30 @@ method.
66
64
  :dependent => :destroy,
67
65
  :class_name => Slug.to_s
68
66
  }
67
+
69
68
  after_save :create_slug
70
- def self.find_by_friendly_id(id)
71
- includes(:slugs).where(slug_history_clause(id)).references(:slugs).first
72
- end
73
69
 
74
- def self.exists_by_friendly_id?(id)
75
- includes(:slugs).where(arel_table[friendly_id_config.query_field].eq(id).or(slug_history_clause(id))).exists?
70
+ def self.friendly
71
+ all.extending(HistoryFinders)
76
72
  end
73
+ end
74
+ end
77
75
 
78
- def self.slug_history_clause(id)
79
- Slug.arel_table[:sluggable_type].eq(base_class.to_s).and(Slug.arel_table[:slug].eq(id))
80
- end
76
+ module HistoryFinders
77
+ include Finders
78
+
79
+ private
80
+
81
+ def first_by_friendly_id(id)
82
+ joins(:slugs).where(slug_history_clause(id)).readonly(false).first
83
+ end
84
+
85
+ def exists_by_friendly_id?(id)
86
+ joins(:slugs).where(arel_table[friendly_id_config.query_field].eq(id).or(slug_history_clause(id))).exists?
87
+ end
88
+
89
+ def slug_history_clause(id)
90
+ Slug.arel_table[:sluggable_type].eq(base_class.to_s).and(Slug.arel_table[:slug].eq(id))
81
91
  end
82
92
  end
83
93