diff --git a/lib/stretchy/relations/query_builder.rb b/lib/stretchy/relations/query_builder.rb index 980ad98..95d2f52 100644 --- a/lib/stretchy/relations/query_builder.rb +++ b/lib/stretchy/relations/query_builder.rb @@ -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 @@ -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 @@ -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| @@ -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 @@ -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 diff --git a/lib/stretchy/relations/query_methods.rb b/lib/stretchy/relations/query_methods.rb index f5015f7..628ca87 100644 --- a/lib/stretchy/relations/query_methods.rb +++ b/lib/stretchy/relations/query_methods.rb @@ -39,6 +39,7 @@ def registry :or_filter, :extending, :skip_callbacks, + :match, :neural_sparse, :neural, :hybrid, diff --git a/lib/stretchy/relations/query_methods/match.rb b/lib/stretchy/relations/query_methods/match.rb new file mode 100644 index 0000000..7a995f1 --- /dev/null +++ b/lib/stretchy/relations/query_methods/match.rb @@ -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 diff --git a/spec/stretchy/relations/query_methods/match_spec.rb b/spec/stretchy/relations/query_methods/match_spec.rb new file mode 100644 index 0000000..c504e50 --- /dev/null +++ b/spec/stretchy/relations/query_methods/match_spec.rb @@ -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 \ No newline at end of file