Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle special characters #332

Closed
grillorafael opened this issue Jan 17, 2014 · 5 comments
Closed

Handle special characters #332

grillorafael opened this issue Jan 17, 2014 · 5 comments

Comments

@grillorafael
Copy link

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?

@jonatack
Copy link
Contributor

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.

@grillorafael
Copy link
Author

Thanks, it worked for me!

@gordonbisnor
Copy link

This is great.

@guilpejon
Copy link

guilpejon commented Apr 13, 2018

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

@llekn
Copy link

llekn commented Nov 21, 2019

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 %>

🎉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants