require File.expand_path("../spec_helper", __dir__) RSpec.describe SearchFlip::Criteria do describe "delegation" do subject { SearchFlip::Criteria.new(target: ProductIndex) } methods = [ :total_entries, :current_page, :previous_page, :prev_page, :next_page, :first_page?, :last_page?, :out_of_range?, :total_pages, :hits, :ids, :count, :size, :length, :took, :aggregations, :suggestions, :scope, :results, :records, :scroll_id, :raw_response ] methods.each do |method| it { should delegate(method).to(:response) } end it { should delegate(:connection).to(:target) } end describe "#to_query" do it "returns the added must, filter and must_not clauses" do query = ProductIndex .must(term: { category: "category1" }) .filter(term: { category: "category2" }) .must_not(term: { category: "category3" }) expect(query.to_query).to eq( bool: { must: [{ term: { category: "category1" } }], filter: [{ term: { category: "category2" } }], must_not: [{ term: { category: "category3" } }] } ) end it "returns only the must clause, if there is only a single must clause" do query = ProductIndex.must(term: { category: "category" }) expect(query.to_query).to eq(term: { category: "category" }) end it "generates an executable query for must, filter and must_not clauses" do query = ProductIndex .must(term: { category: "category1" }) .filter(term: { category: "category2" }) .must_not(term: { category: "category3" }) expect { ProductIndex.must(query.to_query).execute }.not_to raise_error end it "returns the added post_must, post_filter and post_must_not clauses" do query = ProductIndex .post_must(term: { category: "category1" }) .post_filter(term: { category: "category2" }) .post_must_not(term: { category: "category3" }) expect(query.to_query).to eq( bool: { must_not: [{ term: { category: "category3" } }], filter: [ { term: { category: "category1" } }, { term: { category: "category2" } } ] } ) end it "generates an executable query for post_must, post_filter and post_must_not clauses" do query = ProductIndex .post_must(term: { category: "category1" }) .post_filter(term: { category: "category2" }) .post_must_not(term: { category: "category3" }) expect { ProductIndex.must(query.to_query).execute }.not_to raise_error end end describe "#merge" do it "merges criterias" do product1 = create(:product, price: 100, category: "category1") product2 = create(:product, price: 200, category: "category2") product3 = create(:product, price: 300, category: "category1") ProductIndex.import [product1, product2, product3] query = ProductIndex.where(price: 50..250).aggregate(:category).merge(ProductIndex.where(category: "category1")) expect(query.records).to eq([product1]) end describe "assignments" do methods = [ :profile_value, :failsafe_value, :terminate_after_value, :timeout_value, :offset_value, :limit_value, :scroll_args, :source_value, :preference_value, :search_type_value, :routing_value, :track_total_hits_value, :explain_value ] methods.each do |method| it "replaces the values" do criteria1 = SearchFlip::Criteria.new(target: TestIndex) criteria1.send("#{method}=", "value1") criteria2 = SearchFlip::Criteria.new(target: TestIndex) criteria2.send("#{method}=", "value2") expect(criteria1.merge(criteria2).send(method)).to eq("value2") end end end describe "array concatenations" do methods = [ :sort_values, :includes_values, :preload_values, :eager_load_values, :must_values, :must_not_values, :filter_values, :post_must_values, :post_must_not_values, :post_filter_values ] methods.each do |method| it "concatenates the values for #{method}" do criteria1 = SearchFlip::Criteria.new(target: TestIndex) criteria1.send("#{method}=", ["value1"]) criteria2 = SearchFlip::Criteria.new(target: TestIndex) criteria2.send("#{method}=", ["value2"]) result = criteria1.merge(criteria2) expect(result.send(method)).to eq(["value1", "value2"]) end end end describe "hash merges" do methods = [ :highlight_values, :suggest_values, :custom_value, :aggregation_values ] methods.each do |method| it "merges the values for #{method}" do criteria1 = SearchFlip::Criteria.new(target: TestIndex) criteria1.send("#{method}=", key1: "value1") criteria2 = SearchFlip::Criteria.new(target: TestIndex) criteria2.send("#{method}=", key2: "value2") result = criteria1.merge(criteria2) expect(result.send(method)).to eq(key1: "value1", key2: "value2") end end end end describe "#criteria" do it "returns self" do criteria = ProductIndex.criteria expect(criteria.criteria.object_id).to eq(criteria.object_id) end end describe "#all" do it "returns self" do criteria = ProductIndex.criteria expect(criteria.all.object_id).to eq(criteria.object_id) end end describe "#timeout" do it "sets the query timeout" do query = ProductIndex.timeout("1s") expect(query.request[:timeout]).to eq("1s") expect { query.execute }.not_to raise_error end end describe "#terminate_after" do it "sets the terminate after value" do query = ProductIndex.terminate_after(1) expect(query.request[:terminate_after]).to eq(1) expect { query.execute }.not_to raise_error end end describe "#where" do it "sets up the constraints correctly and is chainable" do product1 = create(:product, price: 100, category: "category1") product2 = create(:product, price: 200, category: "category2") product3 = create(:product, price: 300, category: "category1") ProductIndex.import [product1, product2, product3] query1 = ProductIndex.where(price: 100..200) query2 = query1.where(category: "category1") expect(query1.records.to_set).to eq([product1, product2].to_set) expect(query2.records).to eq([product1]) end it "works with arrays" do expected1 = create(:product, title: "expected1") expected2 = create(:product, title: "expected2") rejected = create(:product, title: "rejected") ProductIndex.import [expected1, expected2, rejected] query = ProductIndex.where(title: ["expected1", "expected2"]) expect(query.records.to_set).to eq([expected1, expected2].to_set) end it "works with ranges" do expected1 = create(:product, price: 100) expected2 = create(:product, price: 200) rejected = create(:product, price: 300) ProductIndex.import [expected1, expected2, rejected] query = ProductIndex.where(price: 100..200) expect(query.records.to_set).to eq([expected1, expected2].to_set) end it "works with nils" do expected = create(:product, price: nil) rejected = create(:product, price: 100) ProductIndex.import [expected, rejected] query = ProductIndex.where(price: nil) expect(query.records).to eq([expected]) end end describe "#where_not" do it "sets up the constraints correctly and is chainable" do product1 = create(:product, price: 100, category: "category1") product2 = create(:product, price: 200, category: "category2") product3 = create(:product, price: 300, category: "category1") ProductIndex.import [product1, product2, product3] query1 = ProductIndex.where_not(price: 250..350) query2 = query1.where_not(category: "category2") expect(query1.records.to_set).to eq([product1, product2].to_set) expect(query2.records).to eq([product1]) end it "works with arrays" do expected = create(:product, title: "expected") rejected1 = create(:product, title: "rejected1") rejected2 = create(:product, title: "rejected2") ProductIndex.import [expected, rejected1, rejected2] query = ProductIndex.where_not(title: ["rejected1", "rejected2"]) expect(query.records).to eq([expected]) end it "works with ranges" do expected = create(:product, price: 100) rejected1 = create(:product, price: 200) rejected2 = create(:product, price: 300) ProductIndex.import [expected, rejected1, rejected2] query = ProductIndex.where_not(price: 200..300) expect(query.records).to eq([expected]) end it "works with nils" do expected = create(:product, price: 100) rejected = create(:product, price: nil) ProductIndex.import [expected, rejected] query = ProductIndex.where_not(price: nil) expect(query.records).to eq([expected]) end end describe "#with_settings" do it "sets the target to the new anonymous class" do query = ProductIndex.where(id: 1).with_settings(index_name: "new_user_index") expect(query.target.name).to be_nil expect(query.target.index_name).to eq("new_user_index") end it "keeps the constraints" do expected = create(:product) rejected = create(:product) ProductIndex.import [expected, rejected] query = ProductIndex.where(id: expected.id).with_settings(index_name: ProductIndex.index_name) expect(query.records).to eq([expected]) end end describe "#filter" do it "sets up the constraints correctly and is chainable" do product1 = create(:product, price: 100, category: "category1") product2 = create(:product, price: 200, category: "category2") product3 = create(:product, price: 300, category: "category1") ProductIndex.import [product1, product2, product3] query1 = ProductIndex.filter(range: { price: { gte: 100, lte: 200 } }) query2 = query1.filter(term: { category: "category1" }) expect(query1.records.to_set).to eq([product1, product2].to_set) expect(query2.records).to eq([product1]) end end describe "#must" do it "sets up the constraints correctly and is chainable" do product1 = create(:product, price: 100, category: "category1") product2 = create(:product, price: 200, category: "category2") product3 = create(:product, price: 300, category: "category1") ProductIndex.import [product1, product2, product3] query1 = ProductIndex.must(range: { price: { gte: 100, lte: 200 } }) query2 = query1.must(term: { category: "category1" }) expect(query1.records.to_set).to eq([product1, product2].to_set) expect(query2.records).to eq([product1]) end end describe "#must_not" do it "sets up the constraints correctly and is chainable" do product1 = create(:product, price: 100, category: "category1") product2 = create(:product, price: 200, category: "category2") product3 = create(:product, price: 300, category: "category1") ProductIndex.import [product1, product2, product3] query1 = ProductIndex.must_not(range: { price: { gt: 200, lte: 300 } }) query2 = query1.must_not(term: { category: "category2" }) expect(query1.records.to_set).to eq([product1, product2].to_set) expect(query2.records).to eq([product1]) end end describe "#should" do it "sets up the constraints correctly and is chainable" do product1 = create(:product, price: 100, category: "category1") product2 = create(:product, price: 200, category: "category2") product3 = create(:product, price: 300, category: "category1") ProductIndex.import [product1, product2, product3] query = ProductIndex.should([ { range: { price: { gte: 100, lt: 200 } } }, { term: { category: "category2" } } ]) expect(query.records.to_set).to eq([product1, product2].to_set) end end describe "#range" do it "sets up the constraints correctly and is chainable" do product1 = create(:product, price: 100) product2 = create(:product, price: 200) product3 = create(:product, price: 300) ProductIndex.import [product1, product2, product3] query1 = ProductIndex.range(:price, gte: 100, lte: 200) query2 = query1.range(:price, gte: 200, lte: 300) expect(query1.records.to_set).to eq([product1, product2].to_set) expect(query2.records).to eq([product2]) end end describe "#match_all" do it "matches all documents" do expected1 = create(:product) expected2 = create(:product) ProductIndex.import [expected1, expected2] query = ProductIndex.match_all expect(query.records.to_set).to eq([expected1, expected2].to_set) end end describe "#exists" do it "sets up the constraints correctly and is chainable" do product1 = create(:product, title: "title1", description: "description1") product2 = create(:product, title: "title2", description: nil) product3 = create(:product, title: nil, description: "description2") ProductIndex.import [product1, product2, product3] query1 = ProductIndex.exists(:title) query2 = query1.exists(:description) expect(query1.records.to_set).to eq([product1, product2].to_set) expect(query2.records).to eq([product1]) end end describe "#exists_not" do it "sets up the constraints correctly and is chainable" do product1 = create(:product, title: nil, description: nil) product2 = create(:product, title: nil, description: "description2") product3 = create(:product, title: "title3", description: "description3") ProductIndex.import [product1, product2, product3] query1 = ProductIndex.exists_not(:title) query2 = query1.exists_not(:description) expect(query1.records.to_set).to eq([product1, product2].to_set) expect(query2.records).to eq([product1]) end end describe "#post_search" do it "sets up the constraints correctly and is chainable" do if ProductIndex.connection.version.to_i >= 2 product1 = create(:product, title: "title1", category: "category1") product2 = create(:product, title: "title2", category: "category2") product3 = create(:product, title: "title3", category: "category1") ProductIndex.import [product1, product2, product3] query1 = ProductIndex.aggregate(:category).post_search("title1 OR title2") query2 = query1.post_search("category1") expect(query1.records.to_set).to eq([product1, product2].to_set) expect(query2.records).to eq([product1]) aggregations1 = query1.aggregations(:category).each_with_object({}) { |(key, agg), hash| hash[key] = agg.doc_count } expect(aggregations1).to eq("category1" => 2, "category2" => 1) aggregations2 = query2.aggregations(:category).each_with_object({}) { |(key, agg), hash| hash[key] = agg.doc_count } expect(aggregations2).to eq("category1" => 2, "category2" => 1) end end end describe "#post_where" do it "sets up the constraints correctly and is chainable" do product1 = create(:product, price: 100, category: "category1") product2 = create(:product, price: 200, category: "category2") product3 = create(:product, price: 300, category: "category1") ProductIndex.import [product1, product2, product3] query1 = ProductIndex.aggregate(:category).post_where(price: 100..200) query2 = query1.post_where(category: "category1") expect(query1.records.to_set).to eq([product1, product2].to_set) expect(query2.records).to eq([product1]) aggregations1 = query1.aggregations(:category).each_with_object({}) { |(key, agg), hash| hash[key] = agg.doc_count } expect(aggregations1).to eq("category1" => 2, "category2" => 1) aggregations2 = query2.aggregations(:category).each_with_object({}) { |(key, agg), hash| hash[key] = agg.doc_count } expect(aggregations2).to eq("category1" => 2, "category2" => 1) end it "works with arrays" do expected1 = create(:product, title: "expected1", category: "category1") expected2 = create(:product, title: "expected2", category: "category2") rejected = create(:product, title: "rejected", category: "category1") ProductIndex.import [expected1, expected2, rejected] query = ProductIndex.aggregate(:category).post_where(title: ["expected1", "expected2"]) expect(query.records.to_set).to eq([expected1, expected2].to_set) aggregations = query.aggregations(:category).each_with_object({}) { |(key, agg), hash| hash[key] = agg.doc_count } expect(aggregations).to eq("category1" => 2, "category2" => 1) end it "works with ranges" do expected1 = create(:product, price: 100, category: "category1") expected2 = create(:product, price: 200, category: "category2") rejected = create(:product, price: 300, category: "category1") ProductIndex.import [expected1, expected2, rejected] query = ProductIndex.aggregate(:category).post_where(price: 100..200) expect(query.records.to_set).to eq([expected1, expected2].to_set) aggregations = query.aggregations(:category).each_with_object({}) { |(key, agg), hash| hash[key] = agg.doc_count } expect(aggregations).to eq("category1" => 2, "category2" => 1) end it "works with nil" do expected1 = create(:product, price: nil, category: "category1") expected2 = create(:product, price: nil, category: "category2") rejected = create(:product, price: 300, category: "category1") ProductIndex.import [expected1, expected2, rejected] query = ProductIndex.aggregate(:category).post_where(price: nil) expect(query.records.to_set).to eq([expected1, expected2].to_set) aggregations = query.aggregations(:category).each_with_object({}) { |(key, agg), hash| hash[key] = agg.doc_count } expect(aggregations).to eq("category1" => 2, "category2" => 1) end end describe "#post_where_not" do it "sets up the constraints correctly and is chainable" do product1 = create(:product, price: 100, category: "category1") product2 = create(:product, price: 200, category: "category2") product3 = create(:product, price: 300, category: "category1") ProductIndex.import [product1, product2, product3] query1 = ProductIndex.aggregate(:category).post_where_not(price: 250..350) query2 = query1.post_where_not(category: "category2") expect(query1.records.to_set).to eq([product1, product2].to_set) expect(query2.records).to eq([product1]) aggregations = query1.aggregations(:category).each_with_object({}) { |(key, agg), hash| hash[key] = agg.doc_count } expect(aggregations).to eq("category1" => 2, "category2" => 1) aggregations = query2.aggregations(:category).each_with_object({}) { |(key, agg), hash| hash[key] = agg.doc_count } expect(aggregations).to eq("category1" => 2, "category2" => 1) end it "works with arrays" do expected = create(:product, title: "expected", category: "category1") rejected1 = create(:product, title: "rejected1", category: "category2") rejected2 = create(:product, title: "rejected2", category: "category1") ProductIndex.import [expected, rejected1, rejected2] query = ProductIndex.aggregate(:category).post_where_not(title: ["rejected1", "rejected2"]) expect(query.records).to eq([expected]) aggregations = query.aggregations(:category).each_with_object({}) { |(key, agg), hash| hash[key] = agg.doc_count } expect(aggregations).to eq("category1" => 2, "category2" => 1) end it "works with ranges" do expected = create(:product, price: 100, category: "category1") rejected1 = create(:product, price: 200, category: "category2") rejected2 = create(:product, price: 300, category: "category1") ProductIndex.import [expected, rejected1, rejected2] query = ProductIndex.aggregate(:category).post_where_not(price: 200..300) expect(query.records).to eq([expected]) aggregations = query.aggregations(:category).each_with_object({}) { |(key, agg), hash| hash[key] = agg.doc_count } expect(aggregations).to eq("category1" => 2, "category2" => 1) end it "works with nils" do expected = create(:product, price: 100, category: "category1") rejected1 = create(:product, price: nil, category: "category2") rejected2 = create(:product, price: nil, category: "category1") ProductIndex.import [expected, rejected1, rejected2] query = ProductIndex.aggregate(:category).post_where_not(price: nil) expect(query.records).to eq([expected]) aggregations = query.aggregations(:category).each_with_object({}) { |(key, agg), hash| hash[key] = agg.doc_count } expect(aggregations).to eq("category1" => 2, "category2" => 1) end end describe "#post_filter" do it "sets up the constraints correctly and is chainable" do product1 = create(:product, price: 100, category: "category1") product2 = create(:product, price: 200, category: "category2") product3 = create(:product, price: 300, category: "category1") ProductIndex.import [product1, product2, product3] query1 = ProductIndex.aggregate(:category).post_filter(range: { price: { gte: 100, lte: 200 } }) query2 = query1.post_filter(term: { category: "category1" }) expect(query1.records.to_set).to eq([product1, product2].to_set) expect(query2.records).to eq([product1]) aggregations = query1.aggregations(:category).each_with_object({}) { |(key, agg), hash| hash[key] = agg.doc_count } expect(aggregations).to eq("category1" => 2, "category2" => 1) aggregations = query2.aggregations(:category).each_with_object({}) { |(key, agg), hash| hash[key] = agg.doc_count } expect(aggregations).to eq("category1" => 2, "category2" => 1) end end describe "#post_must" do it "sets up the constraints correctly and is chainable" do product1 = create(:product, price: 100, category: "category1") product2 = create(:product, price: 200, category: "category2") product3 = create(:product, price: 300, category: "category1") ProductIndex.import [product1, product2, product3] query1 = ProductIndex.aggregate(:category).post_must(range: { price: { gte: 100, lte: 200 } }) query2 = query1.post_must(term: { category: "category1" }) expect(query1.records.to_set).to eq([product1, product2].to_set) expect(query2.records).to eq([product1]) aggregations = query1.aggregations(:category).each_with_object({}) { |(key, agg), hash| hash[key] = agg.doc_count } expect(aggregations).to eq("category1" => 2, "category2" => 1) aggregations = query2.aggregations(:category).each_with_object({}) { |(key, agg), hash| hash[key] = agg.doc_count } expect(aggregations).to eq("category1" => 2, "category2" => 1) end end describe "#post_must_not" do it "sets up the constraints correctly and is chainable" do product1 = create(:product, price: 100, category: "category1") product2 = create(:product, price: 200, category: "category2") product3 = create(:product, price: 300, category: "category1") ProductIndex.import [product1, product2, product3] query1 = ProductIndex.aggregate(:category).post_must_not(range: { price: { gte: 50, lte: 150 } }) query2 = query1.post_must_not(term: { category: "category1" }) expect(query1.records.to_set).to eq([product2, product3].to_set) expect(query2.records).to eq([product2]) aggregations = query1.aggregations(:category).each_with_object({}) { |(key, agg), hash| hash[key] = agg.doc_count } expect(aggregations).to eq("category1" => 2, "category2" => 1) aggregations = query2.aggregations(:category).each_with_object({}) { |(key, agg), hash| hash[key] = agg.doc_count } expect(aggregations).to eq("category1" => 2, "category2" => 1) end end describe "#post_should" do it "sets up the constraints correctly and is chainable" do product1 = create(:product, price: 100, category: "category1") product2 = create(:product, price: 200, category: "category3") product3 = create(:product, price: 300, category: "category2") product4 = create(:product, price: 400, category: "category1") ProductIndex.import [product1, product2, product3, product4] query1 = ProductIndex.aggregate(:category).post_should([ { term: { category: "category1" } }, { term: { category: "category2" } } ]) query2 = query1.post_should([ { range: { price: { gte: 50, lte: 150 } } }, { range: { price: { gte: 250, lte: 350 } } } ]) expect(query1.records.to_set).to eq([product1, product3, product4].to_set) expect(query2.records.to_set).to eq([product1, product3].to_set) aggregations = query1.aggregations(:category).each_with_object({}) { |(key, agg), hash| hash[key] = agg.doc_count } expect(aggregations).to eq("category1" => 2, "category2" => 1, "category3" => 1) aggregations = query2.aggregations(:category).each_with_object({}) { |(key, agg), hash| hash[key] = agg.doc_count } expect(aggregations).to eq("category1" => 2, "category2" => 1, "category3" => 1) end end describe "#post_range" do it "sets up the constraints correctly and is chainable" do product1 = create(:product, price: 100, category: "category1") product2 = create(:product, price: 200, category: "category2") product3 = create(:product, price: 300, category: "category1") ProductIndex.import [product1, product2, product3] query1 = ProductIndex.aggregate(:category).post_range(:price, gte: 100, lte: 200) query2 = query1.post_range(:price, gte: 200, lte: 300) expect(query1.records.to_set).to eq([product1, product2].to_set) expect(query2.records).to eq([product2]) aggregations = query1.aggregations(:category).each_with_object({}) { |(key, agg), hash| hash[key] = agg.doc_count } expect(aggregations).to eq("category1" => 2, "category2" => 1) aggregations = query2.aggregations(:category).each_with_object({}) { |(key, agg), hash| hash[key] = agg.doc_count } expect(aggregations).to eq("category1" => 2, "category2" => 1) end end describe "#post_exists" do it "sets up the constraints correctly and is chainable" do product1 = create(:product, title: "title1", description: "description1", category: "category1") product2 = create(:product, title: "title2", description: nil, category: "category2") product3 = create(:product, title: nil, description: "description2", category: "category1") ProductIndex.import [product1, product2, product3] query1 = ProductIndex.aggregate(:category).post_exists(:title) query2 = query1.post_exists(:description) expect(query1.records.to_set).to eq([product1, product2].to_set) expect(query2.records).to eq([product1]) aggregations = query1.aggregations(:category).each_with_object({}) { |(key, agg), hash| hash[key] = agg.doc_count } expect(aggregations).to eq("category1" => 2, "category2" => 1) aggregations = query2.aggregations(:category).each_with_object({}) { |(key, agg), hash| hash[key] = agg.doc_count } expect(aggregations).to eq("category1" => 2, "category2" => 1) end end describe "#post_exists" do it "sets up the constraints correctly and is chainable" do product1 = create(:product, title: "title1", description: "description1", category: "category1") product2 = create(:product, title: "title2", description: nil, category: "category2") product3 = create(:product, title: nil, description: "description2", category: "category1") ProductIndex.import [product1, product2, product3] query1 = ProductIndex.aggregate(:category).post_exists(:title) query2 = query1.post_exists(:description) expect(query1.records.to_set).to eq([product1, product2].to_set) expect(query2.records).to eq([product1]) aggregations = query1.aggregations(:category).each_with_object({}) { |(key, agg), hash| hash[key] = agg.doc_count } expect(aggregations).to eq("category1" => 2, "category2" => 1) aggregations = query2.aggregations(:category).each_with_object({}) { |(key, agg), hash| hash[key] = agg.doc_count } expect(aggregations).to eq("category1" => 2, "category2" => 1) end end describe "#post_exists" do it "sets up the constraints correctly and is chainable" do product1 = create(:product, title: "title1", description: "description1", category: "category1") product2 = create(:product, title: "title2", description: nil, category: "category2") product3 = create(:product, title: nil, description: "description2", category: "category1") ProductIndex.import [product1, product2, product3] query1 = ProductIndex.aggregate(:category).post_exists(:title) query2 = query1.post_exists(:description) expect(query1.records.to_set).to eq([product1, product2].to_set) expect(query2.records).to eq([product1]) aggregations = query1.aggregations(:category).each_with_object({}) { |(key, agg), hash| hash[key] = agg.doc_count } expect(aggregations).to eq("category1" => 2, "category2" => 1) aggregations = query2.aggregations(:category).each_with_object({}) { |(key, agg), hash| hash[key] = agg.doc_count } expect(aggregations).to eq("category1" => 2, "category2" => 1) end end describe "#post_exists_not" do it "sets up the constraints correctly and is chainable" do product1 = create(:product, title: nil, description: nil, category: "category1") product2 = create(:product, title: nil, description: "description2", category: "category2") product3 = create(:product, title: "title3", description: "description3", category: "category1") ProductIndex.import [product1, product2, product3] query1 = ProductIndex.aggregate(:category).post_exists_not(:title) query2 = query1.post_exists_not(:description) expect(query1.records.to_set).to eq([product1, product2].to_set) expect(query2.records).to eq([product1]) aggregations = query1.aggregations(:category).each_with_object({}) { |(key, agg), hash| hash[key] = agg.doc_count } expect(aggregations).to eq("category1" => 2, "category2" => 1) aggregations = query2.aggregations(:category).each_with_object({}) { |(key, agg), hash| hash[key] = agg.doc_count } expect(aggregations).to eq("category1" => 2, "category2" => 1) end end describe "#aggregate" do it "sets up the constraints correctly and is chainable" do ProductIndex.import create_list(:product, 3, category: "category1", price: 10) ProductIndex.import create_list(:product, 2, category: "category2", price: 20) ProductIndex.import create_list(:product, 1, category: "category3", price: 30) query = ProductIndex.aggregate(:category, size: 2).aggregate(price_sum: { sum: { field: "price" } }) category_aggregations = query.aggregations(:category).each_with_object({}) { |(key, agg), hash| hash[key] = agg.doc_count } price_aggregation = query.aggregations(:price_sum).value expect(category_aggregations).to eq("category1" => 3, "category2" => 2) expect(price_aggregation).to eq(100) end it "works with hashes" do ProductIndex.import create_list(:product, 3, category: "category1") ProductIndex.import create_list(:product, 2, category: "category2") ProductIndex.import create_list(:product, 1, category: "category3") query = ProductIndex.aggregate(category: { terms: { field: :category } }) aggregations = query.aggregations(:category).each_with_object({}) { |(key, agg), hash| hash[key] = agg.doc_count } expect(aggregations).to eq("category1" => 3, "category2" => 2, "category3" => 1) end it "allows sub-aggregations" do ProductIndex.import create_list(:product, 3, category: "category1", price: 15) ProductIndex.import create_list(:product, 2, category: "category2", price: 20) ProductIndex.import create_list(:product, 1, category: "category3", price: 25) query = ProductIndex.aggregate(:category) do |aggregation| aggregation.aggregate(price_sum: { sum: { field: "price" } }) end category_aggregations = query.aggregations(:category).each_with_object({}) { |(key, agg), hash| hash[key] = agg.doc_count } price_sum_aggregations = query.aggregations(:category).each_with_object({}) { |(key, agg), hash| hash[key] = agg.price_sum.value } expect(category_aggregations).to eq("category1" => 3, "category2" => 2, "category3" => 1) expect(price_sum_aggregations).to eq("category1" => 45, "category2" => 40, "category3" => 25) end end describe "#profile" do it "sets up the constraints correctly" do if ProductIndex.connection.version.to_i >= 2 expect(ProductIndex.profile(true).raw_response["profile"]).not_to be_nil end end end describe "#scroll" do it "scrolls over the full result set" do products = create_list(:product, 15) ProductIndex.import products criteria = ProductIndex.limit(10).scroll(timeout: "1m") result = [] iterations = 0 until criteria.records.empty? result += criteria.records iterations += 1 criteria = criteria.scroll(id: criteria.scroll_id, timeout: "1m") end expect(products.to_set).to eq(result.to_set) expect(iterations).to eq(2) end end describe "#delete" do it "delets the matching documents" do product1, product2, product3 = create_list(:product, 3) ProductIndex.import [product1, product2, product3] expect { ProductIndex.where(id: [product1.id, product2.id]).delete }.to change { ProductIndex.total_count }.by(-2) end it "accepts additional params" do product = create(:product) ProductIndex.import(product) expect { ProductIndex.where(id: product.id).delete(conflicts: "proceed") }.to change { ProductIndex.total_count }.by(-1) end end describe "#source" do it "constraints the returned source fields" do product = create(:product, title: "Title", price: 10) ProductIndex.import product results = ProductIndex.where(id: product.id).results expect(results.first.id).not_to be_nil expect(results.first.title).not_to be_nil expect(results.first.price).not_to be_nil results = ProductIndex.where(id: product.id).source([:id, :price]).results expect(results.first.id).not_to be_nil expect(results.first.title).to be_nil expect(results.first.price).not_to be_nil end end describe "#includes" do it "does not raise any errors" do user = create(:user) comments = create_list(:comment, 2) product = create(:product, user: user, comments: comments) ProductIndex.import product record = ProductIndex.includes(:user).includes(:comments).records.first expect(record).not_to be_nil expect(record.user).to eq(user) expect(record.comments.to_set).to eq(comments.to_set) end end describe "#eager_load" do it "does not raise any errors" do user = create(:user) comments = create_list(:comment, 2) product = create(:product, user: user, comments: comments) ProductIndex.import product record = ProductIndex.eager_load(:user).eager_load(:comments).records.first expect(record).not_to be_nil expect(record.user).to eq(user) expect(record.comments.to_set).to eq(comments.to_set) end end describe "#preload" do it "does not raise any errors" do user = create(:user) comments = create_list(:comment, 2) product = create(:product, user: user, comments: comments) ProductIndex.import product record = ProductIndex.preload(:user).preload(:comments).records.first expect(record).not_to be_nil expect(record.user).to eq(user) expect(record.comments.to_set).to eq(comments.to_set) end end describe "#sort" do it "sorts correctly and is chainable" do product1 = create(:product, rank: 2, price: 100) product2 = create(:product, rank: 2, price: 90) product3 = create(:product, rank: 1, price: 120) product4 = create(:product, rank: 0, price: 110) ProductIndex.import [product1, product2, product3, product4] expect(ProductIndex.sort({ rank: :desc }, price: :asc).records).to eq([product2, product1, product3, product4]) expect(ProductIndex.sort(rank: :desc).sort(:price).records).to eq([product2, product1, product3, product4]) expect(ProductIndex.sort(:price).sort(rank: :desc).records).to eq([product2, product1, product4, product3]) end end describe "#resort" do it "overwrites existing sort criterias" do product1 = create(:product, rank: 2, price: 100) product2 = create(:product, rank: 2, price: 90) product3 = create(:product, rank: 1, price: 120) product4 = create(:product, rank: 0, price: 110) ProductIndex.import [product1, product2, product3, product4] expect(ProductIndex.sort(:price).resort({ rank: :desc }, price: :asc).records).to eq([product2, product1, product3, product4]) expect(ProductIndex.sort(rank: :desc).resort(:price).records).to eq([product2, product1, product4, product3]) end end describe "#offset" do it "sets the query document offset" do product1 = create(:product, rank: 1) product2 = create(:product, rank: 2) product3 = create(:product, rank: 3) ProductIndex.import [product1, product2, product3] query = ProductIndex.sort(:rank).offset(1) expect(query.records).to eq([product2, product3]) expect(query.offset(2).records).to eq([product3]) end end describe "#limit" do it "sets the query document limit" do product1 = create(:product, rank: 1) product2 = create(:product, rank: 2) product3 = create(:product, rank: 3) ProductIndex.import [product1, product2, product3] query = ProductIndex.sort(:rank).limit(1) expect(query.records).to eq([product1]) expect(query.limit(2).records).to eq([product1, product2]) end end describe "#paginate" do it "sets the query document offset and limit" do product1 = create(:product, rank: 1) product2 = create(:product, rank: 2) product3 = create(:product, rank: 3) ProductIndex.import [product1, product2, product3] query = ProductIndex.sort(:rank).paginate(page: 1, per_page: 2) expect(query.records).to eq([product1, product2]) expect(query.paginate(page: 2, per_page: 2).records).to eq([product3]) end end describe "#page" do it "sets the query document offset" do expect(ProductIndex.page(1).offset_value).to eq(0) expect(ProductIndex.page(2).offset_value).to eq(30) expect(ProductIndex.page(3).per(50).offset_value).to eq(100) end end describe "#limit" do it "sets the query document limit" do expect(ProductIndex.per(50).limit_value).to eq(50) end end describe "#search" do it "sets up the constraints correctly" do product1 = create(:product, title: "Title1", description: "Description1", price: 10) product2 = create(:product, title: "Title2", description: "Description2", price: 20) product3 = create(:product, title: "Title3", description: "Description2", price: 30) ProductIndex.import [product1, product2, product3] expect(ProductIndex.search("Title1 OR Title3").records.to_set).to eq([product1, product3].to_set) expect(ProductIndex.search("Title1 Title3", default_operator: :OR).records.to_set).to eq([product1, product3].to_set) expect(ProductIndex.search("Title1 OR Title2").search("Title1 OR Title3").records).to eq([product1]) expect(ProductIndex.search("Title1 OR Title3").where(price: 5..15).records).to eq([product1]) end end describe "#highlight" do it "adds highlighting to the query and is chainable" do product1 = create(:product, title: "Title1 highlight", description: "Description1 highlight") product2 = create(:product, title: "Title2 highlight", description: "Description2 highlight") ProductIndex.import [product1, product2] results = ProductIndex.sort(:id).highlight([:title, :description]).search("title:highlight description:highlight").results expect(results[0]._hit.highlight.title).to eq(["Title1 highlight"]) expect(results[0]._hit.highlight.description).to eq(["Description1 highlight"]) expect(results[1]._hit.highlight.title).to eq(["Title2 highlight"]) expect(results[1]._hit.highlight.description).to eq(["Description2 highlight"]) results = ProductIndex.sort(:id).highlight([:title, :description], require_field_match: false).search("highlight").results expect(results[0]._hit.highlight.title).to eq(["Title1 highlight"]) expect(results[0]._hit.highlight.description).to eq(["Description1 highlight"]) expect(results[1]._hit.highlight.title).to eq(["Title2 highlight"]) expect(results[1]._hit.highlight.description).to eq(["Description2 highlight"]) query = ProductIndex.sort(:id).search("title:highlight") query = query.highlight(:title, require_field_match: true).highlight(:description, require_field_match: true) results = query.results expect(results[0]._hit.highlight.title).to eq(["Title1 highlight"]) expect(results[0]._hit.highlight.description).to be_nil expect(results[1]._hit.highlight.title).to eq(["Title2 highlight"]) expect(results[1]._hit.highlight.description).to be_nil end end describe "#suggest" do it "adds suggest to the query" do product = create(:product, title: "Title", description: "Description") ProductIndex.import product suggestions = ProductIndex.suggest(:suggestion, text: "Desciption", term: { field: "description" }).suggestions(:suggestion) expect(suggestions.first["text"]).to eq("description") end end describe "#find_in_batches" do it "iterates the records in batches of the specified size" do expected1 = create(:product, title: "expected", rank: 1) expected2 = create(:product, title: "expected", rank: 2) expected3 = create(:product, title: "expected", rank: 3) rejected = create(:product, title: "rejected") create :product, title: "rejected" ProductIndex.import [expected1, expected2, expected3, rejected] actual = ProductIndex.where(title: "expected").sort(:rank).find_in_batches(batch_size: 2).to_a expect(actual).to eq([[expected1, expected2], [expected3]]) end end describe "#find_results_in_batches" do it "iterates the results in batches of the specified size" do expected1 = create(:product, title: "expected", rank: 1) expected2 = create(:product, title: "expected", rank: 2) expected3 = create(:product, title: "expected", rank: 3) rejected = create(:product, title: "rejected") create :product, title: "rejected" ProductIndex.import [expected1, expected2, expected3, rejected] actual = ProductIndex.where(title: "expected").sort(:rank).find_results_in_batches(batch_size: 2).map { |batch| batch.map(&:id) } expect(actual).to eq([[expected1.id, expected2.id], [expected3.id]]) end end describe "#find_each" do it "iterates the records" do expected1 = create(:product, title: "expected", rank: 1) expected2 = create(:product, title: "expected", rank: 2) expected3 = create(:product, title: "expected", rank: 3) rejected = create(:product, title: "rejected") create :product, title: "rejected" ProductIndex.import [expected1, expected2, expected3, rejected] actual = ProductIndex.where(title: "expected").sort(:rank).find_each(batch_size: 2).to_a expect(actual).to eq([expected1, expected2, expected3]) end end describe "#find_each_result" do it "iterates the results" do expected1 = create(:product, title: "expected", rank: 1) expected2 = create(:product, title: "expected", rank: 2) expected3 = create(:product, title: "expected", rank: 3) rejected = create(:product, title: "rejected") create :product, title: "rejected" ProductIndex.import [expected1, expected2, expected3, rejected] actual = ProductIndex.where(title: "expected").sort(:rank).find_each_result(batch_size: 2).map(&:id) expect(actual).to eq([expected1.id, expected2.id, expected3.id]) end end describe "#failsafe" do it "prevents query syntax exceptions" do expect { ProductIndex.search("syntax/error").records }.to raise_error(SearchFlip::ResponseError) query = ProductIndex.failsafe(true).search("syntax/error") expect(query.records).to eq([]) expect(query.total_entries).to eq(0) end end describe "#fresh" do it "returns a new criteria without a cached response" do create :product query = ProductIndex.criteria.tap(&:records) expect(query.instance_variable_get(:@response)).not_to be_nil expect(query.object_id).not_to eq(query.fresh.object_id) expect(query.fresh.instance_variable_get(:@response)).to be_nil end end describe "#respond_to?" do it "checks whether or not the index class responds to the method" do temp_index = Class.new(ProductIndex) expect(temp_index.criteria.respond_to?(:test_scope)).to eq(false) temp_index.scope(:test_scope) { match_all } expect(temp_index.criteria.respond_to?(:test_scope)).to eq(true) end end describe "#method_missing" do it "delegates to the index class" do temp_index = Class.new(ProductIndex) expected = create(:product, title: "expected") rejected = create(:product, title: "rejected") temp_index.import [expected, rejected] temp_index.scope(:with_title) { |title| where(title: title) } records = temp_index.criteria.with_title("expected").records expect(records).to eq([expected]) end end describe "#track_total_hits" do it "is added to the request" do if ProductIndex.connection.version.to_i >= 7 query = ProductIndex.track_total_hits(false) expect(query.request[:track_total_hits]).to eq(false) expect { query.execute }.not_to raise_error end end end describe "#explain" do it "returns the explaination" do ProductIndex.import create(:product) query = ProductIndex.match_all.explain(true) expect(query.results.first._hit.key?(:_explanation)).to eq(true) end end describe "#custom" do it "adds a custom entry to the request" do request = ProductIndex.custom(custom_key: "custom_value").request expect(request[:custom_key]).to eq("custom_value") end end describe "#preference" do it "sets the preference" do stub_request(:post, "http://127.0.0.1:9200/products/products/_search?preference=value") .to_return(status: 200, headers: { content_type: "application/json" }, body: "{}") ProductIndex.preference("value").execute end end describe "#search_type" do it "sets the search_type" do stub_request(:post, "http://127.0.0.1:9200/products/products/_search?search_type=value") .to_return(status: 200, headers: { content_type: "application/json" }, body: "{}") ProductIndex.search_type("value").execute end end describe "#routing" do it "sets the search_type" do stub_request(:post, "http://127.0.0.1:9200/products/products/_search?routing=value") .to_return(status: 200, headers: { content_type: "application/json" }, body: "{}") ProductIndex.routing("value").execute end end end