friendly_id 3.1.8 → 3.2.0.beta1
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 +47 -14
- data/Rakefile +1 -0
- data/lib/friendly_id/active_record_adapter/finders.rb +0 -18
- data/lib/friendly_id/active_record_adapter/relation.rb +12 -36
- data/lib/friendly_id/version.rb +3 -3
- data/test/active_record_adapter/scoped_model_test.rb +10 -41
- metadata +16 -12
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.2.0 (NOT RELEASED YET)
|
10
|
+
|
11
|
+
* Removes `:scope` as a find parameter, allowing more flexible finds with
|
12
|
+
scoped records.
|
13
|
+
|
9
14
|
## 3.1.8 (2010-11-22)
|
10
15
|
|
11
16
|
* Fix compatibility with Active Record 3.0.3.
|
data/Guide.md
CHANGED
@@ -445,8 +445,8 @@ the slug "joes-diner" if it's located in a different city:
|
|
445
445
|
http://example.org/cities/seattle/restaurants/joes-diner
|
446
446
|
http://example.org/cities/chicago/restaurants/joes-diner
|
447
447
|
|
448
|
-
|
449
|
-
|
448
|
+
City.find("seattle").restaurants.find("joes-diner") # returns 1 record
|
449
|
+
City.find("chicago").restaurants.find("joes-diner") # returns 1 record
|
450
450
|
|
451
451
|
The value for the `:scope` key in your model can be a custom method you
|
452
452
|
define, or the name of a relation. If it's the name of a relation, then the
|
@@ -454,15 +454,45 @@ scope's text value will be the result of calling `to_param` on the related
|
|
454
454
|
model record. In the example above, the city model also uses FriendlyId and so
|
455
455
|
its `to_param` method returns its friendly_id: "chicago" or "seattle".
|
456
456
|
|
457
|
+
### Complications with Scoped Slugs
|
458
|
+
|
459
|
+
#### Finding Records by friendly\_id
|
460
|
+
|
461
|
+
If you are using scopes your friendly ids may not be unique, so a simple find like
|
462
|
+
|
463
|
+
Restaurant.find("joes-diner")
|
464
|
+
|
465
|
+
may return the wrong record. In these cases when you want to use the friendly\_id for queries,
|
466
|
+
either query as a relation, or specify the scope in your query conditions:
|
467
|
+
|
468
|
+
# will only return restaurants named "Joe's Diner" in the given city
|
469
|
+
@city.restaurants.find("joes-diner")
|
470
|
+
|
471
|
+
# or
|
472
|
+
|
473
|
+
Restaurants.find("joes-diner", :include => :slugs, :conditions => {:slugs => {:scope => @city.to_param}})
|
474
|
+
|
475
|
+
|
476
|
+
#### Finding All Records That Match a Scoped ID
|
477
|
+
|
457
478
|
If you want to find all records with a partular friendly\_id regardless of scope,
|
458
|
-
|
479
|
+
the easiest way is to use cached slugs and query this column directly:
|
480
|
+
|
481
|
+
Restaurant.find_all_by_cached_slug("joes-diner")
|
482
|
+
# Another option, with Active Record 3.x
|
483
|
+
Restaurant.where(:cached_slug => "joes-diner")
|
484
|
+
|
485
|
+
|
486
|
+
If you're not using cached slugs, then this is slightly more complicated, but
|
487
|
+
still doable:
|
459
488
|
|
460
489
|
name, sequence = params[:id].parse_friendly_id
|
461
|
-
Restaurant.
|
490
|
+
Restaurant.all(:include => :slugs, :conditions => {
|
462
491
|
:slugs => {:name => name, :sequence => sequence}
|
463
492
|
})
|
464
493
|
|
465
|
-
|
494
|
+
|
495
|
+
#### Updating a Relation's Scoped Slugs
|
466
496
|
|
467
497
|
When using a relation as the scope, updating the relation will update the
|
468
498
|
slugs, but only if both models have specified the relationship. In the above
|
@@ -477,23 +507,26 @@ this up:
|
|
477
507
|
|
478
508
|
# in routes.rb
|
479
509
|
map.resources :restaurants
|
480
|
-
map.restaurant "/restaurants/:
|
510
|
+
map.restaurant "/restaurants/:city_id/:id", :controller => "restaurants"
|
481
511
|
|
482
512
|
# in views
|
483
513
|
link_to 'Show', restaurant_path(restaurant.city, restaurant)
|
484
514
|
|
485
515
|
# in controllers
|
486
|
-
@
|
516
|
+
@city = City.find(params[:city_id])
|
517
|
+
@restaurant = @city.resaturants.find(params[:id])
|
487
518
|
|
488
519
|
### Scoped Models and Cached Slugs
|
489
520
|
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
521
|
+
If you want to use cached slugs with scoped models, be sure not to create a unique index on the
|
522
|
+
`cached_slug` column.
|
523
|
+
|
524
|
+
### Scoped Models in FriendyId Before 3.2.0
|
525
|
+
|
526
|
+
In older versions of FriendlyId, you could specify a non-standard `:scope`
|
527
|
+
argument to finds. This feature has been removed in 3.2.0 in favor of the query
|
528
|
+
stategies described above.
|
529
|
+
|
497
530
|
|
498
531
|
## FriendlyId Rake Tasks
|
499
532
|
|
data/Rakefile
CHANGED
@@ -12,7 +12,6 @@ module FriendlyId
|
|
12
12
|
attr :klass
|
13
13
|
attr :id
|
14
14
|
attr :options
|
15
|
-
attr :scope_val
|
16
15
|
attr :result
|
17
16
|
attr :friendly_ids
|
18
17
|
attr :unfriendly_ids
|
@@ -21,8 +20,6 @@ module FriendlyId
|
|
21
20
|
@klass = klass
|
22
21
|
@id = id
|
23
22
|
@options = options
|
24
|
-
@scope_val = options.delete(:scope)
|
25
|
-
@scope_val = @scope_val.to_param if @scope_val && @scope_val.respond_to?(:to_param)
|
26
23
|
end
|
27
24
|
|
28
25
|
def find_one
|
@@ -37,9 +34,6 @@ module FriendlyId
|
|
37
34
|
scope = some_friendly_scope
|
38
35
|
if use_slugs? && @friendly_ids.present?
|
39
36
|
scope = scope.scoped(:joins => :slugs)
|
40
|
-
if fc.scope?
|
41
|
-
scope = scope.scoped(:conditions => {:slugs => {:scope => scope_val}})
|
42
|
-
end
|
43
37
|
end
|
44
38
|
options[:readonly] = false unless options[:readonly]
|
45
39
|
@result = scope.all(options).uniq
|
@@ -47,13 +41,6 @@ module FriendlyId
|
|
47
41
|
@result.each { |record| record.friendly_id_status.name = id }
|
48
42
|
end
|
49
43
|
|
50
|
-
def raise_error(error)
|
51
|
-
raise(error) unless fc.scope?
|
52
|
-
scope_message = scope_val || "expected, but none given"
|
53
|
-
message = "%s, scope: %s" % [error.message, scope_message]
|
54
|
-
raise ActiveRecord::RecordNotFound, message
|
55
|
-
end
|
56
|
-
|
57
44
|
private
|
58
45
|
|
59
46
|
def find_one_with_cached_slug
|
@@ -64,7 +51,6 @@ module FriendlyId
|
|
64
51
|
def find_one_with_slug
|
65
52
|
name, seq = id.to_s.parse_friendly_id
|
66
53
|
scope = scoped(:joins => :slugs, :conditions => {:slugs => {:name => name, :sequence => seq}})
|
67
|
-
scope = scope.scoped(:conditions => {:slugs => {:scope => scope_val}}) if fc.scope?
|
68
54
|
options[:readonly] = false unless options[:readonly]
|
69
55
|
@result = scope.first(options)
|
70
56
|
assign_status
|
@@ -146,16 +132,12 @@ module FriendlyId
|
|
146
132
|
return super if id.blank? || id.unfriendly_id?
|
147
133
|
finder = Find.new(self, id, options)
|
148
134
|
finder.find_one or super
|
149
|
-
rescue ActiveRecord::RecordNotFound => error
|
150
|
-
finder ? finder.raise_error(error) : raise(error)
|
151
135
|
end
|
152
136
|
|
153
137
|
def find_some(ids, options)
|
154
138
|
return super if ids.empty?
|
155
139
|
finder = Find.new(self, ids, options)
|
156
140
|
finder.find_some
|
157
|
-
rescue ActiveRecord::RecordNotFound => error
|
158
|
-
finder ? finder.raise_error(error) : raise(error)
|
159
141
|
end
|
160
142
|
end
|
161
143
|
end
|
@@ -9,8 +9,7 @@ module FriendlyId
|
|
9
9
|
attr :ids
|
10
10
|
alias id ids
|
11
11
|
|
12
|
-
def_delegators :relation, :arel, :arel_table, :
|
13
|
-
:klass, :limit_value, :offset_value, :where
|
12
|
+
def_delegators :relation, :arel, :arel_table, :klass, :limit_value, :offset_value, :where
|
14
13
|
def_delegators :klass, :connection, :friendly_id_config
|
15
14
|
alias fc friendly_id_config
|
16
15
|
|
@@ -20,7 +19,7 @@ module FriendlyId
|
|
20
19
|
end
|
21
20
|
|
22
21
|
def find_one
|
23
|
-
if fc.cache_column?
|
22
|
+
if fc.cache_column?
|
24
23
|
find_one_with_cached_slug
|
25
24
|
elsif fc.use_slugs?
|
26
25
|
find_one_with_slug
|
@@ -39,13 +38,6 @@ module FriendlyId
|
|
39
38
|
validate_expected_size!(ids, records)
|
40
39
|
end
|
41
40
|
|
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
|
47
|
-
end
|
48
|
-
|
49
41
|
private
|
50
42
|
|
51
43
|
def assign_status
|
@@ -67,7 +59,14 @@ module FriendlyId
|
|
67
59
|
end
|
68
60
|
|
69
61
|
def find_one_with_slug
|
70
|
-
|
62
|
+
sluggable_ids = sluggable_ids_for([id])
|
63
|
+
|
64
|
+
if sluggable_ids.size > 1 && fc.scope?
|
65
|
+
return relation.where(relation.primary_key.in(sluggable_ids)).first
|
66
|
+
end
|
67
|
+
|
68
|
+
sluggable_id = sluggable_ids.first
|
69
|
+
|
71
70
|
if sluggable_id
|
72
71
|
name, seq = id.to_s.parse_friendly_id
|
73
72
|
record = relation.send(:find_one_without_friendly, sluggable_id)
|
@@ -80,7 +79,7 @@ module FriendlyId
|
|
80
79
|
end
|
81
80
|
|
82
81
|
def friendly_records(friendly_ids, unfriendly_ids)
|
83
|
-
use_slugs_table = fc.use_slugs? && (
|
82
|
+
use_slugs_table = fc.use_slugs? && (!fc.cache_column?)
|
84
83
|
return find_some_using_slug(friendly_ids, unfriendly_ids) if use_slugs_table
|
85
84
|
column = fc.cache_column || fc.column
|
86
85
|
friendly = arel_table[column].in(friendly_ids)
|
@@ -105,14 +104,6 @@ module FriendlyId
|
|
105
104
|
string = fragment % [connection.quote(klass.base_class), connection.quote(name), seq]
|
106
105
|
clause ? clause + " OR #{string}" : string
|
107
106
|
end
|
108
|
-
if fc.scope?
|
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
|
115
|
-
end
|
116
107
|
sql = "SELECT sluggable_id FROM slugs WHERE (%s)" % conditions
|
117
108
|
connection.select_values sql
|
118
109
|
end
|
@@ -142,16 +133,6 @@ module FriendlyId
|
|
142
133
|
end
|
143
134
|
end
|
144
135
|
|
145
|
-
attr :friendly_id_scope
|
146
|
-
|
147
|
-
# This method overrides Active Record's default in order to allow the :scope option to
|
148
|
-
# be passed to finds.
|
149
|
-
def apply_finder_options(options)
|
150
|
-
@friendly_id_scope = options.delete(:scope)
|
151
|
-
@friendly_id_scope = @friendly_id_scope.to_param if @friendly_id_scope.respond_to?(:to_param)
|
152
|
-
super
|
153
|
-
end
|
154
|
-
|
155
136
|
protected
|
156
137
|
|
157
138
|
def find_one(id)
|
@@ -159,8 +140,6 @@ module FriendlyId
|
|
159
140
|
return super if !klass.uses_friendly_id? or id.unfriendly_id?
|
160
141
|
find = Find.new(self, id)
|
161
142
|
find.find_one or super
|
162
|
-
rescue ActiveRecord::RecordNotFound => error
|
163
|
-
find ? find.raise_error(error) : raise(error)
|
164
143
|
end
|
165
144
|
end
|
166
145
|
|
@@ -172,10 +151,7 @@ module FriendlyId
|
|
172
151
|
ids = ids.map {|id| (id.respond_to?(:friendly_id_config) ? id.id : id).to_i}
|
173
152
|
super
|
174
153
|
end
|
175
|
-
rescue ActiveRecord::RecordNotFound => error
|
176
|
-
find ? find.raise_error(error) : raise(error)
|
177
154
|
end
|
178
|
-
|
179
155
|
end
|
180
156
|
end
|
181
|
-
end
|
157
|
+
end
|
data/lib/friendly_id/version.rb
CHANGED
@@ -29,7 +29,7 @@ module FriendlyId
|
|
29
29
|
test "should not use cached slug column with scopes" do
|
30
30
|
@tourist = Tourist.create!(:name => "John Smith", :country => @usa)
|
31
31
|
@tourist2 = Tourist.create!(:name => "John Smith", :country => @canada)
|
32
|
-
assert_equal @canada,
|
32
|
+
assert_equal @canada, @canada.residents.find(@tourist2.friendly_id).country
|
33
33
|
end
|
34
34
|
|
35
35
|
test "a slugged model should auto-detect that it is being used as a parent scope" do
|
@@ -81,49 +81,18 @@ module FriendlyId
|
|
81
81
|
:slugs => {:name => name, :sequence => sequence}}).size
|
82
82
|
end
|
83
83
|
|
84
|
-
test "should find a
|
85
|
-
assert Resident.find(@resident.friendly_id
|
84
|
+
test "should find a scoped record by friendly_id" do
|
85
|
+
assert Resident.find(@resident.friendly_id)
|
86
86
|
end
|
87
87
|
|
88
|
-
test "should find a
|
89
|
-
|
88
|
+
test "should find a scope record as a relation member" do
|
89
|
+
assert_equal @resident, @usa.residents.find("john-smith")
|
90
|
+
assert_equal @resident2, @canada.residents.find("john-smith")
|
90
91
|
end
|
91
92
|
|
92
|
-
test "should find a single scoped record
|
93
|
-
|
94
|
-
|
95
|
-
end
|
96
|
-
|
97
|
-
test "should find a multiple scoped records with a scope" do
|
98
|
-
r1 = Resident.create!(:name => "John Smith", :country => @usa)
|
99
|
-
r2 = Resident.create!(:name => "Jane Smith", :country => @usa)
|
100
|
-
result = Resident.find([r1, r2].map(&:friendly_id), :scope => @resident.country)
|
101
|
-
assert_equal 2, result.size
|
102
|
-
end
|
103
|
-
|
104
|
-
|
105
|
-
test "should raise an error when finding a single scoped record with no scope" do
|
106
|
-
assert_raises ActiveRecord::RecordNotFound do
|
107
|
-
Resident.find(@resident.friendly_id)
|
108
|
-
end
|
109
|
-
end
|
110
|
-
|
111
|
-
test "should append scope error info when missing scope causes a find to fail" do
|
112
|
-
begin
|
113
|
-
Resident.find(@resident.friendly_id)
|
114
|
-
fail "The find should not have succeeded"
|
115
|
-
rescue ActiveRecord::RecordNotFound => e
|
116
|
-
assert_match(/scope: expected/, e.message)
|
117
|
-
end
|
118
|
-
end
|
119
|
-
|
120
|
-
test "should append scope error info when the scope value causes a find to fail" do
|
121
|
-
begin
|
122
|
-
Resident.find(@resident.friendly_id, :scope => "badscope")
|
123
|
-
fail "The find should not have succeeded"
|
124
|
-
rescue ActiveRecord::RecordNotFound => e
|
125
|
-
assert_match(/scope: badscope/, e.message)
|
126
|
-
end
|
93
|
+
test "should find a single scoped record using slug conditions" do
|
94
|
+
assert_equal @resident, Resident.find(@resident.friendly_id, :include => :slugs,
|
95
|
+
:conditions => {:slugs => {:scope => @resident.country.to_param}})
|
127
96
|
end
|
128
97
|
|
129
98
|
test "should update the sluggable field when a polymorphic relationship exists" do
|
@@ -141,4 +110,4 @@ module FriendlyId
|
|
141
110
|
end
|
142
111
|
end
|
143
112
|
end
|
144
|
-
end
|
113
|
+
end
|
metadata
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: friendly_id
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
prerelease:
|
4
|
+
prerelease: true
|
5
5
|
segments:
|
6
6
|
- 3
|
7
|
-
-
|
8
|
-
-
|
9
|
-
|
7
|
+
- 2
|
8
|
+
- 0
|
9
|
+
- beta1
|
10
|
+
version: 3.2.0.beta1
|
10
11
|
platform: ruby
|
11
12
|
authors:
|
12
13
|
- Norman Clarke
|
@@ -16,12 +17,11 @@ autorequire:
|
|
16
17
|
bindir: bin
|
17
18
|
cert_chain: []
|
18
19
|
|
19
|
-
date: 2010-11-
|
20
|
+
date: 2010-11-23 00:00:00 -03:00
|
20
21
|
default_executable:
|
21
22
|
dependencies:
|
22
23
|
- !ruby/object:Gem::Dependency
|
23
24
|
name: babosa
|
24
|
-
prerelease: false
|
25
25
|
requirement: &id001 !ruby/object:Gem::Requirement
|
26
26
|
none: false
|
27
27
|
requirements:
|
@@ -33,10 +33,10 @@ dependencies:
|
|
33
33
|
- 0
|
34
34
|
version: 0.2.0
|
35
35
|
type: :runtime
|
36
|
+
prerelease: false
|
36
37
|
version_requirements: *id001
|
37
38
|
- !ruby/object:Gem::Dependency
|
38
39
|
name: activerecord
|
39
|
-
prerelease: false
|
40
40
|
requirement: &id002 !ruby/object:Gem::Requirement
|
41
41
|
none: false
|
42
42
|
requirements:
|
@@ -48,10 +48,10 @@ dependencies:
|
|
48
48
|
- 0
|
49
49
|
version: 3.0.0
|
50
50
|
type: :development
|
51
|
+
prerelease: false
|
51
52
|
version_requirements: *id002
|
52
53
|
- !ruby/object:Gem::Dependency
|
53
54
|
name: mocha
|
54
|
-
prerelease: false
|
55
55
|
requirement: &id003 !ruby/object:Gem::Requirement
|
56
56
|
none: false
|
57
57
|
requirements:
|
@@ -62,10 +62,10 @@ dependencies:
|
|
62
62
|
- 9
|
63
63
|
version: "0.9"
|
64
64
|
type: :development
|
65
|
+
prerelease: false
|
65
66
|
version_requirements: *id003
|
66
67
|
- !ruby/object:Gem::Dependency
|
67
68
|
name: sqlite3-ruby
|
68
|
-
prerelease: false
|
69
69
|
requirement: &id004 !ruby/object:Gem::Requirement
|
70
70
|
none: false
|
71
71
|
requirements:
|
@@ -75,6 +75,7 @@ dependencies:
|
|
75
75
|
- 1
|
76
76
|
version: "1"
|
77
77
|
type: :development
|
78
|
+
prerelease: false
|
78
79
|
version_requirements: *id004
|
79
80
|
description: " FriendlyId is the \"Swiss Army bulldozer\" of slugging and permalink plugins\n for Ruby on Rails. It allows you to create pretty URL's and work with\n human-friendly strings as if they were numeric ids for ActiveRecord models.\n"
|
80
81
|
email:
|
@@ -159,17 +160,20 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
159
160
|
requirements:
|
160
161
|
- - ">="
|
161
162
|
- !ruby/object:Gem::Version
|
163
|
+
hash: -2292774950521045189
|
162
164
|
segments:
|
163
165
|
- 0
|
164
166
|
version: "0"
|
165
167
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
166
168
|
none: false
|
167
169
|
requirements:
|
168
|
-
- - "
|
170
|
+
- - ">"
|
169
171
|
- !ruby/object:Gem::Version
|
170
172
|
segments:
|
171
|
-
-
|
172
|
-
|
173
|
+
- 1
|
174
|
+
- 3
|
175
|
+
- 1
|
176
|
+
version: 1.3.1
|
173
177
|
requirements: []
|
174
178
|
|
175
179
|
rubyforge_project: friendly-id
|