friendly_id 2.3.4 → 3.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. data/Changelog.md +10 -0
  2. data/Contributors.md +1 -0
  3. data/Guide.md +48 -4
  4. data/README.md +6 -4
  5. data/Rakefile +1 -1
  6. data/extras/extras.rb +1 -1
  7. data/generators/friendly_id/friendly_id_generator.rb +1 -1
  8. data/lib/friendly_id.rb +4 -6
  9. data/lib/friendly_id/{active_record2 → acktive_record}/configuration.rb +2 -2
  10. data/lib/friendly_id/{active_record2 → acktive_record}/finders.rb +24 -10
  11. data/lib/friendly_id/{active_record2 → acktive_record}/simple_model.rb +12 -50
  12. data/lib/friendly_id/acktive_record/slug.rb +66 -0
  13. data/lib/friendly_id/{active_record2 → acktive_record}/slugged_model.rb +9 -85
  14. data/lib/friendly_id/{active_record2 → acktive_record}/tasks.rb +5 -2
  15. data/lib/friendly_id/{active_record2 → acktive_record}/tasks/friendly_id.rake +1 -1
  16. data/lib/friendly_id/{active_record2.rb → active_record.rb} +18 -13
  17. data/lib/friendly_id/configuration.rb +11 -26
  18. data/lib/friendly_id/finders.rb +5 -3
  19. data/lib/friendly_id/slug_string.rb +11 -10
  20. data/lib/friendly_id/slugged.rb +11 -4
  21. data/lib/friendly_id/test.rb +46 -5
  22. data/lib/friendly_id/version.rb +5 -4
  23. data/lib/generators/friendly_id_generator.rb +32 -0
  24. data/rails/init.rb +1 -1
  25. data/test/{active_record2 → acktive_record}/basic_slugged_model_test.rb +3 -3
  26. data/test/{active_record2 → acktive_record}/cached_slug_test.rb +3 -3
  27. data/test/{active_record2 → acktive_record}/core.rb +1 -1
  28. data/test/{active_record2 → acktive_record}/custom_normalizer_test.rb +3 -3
  29. data/test/{active_record2 → acktive_record}/custom_table_name_test.rb +3 -3
  30. data/test/{active_record2 → acktive_record}/scoped_model_test.rb +2 -2
  31. data/test/{active_record2 → acktive_record}/simple_test.rb +4 -1
  32. data/test/{active_record2 → acktive_record}/slug_test.rb +0 -0
  33. data/test/{active_record2 → acktive_record}/slugged.rb +1 -1
  34. data/test/{active_record2 → acktive_record}/slugged_status_test.rb +1 -1
  35. data/test/{active_record2 → acktive_record}/sti_test.rb +3 -3
  36. data/test/{active_record2 → acktive_record}/support/database.mysql.yml +0 -0
  37. data/test/{active_record2 → acktive_record}/support/database.postgres.yml +0 -0
  38. data/test/{active_record2 → acktive_record}/support/database.sqlite3.yml +0 -0
  39. data/test/{active_record2 → acktive_record}/support/models.rb +5 -6
  40. data/test/{active_record2 → acktive_record}/tasks_test.rb +1 -1
  41. data/test/acktive_record/temp_test.rb +32 -0
  42. data/test/{active_record2 → acktive_record}/test_helper.rb +4 -10
  43. data/test/slug_string_test.rb +5 -1
  44. data/test/test_helper.rb +9 -3
  45. metadata +48 -44
  46. data/lib/friendly_id/active_record2/slug.rb +0 -111
  47. data/test/active_record2/deprecated_test.rb +0 -23
data/Changelog.md CHANGED
@@ -6,6 +6,16 @@ suggestions, ideas and improvements to FriendlyId.
6
6
  * Table of Contents
7
7
  {:toc}
8
8
 
