friendly_id 3.1.6 → 3.1.7.pre

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/Changelog.md CHANGED
@@ -6,6 +6,14 @@ suggestions, ideas and improvements to FriendlyId.
6
6
  * Table of Contents
7
7
  {:toc}
8
8
 
9
+ ## 3.1.7 (NOT RELEASED YET)
10
+
11
+ * Reserved words can now be regular expressions.
12
+ * Fix broken SQL when finding with a nil scope on AR3 (Tony Primerano)
13
+ * Slug#sluggable now works around default scopes (Cyrille Stepanyk)
14
+ * Explicitly make slug attributes accessible (William Melody)
15
+ * Improve abstraction support for DataMapper and Sequel (Alex Coles).
16
+
9
17
  ## 3.1.6 (2010-09-02)
10
18
 
11
19
  * Fix missing sluggable type in AR3 slug query. This was a fairly major oversight, and if you
data/Contributors.md CHANGED
@@ -2,6 +2,7 @@ We are grateful for many contributions from the Ruby and Rails
2
2
  community, in particular from the following people:
3
3
 
4
4
  * Adam Cigánek
5
+ * Alex Coles
5
6
  * Alexander Gräfe
6
7
  * Alistair Holt
7
8
  * Andre Duffeck
@@ -11,6 +12,7 @@ community, in particular from the following people:
11
12
  * Brian Collins
12
13
  * Bruno Michel
13
14
  * Chris Nolan
15
+ * Cyrille Stepanyk
14
16
  * David Ramalho
15
17
  * Diego Carrion
16
18
  * Diego R. V.
@@ -38,3 +40,4 @@ community, in particular from the following people:
38
40
  * Steve Luscher
39
41
  * Steven Noble
40
42
  * Tim Kadom
43
+ * William Melody
data/Guide.md CHANGED
@@ -110,6 +110,7 @@ After installing either as a gem or plugin, run:
110
110
 
111
111
 
112
112
  rails generate friendly_id
113
+ # or "./script generate friendly_id" on Rails 2.3
113
114
  rake db:migrate
114
115
 
115
116
  This will install the Rake tasks and slug migration for FriendlyId. If you are
@@ -128,9 +129,9 @@ FriendlyId is configured in your model using the `has_friendly_id` method:
128
129
  class Post < ActiveRecord::Base
129
130
  # use the "title" column as the basis of the friendly_id, and use slugs
130
131
  has_friendly_id :title, :use_slug => true,
131
- # remove accents and other diacritics from Western characters
132
+ # remove accents and other diacritics from Latin characters
132
133
  :approximate_ascii => true,
133
- # don't use slugs longer than 50 chars
134
+ # don't use slugs larger than 50 bytes
134
135
  :max_length => 50
135
136
  end
136
137
 
@@ -193,7 +194,7 @@ Greek, etc:
193
194
 
194
195
  ### ASCII Slugs
195
196
 
196
- You can also configure FriendlyId using `:strip_non_ascii` to completely delete
197
+ You can also configure FriendlyId using `:strip_non_ascii` to simply delete
197
198
  any non-ascii characters:
198
199
 
199
200
  class Post < ActiveRecord::Base
@@ -244,14 +245,13 @@ order to fine-tune the output:
244
245
  end
245
246
 
246
247
  The normalize_friendly_id method takes a single argument and receives an
247
- instance of {FriendlyId::SlugString}, a class which wraps a regular Ruby
248
- string with some additional formatting options inherits Multibyte support from
249
- ActiveSupport::Multibyte::Chars.
248
+ instance of {FriendlyId::SlugString}, a class which wraps a regular Ruby string
249
+ with additional formatting options.
250
250
 
251
- ### Converting non-Western characters to ASCII with Stringex
251
+ ### Converting non-Latin characters to ASCII with Stringex
252
252
 
253
253
  Stringex is a library which provides some interesting options for transliterating
254
- non-Western strings to ASCII:
254
+ non-Latin strings to ASCII:
255
255
 
256
256
  "你好".to_url => "ni-hao"
257
257
 
@@ -260,13 +260,21 @@ the `stringex` gem, and overriding the `normalize_friendly_id` method in your
260
260
  model:
