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.
@@ -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
- Restaurant.find("joes-diner", :scope => "seattle") # returns 1 record
449
- Restaurant.find("joes-diner", :scope => "chicago") # returns 1 record
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
- this is a slightly more complicated, but doable:
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.find(:all, :joins => :slugs, :conditions => {
490
+ Restaurant.all(:include => :slugs, :conditions => {
462
491
  :slugs => {:name => name, :sequence => sequence}
463
492
  })
464
493
 
465
- ### Updating a Relation's Scoped Slugs
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/:scope/:id", :controller => "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
- @restaurant = Restaurant.find(params[:id], :scope => params[:scope])
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
- Be aware that you can't use cached slugs with scoped models. This is because in
491
- order to uniquely identify a scoped model, you need to consult two fields rather
492
- than one. I've considered adding a "cached scope" column as well as one for the
493
- cached slug, but I'm not convinced this is a good idea. FriendlyId 3.1.6 and
494
- above included significant performance optimizations for models that don't use
495
- cached slugs, so in practice this ends up being [less of a performance problem
496
- than you may imagine](#some_benchmarks).
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
@@ -1,4 +1,5 @@
1
1
  require "rubygems"
2
+ require "bundler/setup"
2
3
  require "rake"
3
4
  require "rake/testtask"
4
5
  require "rake/gempackagetask"
@@ -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, :friendly_id_scope,
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? && !fc.scope?
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
- sluggable_id = sluggable_ids_for([id]).first
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? && (fc.scope? || !fc.cache_column?)
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
@@ -1,9 +1,9 @@
1
1
  module FriendlyId
2
2
  module Version
3
3
  MAJOR = 3
4
- MINOR = 1
5
- TINY = 8
6
- BUILD = nil
4
+ MINOR = 2
5
+ TINY = 0
6
+ BUILD = "beta1"
7
7
  STRING = [MAJOR, MINOR, TINY, BUILD].compact.join('.')
8
8
  end
9
9
  end
@@ -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, Tourist.find(@tourist2.friendly_id, :scope => @canada).country
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 single scoped record with a scope as a string" do
85
- assert Resident.find(@resident.friendly_id, :scope => @resident.country.to_param)
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 single scoped record with a scope" do
89
- assert Resident.find(@resident.friendly_id, :scope => @resident.country)
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 with a nil scope" do
93
- nomad = Resident.create!(:name => "Homer", :country => nil)
94
- assert Resident.find(nomad.friendly_id, :scope => nil)
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: false
4
+ prerelease: true
5
5
  segments:
6
6
  - 3
7
- - 1
8
- - 8
9
- version: 3.1.8
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-22 00:00:00 -03:00
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
- - 0
172
- version: "0"
173
+ - 1
174
+ - 3
175
+ - 1
176
+ version: 1.3.1
173
177
  requirements: []
174
178
 
175
179
  rubyforge_project: friendly-id