friendly_id4 4.0.0.beta1 → 4.0.0.beta3

Sign up to get free protection for your applications and to get access to all the features.
data/.gemtest ADDED
File without changes
data/Gemfile CHANGED
@@ -7,12 +7,13 @@ platform :jruby do
7
7
  end
8
8
 
9
9
  platform :ruby do
10
- gem "mysql"
11
- gem "pg"
10
+ # gem "mysql"
11
+ # gem "pg"
12
12
  gem "sqlite3"
13
13
  end
14
14
 
15
- gem "activerecord", ENV["AR"] || "3.0.9"
15
+ #gem "activerecord", ENV["AR"] || "3.0.9"
16
+ gem "rails", :path => "../rails"
16
17
 
17
18
  gem "ffaker"
18
19
  gem "minitest"
data/Guide.md CHANGED
@@ -25,8 +25,9 @@ column with no spaces or special characters, and that is seldom or never
25
25
  updated. The most common example of this is a user name or login column:
26
26
 
27
27
  class User < ActiveRecord::Base
28
+ extend FriendlyId
28
29
  validates_format_of :login, :with => /\A[a-z0-9]+\z/i
29
- has_friendly_id :login
30
+ friendly_id :login
30
31
  end
31
32
 
32
33
  @user = User.find "joe" # the old User.find(1) still works, too
@@ -38,7 +39,8 @@ modify the value of the column, and your application should ensure that the valu
38
39
  is admissible in a URL:
39
40
 
40
41
  class City < ActiveRecord::Base
41
- has_friendly_id :name
42
+ extend FriendlyId
43
+ friendly_id :name
42
44
  end
43
45
 
44
46
  @city.find "Viña del Mar"
@@ -55,8 +57,8 @@ title, which may have spaces, uppercase characters, or other attributes you
55
57
  wish to modify to make them more suitable for use in URL's.
56
58
 
57
59
  class Post < ActiveRecord::Base
58
- include FriendlyId::Slugged
59
- has_friendly_id :title
60
+ extend FriendlyId
61
+ friendly_id :title, :use => :slugged
60
62
  end
61
63
 
62
64
  @post = Post.create(:title => "This is the first post!")
@@ -83,16 +85,13 @@ dropped until a stable release of 3.2 is out, or possibly longer.
83
85
 
84
86
  ## Configuration
85
87
 
86
- FriendlyId is configured in your model using the `has_friendly_id` method. Additional
87
- features can be activated by including various modules:
88
+ FriendlyId is configured in your model using the `friendly_id` method. Additional
89
+ features can be passing the names of modules into the `:use` option:
88
90
 
89
91
  class Post < ActiveRecord::Base
90
- # use slugs
91
- include FriendlyId::Slugged
92
- # record slug history
93
- include FriendlyId::History
92
+ extend FriendlyId
94
93
  # use the "title" accessor as the basis of the friendly_id
95
- has_friendly_id :title
94
+ friendly_id :title, :use => [:slugged, :history]
96
95
  end
97
96
 
98
97
  Read on to learn about the various features that can be configured. For the
@@ -121,10 +120,9 @@ FriendlyId can use either a column or a method to generate the slug text for
121
120
  your model:
122
121
 
123
122
  class City < ActiveRecord::Base
124
-
123
+ extend FriendlyId
125
124
  belongs_to :country
126
- include FriendlyId::Slugged
127
- has_friendly_id :name_and_country
125
+ friendly_id :name_and_country, :use => :slugged
128
126
 
129
127
  def name_and_country
130
128
  #{name} #{country.name}
@@ -148,9 +146,8 @@ you can override the `normalize_friendly_id` method in your model class in
148
146
  order to fine-tune the output:
149
147
 
150
148
  class City < ActiveRecord::Base
151
-
152
- include FriendlyId::Slugged
153
- has_friendly_id :whatever
149
+ extend FriendlyId
150
+ friendly_id :whatever, :use => :slugged
154
151
 
155
152
  def normalize_friendly_id(text)
156
153
  my_text_modifier_method(text)
@@ -186,9 +183,8 @@ FriendlyId can maintain a history of your record's older slugs, so if your
186
183
  record's friendly_id changes, your URL's won't break.
187
184
 
188
185
  class Post < ActiveRecord::Base
189
- include FriendlyId::Slugged
190
- include FriendlyId::History
191
- has_friendly_id :title
186
+ extend FriendlyId
187
+ friendly_id :title, :use => :history
192
188
  end
193
189
 
194
190
  class PostsController < ApplicationController
@@ -230,10 +226,9 @@ the rest of the slug. This is important to enable having slugs like:
230
226
  /cars/peugeot-206--2
231
227
 
232
228
  You can configure the separator string used by your model by setting the
233
- `:sequence_separator` option in `has_friendly_id`:
229
+ `:sequence_separator` option in `friendly_id`:
234
230
 
235
- include FriendlyId::Slugged
236
- has_friendly_id :title, :sequence_separator => ":"
231
+ friendly_id :title, :use => :slugged, :sequence_separator => ":"
237
232
 
238
233
  You can also override the default used in
239
234
  {FriendlyId::Configuration::DEFAULTS} to set the value for any model using
@@ -259,16 +254,15 @@ names unique for each city, so that the second "Joe's Diner" can also have the
259
254
  slug "joes-diner", as long as it's located in a different city:
260
255
 
261
256
  class Restaurant < ActiveRecord::Base
257
+ extend FriendlyId
262
258
  belongs_to :city
263
- include FriendlyId::Slugged
264
- include FriendlyId::Scoped
265
- has_friendly_id :name, :scope => :city
259
+ friendly_id :name, :use => :scoped, :scope => :city
266
260
  end
267
261
 
268
262
  class City < ActiveRecord::Base
263
+ extend FriendlyId
269
264
  has_many :restaurants
