friendly_id 4.1.0.beta.1 → 5.0.0.alpha.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -8,7 +8,7 @@ module FriendlyId
8
8
  # be sufficient to avoid conflicts with other libraries.
9
9
  module ObjectUtils
10
10
 
11
- # True is the id is definitely friendly, false if definitely unfriendly,
11
+ # True if the id is definitely friendly, false if definitely unfriendly,
12
12
  # else nil.
13
13
  #
14
14
  # An object is considired "definitely unfriendly" if its class is or
@@ -28,11 +28,33 @@ This allows, for example, two restaurants in different cities to have the slug
28
28
  City.find("chicago").restaurants.find("joes-diner")
29
29
 
30
30
  Without :scoped in this case, one of the restaurants would have the slug
31
- +joes-diner+ and the other would have +joes-diner--2+.
31
+ +joes-diner+ and the other would have +joes-diner-f9f3789a-daec-4156-af1d-fab81aa16ee5+.
32
32
 
33
33
  The value for the +:scope+ option can be the name of a +belongs_to+ relation, or
34
34
  a column.
35
35
 
36
+ Additionally, the +:scope+ option can receive an array of scope values:
37
+
38
+ class Cuisine < ActiveRecord::Base
39
+ extend FriendlyId
40
+ has_many :restaurants
41
+ friendly_id :name, :use => :slugged
42
+ end
43
+
44
+ class City < ActiveRecord::Base
45
+ extend FriendlyId
46
+ has_many :restaurants
47
+ friendly_id :name, :use => :slugged
48
+ end
49
+
50
+ class Restaurant < ActiveRecord::Base
51
+ extend FriendlyId
52
+ belongs_to :city
53
+ friendly_id :name, :use => :scoped, :scope => [:city, :cuisine]
54
+ end
55
+
56
+ All supplied values will be used to determine scope.
57
+
36
58
  === Finding Records by Friendly ID
37
59
 
38
60
  If you are using scopes your friendly ids may not be unique, so a simple find
@@ -81,14 +103,17 @@ an example of one way to set this up:
81
103
  =end
82
104
  module Scoped
83
105
 
106
+ # FriendlyId::Config.use will invoke this method when present, to allow
107
+ # loading dependent modules prior to overriding them when necessary.
108
+ def self.setup(model_class)
109
+ model_class.friendly_id_config.use :slugged
110
+ end
84
111
 
85
112
  # Sets up behavior and configuration options for FriendlyId's scoped slugs
86
113
  # feature.
87
114
  def self.included(model_class)
88
- model_class.instance_eval do
89
- include Slugged unless self < Slugged
115
+ model_class.class_eval do
90
116
  friendly_id_config.class.send :include, Configuration
91
- friendly_id_config.slug_generator_class.send :include, SlugGenerator
92
117
  end
93
118
  end
94
119
 
@@ -96,6 +121,15 @@ an example of one way to set this up:
96
121
  friendly_id_config.scope_columns.sort.map { |column| "#{column}:#{send(column)}" }.join(",")
97
122
  end
98
123
 
124
+ def slug_generator
125
+ relation = self.class.unscoped.friendly
126
+ friendly_id_config.scope_columns.each do |column|
127
+ relation = relation.where(column => send(column))
128
+ end
129
+ friendly_id_config.slug_generator_class.new(relation)
130
+ end
131
+ private :slug_generator
132
+
99
133
  # This module adds the +:scope+ configuration option to
100
134
  # {FriendlyId::Configuration FriendlyId::Configuration}.
101
135
  module Configuration
@@ -122,36 +156,8 @@ an example of one way to set this up:
122
156
 
123
157
  private
124
158
 
125
- if ActiveRecord::VERSION::STRING < "3.1"
126
- def reflection_foreign_key(scope)
127
- model_class.reflections[scope].try(:primary_key_name)
128
- end
129
- else
130
- def reflection_foreign_key(scope)
131
- model_class.reflections[scope].try(:foreign_key)
132
- end
133
- end
134
- end
135
-
136
- # This module overrides {FriendlyId::SlugGenerator#conflict} to consider
137
- # scope, to avoid adding sequences to slugs under different scopes.
138
- module SlugGenerator
139
-
140
- private
141
-
142
- def conflict
143
- if friendly_id_config.uses?(:history)
144
- # When using the :history module +conflicts+ already returns only real conflicts, so there's no need to check
145
- # for the scope columns again
146
- conflicts.first
147
- else
148
- columns = friendly_id_config.scope_columns
149
- matched = columns.inject(conflicts) do |memo, column|
150
- memo.where(column => sluggable.send(column))
151
- end
152
-
153
- matched.first
154
- end
159
+ def reflection_foreign_key(scope)
160
+ model_class.reflections[scope].try(:foreign_key)
155
161
  end
