friendly_id 3.1.3 → 3.1.4
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 +5 -0
- data/Guide.md +52 -39
- data/{LICENSE → MIT-LICENSE} +0 -0
- data/README.md +13 -13
- data/Rakefile +2 -0
- data/extras/bench.rb +1 -1
- data/lib/friendly_id/active_record_adapter/finders.rb +5 -5
- data/lib/friendly_id/active_record_adapter/relation.rb +137 -95
- data/lib/friendly_id/active_record_adapter/slugged_model.rb +4 -3
- data/lib/friendly_id/configuration.rb +24 -2
- data/lib/friendly_id/slug_string.rb +2 -4
- data/lib/friendly_id/test.rb +0 -6
- data/lib/friendly_id/version.rb +1 -1
- data/test/active_record_adapter/ar_test_helper.rb +5 -0
- data/test/active_record_adapter/cached_slug_test.rb +5 -0
- data/test/active_record_adapter/core.rb +6 -0
- data/test/active_record_adapter/scoped_model_test.rb +17 -14
- data/test/friendly_id_test.rb +46 -1
- data/test/test_helper.rb +1 -3
- metadata +10 -9
- data/test/active_record_adapter/support/database.yml +0 -6
data/Changelog.md
CHANGED
@@ -6,6 +6,11 @@ suggestions, ideas and improvements to FriendlyId.
|
|
6
6
|
* Table of Contents
|
7
7
|
{:toc}
|
8
8
|
|
9
|
+
## 3.1.4 (2010-08-30)
|
10
|
+
|
11
|
+
* Significantly improve performance of queries using slugs with no cache on AR3.
|
12
|
+
* Fix callbacks being invoked after setting cached slugs.
|
13
|
+
|
9
14
|
## 3.1.3 (2010-08-11)
|
10
15
|
|
11
16
|
* Reverted approach to read-only fix from previous release.
|
data/Guide.md
CHANGED
@@ -5,9 +5,9 @@
|
|
5
5
|
|
6
6
|
## Overview
|
7
7
|
|
8
|
-
FriendlyId is
|
9
|
-
as if they were numeric ids
|
10
|
-
"unfriendly" URL's like
|
8
|
+
FriendlyId is an ORM-centric Ruby library that lets you work with human-friendly
|
9
|
+
strings as if they were numeric ids. Among other things, this facilitates
|
10
|
+
replacing "unfriendly" URL's like:
|
11
11
|
|
12
12
|
http://example.com/states/4323454
|
13
13
|
|
@@ -15,6 +15,10 @@ with "friendly" ones such as:
|
|
15
15
|
|
16
16
|
http://example.com/states/washington
|
17
17
|
|
18
|
+
FriendlyId is typically used with Rails and Active Record, but can also be used in
|
19
|
+
non-Rails applications, and with [Sequel](http://github.com/norman/friendly_id_sequel) and
|
20
|
+
[DataMapper](http://github.com/myabc/friendly_id_datamapper).
|
21
|
+
|
18
22
|
## Simple Models
|
19
23
|
|
20
24
|
The simplest way to use FriendlyId is with a model that has a uniquely indexed
|
@@ -30,9 +34,9 @@ updated. The most common example of this is a user name or login column:
|
|
30
34
|
@user.to_param # returns "joe"
|
31
35
|
redirect_to @user # the URL will be /users/joe
|
32
36
|
|
33
|
-
In this case, FriendlyId assumes you want to use the column as-is;
|
34
|
-
|
35
|
-
|
37
|
+
In this case, FriendlyId assumes you want to use the column as-is; it will never
|
38
|
+
modify the value of the column, and your application should ensure that the value
|
39
|
+
is admissible in a URL:
|
36
40
|
|
37
41
|
class City < ActiveRecord::Base
|
38
42
|
has_friendly_id :name
|
@@ -60,8 +64,8 @@ wish to modify to make them more suitable for use in URL's.
|
|
60
64
|
redirect_to @post # the URL will be /posts/this-is-the-first-post
|
61
65
|
|
62
66
|
If you are unsure whether to use slugs, then your best bet is to use them,
|
63
|
-
because FriendlyId provides many useful features that only work with
|
64
|
-
These features are explained in detail {file:Guide.md#features below}.
|
67
|
+
because FriendlyId provides many useful features that only work with this
|
68
|
+
feature. These features are explained in detail {file:Guide.md#features below}.
|
65
69
|
|
66
70
|
## Installation
|
67
71
|
|
@@ -76,13 +80,13 @@ with Rails 2.3.x. and 3.0.
|
|
76
80
|
|
77
81
|
After installing the gem, add an entry in environment.rb:
|
78
82
|
|
79
|
-
config.gem "friendly_id", :version => "~>
|
83
|
+
config.gem "friendly_id", :version => "~> 3.1"
|
80
84
|
|
81
85
|
### Rails 3.0
|
82
86
|
|
83
87
|
After installing the gem, add an entry in the Gemfile:
|
84
88
|
|
85
|
-
gem "friendly_id", "~> 3.
|
89
|
+
gem "friendly_id", "~> 3.1"
|
86
90
|
|
87
91
|
### As a Plugin
|
88
92
|
|
@@ -94,17 +98,24 @@ However, installing as a gem offers simpler version control than plugin
|
|
94
98
|
installation. Whenever possible, install as a gem instead. Plugin support may
|
95
99
|
eventually be removed in a future version.
|
96
100
|
|
101
|
+
### Future Compatibility
|
102
|
+
|
103
|
+
FriendlyId will always remain compatible with the current release of Rails, and
|
104
|
+
at least one stable release behind. That means that support for 2.3.x will not be
|
105
|
+
dropped until a stable release of 3.1 is out, or possibly longer.
|
106
|
+
|
97
107
|
### Setup
|
98
108
|
|
99
109
|
After installing either as a gem or plugin, run:
|
100
110
|
|
101
|
-
|
111
|
+
|
112
|
+
rails generate friendly_id
|
102
113
|
rake db:migrate
|
103
114
|
|
104
115
|
This will install the Rake tasks and slug migration for FriendlyId. If you are
|
105
|
-
not going to use slugs, you can
|
116
|
+
not going to use slugs, you can use the `skip-migration` option:
|
106
117
|
|
107
|
-
|
118
|
+
rails generate friendly_id --skip-migration
|
108
119
|
|
109
120
|
FriendlyId is now set up and ready for you to use.
|
110
121
|
|
@@ -147,7 +158,7 @@ dashes, and non-word characters other than "-" are removed.
|
|
147
158
|
|
148
159
|
### Replacing Accented Characters
|
149
160
|
|
150
|
-
If your strings use
|
161
|
+
If your strings use Latin characters, you can use the `:approximate_ascii` option to remove
|
151
162
|
accents and other diacritics:
|
152
163
|
|
153
164
|
class City < ActiveRecord::Base
|
@@ -169,7 +180,7 @@ There are special options for some languages:
|
|
169
180
|
|
170
181
|
FriendlyId supports whatever languages are supported by
|
171
182
|
[Babosa](https://github.com/norman/babosa); at the time of writing, this
|
172
|
-
includes German,
|
183
|
+
includes Danish, German, Serbian and Spanish.
|
173
184
|
|
174
185
|
### Unicode Slugs
|
175
186
|
|
@@ -289,8 +300,8 @@ unique if necessary:
|
|
289
300
|
...
|
290
301
|
etc.
|
291
302
|
|
292
|
-
Note that the number is preceded by "--" to distinguish it from
|
293
|
-
rest of the slug. This is important to enable having slugs like:
|
303
|
+
Note that the number is preceded by "--" rather than "-" to distinguish it from
|
304
|
+
the rest of the slug. This is important to enable having slugs like:
|
294
305
|
|
295
306
|
/cars/peugeot-206
|
296
307
|
/cars/peugeot-206--2
|
@@ -298,7 +309,7 @@ rest of the slug. This is important to enable having slugs like:
|
|
298
309
|
You can configure the separator string used by your model by setting the
|
299
310
|
`:sequence_separator` option in `has_friendly_id`:
|
300
311
|
|
301
|
-
has_friendly_id :title, :use_slug => true, :sequence_separator => "
|
312
|
+
has_friendly_id :title, :use_slug => true, :sequence_separator => ":"
|
302
313
|
|
303
314
|
You can also override the default used in
|
304
315
|
{FriendlyId::Configuration::DEFAULTS} to set the value for any model using
|
@@ -334,13 +345,12 @@ Checking the slugs table all the time has an impact on performance, so as of
|
|
334
345
|
### Automatic setup
|
335
346
|
|
336
347
|
To enable slug caching, simply add a column named "cached_slug" to your model.
|
337
|
-
Is also advised to index this column for performance reason.
|
338
348
|
FriendlyId will automatically use this column if it detects it:
|
339
349
|
|
340
350
|
class AddCachedSlugToUsers < ActiveRecord::Migration
|
341
351
|
def self.up
|
342
352
|
add_column :users, :cached_slug, :string
|
343
|
-
add_index :users, :cached_slug
|
353
|
+
add_index :users, :cached_slug, :unique => true
|
344
354
|
end
|
345
355
|
|
346
356
|
def self.down
|
@@ -353,8 +363,7 @@ Then, redo the slugs:
|
|
353
363
|
rake friendly_id:redo_slugs MODEL=User
|
354
364
|
|
355
365
|
FriendlyId will automatically query against the cache column if it's available,
|
356
|
-
which
|
357
|
-
passing an array of friendly ids to #find.
|
366
|
+
which will <a href="#some_benchmarks">improve the performance</a> of many queries.
|
358
367
|
|
359
368
|
A few warnings when using this feature:
|
360
369
|
|
@@ -376,6 +385,8 @@ You can also use a different name for the column if you choose, via the
|
|
376
385
|
has_friendly_id :name, :use_slug => true, :cache_column => 'my_cached_slug'
|
377
386
|
end
|
378
387
|
|
388
|
+
Don't use "slug" or "slugs" because FriendlyId needs those names for its own
|
389
|
+
purposes.
|
379
390
|
|
380
391
|
## Nil slugs and skipping validations
|
381
392
|
|
@@ -504,10 +515,10 @@ Or even better, unless you're using a custom primary key:
|
|
504
515
|
because sorting by a unique integer column is faster than sorting by a date
|
505
516
|
column.
|
506
517
|
|
507
|
-
## MySQL
|
518
|
+
## MySQL MyISAM tables
|
508
519
|
|
509
|
-
Currently, the default FriendlyId migration will not work with
|
510
|
-
because it creates
|
520
|
+
Currently, the default FriendlyId migration will not work with MyISAM tables
|
521
|
+
because it creates an index that's too large. The easiest way to work around
|
511
522
|
this is to change the generated migration to add limits on some column lengths.
|
512
523
|
Please see [this issue](http://github.com/norman/friendly_id/issues#issue/50) in
|
513
524
|
the FriendlyId issue tracker for more information.
|
@@ -537,29 +548,31 @@ enabled. But if it is, then your patches would be very welcome!
|
|
537
548
|
|
538
549
|
|
539
550
|
activerecord (2.3.8)
|
540
|
-
ruby 1.9.
|
541
|
-
friendly_id (3.1.
|
551
|
+
ruby 1.9.2p0 (2010-08-18 revision 29036) [x86_64-darwin10.4.0]
|
552
|
+
friendly_id (3.1.4)
|
553
|
+
sqlite3-ruby (1.3.1)
|
542
554
|
sqlite3 3.6.12 in-memory database
|
543
555
|
|
544
556
|
| DEFAULT | NO_SLUG | SLUG | CACHED_SLUG |
|
545
557
|
------------------------------------------------------------------------------------------------
|
546
|
-
find model by id x1000 | 0.
|
547
|
-
find model using array of ids x1000 | 0.
|
548
|
-
find model using id, then to_param x1000 | 0.
|
558
|
+
find model by id x1000 | 0.370 | 0.503 | 0.940 | 0.562 |
|
559
|
+
find model using array of ids x1000 | 0.612 | 0.615 | 1.054 | 0.957 |
|
560
|
+
find model using id, then to_param x1000 | 0.374 | 0.535 | 1.396 | 0.567 |
|
549
561
|
================================================================================================
|
550
|
-
Total | 1.
|
551
|
-
|
562
|
+
Total | 1.356 | 1.653 | 3.390 | 2.086 |
|
552
563
|
|
553
564
|
|
554
|
-
activerecord (3.0.0
|
555
|
-
ruby 1.9.
|
556
|
-
friendly_id (3.1.
|
565
|
+
activerecord (3.0.0)
|
566
|
+
ruby 1.9.2p0 (2010-08-18 revision 29036) [x86_64-darwin10.4.0]
|
567
|
+
friendly_id (3.1.4)
|
568
|
+
sqlite3-ruby (1.3.1)
|
557
569
|
sqlite3 3.6.12 in-memory database
|
558
570
|
|
559
571
|
| DEFAULT | NO_SLUG | SLUG | CACHED_SLUG |
|
560
572
|
------------------------------------------------------------------------------------------------
|
561
|
-
find model by id x1000 | 0.
|
562
|
-
find model using array of ids x1000 | 0.
|
563
|
-
find model using id, then to_param x1000 | 0.
|
573
|
+
find model by id x1000 | 0.286 | 0.365 | 0.518 | 0.393 |
|
574
|
+
find model using array of ids x1000 | 0.329 | 0.441 | 0.709 | 0.475 |
|
575
|
+
find model using id, then to_param x1000 | 0.321 | 0.332 | 0.976 | 0.399 |
|
564
576
|
================================================================================================
|
565
|
-
Total |
|
577
|
+
Total | 0.936 | 1.138 | 2.203 | 1.266 |
|
578
|
+
|
data/{LICENSE → MIT-LICENSE}
RENAMED
File without changes
|
data/README.md
CHANGED
@@ -19,13 +19,21 @@ 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 [FriendlyId Guide](http://norman.github.com/friendly_id/file.Guide.html).
|
21
21
|
|
22
|
-
FriendlyId is compatible with Active Record 2.3.x and 3.0
|
22
|
+
FriendlyId is compatible with Active Record **2.3.x** and **3.0**.
|
23
|
+
|
24
|
+
## Docs, Info and Support
|
25
|
+
|
26
|
+
* [FriendlyId Guide](http://norman.github.com/friendly_id/file.Guide.html)
|
27
|
+
* [API Docs](http://norman.github.com/friendly_id)
|
28
|
+
* [Google Group](http://groups.google.com/group/friendly_id)
|
29
|
+
* [Source Code](http://github.com/norman/friendly_id/)
|
30
|
+
* [Issue Tracker](http://github.com/norman/friendly_id/issues)
|
23
31
|
|
24
32
|
## Rails Quickstart
|
25
33
|
|
26
34
|
gem install friendly_id
|
27
35
|
|
28
|
-
rails my_app
|
36
|
+
rails new my_app
|
29
37
|
|
30
38
|
cd my_app
|
31
39
|
|
@@ -44,7 +52,7 @@ FriendlyId is compatible with Active Record 2.3.x and 3.0.
|
|
44
52
|
|
45
53
|
User.create! :name => "Joe Schmoe"
|
46
54
|
|
47
|
-
|
55
|
+
rails server
|
48
56
|
|
49
57
|
GET http://0.0.0.0:3000/users/joe-schmoe
|
50
58
|
|
@@ -56,15 +64,7 @@ in progress. To find out more, check out the Github projects:
|
|
56
64
|
* [http://github.com/norman/friendly_id_sequel](http://github.com/norman/friendly_id_sequel)
|
57
65
|
* [http://github.com/myabc/friendly_id_datamapper](http://github.com/myabc/friendly_id_datamapper)
|
58
66
|
|
59
|
-
##
|
60
|
-
|
61
|
-
* [FriendlyId Guide](http://norman.github.com/friendly_id/file.Guide.html)
|
62
|
-
* [API Docs](http://norman.github.com/friendly_id)
|
63
|
-
* [Google Group](http://groups.google.com/group/friendly_id)
|
64
|
-
* [Source Code](http://github.com/norman/friendly_id/)
|
65
|
-
* [Issue Tracker](http://github.com/norman/friendly_id/issues)
|
66
|
-
|
67
|
-
## Bugs:
|
67
|
+
## Bugs
|
68
68
|
|
69
69
|
Please report them on the [Github issue tracker](http://github.com/norman/friendly_id/issues)
|
70
70
|
for this project.
|
@@ -79,7 +79,7 @@ If you have a bug to report, please include the following information:
|
|
79
79
|
If you are able to, it helps even more if you can fork FriendlyId on Github,
|
80
80
|
and add a test that reproduces the error you are experiencing.
|
81
81
|
|
82
|
-
## Credits
|
82
|
+
## Credits
|
83
83
|
|
84
84
|
FriendlyId was created by Norman Clarke, Adrian Mugnolo, and Emilio Tagua.
|
85
85
|
|
data/Rakefile
CHANGED
data/extras/bench.rb
CHANGED
@@ -26,8 +26,8 @@ module FriendlyId
|
|
26
26
|
end
|
27
27
|
|
28
28
|
def find_one
|
29
|
-
return
|
30
|
-
return
|
29
|
+
return find_one_with_cached_slug if cache_column?
|
30
|
+
return find_one_with_slug if use_slugs?
|
31
31
|
@result = scoped(:conditions => ["#{table_name}.#{fc.column} = ?", id]).first(options)
|
32
32
|
assign_status
|
33
33
|
end
|
@@ -56,12 +56,12 @@ module FriendlyId
|
|
56
56
|
|
57
57
|
private
|
58
58
|
|
59
|
-
def
|
59
|
+
def find_one_with_cached_slug
|
60
60
|
@result = scoped(:conditions => ["#{table_name}.#{cache_column} = ?", id]).first(options)
|
61
|
-
assign_status or
|
61
|
+
assign_status or find_one_with_slug
|
62
62
|
end
|
63
63
|
|
64
|
-
def
|
64
|
+
def find_one_with_slug
|
65
65
|
name, seq = id.to_s.parse_friendly_id
|
66
66
|
scope = scoped(:joins => :slugs, :conditions => {:slugs => {:name => name, :sequence => seq}})
|
67
67
|
scope = scope.scoped(:conditions => {:slugs => {:scope => scope_val}}) if fc.scope?
|
@@ -2,128 +2,170 @@ module FriendlyId
|
|
2
2
|
module ActiveRecordAdapter
|
3
3
|
module Relation
|
4
4
|
|
5
|
-
|
5
|
+
class Find
|
6
|
+
extend Forwardable
|
6
7
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
@friendly_id_scope = options.delete(:scope)
|
11
|
-
@friendly_id_scope = @friendly_id_scope.to_param if @friendly_id_scope.respond_to?(:to_param)
|
12
|
-
super
|
13
|
-
end
|
8
|
+
attr :relation
|
9
|
+
attr :ids
|
10
|
+
alias id ids
|
14
11
|
|
15
|
-
|
12
|
+
def_delegators :relation, :arel, :arel_table, :friendly_id_scope,
|
13
|
+
:klass, :limit_value, :offset_value, :where
|
14
|
+
def_delegators :klass, :connection, :friendly_id_config
|
15
|
+
alias fc friendly_id_config
|
16
16
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
if
|
24
|
-
|
25
|
-
|
17
|
+
def initialize(relation, ids)
|
18
|
+
@relation = relation
|
19
|
+
@ids = ids
|
20
|
+
end
|
21
|
+
|
22
|
+
def find_one
|
23
|
+
if fc.cache_column?
|
24
|
+
find_one_with_cached_slug
|
25
|
+
elsif fc.use_slugs?
|
26
|
+
find_one_with_slug
|
26
27
|
else
|
27
|
-
|
28
|
+
find_one_without_slug
|
28
29
|
end
|
29
|
-
rescue ActiveRecord::RecordNotFound => error
|
30
|
-
uses_friendly_id? && friendly_id_config.scope? ? raise_scoped_error(error) : raise(error)
|
31
30
|
end
|
32
|
-
end
|
33
31
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
32
|
+
def find_some
|
33
|
+
ids = @ids.compact.uniq.map {|id| id.respond_to?(:friendly_id_config) ? id.id.to_i : id}
|
34
|
+
friendly_ids, unfriendly_ids = ids.partition {|id| id.friendly_id?}
|
35
|
+
return if friendly_ids.empty?
|
36
|
+
records = friendly_records(friendly_ids, unfriendly_ids).each do |record|
|
37
|
+
record.friendly_id_status.name = ids
|
38
|
+
end
|
39
|
+
validate_expected_size!(ids, records)
|
39
40
|
end
|
40
|
-
validate_expected_size!(ids, records)
|
41
|
-
end
|
42
41
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
:sluggable_type => @klass.base_class.to_s).first
|
49
|
-
if slug
|
50
|
-
record = find_one(slug.sluggable_id.to_i)
|
51
|
-
record.friendly_id_status.name = name
|
52
|
-
record.friendly_id_status.sequence = seq
|
53
|
-
record.friendly_id_status.slug = slug
|
54
|
-
record
|
55
|
-
else
|
56
|
-
find_one_without_friendly(id)
|
42
|
+
def raise_error(error)
|
43
|
+
raise(error) unless fc.scope
|
44
|
+
scope_message = friendly_id_scope || "expected, but none given"
|
45
|
+
message = "%s, scope: %s" % [error.message, scope_message]
|
46
|
+
raise ActiveRecord::RecordNotFound, message
|
57
47
|
end
|
58
|
-
end
|
59
48
|
|
60
|
-
|
61
|
-
|
62
|
-
|
49
|
+
private
|
50
|
+
|
51
|
+
def assign_status
|
52
|
+
return unless @result
|
63
53
|
name, seq = id.to_s.parse_friendly_id
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
else
|
68
|
-
find_one_using_slug(id)
|
54
|
+
@result.friendly_id_status.name = name
|
55
|
+
@result.friendly_id_status.sequence = seq if fc.use_slugs?
|
56
|
+
@result
|
69
57
|
end
|
70
|
-
end
|
71
58
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
end
|
59
|
+
def find_one_without_slug
|
60
|
+
@result = where(fc.column => id).first
|
61
|
+
assign_status
|
62
|
+
end
|
77
63
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
friendly = use_slugs ? slugged_conditions(friendly_ids) : arel_table[column].in(friendly_ids)
|
82
|
-
unfriendly = arel_table[primary_key].in unfriendly_ids
|
83
|
-
if friendly_ids.present? && unfriendly_ids.present?
|
84
|
-
clause = friendly.or(unfriendly)
|
85
|
-
elsif friendly_ids.present?
|
86
|
-
clause = friendly
|
87
|
-
elsif unfriendly_ids.present?
|
88
|
-
clause = unfriendly
|
64
|
+
def find_one_with_cached_slug
|
65
|
+
@result = where(fc.cache_column => id).first
|
66
|
+
assign_status or find_one_with_slug
|
89
67
|
end
|
90
|
-
use_slugs ? includes(:slugs).where(clause) : where(clause)
|
91
|
-
end
|
92
68
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
69
|
+
def find_one_with_slug
|
70
|
+
sluggable_id = sluggable_ids_for([id]).first
|
71
|
+
if sluggable_id
|
72
|
+
name, seq = id.to_s.parse_friendly_id
|
73
|
+
record = relation.send(:find_one_without_friendly, sluggable_id)
|
74
|
+
record.friendly_id_status.name = name
|
75
|
+
record.friendly_id_status.sequence = seq
|
76
|
+
record
|
77
|
+
else
|
78
|
+
relation.send(:find_one_without_friendly, id)
|
79
|
+
end
|
99
80
|
end
|
100
|
-
ids.inject(nil) {|clause, id| clause ? clause.or(conditions.call(id)) : conditions.call(id) }
|
101
|
-
end
|
102
81
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
82
|
+
def friendly_records(friendly_ids, unfriendly_ids)
|
83
|
+
use_slugs_table = fc.use_slugs? && (friendly_id_scope || !fc.cache_column?)
|
84
|
+
return find_some_using_slug(friendly_ids, unfriendly_ids) if use_slugs_table
|
85
|
+
column = fc.cache_column || fc.column
|
86
|
+
friendly = arel_table[column].in(friendly_ids)
|
87
|
+
unfriendly = arel_table[relation.primary_key].in unfriendly_ids
|
88
|
+
if friendly_ids.present? && unfriendly_ids.present?
|
89
|
+
where(friendly.or(unfriendly))
|
107
90
|
else
|
108
|
-
|
91
|
+
where(friendly)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def find_some_using_slug(friendly_ids, unfriendly_ids)
|
96
|
+
ids = [unfriendly_ids + sluggable_ids_for(friendly_ids)].flatten.uniq
|
97
|
+
where(arel_table[relation.primary_key].in(ids))
|
98
|
+
end
|
99
|
+
|
100
|
+
def sluggable_ids_for(ids)
|
101
|
+
return [] if ids.empty?
|
102
|
+
fragment = "(slugs.name = %s AND slugs.sequence = %d)"
|
103
|
+
conditions = ids.inject(nil) do |clause, id|
|
104
|
+
name, seq = id.parse_friendly_id
|
105
|
+
string = fragment % [connection.quote(name), seq]
|
106
|
+
clause ? clause + " OR #{string}" : string
|
109
107
|
end
|
108
|
+
if fc.scope?
|
109
|
+
scope = connection.quote(friendly_id_scope)
|
110
|
+
conditions = "slugs.scope = %s AND (%s)" % [scope, conditions]
|
111
|
+
end
|
112
|
+
connection.select_values "SELECT sluggable_id FROM slugs WHERE (%s)" % conditions
|
113
|
+
end
|
110
114
|
|
111
|
-
|
112
|
-
|
113
|
-
|
115
|
+
def validate_expected_size!(ids, result)
|
116
|
+
expected_size =
|
117
|
+
if limit_value && ids.size > limit_value
|
118
|
+
limit_value
|
119
|
+
else
|
120
|
+
ids.size
|
121
|
+
end
|
122
|
+
|
123
|
+
# 11 ids with limit 3, offset 9 should give 2 results.
|
124
|
+
if offset_value && (ids.size - offset_value < expected_size)
|
125
|
+
expected_size = ids.size - offset_value
|
126
|
+
end
|
127
|
+
|
128
|
+
if result.size == expected_size
|
129
|
+
result
|
130
|
+
else
|
131
|
+
conditions = arel.send(:where_clauses).join(', ')
|
132
|
+
conditions = " [WHERE #{conditions}]" if conditions.present?
|
133
|
+
error = "Couldn't find all #{klass.name.pluralize} with IDs "
|
134
|
+
error << "(#{ids.join(", ")})#{conditions} (found #{result.size} results, but was looking for #{expected_size})"
|
135
|
+
raise ActiveRecord::RecordNotFound, error
|
136
|
+
end
|
114
137
|
end
|
138
|
+
end
|
139
|
+
|
140
|
+
attr :friendly_id_scope
|
141
|
+
|
142
|
+
# This method overrides Active Record's default in order to allow the :scope option to
|
143
|
+
# be passed to finds.
|
144
|
+
def apply_finder_options(options)
|
145
|
+
@friendly_id_scope = options.delete(:scope)
|
146
|
+
@friendly_id_scope = @friendly_id_scope.to_param if @friendly_id_scope.respond_to?(:to_param)
|
147
|
+
super
|
148
|
+
end
|
115
149
|
|
116
|
-
|
117
|
-
result
|
118
|
-
else
|
119
|
-
conditions = arel.send(:where_clauses).join(', ')
|
120
|
-
conditions = " [WHERE #{conditions}]" if conditions.present?
|
150
|
+
protected
|
121
151
|
|
122
|
-
|
123
|
-
|
124
|
-
|
152
|
+
def find_one(id)
|
153
|
+
begin
|
154
|
+
return super if !klass.uses_friendly_id? or id.unfriendly_id?
|
155
|
+
find = Find.new(self, id)
|
156
|
+
find.find_one or super
|
157
|
+
rescue ActiveRecord::RecordNotFound => error
|
158
|
+
find ? find.raise_error(error) : raise(error)
|
125
159
|
end
|
126
160
|
end
|
161
|
+
|
162
|
+
def find_some(ids)
|
163
|
+
return super unless klass.uses_friendly_id?
|
164
|
+
Find.new(self, ids).find_some or super
|
165
|
+
rescue ActiveRecord::RecordNotFound => error
|
166
|
+
find ? find.raise_error(error) : raise(error)
|
167
|
+
end
|
168
|
+
|
127
169
|
end
|
128
170
|
end
|
129
171
|
end
|
@@ -101,10 +101,11 @@ module FriendlyId
|
|
101
101
|
# This method was removed in ActiveRecord 3.0.
|
102
102
|
if !ActiveRecord::Base.private_method_defined? :update_without_callbacks
|
103
103
|
def update_without_callbacks
|
104
|
-
|
104
|
+
attributes_with_values = arel_attributes_values(false, false, attribute_names)
|
105
|
+
return false if attributes_with_values.empty?
|
106
|
+
self.class.unscoped.where(self.class.arel_table[self.class.primary_key].eq(id)).arel.update(attributes_with_values)
|
105
107
|
end
|
106
108
|
end
|
107
|
-
|
108
109
|
end
|
109
110
|
end
|
110
|
-
end
|
111
|
+
end
|
@@ -85,6 +85,14 @@ module FriendlyId
|
|
85
85
|
yield self if block_given?
|
86
86
|
end
|
87
87
|
|
88
|
+
def cache_column=(value)
|
89
|
+
@cache_column = value.to_s.strip.to_sym
|
90
|
+
if [:slug, :slugs, :""].include?(@cache_column)
|
91
|
+
raise ArgumentError, "FriendlyId cache column can not be named '#{value}'"
|
92
|
+
end
|
93
|
+
@cache_column
|
94
|
+
end
|
95
|
+
|
88
96
|
# This should be overridden by adapters that implement caching.
|
89
97
|
def cache_column?
|
90
98
|
false
|
@@ -107,6 +115,13 @@ module FriendlyId
|
|
107
115
|
@scope = scope
|
108
116
|
end
|
109
117
|
|
118
|
+
def sequence_separator=(string)
|
119
|
+
if string == "-" || string =~ /\s/
|
120
|
+
raise ArgumentError, "FriendlyId sequence_separator can not be '#{string}'"
|
121
|
+
end
|
122
|
+
@sequence_separator = string
|
123
|
+
end
|
124
|
+
|
110
125
|
# This will be set if FriendlyId's scope feature is used in any model. It is here
|
111
126
|
# to provide a way to avoid invoking costly scope lookup methods when the scoped
|
112
127
|
# slug feature is not being used by any models.
|
@@ -121,7 +136,7 @@ module FriendlyId
|
|
121
136
|
end
|
122
137
|
|
123
138
|
%w[approximate_ascii scope strip_non_ascii use_slug].each do |method|
|
124
|
-
class_eval(<<-EOM)
|
139
|
+
class_eval(<<-EOM, __FILE__, __LINE__ +1)
|
125
140
|
def #{method}?
|
126
141
|
!! #{method}
|
127
142
|
end
|
@@ -130,6 +145,13 @@ module FriendlyId
|
|
130
145
|
|
131
146
|
alias :use_slugs? :use_slug?
|
132
147
|
|
148
|
+
def babosa_options
|
149
|
+
{
|
150
|
+
:to_ascii => strip_non_ascii?,
|
151
|
+
:transliterate => approximate_ascii?,
|
152
|
+
:transliterations => ascii_approximation_options,
|
153
|
+
:max_length => max_length
|
154
|
+
}
|
155
|
+
end
|
133
156
|
end
|
134
|
-
|
135
157
|
end
|
@@ -1,14 +1,12 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
module FriendlyId
|
3
3
|
|
4
|
-
class SlugString < Babosa::
|
4
|
+
class SlugString < Babosa::Identifier
|
5
5
|
# Normalize the string for a given {FriendlyId::Configuration}.
|
6
6
|
# @param config [FriendlyId::Configuration]
|
7
7
|
# @return String
|
8
8
|
def normalize_for!(config)
|
9
|
-
|
10
|
-
to_ascii! if config.strip_non_ascii?
|
11
|
-
normalize!
|
9
|
+
normalize!(config.babosa_options)
|
12
10
|
end
|
13
11
|
|
14
12
|
# Validate that the slug string is not blank or reserved, and truncate
|
data/lib/friendly_id/test.rb
CHANGED
@@ -94,12 +94,6 @@ module FriendlyId
|
|
94
94
|
assert_equal instance, klass.send(find_method, "206")
|
95
95
|
end
|
96
96
|
|
97
|
-
test "failing finds with unfriendly_id should raise errors normally" do
|
98
|
-
assert_raise ActiveRecord::RecordNotFound do
|
99
|
-
klass.send(find_method, 0)
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
103
97
|
test "creation should raise an error if the friendly_id text is reserved" do
|
104
98
|
assert_validation_error do
|
105
99
|
klass.send(create_method, :name => "new")
|
data/lib/friendly_id/version.rb
CHANGED
@@ -35,6 +35,11 @@ CreateSupportModels.up
|
|
35
35
|
# A model that uses the automagically configured "cached_slug" column
|
36
36
|
class District < ActiveRecord::Base
|
37
37
|
has_friendly_id :name, :use_slug => true
|
38
|
+
before_save :say_hello
|
39
|
+
|
40
|
+
def say_hello
|
41
|
+
@said_hello = true
|
42
|
+
end
|
38
43
|
end
|
39
44
|
|
40
45
|
# A model with optimistic locking enabled
|
@@ -64,6 +64,11 @@ module FriendlyId
|
|
64
64
|
instance.friendly_id(true)
|
65
65
|
end
|
66
66
|
|
67
|
+
test "should not fire callbacks when updating slug cache" do
|
68
|
+
instance.expects(:say_hello).once
|
69
|
+
instance.update_attributes(:name => "new name")
|
70
|
+
assert_equal instance.slug.to_friendly_id, cached_slug
|
71
|
+
end
|
67
72
|
end
|
68
73
|
end
|
69
74
|
end
|
@@ -114,6 +114,12 @@ module FriendlyId
|
|
114
114
|
end
|
115
115
|
end
|
116
116
|
|
117
|
+
test "failing finds with unfriendly_id should raise errors normally" do
|
118
|
+
assert_raise ActiveRecord::RecordNotFound do
|
119
|
+
klass.send(find_method, 0)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
117
123
|
test "instances found by a single id should not be read-only" do
|
118
124
|
i = klass.find(instance.friendly_id)
|
119
125
|
assert !i.readonly?, "expected instance not to be readonly"
|
@@ -5,20 +5,15 @@ module FriendlyId
|
|
5
5
|
|
6
6
|
class ScopedModelTest < ::Test::Unit::TestCase
|
7
7
|
|
8
|
-
include FriendlyId::Test::Generic
|
9
|
-
include FriendlyId::Test::Slugged
|
10
|
-
include FriendlyId::Test::ActiveRecordAdapter::Slugged
|
11
|
-
include FriendlyId::Test::ActiveRecordAdapter::Core
|
12
|
-
|
13
8
|
def setup
|
14
|
-
@user
|
15
|
-
@house
|
16
|
-
@usa
|
17
|
-
@canada
|
18
|
-
@resident
|
9
|
+
@user = User.create!(:name => "john")
|
10
|
+
@house = House.create!(:name => "123 Main", :user => @user)
|
11
|
+
@usa = Country.create!(:name => "USA")
|
12
|
+
@canada = Country.create!(:name => "Canada")
|
13
|
+
@resident = Resident.create!(:name => "John Smith", :country => @usa)
|
19
14
|
@resident2 = Resident.create!(:name => "John Smith", :country => @canada)
|
20
|
-
@owner
|
21
|
-
@site
|
15
|
+
@owner = Company.create!(:name => "Acme Events")
|
16
|
+
@site = Site.create!(:name => "Downtown Venue", :owner => @owner)
|
22
17
|
end
|
23
18
|
|
24
19
|
def teardown
|
@@ -77,13 +72,21 @@ module FriendlyId
|
|
77
72
|
end
|
78
73
|
|
79
74
|
test "should find a single scoped record with a scope as a string" do
|
80
|
-
assert Resident.find(@resident.friendly_id, :scope => @resident.country)
|
75
|
+
assert Resident.find(@resident.friendly_id, :scope => @resident.country.to_param)
|
81
76
|
end
|
82
77
|
|
83
78
|
test "should find a single scoped record with a scope" do
|
84
79
|
assert Resident.find(@resident.friendly_id, :scope => @resident.country)
|
85
80
|
end
|
86
81
|
|
82
|
+
test "should find a multiple scoped records with a scope" do
|
83
|
+
r1 = Resident.create!(:name => "John Smith", :country => @usa)
|
84
|
+
r2 = Resident.create!(:name => "Jane Smith", :country => @usa)
|
85
|
+
result = Resident.find([r1, r2].map(&:friendly_id), :scope => @resident.country)
|
86
|
+
assert_equal 2, result.size
|
87
|
+
end
|
88
|
+
|
89
|
+
|
87
90
|
test "should raise an error when finding a single scoped record with no scope" do
|
88
91
|
assert_raises ActiveRecord::RecordNotFound do
|
89
92
|
Resident.find(@resident.friendly_id)
|
@@ -123,4 +126,4 @@ module FriendlyId
|
|
123
126
|
end
|
124
127
|
end
|
125
128
|
end
|
126
|
-
end
|
129
|
+
end
|
data/test/friendly_id_test.rb
CHANGED
@@ -1,7 +1,52 @@
|
|
1
|
+
# encoding: utf-8
|
1
2
|
require File.expand_path('../test_helper', __FILE__)
|
2
3
|
|
3
4
|
module FriendlyId
|
4
5
|
module Test
|
6
|
+
|
7
|
+
class ConfigurationTest < ::Test::Unit::TestCase
|
8
|
+
test "should validate sequence separator name" do
|
9
|
+
["-", " ", "\n", "\t"].each do |string|
|
10
|
+
assert_raise ArgumentError do
|
11
|
+
Configuration.new(NilClass, :hello, :sequence_separator => string)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
test "should validate cached slug name" do
|
17
|
+
["slug", "slugs", " "].each do |string|
|
18
|
+
assert_raise ArgumentError do
|
19
|
+
Configuration.new(NilClass, :hello, :cache_column => string)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class SlugStringTest < ::Test::Unit::TestCase
|
26
|
+
test "should not transliterate by default" do
|
27
|
+
s = SlugString.new("über")
|
28
|
+
assert_equal "über", s.normalize_for!(Configuration.new(nil, :name))
|
29
|
+
end
|
30
|
+
|
31
|
+
test "should transliterate if specified" do
|
32
|
+
s = SlugString.new("über")
|
33
|
+
options = {:approximate_ascii => true}
|
34
|
+
assert_equal "uber", s.normalize_for!(Configuration.new(nil, :name, options))
|
35
|
+
end
|
36
|
+
|
37
|
+
test "should strip non-ascii if specified" do
|
38
|
+
s = SlugString.new("über")
|
39
|
+
options = {:strip_non_ascii => true}
|
40
|
+
assert_equal "ber", s.normalize_for!(Configuration.new(nil, :name, options))
|
41
|
+
end
|
42
|
+
|
43
|
+
test "should use transliterations if given" do
|
44
|
+
s = SlugString.new("über")
|
45
|
+
options = {:approximate_ascii => true, :ascii_approximation_options => :german}
|
46
|
+
assert_equal "ueber", s.normalize_for!(Configuration.new(nil, :name, options))
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
5
50
|
class FriendlyIdTest < ::Test::Unit::TestCase
|
6
51
|
test "should parse a friendly_id name and sequence" do
|
7
52
|
assert_equal ["test", 2], "test--2".parse_friendly_id
|
@@ -48,4 +93,4 @@ module FriendlyId
|
|
48
93
|
end
|
49
94
|
end
|
50
95
|
end
|
51
|
-
end
|
96
|
+
end
|
data/test/test_helper.rb
CHANGED
@@ -5,11 +5,9 @@ $:.uniq!
|
|
5
5
|
$KCODE = "UTF8" if RUBY_VERSION < "1.9"
|
6
6
|
$VERBOSE = false
|
7
7
|
require "rubygems"
|
8
|
-
require "bundler"
|
9
|
-
Bundler.setup
|
8
|
+
require "bundler/setup"
|
10
9
|
require "test/unit"
|
11
10
|
require "mocha"
|
12
11
|
require "active_support"
|
13
|
-
# require "ruby-debug"
|
14
12
|
require "friendly_id"
|
15
13
|
require "friendly_id/test"
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 3
|
7
7
|
- 1
|
8
|
-
-
|
9
|
-
version: 3.1.
|
8
|
+
- 4
|
9
|
+
version: 3.1.4
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Norman Clarke
|
@@ -16,23 +16,23 @@ autorequire:
|
|
16
16
|
bindir: bin
|
17
17
|
cert_chain: []
|
18
18
|
|
19
|
-
date: 2010-
|
19
|
+
date: 2010-09-01 00:00:00 -03:00
|
20
20
|
default_executable:
|
21
21
|
dependencies:
|
22
22
|
- !ruby/object:Gem::Dependency
|
23
23
|
name: babosa
|
24
|
-
prerelease: false
|
25
24
|
requirement: &id001 !ruby/object:Gem::Requirement
|
26
25
|
none: false
|
27
26
|
requirements:
|
28
|
-
- -
|
27
|
+
- - ~>
|
29
28
|
- !ruby/object:Gem::Version
|
30
29
|
segments:
|
31
30
|
- 0
|
32
|
-
-
|
31
|
+
- 2
|
33
32
|
- 0
|
34
|
-
version: 0.
|
33
|
+
version: 0.2.0
|
35
34
|
type: :runtime
|
35
|
+
prerelease: false
|
36
36
|
version_requirements: *id001
|
37
37
|
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
38
|
email:
|
@@ -70,7 +70,7 @@ files:
|
|
70
70
|
- Contributors.md
|
71
71
|
- Guide.md
|
72
72
|
- README.md
|
73
|
-
- LICENSE
|
73
|
+
- MIT-LICENSE
|
74
74
|
- Rakefile
|
75
75
|
- rails/init.rb
|
76
76
|
- generators/friendly_id/friendly_id_generator.rb
|
@@ -92,7 +92,6 @@ files:
|
|
92
92
|
- test/active_record_adapter/support/database.mysql.yml
|
93
93
|
- test/active_record_adapter/support/database.postgres.yml
|
94
94
|
- test/active_record_adapter/support/database.sqlite3.yml
|
95
|
-
- test/active_record_adapter/support/database.yml
|
96
95
|
- test/active_record_adapter/support/models.rb
|
97
96
|
- test/active_record_adapter/tasks_test.rb
|
98
97
|
- test/friendly_id_test.rb
|
@@ -117,6 +116,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
117
116
|
requirements:
|
118
117
|
- - ">="
|
119
118
|
- !ruby/object:Gem::Version
|
119
|
+
hash: 4295902025361844549
|
120
120
|
segments:
|
121
121
|
- 0
|
122
122
|
version: "0"
|
@@ -125,6 +125,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
125
125
|
requirements:
|
126
126
|
- - ">="
|
127
127
|
- !ruby/object:Gem::Version
|
128
|
+
hash: 4295902025361844549
|
128
129
|
segments:
|
129
130
|
- 0
|
130
131
|
version: "0"
|