270
- include FriendlyId::Slugged
271
- has_friendly_id :name
265
+ friendly_id :name, :use => :slugged
272
266
  end
273
267
 
274
268
  City.find("seattle").restaurants.find("joes-diner")
@@ -355,14 +349,15 @@ performance of your application. Of course your results may vary.
355
349
 
356
350
  activerecord (3.0.9)
357
351
  ruby 1.9.2p180 (2011-02-18 revision 30909) [x86_64-darwin10.6.0]
358
- friendly_id (4.0.0.pre3)
352
+ friendly_id (4.0.0.beta1)
359
353
  sqlite3 (1.3.3) gem
360
354
  sqlite3 3.6.12 in-memory database
361
355
 
362
- user system total real
363
- find (without FriendlyId) 0.280000 0.000000 0.280000 ( 0.278086)
364
- find (in-table slug) 0.320000 0.000000 0.320000 ( 0.320151)
365
- find (external slug) 3.040000 0.010000 3.050000 ( 3.048054)
366
- insert (without FriendlyId) 0.780000 0.000000 0.780000 ( 0.785427)
367
- insert (in-table-slug) 1.520000 0.010000 1.530000 ( 1.532350)
368
- insert (external slug) 3.310000 0.020000 3.330000 ( 3.335548)
356
+
357
+ user system total real
358
+ find (without FriendlyId) 0.300000 0.000000 0.300000 ( 0.306729)
359
+ find (in-table slug) 0.350000 0.000000 0.350000 ( 0.351760)
360
+ find (external slug) 3.320000 0.000000 3.320000 ( 3.326749)
361
+ insert (without FriendlyId) 0.810000 0.010000 0.820000 ( 0.810513)
362
+ insert (in-table-slug) 1.740000 0.000000 1.740000 ( 1.743511)
363
+ insert (external slug) 3.540000 0.010000 3.550000 ( 3.544898)
data/README.md CHANGED
@@ -58,8 +58,8 @@ FriendlyId is compatible with Active Record **3.0** and **3.1**.
58
58
 
59
59
  # edit app/models/user.rb
60
60
  class User < ActiveRecord::Base
61
- include FriendlyId::Slugged
62
- has_friendly_id :name
61
+ extend FriendlyId
62
+ friendly_id :name, :use => :slugged
63
63
  end
64
64
 
65
65
  User.create! :name => "Joe Schmoe"
@@ -70,14 +70,14 @@ FriendlyId is compatible with Active Record **3.0** and **3.1**.
70
70
 
71
71
  ## Bugs
72
72
 
