friendly_id 3.1.6 → 3.1.7.pre

Sign up to get free protection for your applications and to get access to all the features.
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