156
162
  end
157
163
  end
@@ -0,0 +1,28 @@
1
+ module FriendlyId
2
+ module Scopes
3
+
4
+ def self.extended(model_class)
5
+ model_class.scope :friendly, ->{model_class.all} do
6
+ def find(*args)
7
+ id = args.first
8
+ return super if args.count != 1 || id.unfriendly_id?
9
+ find_by_friendly_id(id) or super
10
+ end
11
+
12
+ def exists?(conditions = :none)
13
+ return super if conditions.unfriendly_id? || conditions.unfriendly_id?.nil?
14
+ exists_by_friendly_id?(conditions)
15
+ end
16
+ end
17
+ end
18
+
19
+ def find_by_friendly_id(id)
20
+ where(friendly_id_config.query_field => id).first
21
+ end
22
+
23
+ def exists_by_friendly_id?(id)
24
+ where(friendly_id_config.query_field => id).exists?
25
+ end
26
+
27
+ end
28
+ end
@@ -17,7 +17,7 @@ must also include the locale in its name.
17
17
 
18
18
  This module is most suitable to applications that need to support few locales.
19
19
  If you need to support two or more locales, you may wish to use the
20
- {FriendlyId::Globalize Globalize} module instead.
20
+ friendly_id_globalize gem instead.
21
21
 
22
22
  === Example migration
23
23
 
@@ -70,9 +70,14 @@ current locale:
70
70
  =end
71
71
  module SimpleI18n
72
72
 
73
+ # FriendlyId::Config.use will invoke this method when present, to allow
74
+ # loading dependent modules prior to overriding them when necessary.
75
+ def self.setup(model_class)
76
+ model_class.friendly_id_config.use :slugged
77
+ end
78
+
73
79
  def self.included(model_class)
74
- model_class.instance_eval do
75
- friendly_id_config.use :slugged
80
+ model_class.class_eval do
76
81
  friendly_id_config.class.send :include, Configuration
77
82
  include Model
78
83
  end
@@ -84,6 +89,11 @@ current locale:
84
89
  set_slug(normalize_friendly_id(text))
85
90
  end
86
91
  end
92
+
93
+ def slug=(value)
94
+ super
95
+ write_attribute friendly_id_config.slug_column, value
96
+ end
87
97
  end
88
98
 
89
99
  module Configuration
@@ -3,7 +3,6 @@ module FriendlyId
3
3
  #
4
4
  # @see FriendlyId::History
5
5
  class Slug < ActiveRecord::Base
6
- self.table_name = "friendly_id_slugs"
7
6
  belongs_to :sluggable, :polymorphic => true
8
7
 
9
8
  def to_param
@@ -2,82 +2,23 @@ module FriendlyId
2
2
  # The default slug generator offers functionality to check slug strings for
3
3
  # uniqueness and, if necessary, appends a sequence to guarantee it.
4
4
  class SlugGenerator
5
- attr_reader :sluggable, :normalized
6
5
 
7
- # Create a new slug generator.
8
- def initialize(sluggable, normalized)
9
- @sluggable = sluggable
10
- @normalized = normalized
6
+ def initialize(scope)
7
+ @scope = scope
11
8
  end
12
9
 
13
- # Given a slug, get the next available slug in the sequence.
14
- def next
15
- "#{normalized}#{separator}#{next_in_sequence}"
10
+ def available?(slug)
11
+ !@scope.exists?(slug)
16
12
  end
17
13
 
18
- # Generate a new sequenced slug.
19
- def generate
20
- conflict? ? self.next : normalized
14
+ def add(slug)
15
+ slug
21
16
  end
22
17
 
23
- private
24
-
25
- def next_in_sequence
26
- last_in_sequence == 0 ? 2 : last_in_sequence.next
27
- end
28
-
29
- def last_in_sequence
30
- @_last_in_sequence ||= extract_sequence_from_slug(conflict.to_param)
31
- end
32
-
33
- def extract_sequence_from_slug(slug)
34
- slug.split("#{normalized}#{separator}").last.to_i
18
+ def generate(candidates)
19
+ candidates.each {|c| return add c if available?(c)}
20
+ nil
35
21
  end
