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

Rename filter to filter_query #47

Merged
merged 3 commits into from
Mar 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions lib/stretchy/querying.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
module Stretchy
module Querying
delegate :first, :first!, :last, :last!, :exists?, :has_field, :any?, :many?, to: :all
delegate :order, :limit, :size, :sort, :where, :rewhere, :eager_load, :includes, :create_with, :none, :unscope, to: :all
delegate :or_filter, :filter, :fields, :source, :highlight, :aggregation, to: :all
delegate :skip_callbacks, :routing, to: :all
delegate :search_options, :routing, to: :all
delegate :must, :must_not, :should, :where_not, :query_string, to: :all
delegate :order, :limit, :size, :sort, :rewhere, :eager_load, :includes, :create_with, :none, :unscope, to: :all
delegate :or_filter, :fields, :source, :highlight, to: :all
delegate *Stretchy::Relations::AggregationMethods::AGGREGATION_METHODS, to: :all

delegate :skip_callbacks, :routing, :search_options, to: :all
delegate :must, :must_not, :should, :where_not, :where, :filter_query, :query_string, to: :all

def fetch_results(es)
unless es.count?
Expand Down
4 changes: 2 additions & 2 deletions lib/stretchy/relation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ module Stretchy
class Relation

# These methods can accept multiple values.
MULTI_VALUE_METHODS = [:order, :where, :or_filter, :filter, :bind, :extending, :unscope, :skip_callbacks]
MULTI_VALUE_METHODS = [:order, :where, :or_filter, :filter_query, :bind, :extending, :unscope, :skip_callbacks]

# These methods can accept a single value.
SINGLE_VALUE_METHODS = [:limit, :offset, :routing, :size]
Expand All @@ -16,7 +16,7 @@ class Relation
VALUE_METHODS = MULTI_VALUE_METHODS + SINGLE_VALUE_METHODS

# Include modules.
include Relations::FinderMethods, Relations::SpawnMethods, Relations::QueryMethods, Relations::SearchOptionMethods, Delegation
include Relations::FinderMethods, Relations::SpawnMethods, Relations::QueryMethods, Relations::AggregationMethods, Relations::SearchOptionMethods, Delegation

# Getters.
attr_reader :klass, :loaded
Expand Down
758 changes: 758 additions & 0 deletions lib/stretchy/relations/aggregation_methods.rb

Large diffs are not rendered by default.

12 changes: 6 additions & 6 deletions lib/stretchy/relations/merger.rb
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def initialize(relation, other)
@other = other
end

NORMAL_VALUES = [:where, :first, :last, :filter]
NORMAL_VALUES = [:where, :first, :last, :filter_query]

def normal_values
NORMAL_VALUES
Expand All @@ -67,7 +67,7 @@ def merge
unless value.nil? || (value.blank? && false != value)
if name == :select
relation._select!(*value)
elsif name == :filter
elsif name == :filter_query
values.each do |v|
relation.send("#{name}!", v.first, v.last)
end
Expand Down Expand Up @@ -114,19 +114,19 @@ def merge_multi_values
lhs_wheres = relation.where_values
rhs_wheres = values[:where] || []

lhs_filters = relation.filter_values
rhs_filters = values[:filter] || []
lhs_filters = relation.filter_query_values
rhs_filters = values[:filter_query] || []

removed, kept = partition_overwrites(lhs_wheres, rhs_wheres)

where_values = kept + rhs_wheres

filters_removed, filters_kept = partition_overwrites(lhs_wheres, rhs_wheres)
filter_values = rhs_filters
filter_query_values = rhs_filters


relation.where_values = where_values.empty? ? nil : where_values
relation.filter_values = filter_values.empty? ? nil : filter_values
relation.filter_query_values = filter_query_values.empty? ? nil : filter_query_values

if values[:reordering]
# override any order specified in the original relation
Expand Down
4 changes: 2 additions & 2 deletions lib/stretchy/relations/query_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def aggregations
end

def filters
values[:filter]
values[:filter_query]
end

def or_filters
Expand Down Expand Up @@ -58,7 +58,7 @@ def sort
end

def query_filters
values[:filter]
values[:filter_query]
end

