Search code examples
ruby-on-railsfriendly-id

Rails validate uniqueness for the ASCII approximation


I hope the title is not too unclear.

I am making arails app and I have a question about rails validation. Consider this code in the User,rb model file:

validates :name,
          presence: true,
          length: { maximum: 50 },
          uniqueness: { case_sensitive: false }

I am using the friendly_id gem to generate slugs for the users. I wont allow users to change their name. What I now need is to ensure that Names are unique in such a way that there will be no UUID's appended to slugs if two people have the same name converted in ascii approximation.

Current behaviour is:

User 1 signs up with a name and gets a slug like this:

name: "Jaiel" slug: "jaiel"

User 2 now does the same name but a bit different:

name: "Jàìèl" slug: "jaiel-6558c3f1-e6a1-4199-a53e-4ccc565657d4"

The problem here as you see I want such a uniqueness validation that User 2 would have been rejected because both names would generate the slug "jaiel" for their friendly_id's

I would appreciate your help on that matter

Thanks


Solution

  • Take a look into ActiveSupport::Inflector.transliterate:

    ActiveSupport::Inflector.transliterate('Ærøskøbing')
    #=> "AEroskobing"
    

    Also, with this type of validation you might want to go with custom validation (alongside the one you already have):

    class User
      validate :unique_slug
    
      private
    
      def unique_slug
        names = self.class.all.map(&:asci_name)
    
        raise ActiveRecord::RecordInvalid.new(self) if names.include?(asci_name)
      end
    
      def asci_name
        ActiveSupport::Inflector.transliterate(name)
      end
    end
    

    Obviously, this is super inefficient to query whole table on each validation, but this is just to point you into one of the possible directions.

    Another option would be going for a callback. Transliterating the name upon creation:

    before_validation: :transliterate_name
    
    def transliterate_name
      self.name = ActiveSupport::Inflector.transliterate(name)
    end
    

    It will first transliterate the name, then validate uniqueness of already transliterated name with the validation you have. Looks like a solution, definitely not as heavy as initial one.