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