My normalize_friendly_id override is never called

155 Views Asked by At

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?

2

There are 2 best solutions below

0
pierre_loic On BEST ANSWER

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.

0
Sunny On

The normalize_friendly_id is only called when using the slugged module to use a slug column to store and find by this column:

friendly_id :code, use: :slugged

Using this you can then override the normalize_friendly_id method.