36
22
 
37
- def column
38
- sluggable.connection.quote_column_name friendly_id_config.slug_column
39
- end
40
-
41
- def conflict?
42
- !! conflict
43
- end
44
-
45
- def conflict
46
- unless defined? @conflict
47
- @conflict = conflicts.first
48
- end
49
- @conflict
50
- end
51
-
52
- def conflicts
53
- sluggable_class = friendly_id_config.model_class.base_class
54
-
55
- pkey = sluggable_class.primary_key
56
- value = sluggable.send pkey
57
- base = "#{column} = ? OR #{column} LIKE ?"
58
- # Awful hack for SQLite3, which does not pick up '\' as the escape character without this.
59
- base << "ESCAPE '\\'" if sluggable.connection.adapter_name =~ /sqlite/i
60
- scope = sluggable_class.unscoped.where(base, normalized, wildcard)
61
- scope = scope.where("#{pkey} <> ?", value) unless sluggable.new_record?
62
-
63
- length_command = "LENGTH"
64
- length_command = "LEN" if sluggable.connection.adapter_name =~ /sqlserver/i
65
- scope = scope.order("#{length_command}(#{column}) DESC, #{column} DESC")
66
- end
67
-
68
- def friendly_id_config
69
- sluggable.friendly_id_config
70
- end
71
-
72
- def separator
73
- friendly_id_config.sequence_separator
74
- end
75
-
76
- def wildcard
77
- # Underscores (matching a single character) and percent signs (matching
78
- # any number of characters) need to be escaped
79
- # (While this seems like an excessive number of backslashes, it is correct)
80
- "#{normalized}#{separator}".gsub(/[_%]/, '\\\\\&') + '%'
81
- end
82
23
  end
83
24
  end
@@ -1,5 +1,6 @@
1
1
  # encoding: utf-8
2
2
  require "friendly_id/slug_generator"
3
+ require "friendly_id/candidates"
3
4
 
4
5
  module FriendlyId
5
6
  =begin
@@ -82,11 +83,11 @@ FriendlyId will append a sequence to the generated slug to ensure uniqueness:
82
83
  car2 = Car.create :title => "Peugot 206"
83
84
 
84
85
  car.friendly_id #=> "peugot-206"
85
- car2.friendly_id #=> "peugot-206--2"
86
+ car2.friendly_id #=> "peugot-206-f9f3789a-daec-4156-af1d-fab81aa16ee5"
86
87
 
87
88
  ==== Sequence Separator - The Two Dashes
88
89
 
89
- By default, FriendlyId uses two dashes to separate the slug from a sequence.
90
+ By default, FriendlyId uses a dash to separate the slug from a sequence.
90
91
 
91
92
  You can change this with the {FriendlyId::Slugged::Configuration#sequence_separator
92
93
  sequence_separator} configuration option.
@@ -185,11 +186,11 @@ second, in concurrent code, either in threads or multiple processes.
185
186
 
186
187
  To solve the nested attributes issue, I recommend simply avoiding them when
