norman/friendly_id — Issue #230: Reuse of an old slug by a new record causes a conflict in history
Author: belucid
Created: 2012-02-13 18:03:23+00:00
State: open

I'm using the history module, so prior slugs are being stored.

Let's say I have class, A, and the slug for A is based on the name field.

b = A.new
b.name = 'foo'
b.save
b.name = 'bar'
b.save

c = A.new
c.name = 'foo'
c.save

That sequence should give you a slug of 'foo--1' for c, since the slug 'foo' has already been used. It's not currently being used, but it's still in the history pointing to b. Instead I get a MySQL index constraint failure as it try to add 'foo' into the slug history for class A since there is already a 'foo' in the history.

Here's a console log of the above with my real class:

Loading development environment (Rails 3.2.1)
ruby-1.9.3-p0 :001 > b = Stash.new
=> #
ruby-1.9.3-p0 :002 > b.name = 'foo'
=> "foo"
ruby-1.9.3-p0 :003 > b.save
(0.2ms) BEGIN
Stash Load (0.6ms) SELECT `stashes`.* FROM `stashes` WHERE (`slug` = 'foo' OR `slug` LIKE 'foo--%') ORDER BY LENGTH(`slug`) DESC, `slug` DESC LIMIT 1
FriendlyId::Slug Load (0.2ms) SELECT id FROM `friendly_id_slugs` WHERE `friendly_id_slugs`.`sluggable_id` IS NULL AND `friendly_id_slugs`.`sluggable_type` = 'Stash' AND `friendly_id_slugs`.`slug` = 'foo' ORDER BY `friendly_id_slugs`.id DESC FOR UPDATE
SQL (0.3ms) INSERT INTO `stashes` (`accessed_at`, `created_at`, `description`, `name`, `slug`, `updated_at`) VALUES ('2012-02-13 18:02:22', '2012-02-13 18:02:22', '', 'foo', 'foo', '2012-02-13 18:02:22')
SQL (0.2ms) INSERT INTO `friendly_id_slugs` (`created_at`, `slug`, `sluggable_id`, `sluggable_type`) VALUES ('2012-02-13 18:02:22', 'foo', 28, 'Stash')
(0.3ms) COMMIT
=> true
ruby-1.9.3-p0 :004 > b.name = 'bar'
=> "bar"
ruby-1.9.3-p0 :005 > b.save
(0.2ms) BEGIN
Stash Load (0.6ms) SELECT `stashes`.* FROM `stashes` WHERE (`slug` = 'bar' OR `slug` LIKE 'bar--%') AND (id <> 28) ORDER BY LENGTH(`slug`) DESC, `slug` DESC LIMIT 1
FriendlyId::Slug Load (0.2ms) SELECT id FROM `friendly_id_slugs` WHERE `friendly_id_slugs`.`sluggable_id` = 28 AND `friendly_id_slugs`.`sluggable_type` = 'Stash' AND `friendly_id_slugs`.`slug` = 'bar' ORDER BY `friendly_id_slugs`.id DESC FOR UPDATE
(0.3ms) UPDATE `stashes` SET `name` = 'bar', `slug` = 'bar', `accessed_at` = '2012-02-13 18:02:32', `updated_at` = '2012-02-13 18:02:32' WHERE `stashes`.`id` = 28
SQL (0.2ms) INSERT INTO `friendly_id_slugs` (`created_at`, `slug`, `sluggable_id`, `sluggable_type`) VALUES ('2012-02-13 18:02:32', 'bar', 28, 'Stash')
(0.4ms) COMMIT
=> true
ruby-1.9.3-p0 :006 > c = Stash.new
=> #
ruby-1.9.3-p0 :007 > c.name = 'foo'
=> "foo"
ruby-1.9.3-p0 :008 > c.save
(0.2ms) BEGIN
Stash Load (0.5ms) SELECT `stashes`.* FROM `stashes` WHERE (`slug` = 'foo' OR `slug` LIKE 'foo--%') ORDER BY LENGTH(`slug`) DESC, `slug` DESC LIMIT 1
FriendlyId::Slug Load (0.2ms) SELECT id FROM `friendly_id_slugs` WHERE `friendly_id_slugs`.`sluggable_id` IS NULL AND `friendly_id_slugs`.`sluggable_type` = 'Stash' AND `friendly_id_slugs`.`slug` = 'foo' ORDER BY `friendly_id_slugs`.id DESC FOR UPDATE
SQL (0.2ms) INSERT INTO `stashes` (`accessed_at`, `created_at`, `description`, `name`, `slug`, `updated_at`) VALUES ('2012-02-13 18:02:48', '2012-02-13 18:02:48', '', 'foo', 'foo', '2012-02-13 18:02:48')
SQL (0.5ms) INSERT INTO `friendly_id_slugs` (`created_at`, `slug`, `sluggable_id`, `sluggable_type`) VALUES ('2012-02-13 18:02:48', 'foo', 29, 'Stash')
(1.3ms) ROLLBACK
ActiveRecord::RecordNotUnique: Mysql2::Error: Duplicate entry 'foo-Stash' for key 'index_friendly_id_slugs_on_slug_and_sluggable_type': INSERT INTO `friendly_id_slugs` (`created_at`, `slug`, `sluggable_id`, `sluggable_type`) VALUES ('2012-02-13 18:02:48', 'foo', 29, 'Stash')
from /Volumes/DataRAID/Users/sean/.rvm/gems/ruby-1.9.3-p0@ferret/gems/activerecord-3.2.1/lib/active_record/connection_adapters/abstract_mysql_adapter.rb:233:in `query'
from /Volumes/DataRAID/Users/sean/.rvm/gems/ruby-1.9.3-p0@ferret/gems/activerecord-3.2.1/lib/active_record/connection_adapters/abstract_mysql_adapter.rb:233:in `block in execute'
from /Volumes/DataRAID/Users/sean/.rvm/gems/ruby-1.9.3-p0@ferret/gems/activerecord-3.2.1/lib/active_record/connection_adapters/abstract_adapter.rb:280:in `block in log'
from /Volumes/DataRAID/Users/sean/.rvm/gems/ruby-1.9.3-p0@ferret/gems/activesupport-3.2.1/lib/active_support/notifications/instrumenter.rb:20:in `instrument'
from /Volumes/DataRAID/Users/sean/.rvm/gems/ruby-1.9.3-p0@ferret/gems/activerecord-3.2.1/lib/active_record/connection_adapters/abstract_adapter.rb:275:in `log'
from /Volumes/DataRAID/Users/sean/.rvm/gems/ruby-1.9.3-p0@ferret/gems/activerecord-3.2.1/lib/active_record/connection_adapters/abstract_mysql_adapter.rb:233:in `execute'
from /Volumes/DataRAID/Users/sean/.rvm/gems/ruby-1.9.3-p0@ferret/gems/activerecord-3.2.1/lib/active_record/connection_adapters/mysql2_adapter.rb:214:in `execute'
from /Volumes/DataRAID/Users/sean/.rvm/gems/ruby-1.9.3-p0@ferret/gems/activerecord-3.2.1/lib/active_record/connection_adapters/mysql2_adapter.rb:241:in `exec_insert'
from /Volumes/DataRAID/Users/sean/.rvm/gems/ruby-1.9.3-p0@ferret/gems/activerecord-3.2.1/lib/active_record/connection_adapters/abstract/database_statements.rb:88:in `insert'
from /Volumes/DataRAID/Users/sean/.rvm/gems/ruby-1.9.3-p0@ferret/gems/activerecord-3.2.1/lib/active_record/connection_adapters/abstract/query_cache.rb:14:in `insert'
from /Volumes/DataRAID/Users/sean/.rvm/gems/ruby-1.9.3-p0@ferret/gems/activerecord-3.2.1/lib/active_record/relation.rb:66:in `insert'
from /Volumes/DataRAID/Users/sean/.rvm/gems/ruby-1.9.3-p0@ferret/gems/activerecord-3.2.1/lib/active_record/persistence.rb:363:in `create'
from /Volumes/DataRAID/Users/sean/.rvm/gems/ruby-1.9.3-p0@ferret/gems/activerecord-3.2.1/lib/active_record/timestamp.rb:57:in `create'
from /Volumes/DataRAID/Users/sean/.rvm/gems/ruby-1.9.3-p0@ferret/gems/activerecord-3.2.1/lib/active_record/callbacks.rb:268:in `block in create'
from /Volumes/DataRAID/Users/sean/.rvm/gems/ruby-1.9.3-p0@ferret/gems/activesupport-3.2.1/lib/active_support/callbacks.rb:403:in `_run__621559765060024628__create__4310444806694483026__callbacks'
from /Volumes/DataRAID/Users/sean/.rvm/gems/ruby-1.9.3-p0@ferret/gems/activesupport-3.2.1/lib/active_support/callbacks.rb:405:in `__run_callback'
... 41 levels...
from /Volumes/DataRAID/Users/sean/.rvm/gems/ruby-1.9.3-p0@ferret/gems/activerecord-3.2.1/lib/active_record/validations.rb:50:in `save'
from /Volumes/DataRAID/Users/sean/.rvm/gems/ruby-1.9.3-p0@ferret/gems/activerecord-3.2.1/lib/active_record/attribute_methods/dirty.rb:22:in `save'
from /Volumes/DataRAID/Users/sean/.rvm/gems/ruby-1.9.3-p0@ferret/gems/activerecord-3.2.1/lib/active_record/transactions.rb:241:in `block (2 levels) in save'
from /Volumes/DataRAID/Users/sean/.rvm/gems/ruby-1.9.3-p0@ferret/gems/activerecord-3.2.1/lib/active_record/transactions.rb:295:in `block in with_transaction_returning_status'
from /Volumes/DataRAID/Users/sean/.rvm/gems/ruby-1.9.3-p0@ferret/gems/activerecord-3.2.1/lib/active_record/connection_adapters/abstract/database_statements.rb:190:in `transaction'
from /Volumes/DataRAID/Users/sean/.rvm/gems/ruby-1.9.3-p0@ferret/gems/activerecord-3.2.1/lib/active_record/transactions.rb:208:in `transaction'
from /Volumes/DataRAID/Users/sean/.rvm/gems/ruby-1.9.3-p0@ferret/gems/activerecord-3.2.1/lib/active_record/transactions.rb:293:in `with_transaction_returning_status'
from /Volumes/DataRAID/Users/sean/.rvm/gems/ruby-1.9.3-p0@ferret/gems/activerecord-3.2.1/lib/active_record/transactions.rb:241:in `block in save'
from /Volumes/DataRAID/Users/sean/.rvm/gems/ruby-1.9.3-p0@ferret/gems/activerecord-3.2.1/lib/active_record/transactions.rb:252:in `rollback_active_record_state!'
from /Volumes/DataRAID/Users/sean/.rvm/gems/ruby-1.9.3-p0@ferret/gems/activerecord-3.2.1/lib/active_record/transactions.rb:240:in `save'
from (irb):8
from /Volumes/DataRAID/Users/sean/.rvm/gems/ruby-1.9.3-p0@ferret/gems/railties-3.2.1/lib/rails/commands/console.rb:47:in `start'
from /Volumes/DataRAID/Users/sean/.rvm/gems/ruby-1.9.3-p0@ferret/gems/railties-3.2.1/lib/rails/commands/console.rb:8:in `start'
from /Volumes/DataRAID/Users/sean/.rvm/gems/ruby-1.9.3-p0@ferret/gems/railties-3.2.1/lib/rails/commands.rb:41:in `'
from script/rails:6:in `require'
from script/rails:6:in `

'ruby-1.9.3-p0 :009 >