Search code examples
ruby-on-railsfriendly-id

How to free up friendly_id slugs when destroying a record?


My Page model has a slug column using FriendlyId, with the history option turned on.

I want to free slugs so that if a page with this slug is destroyed, then recreated, it can reuse the same slug.

Site.create(slug: "alpha") # => adds an entry into FriendlyId::Slug
Site.find_by(slug: "alpha").destroy # => Leaves an orphaned slug entry
Site.create(slug: "alpha") # Cannot reuse the old slug
Site.last.slug # "alpha-{uuid}"

Solution

  • You don't actually have to do anything.

    FriendlyId's history module adds an association with the dependent: :destroy option anyways.

    module FriendlyId
      module History
        module Configuration
          def dependent_value
            dependent.nil? ? :destroy : dependent
          end
        end
        # ....
        # Configures the model instance to use the History add-on.
        def self.included(model_class)
          model_class.class_eval do
            has_many :slugs, -> { order(id: :desc) }, **{
              as: :sluggable,
              dependent: @friendly_id_config.dependent_value,
              class_name: Slug.to_s
            }
    
            after_save :create_slug
          end
        end
        # ...
      end
    end
    

    If you want to change it there is a configuration option. It's just not documented besides a line comment in the configuration file. You can either add it to the initializer file or pass it to the friendly_id method.

    Your "fix" is actually not doing anything except removing the ordering. Instead the issue is either a bug or just that your method of testing is flawed (it's not reproducable and destroy can fail).

    There are actual passing tests in the gem that disprove the premise of the question.

    class DependentDestroyTest < TestCaseClass
      include FriendlyId::Test
    
      class FalseManual < ActiveRecord::Base
        self.table_name = "manuals"
    
        extend FriendlyId
        friendly_id :name, use: :history, dependent: false
      end
    
      class DefaultManual < ActiveRecord::Base
        self.table_name = "manuals"
    
        extend FriendlyId
        friendly_id :name, use: :history
      end
    
      test "should allow disabling of dependent destroy" do
        transaction do
          assert FriendlyId::Slug.find_by_slug("foo").nil?
          l = FalseManual.create! name: "foo"
          assert FriendlyId::Slug.find_by_slug("foo").present?
          l.destroy
          assert FriendlyId::Slug.find_by_slug("foo").present?
        end
      end
    
      test "should dependently destroy by default" do
        transaction do
          assert FriendlyId::Slug.find_by_slug("baz").nil?
          l = DefaultManual.create! name: "baz"
          assert FriendlyId::Slug.find_by_slug("baz").present?
          l.destroy
          assert FriendlyId::Slug.find_by_slug("baz").nil?
        end
      end
    end