261
261
 
262
262
  class City < ActiveRecord::Base
263
-
264
263
  def normalize_friendly_id(text)
265
264
  text.to_url
266
265
  end
267
-
268
266
  end
269
267
 
268
+ However, be aware of some limitations of Stringex - it just does a context-free
269
+ character-by-character approximation for Unicode strings without sensitivity to
270
+ the string's language. This means, for example, that the Han characters used by
271
+ Japanese, Mandarin, Cantonese, and other languages are all replaced with the
272
+ same ASCII text. For Han characters, Stringex uses Mandarin, which makes its
273
+ output on Japanese text useless. You can read more about the limitations of
274
+ Stringex in [the documentation for
275
+ Unidecoder](http://search.cpan.org/~sburke/Text-Unidecode-0.04/lib/Text/Unidecode.pm#DESIGN_GOALS_AND_CONSTRAINTS),
276
+ the Perl library upon which Stringex is based.
277
+
270
278
  ## Redirecting to the Current Friendly URL
271
279
 
272
280
  FriendlyId maintains a history of your record's older slugs, so if your
@@ -316,6 +324,9 @@ You can also override the default used in
316
324
  FriendlyId. If you change this value in an existing application, be sure to
317
325
  {file:Guide.md#regenerating_slugs regenerate the slugs} afterwards.
318
326
 
327
+ For reasons I hope are obvious, you can't change this value to "-". If you try,
328
+ FriendlyId will raise an error.
329
+
319
330
  ## Reserved Words
320
331
 
321
332
  You can configure a list of strings as reserved so that, for example, you
@@ -331,6 +342,9 @@ Reserved words are configured using the `:reserved_words` option:
331
342
  has_friendly_id :name, :use_slug => true, :reserved_words => ["my", "values"]
332
343
  end
333
344
 
345
+ The reserved words can be specified as an array or (since 3.1.7) as a regular
346
+ expression.
347
+
334
348
  The strings "new" and "index" are reserved by default. When you attempt to
335
349
  store a reserved value, FriendlyId raises a
336
350
  {FriendlyId::ReservedError}. You can also override the default
@@ -372,9 +386,8 @@ A few warnings when using this feature:
372
386
  unless you have already invoked `attr_accessible`. If you wish to use
373
387
  `attr_accessible`, you must invoke it BEFORE you invoke `has_friendly_id` in
374
388
  your class.
375
- * FriendlyId can not query against the slug cache when you pass a :scope
376
- argument to #find. Try to avoid passing an array of friendly id's and a
377
- scope to #find, as this will result in weak performance.
389
+ * Cached slugs [are incompatible with scopes](#scoped_models_and_cached_slugs) and
390
+ are ignored if your model uses the `:scope option`.
378
391
 
379
392
  ### Using a custom column name
380
393
 
@@ -434,7 +447,7 @@ the slug "joes-diner" if it's located in a different city:
434
447
 
435
448
  Restaurant.find("joes-diner", :scope => "seattle") # returns 1 record
436
449
  Restaurant.find("joes-diner", :scope => "chicago") # returns 1 record
437
- Restaurant.find("joes-diner") # returns both records
450
+ Restaurant.find(:all, "joes-diner") # returns both records
438
451
 
439
452
  The value for the `:scope` key in your model can be a custom method you
440
453
  define, or the name of a relation. If it's the name of a relation, then the
@@ -465,6 +478,15 @@ this up:
465
478
  # in controllers
466
479
  @restaurant = Restaurant.find(params[:id], :scope => params[:scope])
467
480
 
481
+ ### Scoped Models and Cached Slugs
482
+
483
+ Be aware that you can't use cached slugs with scoped models. This is because in
484
+ order to uniquely identify a scoped model, you need to consult two fields rather
485
+ than one. I've considered adding a "cached scope" column as well as one for the
486
+ cached slug, but I'm not convinced this is a good idea. FriendlyId 3.1.6 and
487
+ above included significant performance optimizations for models that don't use
488
+ cached slugs, so in practice this ends up being [less of a performance problem
489
+ than you may imagine](#some_benchmarks).
468
490
 
469
491
  ## FriendlyId Rake Tasks
470
492
 
data/README.md CHANGED
@@ -31,6 +31,9 @@ FriendlyId is compatible with Active Record **2.3.x** and **3.0**.
31
31
 
32
32
  ## Rails Quickstart
33
33
 
34
+ Note that the example below uses Rails 3. But don't worry: FriendlyId will
35
+ continue to support 2.3.x until Rails 3.1 is released.
36
+
34
37
  gem install friendly_id
35
38
 
36
39
  rails new my_app
@@ -58,11 +61,13 @@ FriendlyId is compatible with Active Record **2.3.x** and **3.0**.
58
61
 
59
62
  ## Sequel and DataMapper, too
60
63
 
61
- FriendlyId has experimental support for Sequel. Support for Datamapper is
62
- in progress. To find out more, check out the Github projects:
64
+ [Alex Coles](http://github.com/myabc) maintains an implemntation of
65
+ [FriendlyId for DataMapper](http://github.com/myabc/friendly_id_datamapper) that supports almost
66
+ all the features of the Active Record version.
63
67
 
64
- * [http://github.com/norman/friendly_id_sequel](http://github.com/norman/friendly_id_sequel)
65
- * [http://github.com/myabc/friendly_id_datamapper](http://github.com/myabc/friendly_id_datamapper)
68
+ Norman Clarke maintains an implementation of
69
+ [FriendlyId forSequel](http://github.com/norman/friendly_id_sequel) with some of the features
70
+ of the Active Record version.
66
71
 
67
72
  ## Bugs
68
73
 
data/Rakefile CHANGED
@@ -41,6 +41,18 @@ namespace :test do
41
41
  Rake::TestTask.new(:friendly_id) { |t| t.pattern = "test/*_test.rb" }
42
42
  Rake::TestTask.new(:ar) { |t| t.pattern = "test/active_record_adapter/*_test.rb" }
43
43
 
44
+ task :pre_release do
45
+ ["ree-1.8.7-2010.02", "ruby-1.9.2-p0"].each do |ruby|
46
+ ["sqlite3", "mysql", "postgres"].each do |driver|
47
+ [2, 3].each do |ar_version|
48
+ command = "rake-#{ruby} test AR=#{ar_version} DB=#{driver}"
49
+ puts command
50
+ puts `#{command}`
51
+ end
52
+ end
53
+ end
54
+ end
55
+
44
56
  namespace :rails do
45
57
  task :plugin do
46
58
  rm_rf "fid"
@@ -48,7 +60,6 @@ namespace :test do
48
60
  sh "cd fid; rake test"
49
61
  end
50
62
  end
51
-
52
63
  end
53
64
 
54
65
  task :pushdocs do
@@ -44,7 +44,8 @@ module FriendlyId
44
44
  end
45
45
 
46
46
  def scope_for(record)
47
- scope? ? record.send(scope).to_param : nil
47
+ return nil unless scope?
48
+ record.send(scope).nil? ? nil : record.send(scope).to_param
48
49
  end
49
50
 
50
51
  def scopes_over?(klass)
@@ -106,12 +106,16 @@ module FriendlyId
106
106
  clause ? clause + " OR #{string}" : string
107
107
  end
108
108
  if fc.scope?
109
- scope = connection.quote(friendly_id_scope)
110
- conditions = "slugs.scope = %s AND (%s)" % [scope, conditions]
109
+ conditions = if friendly_id_scope
110
+ scope = connection.quote(friendly_id_scope)
111
+ "slugs.scope = %s AND (%s)" % [scope, conditions]
112
+ else
113
+ "slugs.scope IS NULL AND (%s)" % [conditions]
114
+ end
111
115
  end
112
116
  sql = "SELECT sluggable_id FROM slugs WHERE (%s)" % conditions
113
117
  connection.select_values sql
114
- end
118
+ end
115
119
 
116
120
  def validate_expected_size!(ids, result)
117
121
  expected_size =
@@ -1,9 +1,9 @@
1
1
  # A Slug is a unique, human-friendly identifier for an ActiveRecord.
2
2
  class Slug < ::ActiveRecord::Base
3
-
3
+ attr_writer :sluggable
4
+ attr_accessible :name, :scope, :sluggable, :sequence
4
5
  def self.named_scope(*args, &block) scope(*args, &block) end if FriendlyId.on_ar3?
5
6
  table_name = "slugs"
6
- belongs_to :sluggable, :polymorphic => true
7
7
  before_save :enable_name_reversion, :set_sequence
8
8
  validate :validate_name
9
9
  named_scope :similar_to, lambda {|slug| {:conditions => {
@@ -15,6 +15,16 @@ class Slug < ::ActiveRecord::Base
15
15
  }
16
16
  }
17
17
 
18
+ def sluggable
19
+ sluggable_id && !@sluggable and begin
20
+ klass = sluggable_type.constantize
21
+ klass.send(:with_exclusive_scope) do
22
+ @sluggable = klass.find(sluggable_id.to_i)
23
+ end
24
+ end
25
+ @sluggable
26
+ end
27
+
18
28
  # Whether this slug is the most recent of its owner's slugs.
19
29
  def current?
20
30
  sluggable.slug == self
@@ -9,16 +9,16 @@ module FriendlyId
9
9
  # attributes below for information on the possible options.
10
10
  #
11
11
  # @example
12
- # has_friendly_id :name,
13
- # :use_slug => true,
14
- # :max_length => 150,
15
- # :approximate_ascii => true,
16
- # :ascii_approximation_options => :german,
17
- # :sequence_separator => ":",
18
- # :reserved_words => ["reserved", "words"],
19
- # :scope => :country,
20
- # :cache_column => :my_cache_column_name
21
- # # etc.
12
+ # has_friendly_id :name,
13
+ # :use_slug => true,
14
+ # :max_length => 150,
15
+ # :approximate_ascii => true,
16
+ # :ascii_approximation_options => :german,
17
+ # :sequence_separator => ":",
18
+ # :reserved_words => ["reserved", "words"],
19
+ # :scope => :country,
20
+ # :cache_column => :my_cache_column_name
21
+ # # etc.
22
22
  class Configuration
23
23
 
24
24
  DEFAULTS = {
@@ -98,12 +98,21 @@ module FriendlyId
98
98
  false
99
99
  end
100
100
 
101
- def reserved_words=(*words)
102
- @reserved_words = words.flatten.uniq
101
+ def reserved_words=(*args)
102
+ if args.first.kind_of?(Regexp)
103
+ @reserved_words = args.first
104
+ else
105
+ @reserved_words = args.flatten.uniq
106
+ end
103
107
  end
104
108
 
105
109
  def reserved?(word)
106
- reserved_words.include? word.to_s
110
+ word = word.to_s
111
+ if reserved_words.kind_of?(Regexp)
112
+ reserved_words =~ word
113
+ else
114
+ reserved_words.include?(word)
115
+ end
107
116
  end
108
117
 
109
118
  def reserved_error_message(word)
@@ -136,7 +145,7 @@ module FriendlyId
136
145
  end
137
146
 
138
147
  %w[approximate_ascii scope strip_non_ascii use_slug].each do |method|
139
- class_eval(<<-EOM, __FILE__, __LINE__ +1)
148
+ class_eval(<<-EOM, __FILE__, __LINE__ + 1)
140
149
  def #{method}?
141
150
  !! #{method}
142
151
  end
@@ -60,7 +60,7 @@ module FriendlyId
60
60
  assert !i
61
61
  else
62
62
  instance = i
63
- assert_not_empty instance.errors
63
+ assert !instance.errors.empty?
64
64
  end
65
65
  end
66
66
  end
@@ -128,6 +128,20 @@ module FriendlyId
128
128
  assert_equal other_instance.friendly_id, instance.friendly_id
129
129
  end
130
130
 
131
+ test "reserved words can be specified as a regular expression" do
132
+ klass.friendly_id_config.stubs(:reserved_words).returns(/jo/)
133
+ assert_validation_error do
134
+ klass.send(create_method, :name => "joe")
135
+ end
136
+ end
137
+
138
+ test "should not raise reserved error unless regexp matches" do
139
+ klass.friendly_id_config.stubs(:reserved_words).returns(/ddsadad/)
140
+ assert_nothing_raised do
141
+ klass.send(create_method, :name => "joe")
142
+ end
143
+ end
144
+
131
145
  end
132
146
 
133
147
  module Simple
@@ -286,7 +300,7 @@ module FriendlyId
286
300
 
287
301
  [:record, :name].each do |symbol|
288
302
  test "should have #{symbol} after find using friendly_id" do
289
- instance2 = klass.find(instance.friendly_id)
303
+ instance2 = klass.send(find_method, instance.friendly_id)
290
304
  assert_not_nil instance2.friendly_id_status.send(symbol)
291
305
  end
292
306
  end
@@ -2,8 +2,8 @@ module FriendlyId
2
2
  module Version
3
3
  MAJOR = 3
4
4
  MINOR = 1
5
- TINY = 6
6
- BUILD = nil
5
+ TINY = 7
6
+ BUILD = 'pre'
7
7
  STRING = [MAJOR, MINOR, TINY, BUILD].compact.join('.')
8
8
  end
9
9
  end
@@ -3,7 +3,7 @@ require "logger"
3
3
  require "active_record"
4
4
  begin
5
5
  require "active_support/log_subscriber"
6
- rescue MissingSourceFile
6
+ rescue LoadError
7
7
  end
8
8
 
9
9
  # If you want to see the ActiveRecord log, invoke the tests using `rake test LOG=true`
@@ -15,11 +15,9 @@ require File.expand_path("../support/models", __FILE__)
15
15
  require File.expand_path('../core', __FILE__)
16
16
  require File.expand_path('../slugged', __FILE__)
17
17
 
18
- local_db_settings = File.expand_path("../support/database.yml", __FILE__)
19
- default_db_settings = File.expand_path("../support/database.sqlite3.yml", __FILE__)
20
-
21
- db_settings = File.exists?(local_db_settings) ? local_db_settings : default_db_settings
22
- ActiveRecord::Base.establish_connection(YAML::load(File.open(db_settings)))
18
+ driver = (ENV["DB"] or "sqlite3").downcase
19
+ db_yaml = File.expand_path("../support/database.#{driver}.yml", __FILE__)
20
+ ActiveRecord::Base.establish_connection(YAML::load(File.open(db_yaml)))
23
21
 
24
22
  class ActiveRecord::Base
25
23
  def log_protected_attribute_removal(*args) end
@@ -148,3 +146,5 @@ end
148
146
  class Company < ActiveRecord::Base
149
147
  has_many :sites, :as => :owner
150
148
  end
149
+
150
+ puts "Using: #{RUBY_VERSION}, #{driver}, AR#{ENV["AR"] or 3}"
@@ -0,0 +1,30 @@
1
+ require File.expand_path('../ar_test_helper', __FILE__)
2
+
3
+ ActiveRecord::Migration.create_table :articles do |t|
4
+ t.string :name
5
+ t.string :status
6
+ end
7
+
8
+ class Article < ActiveRecord::Base
9
+ has_friendly_id :name, :use_slug => true
10
+ default_scope :conditions => "articles.status = 'published'"
11
+ end
12
+
13
+ module FriendlyId
14
+ module Test
15
+ module ActiveRecordAdapter
16
+ class DefaultScopeTest < ::Test::Unit::TestCase
17
+
18
+ def setup
19
+ Article.delete_all
20
+ Slug.delete_all
21
+ end
22
+
23
+ test "slug should load sluggable without default scope" do
24
+ Article.create!(:name => "hello world", :status => "draft")
25
+ assert_not_nil Slug.first.sluggable
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -86,6 +86,11 @@ module FriendlyId
86
86
  assert Resident.find(@resident.friendly_id, :scope => @resident.country)
87
87
  end
88
88
 
89
+ test "should find a single scoped record with a nil scope" do
90
+ nomad = Resident.create!(:name => "Homer", :country => nil)
91
+ assert Resident.find(nomad.friendly_id, :scope => nil)
92
+ end
93
+
89
94
  test "should find a multiple scoped records with a scope" do
90
95
  r1 = Resident.create!(:name => "John Smith", :country => @usa)
91
96
  r2 = Resident.create!(:name => "Jane Smith", :country => @usa)
@@ -18,8 +18,11 @@ module FriendlyId
18
18
  @instance ||= klass.create! :name => "hello world"
19
19
  end
20
20
 
21
+ def find_method
22
+ :find
23
+ end
24
+
21
25
  end
22
26
  end
23
27
  end
24
28
  end
25
-
metadata CHANGED
@@ -1,12 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: friendly_id
3
3
  version: !ruby/object:Gem::Version
4
- prerelease: false
4
+ hash: 961916000
5
+ prerelease: true
5
6
  segments:
6
7
  - 3
7
8
  - 1
8
- - 6
9
- version: 3.1.6
9
+ - 7
10
+ - pre
11
+ version: 3.1.7.pre
10
12
  platform: ruby
11
13
  authors:
12
14
  - Norman Clarke
@@ -16,24 +18,25 @@ autorequire:
16
18
  bindir: bin
17
19
  cert_chain: []
18
20
 
19
- date: 2010-09-02 00:00:00 -03:00
21
+ date: 2010-09-14 00:00:00 -03:00
20
22
  default_executable:
21
23
  dependencies:
22
24
  - !ruby/object:Gem::Dependency
25
+ prerelease: false
26
+ type: :runtime
23
27
  name: babosa
24
- requirement: &id001 !ruby/object:Gem::Requirement
28
+ version_requirements: &id001 !ruby/object:Gem::Requirement
25
29
  none: false
26
30
  requirements:
27
31
  - - ~>
28
32
  - !ruby/object:Gem::Version
33
+ hash: 23
29
34
  segments:
30
35
  - 0
31
36
  - 2
32
37
  - 0
33
38
  version: 0.2.0
34
- type: :runtime
35
- prerelease: false
36
- version_requirements: *id001
39
+ requirement: *id001
37
40
  description: " FriendlyId is the \"Swiss Army bulldozer\" of slugging and permalink plugins\n for Ruby on Rails. It allows you to create pretty URL\xE2\x80\x99s and work with\n human-friendly strings as if they were numeric ids for ActiveRecord models.\n"
38
41
  email:
39
42
  - norman@njclarke.com
@@ -81,6 +84,7 @@ files:
81
84
  - test/active_record_adapter/core.rb
82
85
  - test/active_record_adapter/custom_normalizer_test.rb
83
86
  - test/active_record_adapter/custom_table_name_test.rb
87
+ - test/active_record_adapter/default_scope_test.rb
84
88
  - test/active_record_adapter/optimistic_locking_test.rb
85
89
  - test/active_record_adapter/scoped_model_test.rb
86
90
  - test/active_record_adapter/simple_test.rb
@@ -116,19 +120,21 @@ required_ruby_version: !ruby/object:Gem::Requirement
116
120
  requirements:
117
121
  - - ">="
118
122
  - !ruby/object:Gem::Version
119
- hash: -182801105258368267
123
+ hash: 3
120
124
  segments:
121
125
  - 0
122
126
  version: "0"
123
127
  required_rubygems_version: !ruby/object:Gem::Requirement
124
128
  none: false
125
129
  requirements:
126
- - - ">="
130
+ - - ">"
127
131
  - !ruby/object:Gem::Version
128
- hash: -182801105258368267
132
+ hash: 25
129
133
  segments:
130
- - 0
131
- version: "0"
134
+ - 1
135
+ - 3
136
+ - 1
137
+ version: 1.3.1
132
138
  requirements: []
133
139
 
134
140
  rubyforge_project: friendly-id
@@ -141,6 +147,7 @@ test_files:
141
147
  - test/active_record_adapter/cached_slug_test.rb
142
148
  - test/active_record_adapter/custom_normalizer_test.rb
143
149
  - test/active_record_adapter/custom_table_name_test.rb
150
+ - test/active_record_adapter/default_scope_test.rb
144
151
  - test/active_record_adapter/optimistic_locking_test.rb
145
152
  - test/active_record_adapter/scoped_model_test.rb
146
153
  - test/active_record_adapter/simple_test.rb