friendly_id 3.1.8 → 3.2.0.beta1
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 +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
|