Back to Repositories

Validating Scoped Slug Generation in friendly_id

This test suite validates the scoped functionality of FriendlyId gem for Ruby on Rails, focusing on slug generation within defined model relationships. It ensures proper handling of scoped slugs across different model associations and validates uniqueness constraints within specified scopes.

Test Coverage Overview

The test suite comprehensively covers scoped slug generation in the FriendlyId gem:

  • Scope detection from belongs_to relationships
  • Duplicate slug handling within and outside scopes
  • Multiple column scope applications
  • Slug regeneration on scope changes
  • Self-referential slug reuse cases

Implementation Analysis

The testing approach utilizes Minitest’s structured framework with transaction-based test cases. It implements custom test helpers and shared examples through module inclusion (FriendlyId::Test, FriendlyId::Test::Shared::Core). The tests leverage ActiveRecord relationships between Novelist, Novel, and Publisher models to validate scoping behavior.

Technical Details

  • Testing Framework: Minitest
  • Database Handling: ActiveRecord transactions
  • Model Structure: Three interconnected models (Novel, Novelist, Publisher)
  • Helper Modules: FriendlyId::Test for shared functionality
  • Scope Implementation: Multiple column support via belongs_to associations

Best Practices Demonstrated

The test suite exemplifies several testing best practices:

  • Isolated test cases using database transactions
  • Comprehensive edge case coverage
  • Clear test naming conventions
  • Modular test organization using shared examples
  • Proper setup and teardown of test data

norman/friendly_id

test/scoped_test.rb

            
require "helper"

class Novelist < ActiveRecord::Base
  extend FriendlyId
  friendly_id :name, use: :slugged
end

class Novel < ActiveRecord::Base
  extend FriendlyId
  belongs_to :novelist
  belongs_to :publisher
  friendly_id :name, use: :scoped, scope: [:publisher, :novelist]

  def should_generate_new_friendly_id?
    new_record? || super
  end
end

class Publisher < ActiveRecord::Base
  has_many :novels
end

class ScopedTest < TestCaseClass
  include FriendlyId::Test
  include FriendlyId::Test::Shared::Core

  def model_class
    Novel
  end

  test "should detect scope column from belongs_to relation" do
    assert_equal ["publisher_id", "novelist_id"], Novel.friendly_id_config.scope_columns
  end

  test "should detect scope column from explicit column name" do
    model_class = Class.new(ActiveRecord::Base) do
      self.abstract_class = true
      extend FriendlyId
      friendly_id :empty, use: :scoped, scope: :dummy
    end
    assert_equal ["dummy"], model_class.friendly_id_config.scope_columns
  end

  test "should allow duplicate slugs outside scope" do
    transaction do
      novel1 = Novel.create! name: "a", novelist: Novelist.create!(name: "a")
      novel2 = Novel.create! name: "a", novelist: Novelist.create!(name: "b")
      assert_equal novel1.friendly_id, novel2.friendly_id
    end
  end

  test "should not allow duplicate slugs inside scope" do
    with_instance_of Novelist do |novelist|
      novel1 = Novel.create! name: "a", novelist: novelist
      novel2 = Novel.create! name: "a", novelist: novelist
      assert novel1.friendly_id != novel2.friendly_id
    end
  end

  test "should apply scope with multiple columns" do
    transaction do
      novelist = Novelist.create! name: "a"
      publisher = Publisher.create! name: "b"
      novel1 = Novel.create! name: "c", novelist: novelist, publisher: publisher
      novel2 = Novel.create! name: "c", novelist: novelist, publisher: Publisher.create(name: "d")
      novel3 = Novel.create! name: "c", novelist: Novelist.create(name: "e"), publisher: publisher
      novel4 = Novel.create! name: "c", novelist: novelist, publisher: publisher
      assert_equal novel1.friendly_id, novel2.friendly_id
      assert_equal novel2.friendly_id, novel3.friendly_id
      assert novel3.friendly_id != novel4.friendly_id
    end
  end

  test "should allow a record to reuse its own slug" do
    with_instance_of(model_class) do |record|
      old_id = record.friendly_id
      record.slug = nil
      record.save!
      assert_equal old_id, record.friendly_id
    end
  end

  test "should generate new slug when scope changes" do
    transaction do
      novelist = Novelist.create! name: "a"
      publisher = Publisher.create! name: "b"
      novel1 = Novel.create! name: "c", novelist: novelist, publisher: publisher
      novel2 = Novel.create! name: "c", novelist: novelist, publisher: Publisher.create(name: "d")
      assert_equal novel1.friendly_id, novel2.friendly_id
      novel2.publisher = publisher
      novel2.save!
      assert novel2.friendly_id != novel1.friendly_id
    end
  end
end