Ransack: Handle special characters

Created on 17 Jan 2014  路  5Comments  路  Source: activerecord-hackery/ransack

Hi, guys!

I'm having a problem here.

I have a record with the following name: 'P茫o de L贸'

When I search for 'p', I get my result fine but when I search for 'pa' I get no results.

Is there any way to match these characters?

Most helpful comment

Since I lost a couple of hours with this issue today, I'm going to share what I had to do to make the proposed solution work. I'm using PostgreSQL and I also wanted to override the cont predicate, so I wouldn't have to change my views.

# on initializers/ransack.rb
Ransack.configure do |config|
  config.add_predicate 'cont', # Name your predicate
      arel_predicate: 'matches',
      formatter: proc { |s| ActiveSupport::Inflector.transliterate("%#{s}%") }, # Note the %%
      validator: proc { |s| s.present? },
      compounds: true,
      type: :string
end

As for the database part, I used another solution, from #349

ransacker :name, type: :string do
  Arel.sql("unaccent(\"name\")")
end

And in some view

<%= f.search_field :name_cont %>

Note that you have to add the unnacent postgres extension for this to work

class AddUnnacentExtension < ActiveRecord::Migration[5.0]
  def up
    execute 'CREATE EXTENSION IF NOT EXISTS unaccent;'
  end

  def down
    execute 'DROP EXTENSION IF EXISTS unaccent CASCADE;'
  end
end

All 5 comments

Hi @grillorafael there are different ways you could handle special characters. One would be to remove them from the search query and the search field in the database. For this I've been using transliterate:

In config/initializers/ransack.rb, create a predicate to remove the accents from the string:

Ransack.configure do |config|
    config.add_predicate 'special_match', # Name your predicate
        arel_predicate: 'matches',
        formatter: proc { |s| transliterate(s) },
        validator: proc { |s| s.present? },
        compounds: true,
        type: :string
end

in your view partial:

<%= f.search_field :search_text_special_match %>

and search against a dedicated, indexed search column in your database with accents removed, using for example a before_save callback:

self.search_text = transliterate(name)

In addition to removing accents, you could also remove spaces and punctuation depending on your needs:

transliterate(name).remove(/[^a-zA-Z0-9]/).remove(/[ ]+/)

Or use a specific character to separate the searched fields.

Make sure you do the same thing in both your predicate method in config/initializers/ransack.rb and your before_save callback, perhaps with a single shared method to keep things DRY.

Thanks, it worked for me!

This is great.

Since I lost a couple of hours with this issue today, I'm going to share what I had to do to make the proposed solution work. I'm using PostgreSQL and I also wanted to override the cont predicate, so I wouldn't have to change my views.

# on initializers/ransack.rb
Ransack.configure do |config|
  config.add_predicate 'cont', # Name your predicate
      arel_predicate: 'matches',
      formatter: proc { |s| ActiveSupport::Inflector.transliterate("%#{s}%") }, # Note the %%
      validator: proc { |s| s.present? },
      compounds: true,
      type: :string
end

As for the database part, I used another solution, from #349

ransacker :name, type: :string do
  Arel.sql("unaccent(\"name\")")
end

And in some view

<%= f.search_field :name_cont %>

Note that you have to add the unnacent postgres extension for this to work

class AddUnnacentExtension < ActiveRecord::Migration[5.0]
  def up
    execute 'CREATE EXTENSION IF NOT EXISTS unaccent;'
  end

  def down
    execute 'DROP EXTENSION IF EXISTS unaccent CASCADE;'
  end
end

Since I lost a couple of hours too I would like to share my solution.

As I wanted to add the capability to use unaccent in multiple places and in complex queries (search in multiple columns, for example), adding a ransacker as proposed in the previous post wouldn't solve my problem.

What I ended doing (and ended working 馃槃 ) was:

Add the arel_extensions gem.

This gem adds a lot of interesting new nodes and predicates for Arel, in particular it adds the ai_imatches predication that enables a case-insensitive and accent-insensitive matcher search.

gem 'arel_extensions'

Create a new ransacker predicate that uses this Arel predication in app/config/initializers/ransack.rb:

Ransack.configure do |config|
  config.add_predicate(
    # produces queries in the form of `WHERE (unaccent (column)) ILIKE unaccent (%foo%bar%)`
    'ucont',
    arel_predicate: 'ai_imatches', # <- thanks arel_extensions !
    formatter: proc { |s| ActiveSupport::Inflector.transliterate("%#{s.tr(' ', '%')}%") },
    validator: proc { |s| s.present? },
    compounds: true,
    type: :string
  )
end

(the replacement of spaces by % will be helpful for matching space separated words with multiple columns)

From now on you can use the ucont predicate on every ransack!

You can also create a ransacker for unaccented, case-insensitive, space separated words in multiple columns. For example:

  ransacker :full_name do |parent|
    Arel::Nodes::InfixOperation.new(
      '||',
      parent.table[:first_name],
      Arel::Nodes::InfixOperation.new(
        '||',
        parent.table[:last_name],
        parent.table[:middle_name]
      )
    )
  end

Then in some view you can do

<%= f.search_field :full_name_ucont %>

馃帀

Was this page helpful?
0 / 5 - 0 ratings

Related issues

mkalmykov picture mkalmykov  路  3Comments

senid231 picture senid231  路  4Comments

seanfcarroll picture seanfcarroll  路  3Comments

tagrudev picture tagrudev  路  3Comments

mbajur picture mbajur  路  5Comments