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.
@@ -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