Skip to content

Commit

Permalink
Merge pull request #103 from theablefew/feature/match
Browse files Browse the repository at this point in the history
Fix Issue #102: Add `.match` and support for multi match
  • Loading branch information
esmarkowski authored Mar 25, 2024
2 parents 4df8032 + b4e46ee commit d6bd219
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 3 deletions.
36 changes: 33 additions & 3 deletions lib/stretchy/relations/query_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ def query
@query ||= compact_where(values[:where])
end

def match_query
@match_query ||= values[:match]
end

def query_strings
@query_string ||= compact_where(values[:query_string], bool: false)
end
Expand Down Expand Up @@ -125,7 +129,7 @@ def missing_neural?
end

def no_query?
missing_bool_query? && missing_query_string? && missing_query_filter? && missing_neural? && ids.nil?
missing_bool_query? && missing_query_string? && missing_query_filter? && missing_neural? && ids.nil? && match_query.nil?
end

def build_query
Expand All @@ -135,6 +139,15 @@ def build_query
structure.values ids.flatten.compact.uniq
end unless ids.nil?

structure.match do
mq = match_query.dup
field, value = mq.first.shift
structure.set! field do
structure.query value
structure.extract! mq.last, *mq.last.keys
end
end unless match_query.nil?

structure.hybrid do
structure.queries do
hybrid[:neural].each do |n|
Expand Down Expand Up @@ -186,8 +199,8 @@ def build_query
end unless neural.blank?

structure.regexp do
build_regexp unless regexes.nil?
end
build_regexp
end unless regexes.nil?

structure.bool do

Expand Down Expand Up @@ -335,6 +348,23 @@ def as_query_string(q)
_and.join(" AND ")
end

def merge_and_append(queries)
builder = {}

queries.each do |q|
q.each do |k, v|
if builder.key?(k)
builder[k] = builder[k].class == Array ? builder[k] : [builder[k]]
builder[k] << v
else
builder[k] = v
end
end
end

builder
end

def extract_highlighter(highlighter)
Jbuilder.new do |highlight|
highlight.extract! highlighter
Expand Down
1 change: 1 addition & 0 deletions lib/stretchy/relations/query_methods.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ def registry
:or_filter,
:extending,
:skip_callbacks,
:match,
:neural_sparse,
:neural,
:hybrid,
Expand Down
52 changes: 52 additions & 0 deletions lib/stretchy/relations/query_methods/match.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
module Stretchy
module Relations
module QueryMethods
module Match

# This method is used to add conditions to the query.
#
# ### Parameters
#
# - `query:` (Required) - Text, number, boolean value or date you wish to find in the provided field. The match query analyzes any provided text before performing a search. This means the match query can search text fields for analyzed tokens rather than an exact term.
# - `options:` (Optional) - A hash of options to customize the match query. Options include:
# - `analyzer:` (string) - Analyzer used to convert the text in the query value into tokens. Defaults to the index-time analyzer mapped for the field. If no analyzer is mapped, the index’s default analyzer is used.
# - `auto_generate_synonyms_phrase_query:` (Boolean) - If true, match phrase queries are automatically created for multi-term synonyms. Defaults to true.
# - `boost:` (float) - Floating point number used to decrease or increase the relevance scores of the query. Defaults to 1.0.
# - `fuzziness:` (string) - Maximum edit distance allowed for matching.
# - `max_expansions:` (integer) - Maximum number of terms to which the query will expand. Defaults to 50.
# - `prefix_length:` (integer) - Number of beginning characters left unchanged for fuzzy matching. Defaults to 0.
# - `fuzzy_transpositions:` (Boolean) - If true, edits for fuzzy matching include transpositions of two adjacent characters (ab → ba). Defaults to true.
# - `fuzzy_rewrite:` (string) - Method used to rewrite the query.
# - `lenient:` (Boolean) - If true, format-based errors, such as providing a text query value for a numeric field, are ignored. Defaults to false.
# - `operator:` (string) - Boolean logic used to interpret text in the query value. Valid values are: OR (Default), AND.
# - `minimum_should_match:` (string) - Minimum number of clauses that must match for a document to be returned.
# - `zero_terms_query:` (string) - Indicates whether no documents are returned if the analyzer removes all tokens, such as when using a stop filter. Valid values are: none (Default), all.
#
# ### Returns
#
# Returns a Stretchy::Relation with the specified conditions applied.
#
# ### Examples
#
# ```ruby
# Model.match(path: "/new/things")
# ```
#
def match(opts = :chain, *rest)
return MatchChain.new(spawn) if opts == :chain
return self if opts.blank?

spawn.match!(opts, *rest)
end

def match!(opts, *rest) # :nodoc:
self.match_values = [Hash[*opts.shift], **opts]
self
end

QueryMethods.register!(:match)

end
end
end
end
60 changes: 60 additions & 0 deletions spec/stretchy/relations/query_methods/match_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
require 'spec_helper'
require 'models/test_model'

describe Stretchy::Relations::QueryMethods::Match do

context 'api' do
let(:model) {TestModel}
let(:relation) { Stretchy::Relation.new(model, {}) }
let(:value_key) { described_class.name.demodulize.underscore.to_sym }
let(:relation_values) { relation.values[value_key] }

it 'registers' do
expect(Stretchy::Relations::QueryMethods.registry).to include(:match)
end

context 'serializes values' do
it 'without options' do
relation.match(path: 'attributes/types')
expect(relation_values).to eq([{path: 'attributes/types'}])
end

it 'with options' do
relation.match(path: 'attributes/types', fuzziness: 3, operator: "AND")
expect(relation_values).to eq([{path: 'attributes/types'}, {fuzziness: 3, operator: "AND"}])
end
end

end

describe Stretchy::Relations::QueryBuilder do
let(:attribute_types) { double('model', attribute_types: { "path": Stretchy::Attributes::Type::Text.new })}
let(:values) { {} }
let(:clause) { subject.to_elastic.deep_symbolize_keys.dig(:query, :match) }

before do
allow(attribute_types).to receive(:[])
end

context 'when built' do
subject { described_class.new(values, attribute_types) }

context 'without a match query' do
it 'match is not present' do
values[:where] = [{first_name: 'Irving' }]
expect(clause).to be_falsey
end
end

context 'with a match query' do
it 'match is present' do
values[:match] = [{ path: 'attributes/types'}, {fuzziness: 3, operator: "AND"}]
expect(clause).to eq({ path: { query: 'attributes/types', fuzziness: 3, operator: "AND" } })
end

end
end

end

end

0 comments on commit d6bd219

Please sign in to comment.