73
- Please report them on the [Github issue tracker](http://github.com/norman/friendly_id/issues)
74
- for this project.
73
+ Please report them on the [Github issue
74
+ tracker](http://github.com/norman/friendly_id/issues) for this project.
75
75
 
76
76
  If you have a bug to report, please include the following information:
77
77
 
78
78
  * **Version information for FriendlyId, Rails and Ruby.**
79
79
  * Stack trace and error message.
80
- * Any snippets of relevant model, view or controller code that shows how your
80
+ * Any snippets of relevant model, view or controller code that shows how you
81
81
  are using FriendlyId.
82
82
 
83
83
  If you are able to, it helps even more if you can fork FriendlyId on Github,
@@ -85,8 +85,9 @@ and add a test that reproduces the error you are experiencing.
85
85
 
86
86
  ## Credits
87
87
 
88
- FriendlyId was created by Norman Clarke, Adrian Mugnolo, and Emilio Tagua, and
89
- has had significant contributions over the years from [many
88
+ FriendlyId was originally created by Norman Clarke and Adrian Mugnolo, with
89
+ significant help early in its life by Emilio Tagua. I'm deeply gratful for the
90
+ generous contributions over the years from [many
90
91
  volunteers](https://github.com/norman/friendly_id/contributors).
91
92
 
92
93
  Copyright (c) 2008-2011 Norman Clarke, released under the MIT license.
data/Rakefile CHANGED
@@ -3,25 +3,28 @@ require "rake/testtask"
3
3
 
4
4
  def rubies(&block)
5
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"]
6
7
  ENV["RB"] = ruby
7
8
  yield
8
- ENV["RB"] = nil
9
+ ENV["RB"] = old
9
10
  end
10
11
  end
11
12
 
12
13
  def versions(&block)
13
14
  ["3.1.0.rc4", "3.0.9"].each do |version|
15
+ old = ENV["AR"]
14
16
  ENV["AR"] = version
15
17
  yield
16
- ENV["AR"] = nil
18
+ ENV["AR"] = old
17
19
  end
18
20
  end
19
21
 
20
22
  def adapters(&block)
21
23
  ["mysql", "postgres", "sqlite3"].each do |adapter|
24
+ old = ENV["DB"]
22
25
  ENV["DB"] = adapter
23
26
  yield
24
- ENV["DB"] = nil
27
+ ENV["DB"] = old
25
28
  end
26
29
  end
27
30
 
@@ -33,7 +36,7 @@ Rake::TestTask.new do |t|
33
36
  end
34
37
 
35
38
  task :clean do
36
- %x{rm -rf *.gem doc pkg}
39
+ %x{rm -rf *.gem doc pkg coverage}
37
40
  %x{rm -f `find . -name '*.rbc'`}
38
41
  end
39
42
 
@@ -42,7 +45,11 @@ task :gem do
42
45
  end
43
46
 
44
47
  task :yard do
45
- %x{bundle exec yard doc --files=*.md}
48
+ puts %x{bundle exec yard doc --files=*.md}
49
+ end
50
+
51
+ task :bench do
52
+ require File.expand_path("../bench", __FILE__)
46
53
  end
47
54
 
48
55
  desc "Bundle for all supported Ruby/AR versions"
data/bench.rb CHANGED
@@ -1,49 +1,41 @@
1
1
  require File.expand_path("../test/helper", __FILE__)
2
2
  require "ffaker"
3
- require "friendly_id/migration"
4
3
 
5
4
  N = 1000
6
5
 
7
- migration do |m|
8
- m.add_column :users, :slug, :string
9
- m.add_index :users, :slug, :unique => true
6
+ def transaction
7
+ ActiveRecord::Base.transaction { yield ; raise ActiveRecord::Rollback }
10
8
  end
11
9
 
12
- migration do |m|
13
- m.create_table :posts do |t|
14
- t.string :name
15
- t.string :slug
16
- end
17
- m.add_index :posts, :slug, :unique => true
18
- end
19
- CreateFriendlyIdSlugs.up
20
-
21
-
22
10
  class Array
23
11
  def rand
24
12
  self[Kernel.rand(length)]
25
13
  end
26
14
  end
27
15
 
28
- class User
16
+ Book = Class.new ActiveRecord::Base
17
+
18
+ class Journalist < ActiveRecord::Base
19
+ extend FriendlyId
29
20
  include FriendlyId::Slugged
30
- has_friendly_id :name
21
+ friendly_id :name
31
22
  end
32
23
 
33
- class Post < ActiveRecord::Base
24
+ class Manual < ActiveRecord::Base
25
+ extend FriendlyId
34
26
  include FriendlyId::History
35
- has_friendly_id :name
27
+ friendly_id :name
36
28
  end
37
29
 
38
- USERS = []
39
- BOOKS = []
40
- POSTS = []
30
+ BOOKS = []
31
+ JOURNALISTS = []
32
+ MANUALS = []
41
33
 
42
34
  100.times do
43
35
  name = Faker::Name.name
44
- USERS << (User.create! :name => name).friendly_id
45
- POSTS << (Post.create! :name => name).friendly_id
46
- BOOKS << (Book.create! :name => name).id
36
+ BOOKS << (Book.create! :name => name).id
37
+ JOURNALISTS << (Journalist.create! :name => name).friendly_id
38
+ MANUALS << (Manual.create! :name => name).friendly_id
47
39
  end
48
40
 
49
41
  Benchmark.bmbm do |x|
@@ -51,10 +43,10 @@ Benchmark.bmbm do |x|
51
43
  N.times {Book.find BOOKS.rand}
52
44
  end
53
45
  x.report 'find (in-table slug)' do
54
- N.times {User.find USERS.rand}
46
+ N.times {Journalist.find JOURNALISTS.rand}
55
47
  end
56
48
  x.report 'find (external slug)' do
57
- N.times {Post.find_by_friendly_id POSTS.rand}
49
+ N.times {Manual.find_by_friendly_id MANUALS.rand}
58
50
  end
59
51
 
60
52
  x.report 'insert (without FriendlyId)' do
@@ -62,10 +54,10 @@ Benchmark.bmbm do |x|
62
54
  end
63
55
 
64
56
  x.report 'insert (in-table-slug)' do
65
- N.times {transaction {User.create :name => Faker::Name.name}}
57
+ N.times {transaction {Journalist.create :name => Faker::Name.name}}
66
58
  end
67
59
 
68
60
  x.report 'insert (external slug)' do
69
- N.times {transaction {Post.create :name => Faker::Name.name}}
61
+ N.times {transaction {Manual.create :name => Faker::Name.name}}
70
62
  end
71
63
  end
@@ -1,29 +1,24 @@
1
1
  module FriendlyId
2
2
  # Class methods that will be added to ActiveRecord::Base.
3
3
  module Base
4
- extend self
5
4
 
6
- def has_friendly_id(*args)
7
- options = args.extract_options!
8
- base = args.shift
9
- friendly_id_config.set options.merge(:base => base)
10
- include Model
11
- # @NOTE: AR-specific code here
12
- validates_exclusion_of base, :in => Configuration::DEFAULTS[:reserved_words]
5
+ def friendly_id(*args, &block)
6
+ if block_given?
7
+ yield(friendly_id_config)
8
+ else
9
+ base = args.shift
10
+ options = args.extract_options!
11
+ @friendly_id_config.use options.delete :use
12
+ @friendly_id_config.send :set, options.merge(:base => base)
13
+ end
13
14
  before_save do |record|
14
15
  record.instance_eval {@current_friendly_id = friendly_id}
15
16
  end
16
- self
17
+ include Model
17
18
  end
18
19
 
19
20
  def friendly_id_config
20
- @friendly_id_config ||= Configuration.new(self)
21
- end
22
-
23
- def uses_friendly_id?
24
- defined? @friendly_id_config
21
+ @friendly_id_config
25
22
  end
26
23
  end
27
24
  end
28
-
29
- ActiveRecord::Base.extend FriendlyId::Base
@@ -1,31 +1,96 @@
1
1
  module FriendlyId
2
- # The configuration paramters passed to +has_friendly_id+ will be stored
3
- # in this object.
2
+ # The configuration paramters passed to +friendly_id+ will be stored in
3
+ # this object.
4
4
  class Configuration
5
- attr_accessor :base
6
- attr_reader :klass
7
5
 
8
- DEFAULTS = {
9
- :config_error_message => 'FriendlyId has no such config option "%s"',
10
- :reserved_words => ["new", "edit"]
6
+ # The base column or method used by FriendlyId as the basis of a friendly id
7
+ # or slug.
8
+ #
9
+ # For models that don't use FriendlyId::Slugged, the base is the column that
10
+ # is used as the FriendlyId directly. For models using FriendlyId::Slugged,
11
+ # the base is a column or method whose value is used as the basis of the
12
+ # slug.
13
+ #
14
+ # For example, if you have a model representing blog posts and that uses
15
+ # slugs, you likely will want to use the "title" attribute as the base, and
16
+ # FriendlyId will take care of transforming the human-readable title into
17
+ # something suitable for use in a URL.
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}:
22
+ #
23
+ # @example
24
+ # class Book < ActiveRecord::Base
25
+ # extend FriendlyId
26
+ # friendly_id :name
27
+ # end
28
+ attr_reader :base
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
36
+ attr_reader :defaults
37
+
38
+ @@defaults = {
39
+ :reserved_words => ["new", "edit"]
11
40
  }
12
41
 
42
+ # The default configuration parameters for models using FriendlyId.
43
+ # @return Hash
44
+ def self.defaults
45
+ @@defaults
46
+ end
47
+
13
48
  def initialize(klass, values = nil)
14
49
  @klass = klass
50
+ @defaults = self.class.defaults.dup
15
51
  set values
16
52
  end
17
53
 
18
- def method_missing(symbol, *args, &block)
19
- option = symbol.to_s.gsub(/=\z/, '')
20
- raise ArgumentError, DEFAULTS[:config_error_message] % option
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
21
59
  end
22
60
 
23
- def set(values)
24
- values and values.each {|name, value| self.send "#{name}=", value}
61
+ # Lets you specify the modules to use with FriendlyId.
62
+ #
63
+ # This method is invoked by {FriendlyId::Base#friendly_id friendly_id} when
64
+ # passing the +:use+ option, or when using {FriendlyId::Base#friendly_id
65
+ # friendly_id} with a block.
66
+ #
67
+ # @example
68
+ # class Book < ActiveRecord::Base
69
+ # extend FriendlyId
70
+ # friendly_id :name, :use => :slugged
71
+ # end
72
+ # @param [#to_s] *modules Arguments should be a symbols or strings that
73
+ # correspond with the name of a module inside the FriendlyId namespace. By
74
+ # default FriendlyId provides +:slugged+, +:history+ and +:scoped+.
75
+ def use(*modules)
76
+ modules.to_a.flatten.compact.map do |name|
77
+ klass.send :include, FriendlyId.const_get(name.to_s.classify)
78
+ end
25
79
  end
26
80
 
81
+ # The column that FriendlyId will use to find the record when querying by
82
+ # friendly id.
83
+ #
84
+ # This method is generally only used internally by FriendlyId.
85
+ # @return String
27
86
  def query_field
28
- base
87
+ base.to_s
88
+ end
89
+
90
+ private
91
+
92
+ def set(values)
93
+ values and values.each {|name, value| self.send "#{name}=", value}
29
94
  end
30
95
  end
31
96
  end
@@ -5,10 +5,8 @@ module FriendlyId
5
5
  protected
6
6
 
7
7
  def find_one(id)
8
- return super if !@klass.uses_friendly_id? or id.unfriendly_id?
8
+ return super if !@klass.respond_to?(:friendly_id) || id.unfriendly_id?
9
9
  where(@klass.friendly_id_config.query_field => id).first or super
10
10
  end
11
11
  end
12
12
  end
13
-
14
- ActiveRecord::Relation.send :include, FriendlyId::FinderMethods
@@ -3,12 +3,14 @@ require "friendly_id/slug"
3
3
  module FriendlyId
4
4
  module History
5
5
 
6
- def self.included(base)
7
- base.class_eval do
8
- include Slugged unless include? Slugged
9
- extend Finder
6
+ def self.included(klass)
7
+ klass.instance_eval do
8
+ raise "FriendlyId::History is incompatibe with FriendlyId::Scoped" if self < Scoped
9
+ include Slugged unless self < Slugged
10
10
  has_many :friendly_id_slugs, :as => :sluggable, :dependent => :destroy
11
11
  before_save :build_friendly_id_slug, :if => lambda {|r| r.slug_sequencer.slug_changed?}
12
+ scope :with_friendly_id, lambda {|id| includes(:friendly_id_slugs).where("friendly_id_slugs.slug = ?", id)}
13
+ extend Finder
12
14
  end
13
15
  end
14
16
 
@@ -21,7 +23,7 @@ module FriendlyId
21
23
 
22
24
  module Finder
23
25
  def find_by_friendly_id(*args)
24
- where("friendly_id_slugs.slug = ?", args.shift).includes(:friendly_id_slugs).first(*args)
26
+ with_friendly_id(args.shift).first(*args)
25
27
  end
26
28
  end
27
29
  end
@@ -6,10 +6,13 @@ module FriendlyId
6
6
 
7
7
  # True is the id is definitely friendly, false if definitely unfriendly,
8
8
  # else nil.
9
+ #
10
+ # An object is considired "definitely unfriendly" if its class is or
11
+ # inherits from Numeric, Symbol or ActiveRecord::Base.
9
12
  def friendly_id?
10
- if kind_of?(Integer) or kind_of?(Symbol) or self.class.respond_to? :friendly_id_config
13
+ if [Numeric, Symbol, ActiveRecord::Base].detect {|klass| self.class < klass}
11
14
  false
12
- elsif to_i.to_s != to_s
15
+ elsif respond_to?(:to_i) && to_i.to_s != to_s
13
16
  true
14
17
  end
15
18
  end
@@ -12,43 +12,38 @@ module FriendlyId
12
12
  # class Restaurant < ActiveRecord::Base
13
13
  # belongs_to :city
14
14
  # include FriendlyId::Scoped
15
- # has_friendly_id :name, :scope => :city
15
+ # friendly_id :name, :scope => :city
16
16
  # end
17
17
  module Scoped
18
18
  def self.included(klass)
19
- klass.send :include, Slugged unless klass.include? Slugged
19
+ klass.instance_eval do
20
+ raise "FriendlyId::Scoped is incompatibe with FriendlyId::History" if self < History
21
+ include Slugged unless self < Slugged
22
+ friendly_id_config.class.send :include, Configuration
23
+ friendly_id_config.slug_sequencer_class.send :include, SlugSequencer
24
+ end
20
25
  end
21
- end
22
-
23
- class SlugSequencer
24
- private
25
26
 
26
- alias conflict_without_scope conflict
27
-
28
- # Checks for naming conflicts, taking scopes into account.
29
- # @return ActiveRecord::Base
30
- def conflict_with_scope
31
- column = friendly_id_config.scope_column
32
- conflicts.where("#{column} = ?", sluggable.send(column)).first
27
+ module Configuration
28
+ attr_accessor :scope
29
+
30
+ # Gets the scope column.
31
+ #
32
+ # Checks to see if the +:scope+ option passed to {#friendly_id}
33
+ # refers to a relation, and if so, returns the realtion's foreign key.
34
+ # Otherwise it assumes the option value was the name of column and returns
35
+ # it cast to a String.
36
+ # @return String The scope column
37
+ def scope_column
38
+ (klass.reflections[@scope].try(:association_foreign_key) || @scope).to_s
39
+ end
33
40
  end
34
41
 
35
- def conflict
36
- friendly_id_config.scope ? conflict_with_scope : conflict_without_scope
37
- end
38
- end
39
-
40
- class Configuration
41
- attr_accessor :scope
42
-
43
- # Gets the scope column.
44
- #
45
- # Checks to see if the +:scope+ option passed to {#has_friendly_id}
46
- # refers to a relation, and if so, returns the realtion's foreign key.
47
- # Otherwise it assumes the option value was the name of column and returns
48
- # it cast to a String.
49
- # @return String The scope column
50
- def scope_column
51
- (klass.reflections[@scope].try(:association_foreign_key) || @scope).to_s
42
+ module SlugSequencer
43
+ def conflict
44
+ column = friendly_id_config.scope_column
45
+ conflicts.where("#{column} = ?", sluggable.send(column)).first
46
+ end
52
47
  end
53
48
  end
54
49
  end
@@ -15,7 +15,7 @@ module FriendlyId
15
15
  end
16
16
 
17
17
  def generate
18
- if slug_changed? or new_record?
18
+ if new_record? or slug_changed?
19
19
  conflict? ? self.next : normalized
20
20
  else
21
21
  sluggable.friendly_id
@@ -48,7 +48,6 @@ module FriendlyId
48
48
  @conflict
49
49
  end
50
50
 
51
- # @NOTE AR-specific code here
52
51
  def conflicts
53
52
  pkey = sluggable.class.primary_key
54
53
  value = sluggable.send pkey
@@ -5,19 +5,22 @@ module FriendlyId
5
5
  # This module adds in-table slugs to an ActiveRecord model.
6
6
  module Slugged
7
7
 
8
- # @NOTE AR-specific code here
9
8
  def self.included(klass)
10
- klass.before_save :set_slug
11
- klass.friendly_id_config.use_slugs = true
9
+ klass.instance_eval do
10
+ friendly_id_config.class.send :include, Configuration
11
+ friendly_id_config.defaults[:slug_column] = 'slug'
12
+ friendly_id_config.defaults[:sequence_separator] = '--'
13
+ friendly_id_config.slug_sequencer_class = Class.new(SlugSequencer)
14
+ before_validation :set_slug
15
+ end
12
16
  end
13
17
 
14
- # @NOTE AS-specific code here
15
18
  def normalize_friendly_id(value)
16
19
  value.to_s.parameterize
17
20
  end
18
21
 
19
22
  def slug_sequencer
20
- SlugSequencer.new(self)
23
+ friendly_id_config.slug_sequencer_class.new(self)
21
24
  end
22
25
 
23
26
  private
@@ -25,27 +28,22 @@ module FriendlyId
25
28
  def set_slug
26
29
  send "#{friendly_id_config.slug_column}=", slug_sequencer.generate
27
30
  end
28
- end
29
-
30
- class Configuration
31
- attr :use_slugs
32
- attr_writer :slug_column, :sequence_separator, :use_slugs
33
31
 
34
- DEFAULTS[:slug_column] = 'slug'
35
- DEFAULTS[:sequence_separator] = '--'
32
+ module Configuration
33
+ attr_writer :slug_column, :sequence_separator
34
+ attr_accessor :slug_sequencer_class
36
35
 
37
- undef query_field
36
+ def query_field
37
+ slug_column
38
+ end
38
39
 
39
- def query_field
40
- use_slugs ? slug_column : base
41
- end
42
-
43
- def sequence_separator
44
- @sequence_separator ||= DEFAULTS[:sequence_separator]
45
- end
40
+ def sequence_separator
41
+ @sequence_separator or defaults[:sequence_separator]
42
+ end
46
43
 
47
- def slug_column
48
- @slug_column ||= DEFAULTS[:slug_column]
44
+ def slug_column
45
+ @slug_column or defaults[:slug_column]
46
+ end
49
47
  end
50
48
  end
51
49
  end
@@ -3,7 +3,7 @@ module FriendlyId
3
3
  MAJOR = 4
4
4
  MINOR = 0
5
5
  TINY = 0
6
- BUILD = 'beta1'
6
+ BUILD = 'beta3'
7
7
  STRING = [MAJOR, MINOR, TINY, BUILD].compact.join('.')
8
8
  end
9
9
  end
data/lib/friendly_id.rb CHANGED
@@ -6,9 +6,34 @@ require "friendly_id/finder_methods"
6
6
 
7
7
  # FriendlyId is a comprehensive Ruby library for ActiveRecord permalinks and
8
8
  # slugs.
9
+ #
9
10
  # @author Norman Clarke
10
11
  module FriendlyId
11
12
  autoload :Slugged, "friendly_id/slugged"
12
13
  autoload :Scoped, "friendly_id/scoped"
13
14
  autoload :History, "friendly_id/history"
15
+
16
+ # FriendlyId takes advantage of `extended` to do basic model setup, primarily
17
+ # extending FriendlyId::Base to add #friendly_id as a class method for
18
+ # configuring how a model is going to use FriendlyId. In previous versions of
19
+ # this library, ActiveRecord::Base was patched by default to include methods
20
+ # needed to configure friendly_id, but this version tries to be a little less
21
+ # invasive.
22
+ #
23
+ # In addition to adding the #friendly_id method, the class instance variable
24
+ # +@friendly_id_config+ is added. This variable is an instance of an anonymous
25
+ # subclass of FriendlyId::Configuration. This is done to allow for
26
+ # subsequently loaded modules like FriendlyId::Slugged to add functionality to
27
+ # the configuration only for the current class, and thereby isolating other
28
+ # classes from large feature changes a module could potentially introduce. The
29
+ # upshot of this is, you can have two Active Record models that both have a
30
+ # @friendly_id_config, but each config object can have different methods and
31
+ # behaviors depending on what modules have been loaded, without conflicts.
32
+ def self.extended(base)
33
+ base.instance_eval do
34
+ extend FriendlyId::Base
35
+ @friendly_id_config = Class.new(FriendlyId::Configuration).new(base)
36
+ end
37
+ ActiveRecord::Relation.send :include, FriendlyId::FinderMethods
38
+ end
14
39
  end
data/test/base_test.rb ADDED
@@ -0,0 +1,31 @@
1
+ require File.expand_path("../helper.rb", __FILE__)
2
+
3
+ class CoreTest < MiniTest::Unit::TestCase
4
+ include FriendlyId::Test
5
+
6
+ test "friendly_id should accept a base and a hash" do
7
+ klass = Class.new(ActiveRecord::Base) do
8
+ extend FriendlyId
9
+ friendly_id :foo, :use => :slugged, :slug_column => :bar
10
+ end
11
+ assert klass < FriendlyId::Slugged
12
+ assert_equal :foo, klass.friendly_id_config.base
13
+ assert_equal :bar, klass.friendly_id_config.slug_column
14
+ end
15
+
16
+
17
+ test "friendly_id should accept a block" do
18
+ klass = Class.new(ActiveRecord::Base) do
19
+ extend FriendlyId
20
+ friendly_id do |config|
21
+ config.use :slugged
22
+ config.base = :foo
23
+ config.slug_column = :bar
24
+ end
25
+ end
26
+ assert klass < FriendlyId::Slugged
27
+ assert_equal :foo, klass.friendly_id_config.base
28
+ assert_equal :bar, klass.friendly_id_config.slug_column
29
+ end
30
+
31
+ end
@@ -0,0 +1,27 @@
1
+ require File.expand_path("../helper", __FILE__)
2
+
3
+ class ConfigurationTest < MiniTest::Unit::TestCase
4
+
5
+ include FriendlyId::Test
6
+
7
+ def setup
8
+ @klass = Class.new(ActiveRecord::Base)
9
+ end
10
+
11
+ test "should set klass on initialization" do
12
+ config = FriendlyId::Configuration.new @klass
13
+ assert_equal @klass, config.klass
14
+ end
15
+
16
+ test "should set options on initialization if present" do
17
+ config = FriendlyId::Configuration.new @klass, :base => "hello"
18
+ assert_equal "hello", config.base
19
+ end
20
+
21
+ test "should raise error if passed unrecognized option" do
22
+ assert_raises NoMethodError do
23
+ FriendlyId::Configuration.new @klass, :foo => "bar"
24
+ end
25
+ end
26
+
27
+ end
data/test/core_test.rb CHANGED
@@ -2,7 +2,8 @@ require File.expand_path("../helper.rb", __FILE__)
2
2
 
3
3
  Author, Book = 2.times.map do
4
4
  Class.new(ActiveRecord::Base) do
5
- has_friendly_id :name
5
+ extend FriendlyId
6
+ friendly_id :name
6
7
  end
7
8
  end
8
9
 
@@ -16,17 +17,11 @@ class CoreTest < MiniTest::Unit::TestCase
16
17
  end
17
18
 
18
19
  test "models don't use friendly_id by default" do
19
- assert !Class.new(ActiveRecord::Base).uses_friendly_id?
20
+ assert !Class.new(ActiveRecord::Base).respond_to?(:friendly_id)
20
21
  end
21
22
 
22
23
  test "model classes should have a friendly id config" do
23
- assert klass.has_friendly_id(:name).friendly_id_config
24
- end
25
-
26
- test "should raise error when bad config options are set" do
27
- assert_raises ArgumentError do
28
- klass.has_friendly_id :name, :garbage => :in
29
- end
24
+ assert klass.friendly_id(:name).friendly_id_config
30
25
  end
31
26
 
32
27
  test "should reserve 'new' and 'edit' by default" do
data/test/helper.rb CHANGED
@@ -7,10 +7,14 @@ require "bundler/setup"
7
7
  require "mocha"
8
8
  require "minitest/unit"
9
9
  require "active_record"
10
+ # require "active_support/core_ext/class"
10
11
 
11
12
  if ENV["COVERAGE"]
12
13
  require 'simplecov'
13
- SimpleCov.start
14
+ SimpleCov.start do
15
+ add_filter "test/"
16
+ add_filter "friendly_id/migration"
17
+ end
14
18
  end
15
19
 
16
20
  require "friendly_id"
@@ -77,7 +81,7 @@ end
77
81
 
78
82
  class Module
79
83
  def test(name, &block)
80
- define_method("test_#{name.gsub(/[^a-z0-9]/i, "_")}".to_sym, &block)
84
+ define_method("test_#{name.gsub(/[^a-z0-9']/i, "_")}".to_sym, &block)
81
85
  end
82
86
  end
83
87
 
data/test/history_test.rb CHANGED
@@ -1,8 +1,8 @@
1
1
  require File.expand_path("../helper.rb", __FILE__)
2
2
 
3
3
  class Manual < ActiveRecord::Base
4
- include FriendlyId::History
5
- has_friendly_id :name
4
+ extend FriendlyId
5
+ friendly_id :name, :use => :history
6
6
  end
7
7
 
8
8
  class HistoryTest < MiniTest::Unit::TestCase
@@ -43,4 +43,13 @@ class HistoryTest < MiniTest::Unit::TestCase
43
43
  assert !found.readonly?
44
44
  end
45
45
  end
46
+
47
+ test "should raise error if used with scoped" do
48
+ klass = Class.new(ActiveRecord::Base)
49
+ klass.extend FriendlyId
50
+ assert_raises RuntimeError do
51
+ klass.friendly_id :name, :use => [:history, :scoped]
52
+ end
53
+ end
54
+
46
55
  end
data/test/scoped_test.rb CHANGED
@@ -1,14 +1,14 @@
1
1
  require File.expand_path("../helper", __FILE__)
2
2
 
3
3
  class Novelist < ActiveRecord::Base
4
- include FriendlyId::Slugged
5
- has_friendly_id :name
4
+ extend FriendlyId
5
+ friendly_id :name, :use => :slugged
6
6
  end
7
7
 
8
8
  class Novel < ActiveRecord::Base
9
- include FriendlyId::Scoped
9
+ extend FriendlyId
10
10
  belongs_to :novelist
11
- has_friendly_id :name, :scope => :novelist
11
+ friendly_id :name, :use => :scoped, :scope => :novelist
12
12
  end
13
13
 
14
14
  class ScopedTest < MiniTest::Unit::TestCase
@@ -26,7 +26,8 @@ class ScopedTest < MiniTest::Unit::TestCase
26
26
 
27
27
  test "should detect scope column from explicit column name" do
28
28
  klass = Class.new(ActiveRecord::Base)
29
- klass.has_friendly_id :empty, :scope => :dummy
29
+ klass.extend FriendlyId
30
+ klass.friendly_id :empty, :use => :scoped, :scope => :dummy
30
31
  assert_equal "dummy", klass.friendly_id_config.scope_column
31
32
  end
32
33
 
@@ -46,4 +47,11 @@ class ScopedTest < MiniTest::Unit::TestCase
46
47
  end
47
48
  end
48
49
 
50
+ test "should raise error if used with history" do
51
+ klass = Class.new(ActiveRecord::Base)
52
+ klass.extend FriendlyId
53
+ assert_raises RuntimeError do
54
+ klass.friendly_id :name, :use => [:scoped, :history]
55
+ end
56
+ end
49
57
  end
data/test/slugged_test.rb CHANGED
@@ -2,8 +2,8 @@ require File.expand_path("../helper.rb", __FILE__)
2
2
 
3
3
  Journalist, Article = 2.times.map do
4
4
  Class.new(ActiveRecord::Base) do
5
- include FriendlyId::Slugged
6
- has_friendly_id :name
5
+ extend FriendlyId
6
+ friendly_id :name, :use => :slugged
7
7
  end
8
8
  end
9
9
 
@@ -53,8 +53,8 @@ class SlugSequencerTest < MiniTest::Unit::TestCase
53
53
  test "should quote column names" do
54
54
  klass = Class.new(ActiveRecord::Base)
55
55
  klass.table_name = "journalists"
56
- klass.send :include, FriendlyId::Slugged
57
- klass.has_friendly_id :name, :slug_column => "strange name"
56
+ klass.extend FriendlyId
57
+ klass.friendly_id :name, :use => :slugged, :slug_column => "strange name"
58
58
  begin
59
59
  with_instance_of(klass) {|record| assert klass.find(record.friendly_id)}
60
60
  rescue ActiveRecord::StatementInvalid
@@ -68,8 +68,8 @@ class SlugSeparatorTest < MiniTest::Unit::TestCase
68
68
  include FriendlyId::Test
69
69
 
70
70
  class Journalist < ActiveRecord::Base
71
- include FriendlyId::Slugged
72
- has_friendly_id :name, :sequence_separator => ":"
71
+ extend FriendlyId
72
+ friendly_id :name, :use => :slugged, :sequence_separator => ":"
73
73
  end
74
74
 
75
75
  def klass
metadata CHANGED
@@ -1,104 +1,104 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: friendly_id4
3
- version: !ruby/object:Gem::Version
3
+ version: !ruby/object:Gem::Version
4
+ version: 4.0.0.beta3
4
5
  prerelease: 6
5
- version: 4.0.0.beta1
6
6
  platform: ruby
7
- authors:
7
+ authors:
8
8
  - Norman Clarke
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
-
13
- date: 2011-07-14 00:00:00 Z
14
- dependencies:
15
- - !ruby/object:Gem::Dependency
12
+ date: 2011-07-26 00:00:00.000000000 -03:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
16
  name: activerecord
17
- prerelease: false
18
- requirement: &id001 !ruby/object:Gem::Requirement
17
+ requirement: &70311865559640 !ruby/object:Gem::Requirement
19
18
  none: false
20
- requirements:
19
+ requirements:
21
20
  - - ~>
22
- - !ruby/object:Gem::Version
23
- version: "3.0"
21
+ - !ruby/object:Gem::Version
22
+ version: '3.0'
24
23
  type: :development
25
- version_requirements: *id001
26
- - !ruby/object:Gem::Dependency
27
- name: sqlite3
28
24
  prerelease: false
29
- requirement: &id002 !ruby/object:Gem::Requirement
25
+ version_requirements: *70311865559640
26
+ - !ruby/object:Gem::Dependency
27
+ name: sqlite3
28
+ requirement: &70311865558600 !ruby/object:Gem::Requirement
30
29
  none: false
31
- requirements:
30
+ requirements:
32
31
  - - ~>
33
- - !ruby/object:Gem::Version
34
- version: "1.3"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.3'
35
34
  type: :development
36
- version_requirements: *id002
37
- - !ruby/object:Gem::Dependency
38
- name: cutest
39
35
  prerelease: false
40
- requirement: &id003 !ruby/object:Gem::Requirement
36
+ version_requirements: *70311865558600
37
+ - !ruby/object:Gem::Dependency
38
+ name: cutest
39
+ requirement: &70311865558040 !ruby/object:Gem::Requirement
41
40
  none: false
42
- requirements:
41
+ requirements:
43
42
  - - ~>
44
- - !ruby/object:Gem::Version
43
+ - !ruby/object:Gem::Version
45
44
  version: 1.1.2
46
45
  type: :development
47
- version_requirements: *id003
48
- - !ruby/object:Gem::Dependency
49
- name: ffaker
50
46
  prerelease: false
51
- requirement: &id004 !ruby/object:Gem::Requirement
47
+ version_requirements: *70311865558040
48
+ - !ruby/object:Gem::Dependency
49
+ name: ffaker
50
+ requirement: &70311865557340 !ruby/object:Gem::Requirement
52
51
  none: false
53
- requirements:
54
- - - ">="
55
- - !ruby/object:Gem::Version
56
- version: "0"
52
+ requirements:
53
+ - - ! '>='
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
57
56
  type: :development
58
- version_requirements: *id004
59
- - !ruby/object:Gem::Dependency
60
- name: maruku
61
57
  prerelease: false
62
- requirement: &id005 !ruby/object:Gem::Requirement
58
+ version_requirements: *70311865557340
59
+ - !ruby/object:Gem::Dependency
60
+ name: maruku
61
+ requirement: &70311865556780 !ruby/object:Gem::Requirement
63
62
  none: false
64
- requirements:
65
- - - ">="
66
- - !ruby/object:Gem::Version
67
- version: "0"
63
+ requirements:
64
+ - - ! '>='
65
+ - !ruby/object:Gem::Version
66
+ version: '0'
68
67
  type: :development
69
- version_requirements: *id005
70
- - !ruby/object:Gem::Dependency
71
- name: yard
72
68
  prerelease: false
73
- requirement: &id006 !ruby/object:Gem::Requirement
69
+ version_requirements: *70311865556780
70
+ - !ruby/object:Gem::Dependency
71
+ name: yard
72
+ requirement: &70311865556100 !ruby/object:Gem::Requirement
74
73
  none: false
75
- requirements:
76
- - - ">="
77
- - !ruby/object:Gem::Version
78
- version: "0"
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
79
78
  type: :development
80
- version_requirements: *id006
81
- - !ruby/object:Gem::Dependency
82
- name: mocha
83
79
  prerelease: false
84
- requirement: &id007 !ruby/object:Gem::Requirement
80
+ version_requirements: *70311865556100
81
+ - !ruby/object:Gem::Dependency
82
+ name: mocha
83
+ requirement: &70311865555640 !ruby/object:Gem::Requirement
85
84
  none: false
86
- requirements:
87
- - - ">="
88
- - !ruby/object:Gem::Version
89
- version: "0"
85
+ requirements:
86
+ - - ! '>='
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
90
89
  type: :development
91
- version_requirements: *id007
92
- description: " FriendlyId is the \"Swiss Army bulldozer\" of slugging and permalink plugins\n for Ruby on Rails. It allows you to create pretty URL's and work with\n human-friendly strings as if they were numeric ids for ActiveRecord models.\n"
93
- email:
90
+ prerelease: false
91
+ version_requirements: *70311865555640
92
+ description: ! " FriendlyId is the \"Swiss Army bulldozer\" of slugging and permalink
93
+ plugins\n for Ruby on Rails. It allows you to create pretty URL's and work with\n
94
+ \ human-friendly strings as if they were numeric ids for ActiveRecord models.\n"
95
+ email:
94
96
  - norman@njclarke.com
95
97
  executables: []
96
-
97
98
  extensions: []
98
-
99
99
  extra_rdoc_files: []
100
-
101
- files:
100
+ files:
101
+ - .gemtest
102
102
  - .gitignore
103
103
  - Gemfile
104
104
  - Guide.md
@@ -121,9 +121,11 @@ files:
121
121
  - lib/friendly_id/slugged.rb
122
122
  - lib/friendly_id/version.rb
123
123
  - lib/generators/friendly_id_generator.rb
124
+ - test/base_test.rb
124
125
  - test/config/mysql.yml
125
126
  - test/config/postgres.yml
126
127
  - test/config/sqlite3.yml
128
+ - test/configuration_test.rb
127
129
  - test/core_test.rb
128
130
  - test/helper.rb
129
131
  - test/history_test.rb
@@ -132,32 +134,29 @@ files:
132
134
  - test/scoped_test.rb
133
135
  - test/shared.rb
134
136
  - test/slugged_test.rb
137
+ has_rdoc: true
135
138
  homepage: http://norman.github.com/friendly_id
136
139
  licenses: []
137
-
138
140
  post_install_message:
139
141
  rdoc_options: []
140
-
141
- require_paths:
142
+ require_paths:
142
143
  - lib
143
- required_ruby_version: !ruby/object:Gem::Requirement
144
+ required_ruby_version: !ruby/object:Gem::Requirement
144
145
  none: false
145
- requirements:
146
- - - ">="
147
- - !ruby/object:Gem::Version
148
- version: "0"
149
- required_rubygems_version: !ruby/object:Gem::Requirement
146
+ requirements:
147
+ - - ! '>='
148
+ - !ruby/object:Gem::Version
149
+ version: '0'
150
+ required_rubygems_version: !ruby/object:Gem::Requirement
150
151
  none: false
151
- requirements:
152
- - - ">"
153
- - !ruby/object:Gem::Version
152
+ requirements:
153
+ - - ! '>'
154
+ - !ruby/object:Gem::Version
154
155
  version: 1.3.1
155
156
  requirements: []
156
-
157
157
  rubyforge_project: friendly_id
158
- rubygems_version: 1.8.5
158
+ rubygems_version: 1.6.2
159
159
  signing_key:
160
160
  specification_version: 3
161
161
  summary: A comprehensive slugging and pretty-URL plugin.
162
162
  test_files: []
163
-