lib/wolf_core/utils/friendly_model_id.rb



# frozen_string_literal: true

# Steps to use this module:
# 1. Create a migration to add the friendly_id column to your model.
# 2. Include the WolfCore::FriendlyModelId in your model.
# 3. Implement the friendly_id_candidates method in your model.
#
# Example:
#
# class MyModel < ApplicationRecord
#   include WolfCore::FriendlyModelId
#
#   def friendly_id_candidates
#     [:fullname, :address]
#   end
# end
#
# This will generate a friendly_id based on the fullname field.
# If the fullname is not unique, it will attempt to use the address field instead.
# If neither can be used to generate a unique friendly_id, it will append the id to
# the value of the first field name in the array returned by the friendly_id_candidates
# method, which is fullname in this case.

module WolfCore
  module FriendlyModelId
    extend ActiveSupport::Concern
    include WolfCore::StringUtils

    included do
      raise "Model #{name} does not have a friendly_id column" unless column_names.include?("friendly_id")

      before_save :set_friendly_id
    end

    class_methods do
      def by_friendly_id(friendly_id)
        find_by(friendly_id: friendly_id)
      end
    end

    def set_friendly_id
      return if friendly_id.present?

      self.friendly_id = generate_friendly_id
    end

    def generate_friendly_id
      friendly_id = find_unique_friendly_id
      return friendly_id if friendly_id.present?

      default_candidate = Array(friendly_id_candidates_array.first) + [:id]
      convert_candidate_to_friendly_id(default_candidate)
    end

    def find_unique_friendly_id
      friendly_id_candidates_array.each do |candidate|
        friendly_id = convert_candidate_to_friendly_id(candidate)
        return friendly_id if friendly_id.present? && !friendly_id_used?(friendly_id)
      end
      nil
    end

    def friendly_id_candidates
      [:id]
    end

    def friendly_id_candidates_array
      Array(friendly_id_candidates)
    end

    def convert_candidate_to_friendly_id(candidate)
      candidate = Array(candidate)
      candidate.map!(&:to_sym).uniq!
      candidate.map do |field|
        field_value = send(field)
        parse_field_value(field_value)
      end.join("-")
    end

    def friendly_id_used?(friendly_id)
      self.class.where.not(id: id).find_by(friendly_id: friendly_id).present?
    end

    def parse_field_value(field_value)
      field_value = to_kebab_case(field_value)
      remove_non_alphanumeric_chars(field_value, exceptions: "-")
    end
  end
end