def search_options
Expand Down
46 changes: 10 additions & 36 deletions lib/stretchy/relations/query_methods.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ module QueryMethods
:query_string,
:aggregation,
:search_option,
:filter,
:filter_query,
:or_filter,
:extending,
:skip_callbacks
Expand Down Expand Up @@ -308,7 +308,7 @@ def should!(opts, *rest) # :nodoc:



# @deprecated in elasticsearch 7.x+ use {#filter} instead
# @deprecated in elasticsearch 7.x+ use {#filter_query} instead
def or_filter(name, options = {}, &block)
spawn.or_filter!(name, options, &block)
end
Expand All @@ -322,53 +322,27 @@ def or_filter!(name, options = {}, &block) # :nodoc:
#
# This method supports all filters supported by Elasticsearch.
#
# @overload filter(type, opts)
# @overload filter_query(type, opts)
# @param type [Symbol] the type of filter to add (:range, :term, etc.)
# @param opts [Hash] a hash containing the attribute and value to filter by
#
# @example
# Model.filter(:range, age: {gte: 30})
# Model.filter(:term, color: :blue)
# Model.filter_query(:range, age: {gte: 30})
# Model.filter_query(:term, color: :blue)
#
# @return [Stretchy::Relation] a new relation, which reflects the filter
def filter(name, options = {}, &block)
spawn.filter!(name, options, &block)
def filter_query(name, options = {}, &block)
spawn.filter_query!(name, options, &block)
end

def filter!(name, options = {}, &block) # :nodoc:
self.filter_values += [{name: name, args: options}]
def filter_query!(name, options = {}, &block) # :nodoc:
self.filter_query_values += [{name: name, args: options}]
self
end



# Adds an aggregation to the query.
#
# @param name [Symbol, String] the name of the aggregation
# @param options [Hash] a hash of options for the aggregation
# @param block [Proc] an optional block to further configure the aggregation
#
# @example
# Model.aggregation(:avg_price, field: :price)
# Model.aggregation(:price_ranges) do
# range field: :price, ranges: [{to: 100}, {from: 100, to: 200}, {from: 200}]
# end
#
# Aggregation results are available in the `aggregations` method of the results under name provided in the aggregation.
#
# @example
# results = Model.where(color: :blue).aggregation(:avg_price, field: :price)
# results.aggregations.avg_price
#
# @return [Stretchy::Relation] a new relation
def aggregation(name, options = {}, &block)
spawn.aggregation!(name, options, &block)
end

def aggregation!(name, options = {}, &block) # :nodoc:
self.aggregation_values += [{name: name, args: assume_keyword_field(options)}]
self
end



Expand Down Expand Up @@ -430,7 +404,7 @@ def source!(*args) # :nodoc:
#
# @return [ActiveRecord::Relation] a new relation, which reflects the exists filter
def has_field(field)
spawn.filter(:exists, {field: field})
spawn.filter_query(:exists, {field: field})
end


Expand Down
4 changes: 2 additions & 2 deletions spec/stretchy/query_builder_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

describe '#filters' do
it 'returns the filters value' do
expect(subject.filters).to eq(values[:filter])
expect(subject.filters).to eq(values[:filter_query])
end
end

Expand Down Expand Up @@ -69,7 +69,7 @@

context 'when using filters' do
let(:subject) { described_class.new(filters) }
let(:filters) { {filter: [name: :active, args: {term: {status: :active}}]} }
let(:filters) { {filter_query: [name: :active, args: {term: {status: :active}}]} }

it 'builds the query structure' do
expect(subject.send(:build_query)[:bool][:filter]).to include({active: {term: {status: :active}}}.with_indifferent_access)
Expand Down
22 changes: 11 additions & 11 deletions spec/stretchy/querying_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
end

it 'with a filter' do
count = described_class.filter(:terms, gender: [:female]).count
count = described_class.filter_query(:terms, gender: [:female]).count
expect(count).to be_a(Integer)
expect(count).to eq(10)
end
Expand Down Expand Up @@ -91,33 +91,33 @@
end
end

