diff --git a/README.md b/README.md index 4ccf1562..9274c089 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,7 @@ end * [`:trigram` (Trigram search)](#trigram-trigram-search) * [`:threshold`](#threshold) * [`:word_similarity`](#word_similarity) + * [`:ilike` (Basic Search)](#ilike-basic-search) * [Limiting Fields When Combining Features](#limiting-fields-when-combining-features) * [Ignoring accent marks](#ignoring-accent-marks) * [Using tsvector columns](#using-tsvector-columns) @@ -548,7 +549,7 @@ search techniques. ```ruby class Beer < ActiveRecord::Base include PgSearch::Model - pg_search_scope :search_name, against: :name, using: [:tsearch, :trigram, :dmetaphone] + pg_search_scope :search_name, against: :name, using: [:tsearch, :trigram, :dmetaphone, :ilike] end ``` @@ -562,7 +563,8 @@ class Beer < ActiveRecord::Base using: { :trigram => {}, :dmetaphone => {}, - :tsearch => { :prefix => true } + :tsearch => { :prefix => true }, + :ilike => {} } end ``` @@ -573,6 +575,7 @@ The currently implemented features are * :trigram - [Trigram search](http://www.postgresql.org/docs/current/static/pgtrgm.html), which requires the trigram extension * :dmetaphone - [Double Metaphone search](http://www.postgresql.org/docs/current/static/fuzzystrmatch.html#AEN177521), which requires the fuzzystrmatch extension +* :ilike - Basic search using built in ilike operator #### :tsearch (Full Text Search) @@ -976,6 +979,22 @@ Sentence.similarity_like("word") # => [] Sentence.word_similarity_like("word") # => [sentence] ``` +### :ilike (Basic Search) + +Basic search using ilike. This will look for anything containing an exact match, ie `%QUERY%`. This is useful in situations where you are looking for a substring. + +```ruby +class Company < ActiveRecord::Base + include PgSearch::Model + pg_search_scope :find_substring, + against: :name, + using: :ilike +end + +macrohard = Company.create! name: "MacroHard" +Website.find_substring("hard") # => [macrohard] +``` + ### Limiting Fields When Combining Features Sometimes when doing queries combining different features you diff --git a/lib/pg_search/features.rb b/lib/pg_search/features.rb index b95cbd50..89ad48f1 100644 --- a/lib/pg_search/features.rb +++ b/lib/pg_search/features.rb @@ -3,6 +3,7 @@ require "pg_search/features/feature" require "pg_search/features/dmetaphone" +require "pg_search/features/ilike" require "pg_search/features/trigram" require "pg_search/features/tsearch" diff --git a/lib/pg_search/features/ilike.rb b/lib/pg_search/features/ilike.rb new file mode 100644 index 00000000..39667a50 --- /dev/null +++ b/lib/pg_search/features/ilike.rb @@ -0,0 +1,29 @@ +module PgSearch + module Features + class ILike < Feature + def conditions + Arel::Nodes::Grouping.new( + Arel::Nodes::InfixOperation.new( + "ILIKE", + normalized_document, + normalized_query + ) + ) + end + + def rank + Arel::Nodes::Grouping.new(Arel.sql("0")) # no ranking or delegate to tsearch like DMetaphone? + end + + private + + def normalized_query + Arel.sql(connection.quote("%#{query}%")) + end + + def normalized_document + Arel::Nodes::Grouping.new(Arel.sql(document)) + end + end + end +end diff --git a/lib/pg_search/scope_options.rb b/lib/pg_search/scope_options.rb index 6fe515dd..24c97177 100644 --- a/lib/pg_search/scope_options.rb +++ b/lib/pg_search/scope_options.rb @@ -135,6 +135,7 @@ def subquery_join FEATURE_CLASSES = { dmetaphone: Features::DMetaphone, + ilike: Features::ILike, tsearch: Features::TSearch, trigram: Features::Trigram }.freeze diff --git a/spec/lib/pg_search/features/i_like_spec.rb b/spec/lib/pg_search/features/i_like_spec.rb new file mode 100644 index 00000000..9afd1ba7 --- /dev/null +++ b/spec/lib/pg_search/features/i_like_spec.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +require "spec_helper" +require "ostruct" + +# rubocop:disable RSpec/MultipleMemoizedHelpers +describe PgSearch::Features::ILike do + subject(:feature) { described_class.new(query, options, columns, Model, normalizer) } + + let(:query) { "lolwut" } + let(:options) { {} } + let(:columns) { + [ + PgSearch::Configuration::Column.new(:name, nil, Model), + PgSearch::Configuration::Column.new(:content, nil, Model) + ] + } + let(:normalizer) { PgSearch::Normalizer.new(config) } + let(:config) { OpenStruct.new(ignore: []) } + + let(:coalesced_columns) do + <<~SQL.squish + coalesce(#{Model.quoted_table_name}."name"::text, '') + || ' ' + || coalesce(#{Model.quoted_table_name}."content"::text, '') + SQL + end + + with_model :Model do + table do |t| + t.string :name + t.string :content + end + end + + describe "conditions" do + it "escapes the search document and query" do + expect(feature.conditions.to_sql).to eq("((#{coalesced_columns}) ILIKE '%#{query}%')") + end + end + + describe "#rank" do + it "doesn't do different ranks" do + expect(feature.rank.to_sql).to eq("(0)") + end + end +end +# rubocop:enable RSpec/MultipleMemoizedHelpers