187
188
  creating more than one nested record for a model that uses FriendlyId. See {this
188
- Github issue}[https://github.com/norman/friendly_id/issues/185] for discussion.
189
+ Github issue}[https://github.com/FriendlyId/friendly_id/issues/185] for discussion.
189
190
 
190
191
  To solve the concurrency issue, I recommend locking the model's table against
191
192
  inserts while when saving the record. See {this Github
192
- issue}[https://github.com/norman/friendly_id/issues/180] for discussion.
193
+ issue}[https://github.com/FriendlyId/friendly_id/issues/180] for discussion.
193
194
 
194
195
  =end
195
196
  module Slugged
@@ -199,9 +200,9 @@ issue}[https://github.com/norman/friendly_id/issues/180] for discussion.
199
200
  def self.included(model_class)
200
201
  model_class.friendly_id_config.instance_eval do
201
202
  self.class.send :include, Configuration
202
- self.slug_generator_class ||= Class.new(SlugGenerator)
203
+ self.slug_generator_class ||= SlugGenerator
203
204
  defaults[:slug_column] ||= 'slug'
204
- defaults[:sequence_separator] ||= '--'
205
+ defaults[:sequence_separator] ||= '-'
205
206
  end
206
207
  model_class.before_validation :set_slug
207
208
  end
@@ -250,34 +251,28 @@ issue}[https://github.com/norman/friendly_id/issues/180] for discussion.
250
251
  # You can override this method in your model if, for example, you only want
251
252
  # slugs to be generated once, and then never updated.
252
253
  def should_generate_new_friendly_id?
253
- base = send(friendly_id_config.base)
254
- slug_value = send(friendly_id_config.slug_column)
255
-
256
- # If the slug base is nil, and the slug field is nil, then we're going to
257
- # leave the slug column NULL.
258
- return false if base.nil? && slug_value.nil?
259
- # Otherwise, if this is a new record, we're definitely going to try to
260
- # create a new slug.
261
- return true if new_record?
262
- slug_base = normalize_friendly_id(base)
263
- separator = Regexp.escape friendly_id_config.sequence_separator
264
- # If the slug base (with and without sequence) is different from either the current
265
- # friendly id or the slug value, then we'll generate a new friendly_id.
266
- compare = (current_friendly_id || slug_value)
267
- slug_base != compare && slug_base != compare.try(:sub, /#{separator}[\d]*\z/, '')
254
+ send(friendly_id_config.slug_column).nil? && !send(friendly_id_config.base).nil?
255
+ end
256
+
257
+ def resolve_friendly_id_conflict(candidates)
258
+ candidates.first + friendly_id_config.sequence_separator + SecureRandom.uuid
268
259
  end
269
260
 
270
261
  # Sets the slug.
271
- # FIXME: This method sucks and the logic is pretty dubious.
272
262
  def set_slug(normalized_slug = nil)
273
- if normalized_slug || should_generate_new_friendly_id?
274
- normalized_slug ||= normalize_friendly_id send(friendly_id_config.base)
275
- generator = friendly_id_config.slug_generator_class.new self, normalized_slug
276
- send "#{friendly_id_config.slug_column}=", generator.generate
263
+ if should_generate_new_friendly_id?
264
+ candidates = FriendlyId::Candidates.new(self, normalized_slug || send(friendly_id_config.base))
265
+ slug = slug_generator.generate(candidates) || resolve_friendly_id_conflict(candidates)
266
+ send "#{friendly_id_config.slug_column}=", slug
277
267
  end
278
268
  end
279
269
  private :set_slug
280
270
 
271
+ def slug_generator
272
+ friendly_id_config.slug_generator_class.new(self.class.base_class.unscoped.friendly)
273
+ end
274
+ private :slug_generator
275
+
281
276
  # This module adds the +:slug_column+, and +:sequence_separator+, and
282
277
  # +:slug_generator_class+ configuration options to
283
278
  # {FriendlyId::Configuration FriendlyId::Configuration}.
@@ -293,17 +288,10 @@ issue}[https://github.com/norman/friendly_id/issues/180] for discussion.
293
288
 
294
289
  # The string used to separate a slug base from a numeric sequence.
295
290
  #
296
- # By default, +--+ is used to separate the slug from the sequence.
297
- # FriendlyId uses two dashes to distinguish sequences from slugs with
298
- # numbers in their name.
299
- #
300
291
  # You can change the default separator by setting the
301
292
  # {FriendlyId::Slugged::Configuration#sequence_separator
302
293
  # sequence_separator} configuration option.
303
- #
304
- # For obvious reasons, you should avoid setting it to "+-+" unless you're
305
- # sure you will never want to have a friendly id with a number in it.
306
- # @return String The sequence separator string. Defaults to "+--+".
294
+ # @return String The sequence separator string. Defaults to "+-+".
307
295
  def sequence_separator
308
296
  @sequence_separator or defaults[:sequence_separator]
309
297
  end
@@ -0,0 +1,3 @@
1
+ module FriendlyId
2
+ VERSION = "5.0.0.alpha.1"
3
+ end
@@ -3,14 +3,15 @@ require "rails/generators/active_record"
3
3
 
4
4
  # This generator adds a migration for the {FriendlyId::History
5
5
  # FriendlyId::History} addon.
6
- class FriendlyIdGenerator < Rails::Generators::Base
7
- include Rails::Generators::Migration
8
- extend ActiveRecord::Generators::Migration
6
+ class FriendlyIdGenerator < ActiveRecord::Generators::Base
7
+ # ActiveRecord::Generators::Base inherits from Rails::Generators::NamedBase which requires a NAME parameter for the
8
+ # new table name. Our generator always uses 'friendly_id_slugs', so we just set a random name here.
9
+ argument :name, type: :string, default: 'random_name'
9
10
 
10
11
  source_root File.expand_path('../../friendly_id', __FILE__)
11
12
 
12
13
  # Copies the migration template to db/migrate.
13
- def copy_files(*args)
14
+ def copy_files
14
15
  migration_template 'migration.rb', 'db/migrate/create_friendly_id_slugs.rb'
15
16
  end
16
17
 
@@ -1,7 +1,7 @@
1
1
  require "rubygems"
2
2
  require "bundler/setup"
3
3
  require "minitest/unit"
4
- require "mocha"
4
+ require "mocha/setup"
5
5
  require "active_record"
6
6
  require 'active_support/core_ext/time/conversions'
7
7
 
@@ -2,7 +2,7 @@ require "helper"
2
2
 
3
3
  class Manual < ActiveRecord::Base
4
4
  extend FriendlyId
5
- friendly_id :name, :use => :history
5
+ friendly_id :name, :use => [:slugged, :history]
6
6
  end
7
7
 
8
8
  class HistoryTest < MiniTest::Unit::TestCase
@@ -29,6 +29,7 @@ class HistoryTest < MiniTest::Unit::TestCase
29
29
  test "should create new slug record when friendly_id changes" do
30
30
  with_instance_of(model_class) do |record|
31
31
  record.name = record.name + "b"
32
+ record.slug = nil
32
33
  record.save!
33
34
  assert_equal 2, FriendlyId::Slug.count
34
35
  end
@@ -38,10 +39,11 @@ class HistoryTest < MiniTest::Unit::TestCase
38
39
  with_instance_of(model_class) do |record|
39
40
  old_friendly_id = record.friendly_id
40
41
  record.name = record.name + "b"
42
+ record.slug = nil
41
43
  record.save!
42
44
  begin
43
- assert model_class.find(old_friendly_id)
44
- assert model_class.exists?(old_friendly_id), "should exist? by old id"
45
+ assert model_class.friendly.find(old_friendly_id)
46
+ assert model_class.friendly.exists?(old_friendly_id), "should exist? by old id"
45
47
  rescue ActiveRecord::RecordNotFound
46
48
  flunk "Could not find record by old id"
47
49
  end
@@ -52,8 +54,9 @@ class HistoryTest < MiniTest::Unit::TestCase
52
54
  transaction do
53
55
  record = model_class.create! :name => "hello"
54
56
  assert_equal 1, FriendlyId::Slug.count
55
- record = model_class.find("hello")
57
+ record = model_class.friendly.find("hello")
56
58
  record.name = "hello again"
59
+ record.slug = nil
57
60
  record.save!
58
61
  assert_equal 2, FriendlyId::Slug.count
59
62
  end
@@ -64,17 +67,7 @@ class HistoryTest < MiniTest::Unit::TestCase
64
67
  old_friendly_id = record.friendly_id
65
68
  record.name = record.name + "b"
66
69
  record.save!
67
- assert !model_class.find(old_friendly_id).readonly?
68
- end
69
- end
70
-
71
- test "should create correct sequence numbers even when some conflicted slugs have changed" do
72
- transaction do
73
- record1 = model_class.create! :name => 'hello'
74
- record2 = model_class.create! :name => 'hello!'
75
- record2.update_attributes :name => 'goodbye'
76
- record3 = model_class.create! :name => 'hello!'
77
- assert_equal 'hello--3', record3.slug
70
+ assert !model_class.friendly.find(old_friendly_id).readonly?
78
71
  end
79
72
  end
80
73
 
@@ -96,16 +89,7 @@ class HistoryTest < MiniTest::Unit::TestCase
96
89
  first_record.save!
97
90
  second_record = model_class.create! :name => "foo"
98
91
  assert second_record.slug != "foo"
99
- assert second_record.slug == "foo--2"
100
- end
101
- end
102
-
103
- test 'should increment the sequence by one for each historic slug' do
104
- transaction do
105
- previous_record = model_class.create! :name => "foo"
106
- first_record = model_class.create! :name => 'another'
107
- second_record = model_class.create! :name => 'another'
108
- assert second_record.slug == "another--2"
92
+ assert_match(/foo-.+/, second_record.slug)
109
93
  end
110
94
  end
111
95
 
@@ -114,13 +98,28 @@ class HistoryTest < MiniTest::Unit::TestCase
114
98
  first_record = model_class.create! :name => "foo"
115
99
  second_record = model_class.create! :name => 'another'
116
100
 
117
- second_record.update_attributes :name => 'foo'
118
- assert second_record.slug == "foo--2"
119
- first_record.update_attributes :name => 'another'
120
- assert first_record.slug == "another--2"
101
+ second_record.update_attributes :name => 'foo', :slug => nil
102
+ assert_match(/foo-.*/, second_record.slug)
103
+
104
+ first_record.update_attributes :name => 'another', :slug => nil
105
+ assert_match(/another-.*/, first_record.slug)
121
106
  end
122
107
  end
123
108
 
109
+ test 'should name table according to prefix and suffix' do
110
+ transaction do
111
+ begin
112
+ prefix = "prefix_"
113
+ without_prefix = FriendlyId::Slug.table_name
114
+ ActiveRecord::Base.table_name_prefix = prefix
115
+ FriendlyId::Slug.reset_table_name
116
+ assert_equal prefix + without_prefix, FriendlyId::Slug.table_name
117
+ ensure
118
+ ActiveRecord::Base.table_name_prefix = ""
119
+ FriendlyId::Slug.table_name = without_prefix
120
+ end
121
+ end
122
+ end
124
123
  end
125
124
 
126
125
  class HistoryTestWithSti < HistoryTest
@@ -137,8 +136,6 @@ class HistoryTestWithSti < HistoryTest
137
136
  end
138
137
  end
139
138
 
140
-
141
-
142
139
  class City < ActiveRecord::Base
143
140
  has_many :restaurants
144
141
  end
@@ -158,45 +155,55 @@ class ScopedHistoryTest < MiniTest::Unit::TestCase
158
155
  end
159
156
 
160
157
  test "should find old scoped slugs" do
161
- city = City.create!
162
- with_instance_of(Restaurant) do |record|
163
- record.city = city
158
+ transaction do
159
+ city = City.create!
160
+ with_instance_of(Restaurant) do |record|
161
+ record.city = city
164
162
 
165
- record.name = "x"
166
- record.save!
163
+ record.name = "x"
164
+ record.slug = nil
165
+ record.save!
167
166
 
168
- record.name = "y"
169
- record.save!
167
+ record.name = "y"
168
+ record.slug = nil
169
+ record.save!
170
170
 
171
- assert_equal city.restaurants.find("x"), city.restaurants.find("y")
171
+ assert_equal city.restaurants.friendly.find("x"), city.restaurants.friendly.find("y")
172
+ end
172
173
  end
173
174
  end
174
175
 
175
176
  test "should consider old scoped slugs when creating slugs" do
176
- city = City.create!
177
- with_instance_of(Restaurant) do |record|
178
- record.city = city
177
+ transaction do
178
+ city = City.create!
179
+ with_instance_of(Restaurant) do |record|
180
+ record.city = city
179
181
 
180
- record.name = "x"
181
- record.save!
182
+ record.name = "x"
183
+ record.slug = nil
184
+ record.save!
182
185
 
183
- record.name = "y"
184
- record.save!
186
+ record.name = "y"
187
+ record.slug = nil
188
+ record.save!
185
189
 
186
- second_record = model_class.create! :city => city, :name => 'x'
187
- assert_equal "x--2", second_record.friendly_id
190
+ second_record = model_class.create! :city => city, :name => 'x'
191
+ assert_match(/x-.+/, second_record.friendly_id)
188
192
 
189
- third_record = model_class.create! :city => city, :name => 'y'
190
- assert_equal "y--2", third_record.friendly_id
193
+ third_record = model_class.create! :city => city, :name => 'y'
194
+ assert_match(/y-.+/, third_record.friendly_id)
195
+ end
191
196
  end
192
197
  end
193
198
 
194
199
  test "should allow equal slugs in different scopes" do
195
- city = City.create!
196
- second_city = City.create!
197
- record = model_class.create! :city => city, :name => 'x'
198
- second_record = model_class.create! :city => second_city, :name => 'x'
200
+ transaction do
201
+ city = City.create!
202
+ second_city = City.create!
203
+ record = model_class.create! :city => city, :name => 'x'
204
+ second_record = model_class.create! :city => second_city, :name => 'x'
199
205
 
200
- assert_equal record.slug, second_record.slug
206
+ assert_equal record.slug, second_record.slug
207
+ end
201
208
  end
202
- end
209
+ end