ruby-on-railsfriendly-id

My normalize_friendly_id override is never called


I am very new to friendly_id and it matches my need to provide friendly URLs 👍

I have a Group model (i.e. a group of users) for which I generate a unique code upon creation. FYI, this code attribute is set as a unique index in my PostgreSQL database.

class Group < ApplicationRecord
  include FriendlyId
  friendly_id :code

  after_create :generate_group_code

  private

  def normalize_friendly_id(value)
    super.upcase
  end

  def generate_group_code(size = 8)
    allowed_chars = ("a".."z").to_a
    code = (1..size).map { allowed_chars[rand(26)] }.join while Group.exists?(code: code)

    update(code: code)
  end
end

I think I have followed the gem's guide properly, I just want the generated code to be upcased in the URLs (i.e. /group/ABCDEFGH).

The friendly_id is indeed set as my code attribute, but it is not upcased. I placed a byebug in the normalize_friendly_id method but it is never triggered. What am I missing?


Solution

  • Sunny's way is probably the way to go in general, as the slugged module is required to edit internal methods such as normalize_friendly_id.

    In my case, I already have a code attribute that is unique. Using the slugged module would create a new attribute called slug, which would be exactly the same as my code. I want to avoid that duplication.

    In the end, I decided to dodge the friendly_id gem and directly override the to_param method the my model (inspired by this gist):

    class Group < ApplicationRecord
      validates :code, format: { with: /\A[a-z]{8}\z/ }, uniqueness: true, on: :update
    
      after_create :generate_group_code
      
      # Override the method to allow '/group/MYGROUPA' paths 
      def to_param
        code.upcase
      end
    
      # A group code is made of letters exclusively.
      # Converting a String (or nil) to an integer leads to 0.
      # In this case, lookup by code, otherwise, lookup by id.
      # Note the 'downcase', which allows URLs to be case insensitive.
      def self.find(input)
        input.to_i == 0 ? find_by_code!(input.downcase) : super
      end
    
      private
    
      def generate_group_code(size = 8)
        allowed_chars = ("a".."z").to_a
        code = (1..size).map { allowed_chars[rand(26)] }.join while Group.exists?(code: code)
    
        update(code: code)
      end
    end
    

    I'll edit this answer if I encounter any side effect, but for now it works.