context '.filter' do
context '.filter_query' do
it 'filters by term' do
expect(described_class.filter(:term, gender: 'male').map(&:gender)).to all(eq('male'))
expect(described_class.filter(:term, gender: 'female').map(&:gender)).to all(eq('female'))
expect(described_class.filter_query(:term, gender: 'male').map(&:gender)).to all(eq('male'))
expect(described_class.filter_query(:term, gender: 'female').map(&:gender)).to all(eq('female'))
end

it 'filters by range' do
expect(described_class.filter(:range, age: {gte: 30}).map(&:age)).to all(be >= 30)
expect(described_class.filter(:range, age: {lte: 30}).map(&:age)).to all(be <= 30)
expect(described_class.filter_query(:range, age: {gte: 30}).map(&:age)).to all(be >= 30)
expect(described_class.filter_query(:range, age: {lte: 30}).map(&:age)).to all(be <= 30)
end

it 'filters by terms' do
expect(described_class.filter(:terms, 'position.name.keyword': ['Software Engineer', 'Product Manager']).map{|r| r.position['name']}).to all(be_in(['Software Engineer', 'Product Manager']))
expect(described_class.filter_query(:terms, 'position.name.keyword': ['Software Engineer', 'Product Manager']).map{|r| r.position['name']}).to all(be_in(['Software Engineer', 'Product Manager']))
end

it 'filters by exists' do
expect(described_class.filter(:exists, field: 'position.level').map{|r| r.position['level']}).to all(be_truthy)
expect(described_class.filter_query(:exists, field: 'position.level').map{|r| r.position['level']}).to all(be_truthy)
end

# Doesn't seem to be supported in 7.x+
xit 'filters by or' do
expect(described_class.filter(:or, [{term: {age: 25}}, {term: {age: 30}}]).map(&:age)).to all(include(25,30))
expect(described_class.filter_query(:or, [{term: {age: 25}}, {term: {age: 30}}]).map(&:age)).to all(include(25,30))
end

# Doesn't seem to be supported in 7.x+
xit 'filters by not' do
expect(described_class.filter(:not, {term: {age: 25}}).map(&:age)).not_to include(25)
expect(described_class.filter_query(:not, {term: {age: 25}}).map(&:age)).not_to include(25)
end
end

Expand Down Expand Up @@ -149,7 +149,7 @@

context 'query string' do
it 'filter with query string' do
result = described_class.filter(:query_string, {query: "Mia OR Isabella", default_field: "name"} )
result = described_class.filter_query(:query_string, {query: "Mia OR Isabella", default_field: "name"} )
expect(result.map(&:name)).to include("Mia Rodriguez", "Isabella Lewis")
end

Expand Down
68 changes: 68 additions & 0 deletions spec/stretchy/relations/aggregation_methods_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
describe Stretchy::Relations::AggregationMethods do
let(:model) do
class TestModel < Stretchy::Record
end
TestModel
end


let(:relation) { Stretchy::Relation.new(model, {}) } # create a real instance

before do
allow(model).to receive(:all).and_return(relation)
end

shared_examples 'an aggregation method' do |method, args, expected_args|
describe "##{method}" do
before do
allow(relation).to receive(:aggregation)
end

it "performs a #{method} aggregation" do
if args.is_a?(Array)
model.send(method, :my_agg, *args)
else
model.send(method, :my_agg, args)
end
expect(relation).to have_received(:aggregation).with(:my_agg, expected_args)
end
end
end