9
+ ## 3.0.0 (NOT RELEASED YET)
10
+
11
+ * Rails 3 support.
12
+ * Removed features deprecated in FriendlyId 2.3.
13
+
14
+ ## 2.3.5 (NOT RELEASED YET)
15
+
16
+ * Fixed searching by numeric friendly_id in non-slugged models.
17
+ * Added allow_nil option (Andre Duffeck and Norman Clarke)
18
+
9
19
  ## 2.3.4 (2010-03-22)
10
20
 
11
21
  * Made slugged status use the slug sequence. This fixes problems with #best?
data/Contributors.md CHANGED
@@ -4,6 +4,7 @@ community, in particular from the following people:
4
4
  * Adam Cigánek
5
5
  * Alexander Gräfe
6
6
  * Alistair Holt
7
+ * Andre Duffeck
7
8
  * Andrew Loe III
8
9
  * Ben Woosley
9
10
  * Bence Nagy
data/Guide.md CHANGED
@@ -66,7 +66,7 @@ These features are explained in detail {file:Guide.md#features below}.
66
66
  ## Installation
67
67
 
68
68
  FriendlyId can be installed as a gem, or as a Rails plugin. It is compatible
69
- with Rails 2.2.x, 2.3.x. Support for Rails 3.x is in progress.
69
+ with Rails 2.2.x, 2.3.x. and 3.0.
70
70
 
71
71
  ### As a Gem
72
72
 
@@ -76,7 +76,13 @@ with Rails 2.2.x, 2.3.x. Support for Rails 3.x is in progress.
76
76
 
77
77
  After installing the gem, add an entry in environment.rb:
78
78
 
79
- config.gem "friendly_id", :version => ">= 2.3"
79
+ config.gem "friendly_id", :version => "~> 2.3"
80
+
81
+ ### Rails 3.0
82
+
83
+ After installing the gem, add an entry in the Gemfile:
84
+
85
+ gem "friendly_id", "~> 3.0"
80
86
 
81
87
  ### As a Plugin
82
88
 
@@ -85,8 +91,8 @@ Plugin installation is simple for all supported versions of Rails:
85
91
  ./script/plugin install git://github.com/norman/friendly_id.git
86
92
 
87
93
  However, installing as a gem offers simpler version control than plugin
88
- installation. Whenever possible, install as a gem instead. Plugin support will
89
- probably be removed in version 3.0.
94
+ installation. Whenever possible, install as a gem instead. Plugin support may
95
+ eventually be removed in a future version.
90
96
 
91
97
  ### Setup
92
98
 
@@ -384,6 +390,24 @@ You can also use a different name for the column if you choose, via the
384
390
  end
385
391
 
386
392
 
393
+ ## Nil slugs and skipping validations
394
+
395
+ You can choose to allow `nil` friendly_ids via the `:allow_nil` config option:
396
+
397
+ class User < ActiveRecord::Base
398
+ has_friendly_id :name, :allow_nil => true
399
+ end
400
+
401
+ This works whether the model uses slugs or not.
402
+
403
+ For slugged models, if the friendly_id text is `nil`, no slug will be created. This can be useful, for example, to only create slugs for published articles and avoid creating many slugs with sequences.
404
+
405
+ For models that don't use slugs, this will make FriendlyId skip all its validations when the friendly_id text is `nil`. This can be useful, for example, if you wish to add the friendly_id value in an `:after_save` callback.
406
+
407
+ For non-slugged models, if you simply wish to skip friendly_ids's validations
408
+ for some reason, you can override the `skip_friendly_id_validations` method.
409
+ Note that this method is **not** used by slugged models.
410
+
387
411
  ## Scoped Slugs
388
412
 
389
413
  FriendlyId can generate unique slugs within a given scope. For example, assume
@@ -467,6 +491,16 @@ Use this task if you wish to delete expired slugs; manually or perhaps via
467
491
  cron. If you don't specify the days option, the default is to remove unused
468
492
  slugs older than 45 days.
469
493
 
494
+ # Misc tips
495
+
496
+ ## MySQL 5.0 or less
497
+
498
+ Currently, the default FriendlyId migration will not work with MySQL 5.0 or less
499
+ because it creates and index that's too large. The easiest way to work around
500
+ this is to change the generated migration to add limits on some column lengths.
501
+ Please see [this issue](http://github.com/norman/friendly_id/issues#issue/50) in
502
+ the FriendlyId issue tracker for more information.
503
+
470
504
  # Hacking FriendlyId
471
505
 
472
506
  A couple of notes for programmers intending to work on FriendlyId:
@@ -478,6 +512,15 @@ Before removing any public or protected methods, FriendlyId will deprecate
478
512
  them through one major release cycle. Private methods may, however, change at
479
513
  any time.
480
514
 
515
+ ## Running the Tests
516
+
517
+ FriendlyId uses [Bundler](http://github.com/carlhuda/bundler) to manage its gem
518
+ dependencies. To run the tests, first make sure you have bundler installed.
519
+ Then, copy Gemfile.default to Gemfile. If you wish to test against different gem
520
+ versions than the ones specifed in the Gemfile (for example, to test with
521
+ Postgres or MySQL rather than SQLite3, or to test against different versions of
522
+ ActiveRecord), then simply modify the Gemfile to suit your dependencies.
523
+
481
524
  ## Some Benchmarks
482
525
 
483
526
  These benchmarks can give you an idea of FriendlyId's impact on the
@@ -491,6 +534,7 @@ own solution is unlikely to be any faster than FriendlyId with cached slugs
491
534
  enabled. But if it is, then your patches would be very welcome!
492
535
 
493
536
  ruby 1.9.1p378 (2010-01-10 revision 26273) [i386-darwin10.2.0]
537
+ activerecord (2.3.5)
494
538
  friendly_id (2.3.0)
495
539
  sqlite3 3.6.19 in-memory database
496
540
  ------------------------------------------------------------------------------------------------
data/README.md CHANGED
@@ -19,6 +19,8 @@ versioning, scoped slugs, reserved words, custom slug generators, and
19
19
  excellent Unicode support. For complete information on using FriendlyId,
20
20
  please see the {http://norman.github.com/friendly_id/file.Guide.html FriendlyId Guide}.
21
21
 
22
+ FriendlyId is compatible with Rails 2.2.x - 3.0.
23
+
22
24
  ## Rails Quickstart
23
25
 
24
26
  gem install friendly_id
@@ -27,11 +29,11 @@ please see the {http://norman.github.com/friendly_id/file.Guide.html FriendlyId
27
29
 
28
30
  cd my_app
29
31
 
30
- # add to config/environment.rb
31
- config.gem "friendly_id", :version => ">= 2.3.0"
32
+ # add to Gemfile
33
+ gem "friendly_id", "~> 3.0"
32
34
 
33
- ./script/generate friendly_id
34
- ./script/generate scaffold user name:string cached_slug:string
35
+ rails generate friendly_id
36
+ rails generate scaffold user name:string cached_slug:string
35
37
 
36
38
  rake db:migrate
37
39
 
data/Rakefile CHANGED
@@ -44,7 +44,7 @@ namespace :test do
44
44
  sh "cd fid; rake test"
45
45
  end
46
46
  Rake::TestTask.new(:friendly_id) { |t| t.pattern = "test/*_test.rb" }
47
- Rake::TestTask.new(:ar) { |t| t.pattern = "test/active_record2/*_test.rb" }
47
+ Rake::TestTask.new(:ar) { |t| t.pattern = "test/acktive_record/*_test.rb" }
48
48
 
49
49
  namespace :rails do
50
50
  task :plugin do
data/extras/extras.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env ruby -KU
2
2
  require File.dirname(__FILE__) + '/../test/test_helper'
3
- require File.dirname(__FILE__) + '/../test/active_record2/test_helper'
3
+ require File.dirname(__FILE__) + '/../test/acktive_record/test_helper'
4
4
  require 'ffaker'
5
5
 
6
6
  TIMES = (ENV['N'] || 100).to_i
@@ -7,7 +7,7 @@ class FriendlyIdGenerator < Rails::Generator::Base
7
7
  end
8
8
  unless options[:skip_tasks]
9
9
  m.directory "lib/tasks"
10
- m.file "/../../../lib/friendly_id/active_record2/tasks/friendly_id.rake", "lib/tasks/friendly_id.rake"
10
+ m.file "/../../../lib/friendly_id/acktive_record/tasks/friendly_id.rake", "lib/tasks/friendly_id.rake"
11
11
  end
12
12
  end
13
13
  end
data/lib/friendly_id.rb CHANGED
@@ -1,9 +1,12 @@
1
+ require "active_support/core_ext/class/attribute_accessors"
1
2
  require File.join(File.dirname(__FILE__), "friendly_id", "slug_string")
2
3
  require File.join(File.dirname(__FILE__), "friendly_id", "configuration")
3
4
  require File.join(File.dirname(__FILE__), "friendly_id", "status")
4
5
  require File.join(File.dirname(__FILE__), "friendly_id", "finders")
5
6
  require File.join(File.dirname(__FILE__), "friendly_id", "slugged")
6
7
 
8
+
9
+
7
10
  # FriendlyId is a comprehensive Ruby library for slugging and permalinks with
8
11
  # ActiveRecord.
9
12
  # @author Norman Clarke
@@ -30,11 +33,6 @@ module FriendlyId
30
33
  # @param [Hash] options For valid configuration options, see
31
34
  # {FriendlyId::Configuration}.
32
35
  #
33
- # @param [block] block An optional block through which to filter the
34
- # friendly_id text; see {FriendlyId::Configuration#normalizer}. Note that
35
- # passing a block parameter is now deprecated and will be removed
36
- # from FriendlyId 3.0.
37
- #
38
36
  # @example
39
37
  #
40
38
  # class User < ActiveRecord::Base
@@ -46,7 +44,7 @@ module FriendlyId
46
44
  # end
47
45
  #
48
46
  # @see FriendlyId::Configuration
49
- def has_friendly_id(method, options = {}, &block)
47
+ def has_friendly_id(method, options = {})
50
48
  raise NotImplementedError
51
49
  end
52
50
 
@@ -1,5 +1,5 @@
1
1
  module FriendlyId
2
- module ActiveRecord2
2
+ module AcktiveRecord
3
3
 
4
4
  class Configuration < FriendlyId::Configuration
5
5
 
@@ -7,7 +7,7 @@ module FriendlyId
7
7
  # FriendlyId will look for a column named +cached_slug+ and use it automatically
8
8
  # if it exists. If for some reason you have a column named +cached_slug+
9
9
  # but don't want FriendlyId to modify it, pass the option
10
- # +:cache_column => false+ to {FriendlyId::ActiveRecord2#has_friendly_id has_friendly_id}.
10
+ # +:cache_column => false+ to {FriendlyId::AcktiveRecord#has_friendly_id has_friendly_id}.
11
11
  attr_accessor :cache_column
12
12
 
13
13
  # An array of classes for which the configured class serves as a
@@ -2,7 +2,7 @@ module FriendlyId
2
2
 
3
3
  # The adapter for Ruby on Rails's ActiveRecord. Compatible with AR 2.2.x -
4
4
  # 2.3.x.
5
- module ActiveRecord2
5
+ module AcktiveRecord
6
6
 
7
7
  # The classes in this module are used internally by FriendlyId, and exist
8
8
  # largely to avoid polluting the ActiveRecord models with too many
@@ -22,10 +22,10 @@ module FriendlyId
22
22
  attr :model_class
23
23
  attr :options
24
24
 
25
- def initialize(ids, model_class, options={})
26
- @ids = ids
25
+ def initialize(model_class, *args, &block)
27
26
  @model_class = model_class
28
- @options = options
27
+ @ids = args.shift
28
+ @options = args.first.kind_of?(Hash) ? args.first : {}
29
29
  end
30
30
 
31
31
  def method_missing(symbol, *args)
@@ -37,22 +37,20 @@ module FriendlyId
37
37
  @finder ||= finder_class.new(ids, model_class, options)
38
38
  end
39
39
 
40
- private
41
-
42
40
  def finder_class
43
41
  @finder_class ||= slugged? ? slugged_finder_class : simple_finder_class
44
42
  end
45
43
 
44
+ def multiple?
45
+ ids.kind_of? Array
46
+ end
47
+
46
48
  private
47
49
 
48
50
  def cache_available?
49
51
  !! model_class.friendly_id_config.cache_column
50
52
  end
51
53
 
52
- def multiple?
53
- ids.kind_of? Array
54
- end
55
-
56
54
  def multiple_slugged_finder_class
57
55
  use_cache? ? SluggedModel::CachedMultipleFinder : SluggedModel::MultipleFinder
58
56
  end
@@ -136,5 +134,21 @@ module FriendlyId
136
134
  end
137
135
 
138
136
  end
137
+
138
+ # The methods in this module override ActiveRecord's +find_one+ and
139
+ # +find_some+ to add FriendlyId's features.
140
+ module FinderMethods
141
+
142
+ def find(*args, &block)
143
+ finder = Finders::FinderProxy.new(self, *args, &block)
144
+ if finder.multiple?
145
+ finder.find
146
+ else
147
+ finder.unfriendly? ? super : finder.find or super
148
+ end
149
+ end
150
+
151
+ end
152
+
139
153
  end
140
154
  end
@@ -1,5 +1,5 @@
1
1
  module FriendlyId
2
- module ActiveRecord2
2
+ module AcktiveRecord
3
3
 
4
4
  module SimpleModel
5
5
 
@@ -21,11 +21,11 @@ module FriendlyId
21
21
  class MultipleFinder
22
22
 
23
23
  include FriendlyId::Finders::Base
24
- include FriendlyId::ActiveRecord2::Finders::Multiple
24
+ include FriendlyId::AcktiveRecord::Finders::Multiple
25
25
  include SimpleFinder
26
26
 
27
27
  def find
28
- @results = with_scope(:find => options) { find_every :conditions => conditions }
28
+ @results = model_class.scoped(:conditions => conditions).scoped(options).all(options)
29
29
  raise(::ActiveRecord::RecordNotFound, error_message) if @results.size != expected_size
30
30
  friendly_results.each { |result| result.friendly_id_status.name = result.to_param }
31
31
  @results
@@ -50,7 +50,7 @@ module FriendlyId
50
50
  include SimpleFinder
51
51
 
52
52
  def find
53
- result = with_scope(:find => find_options) { find_initial options }
53
+ result = model_class.scoped(find_options).first(options)
54
54
  raise ::ActiveRecord::RecordNotFound.new if friendly? && !result
55
55
  result.friendly_id_status.name = id if result
56
56
  result
@@ -64,56 +64,14 @@ module FriendlyId
64
64
 
65
65
  end
66
66
 
67
- # The methods in this module override ActiveRecord's +find_one+ and
68
- # +find_some+ to add FriendlyId's features.
69
- module FinderMethods
70
- protected
71
-
72
- def find_one(id, options)
73
- finder = Finders::FinderProxy.new(id, self, options)
74
- !finder.friendly? ? super : finder.find
75
- end
76
-
77
- def find_some(ids_and_names, options)
78
- Finders::FinderProxy.new(ids_and_names, self, options).find
79
- end
80
- end
81
-
82
- # These methods will be removed in FriendlyId 3.0.
83
- module DeprecatedMethods
84
-
85
- # Was the record found using one of its friendly ids?
86
- # @deprecated Please use #friendly_id_status.friendly?
87
- def found_using_friendly_id?
88
- warn("found_using_friendly_id? is deprecated and will be removed in 3.0. Please use #friendly_id_status.friendly?")
89
- friendly_id_status.friendly?
90
- end
91
-
92
- # Was the record found using its numeric id?
93
- # @deprecated Please use #friendly_id_status.numeric?
94
- def found_using_numeric_id?
95
- warn("found_using_numeric_id is deprecated and will be removed in 3.0. Please use #friendly_id_status.numeric?")
96
- friendly_id_status.numeric?
97
- end
98
-
99
- # Was the record found using an old friendly id, or its numeric id?
100
- # @deprecated Please use !#friendly_id_status.best?
101
- def has_better_id?
102
- warn("has_better_id? is deprecated and will be removed in 3.0. Please use !#friendly_id_status.best?")
103
- ! friendly_id_status.best?
104
- end
105
-
106
- end
107
-
108
67
  def self.included(base)
109
68
  base.class_eval do
110
69
  column = friendly_id_config.column
111
- validate :validate_friendly_id
112
- validates_presence_of column
113
- validates_length_of column, :maximum => friendly_id_config.max_length
70
+ validate :validate_friendly_id, :unless => :skip_friendly_id_validations
71
+ validates_presence_of column, :unless => :skip_friendly_id_validations
72
+ validates_length_of column, :maximum => friendly_id_config.max_length, :unless => :skip_friendly_id_validations
114
73
  after_update :update_scopes
115
- extend FinderMethods
116
- include DeprecatedMethods
74
+ extend FriendlyId::AcktiveRecord::FinderMethods
117
75
  end
118
76
  end
119
77
 
@@ -150,6 +108,10 @@ module FriendlyId
150
108
  end
151
109
  end
152
110
 
111
+ def skip_friendly_id_validations
112
+ friendly_id.nil? && self.class.friendly_id_config.allow_nil?
113
+ end
114
+
153
115
  def validate_friendly_id
154
116
  if result = friendly_id_config.reserved_error_message(friendly_id)
155
117
  self.errors.add(*result)
@@ -0,0 +1,66 @@
1
+ # A Slug is a unique, human-friendly identifier for an ActiveRecord.
2
+ class Slug < ::ActiveRecord::Base
3
+
4
+ table_name = "slugs"
5
+ belongs_to :sluggable, :polymorphic => true
6
+ before_save :enable_name_reversion, :set_sequence
7
+ validate :validate_name
8
+ send FriendlyId::AcktiveRecord::Compat.scope_method, :similar_to, lambda {|slug| {:conditions => {
9
+ :name => slug.name,
10
+ :scope => slug.scope,
11
+ :sluggable_type => slug.sluggable_type
12
+ },
13
+ :order => "sequence ASC"
14
+ }
15
+ }
16
+
17
+ # Whether this slug is the most recent of its owner's slugs.
18
+ def current?
19
+ sluggable.slug == self
20
+ end
21
+
22
+ def outdated?
23
+ !current?
24
+ end
25
+
26
+ def to_friendly_id
27
+ sequence > 1 ? friendly_id_with_sequence : name
28
+ end
29
+
30
+ # Raise a FriendlyId::SlugGenerationError if the slug name is blank.
31
+ def validate_name
32
+ if name.blank?
33
+ raise FriendlyId::BlankError.new("slug.name can not be blank.")
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ # If we're renaming back to a previously used friendly_id, delete the
40
+ # slug so that we can recycle the name without having to use a sequence.
41
+ def enable_name_reversion
42
+ sluggable.slugs.find_all_by_name_and_scope(name, scope).each { |slug| slug.destroy }
43
+ end
44
+
45
+ def friendly_id_with_sequence
46
+ "#{name}#{separator}#{sequence}"
47
+ end
48
+
49
+ def similar_to_other_slugs?
50
+ !similar_slugs.empty?
51
+ end
52
+
53
+ def similar_slugs
54
+ self.class.similar_to(self)
55
+ end
56
+
57
+ def separator
58
+ sluggable.friendly_id_config.sequence_separator
59
+ end
60
+
61
+ def set_sequence
62
+ return unless new_record?
63
+ self.sequence = similar_slugs.last.sequence.succ if similar_to_other_slugs?
64
+ end
65
+
66
+ end