it_behaves_like 'an aggregation method', :avg, {field: :price}, {avg: {field: :price}}
it_behaves_like 'an aggregation method', :bucket_script, {script: "params.tShirtsSold * params.price", buckets_path: {tShirtsSold: "tShirtsSold", price: "price"}}, {bucket_script: {script: "params.tShirtsSold * params.price", buckets_path: {tShirtsSold: "tShirtsSold", price: "price"}}}
it_behaves_like 'an aggregation method', :bucket_selector, {script: "params.totalSales > 200", buckets_path: {totalSales: "totalSales"}}, {bucket_selector: {script: "params.totalSales > 200", buckets_path: {totalSales: "totalSales"}}}
it_behaves_like 'an aggregation method', :cardinality, {field: :category}, {cardinality: {field: :category}}
it_behaves_like 'an aggregation method', :date_histogram, {field: :created_at, interval: 'month'}, {date_histogram: {field: :created_at, interval: 'month'}}
it_behaves_like 'an aggregation method', :extended_stats, {field: :price}, {extended_stats: {field: :price}}
it_behaves_like 'an aggregation method', :filter, {term: {category: 'electronics'}}, {filter: {term: {category: 'electronics'}}}
it_behaves_like 'an aggregation method', :filters, {filters: {electronics: {term: {category: 'electronics'}}, books: {term: {category: 'books'}}}}, {filters: {filters: {electronics: {term: {category: 'electronics'}}, books: {term: {category: 'books'}}}}}
it_behaves_like 'an aggregation method', :geo_bounds, {field: :location}, {geo_bounds: {field: :location}}
it_behaves_like 'an aggregation method', :geo_centroid, {field: :location}, {geo_centroid: {field: :location}}
it_behaves_like 'an aggregation method', :global, {}, {global: {}}
it_behaves_like 'an aggregation method', :histogram, {field: :price, interval: 10}, {histogram: {field: :price, interval: 10}}
it_behaves_like 'an aggregation method', :ip_range, {field: :ip, ranges: [{to: '10.0.0.5'}, {from: '10.0.0.5'}]}, {ip_range: {field: :ip, ranges: [{to: '10.0.0.5'}, {from: '10.0.0.5'}]}}
it_behaves_like 'an aggregation method', :max, {field: :price}, {max: {field: :price}}
it_behaves_like 'an aggregation method', :min, {field: :price}, {min: {field: :price}}
it_behaves_like 'an aggregation method', :missing, {field: :price}, {missing: {field: :price}}
it_behaves_like 'an aggregation method', :nested, {path: 'comments'}, {nested: {path: 'comments'}}
it_behaves_like 'an aggregation method', :percentile_ranks, {field: :price, values: [100, 200]}, {percentile_ranks: {field: :price, values: [100, 200]}}
it_behaves_like 'an aggregation method', :percentiles, {field: :price, percents: [25, 50, 75]}, {percentiles: {field: :price, percents: [25, 50, 75]}}
it_behaves_like 'an aggregation method', :range, {field: :price, ranges: [{to: 100}, {from: 100, to: 200}, {from: 200}]}, {range: {field: :price, ranges: [{to: 100}, {from: 100, to: 200}, {from: 200}]}}
it_behaves_like 'an aggregation method', :reverse_nested, {}, {reverse_nested: {}}
it_behaves_like 'an aggregation method', :sampler, {shard_size: 200}, {sampler: {shard_size: 200}}
it_behaves_like 'an aggregation method', :scripted_metric, {init_script: "_agg['sales'] = 0", map_script: "_agg['sales'] += params.sales", combine_script: "return _agg['sales']", reduce_script: "return _agg['sales']"}, {scripted_metric: {init_script: "_agg['sales'] = 0", map_script: "_agg['sales'] += params.sales", combine_script: "return _agg['sales']", reduce_script: "return _agg['sales']"}}
it_behaves_like 'an aggregation method', :significant_terms, {field: :category}, {significant_terms: {field: :category}}
it_behaves_like 'an aggregation method', :stats, {field: :price}, {stats: {field: :price}}
it_behaves_like 'an aggregation method', :sum, {field: :price}, {sum: {field: :price}}
it_behaves_like 'an aggregation method', :terms, {field: :category}, {terms: {field: :category}}
it_behaves_like 'an aggregation method', :top_hits, {size: 1, sort: [{price: 'desc'}]}, {top_hits: {size: 1, sort: [{price: 'desc'}]}}
it_behaves_like 'an aggregation method', :top_metrics, {metrics: [{field: :price}]}, {top_metrics: {metrics: [{field: :price}]}}
it_behaves_like 'an aggregation method', :value_count, {field: :price}, {value_count: {field: :price}}
it_behaves_like 'an aggregation method', :weighted_avg, {value: {field: :price}, weight: {field: :sales}}, {weighted_avg: {value: {field: :price}, weight: {field: :sales}}}

context 'when passing nested aggregations' do
it_behaves_like 'an aggregation method', :terms, [{field: :category}, {aggs: {avg_price: {avg: {field: :price}}}}], {terms: {field: :category}, aggs: {avg_price: {avg: {field: :price}}}}
end

end
Loading