Skip to content

Commit

Permalink
Add ability to filter invoice by absence of metadata (#3082)
Browse files Browse the repository at this point in the history
## Context

Improvements for invoice filtering

## Description

Add ability to find invoices where particular metadata key is absent.
  • Loading branch information
floganz authored Jan 22, 2025
1 parent 26630c2 commit 10a9e25
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 19 deletions.
23 changes: 17 additions & 6 deletions app/queries/invoices_query.rb
Original file line number Diff line number Diff line change
Expand Up @@ -112,20 +112,31 @@ def with_amount_range(scope)
end

def with_metadata(scope)
base_scope = scope.joins(:metadata)
base_scope = scope.left_joins(:metadata)
subquery = base_scope

filters.metadata.each_with_index do |(key, value), index|
presence_filters = filters.metadata.select { |_k, v| v.present? }
absence_filters = filters.metadata.select { |_k, v| v.blank? }

presence_filters.each_with_index do |(key, value), index|
subquery = if index.zero?
base_scope.where(metadata: {key:, value:})
subquery.where(metadata: {key:, value:})
else
subquery.or(base_scope.where(metadata: {key:, value:}))
end
end

subquery = subquery
.group("invoices.id")
.having("COUNT(DISTINCT metadata.key) = ?", filters.metadata.size)
if presence_filters.any?
subquery = subquery
.group("invoices.id")
.having("COUNT(DISTINCT metadata.key) = ?", presence_filters.size)
end

if absence_filters.any?
subquery = subquery.where.not(
id: base_scope.where(metadata: {key: absence_filters.keys}).select(:invoice_id)
)
end

scope.where(id: subquery.select(:id))
end
Expand Down
64 changes: 51 additions & 13 deletions spec/queries/invoices_query_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -612,43 +612,81 @@
let(:filters) { {metadata:} }

context "when single filter provided" do
let(:metadata) { {red: 5} }
context "when value is present" do
let(:metadata) { {red: 5} }
let(:matching_invoice) { create(:invoice, organization:) }

let!(:matching_invoice) { create(:invoice, organization:) }
before do
create(:invoice_metadata, invoice: matching_invoice, key: :red, value: 5)

before do
create(:invoice_metadata, invoice: matching_invoice, key: :red, value: 5)
create(:invoice, organization:) do |invoice|
create(:invoice_metadata, invoice:)
end
end

create(:invoice, organization:) do |invoice|
create(:invoice_metadata, invoice:)
it "returns invoices with matching metadata filters" do
expect(result).to be_success
expect(result.invoices.pluck(:id)).to contain_exactly matching_invoice.id
end
end

it "returns invoices with matching metadata filters" do
expect(result).to be_success
expect(result.invoices.pluck(:id)).to contain_exactly matching_invoice.id
context "when value is absent" do
let(:metadata) { {red: ""} }

let!(:matching_invoices) do
[
create(:invoice, organization:),
create(:invoice, organization:) do |invoice|
create(:invoice_metadata, invoice:, key: :orange, value: 3)
end
]
end

before do
create(:invoice, organization:) do |invoice|
create(:invoice_metadata, invoice:, key: :red, value: 5)
end

[invoice_first, invoice_second, invoice_third, invoice_fourth, invoice_fifth, invoice_sixth].each do |invoice|
create(:invoice_metadata, invoice:, key: :red, value: 5)
end
end

it "returns invoices without provided key metadata or without metadata at all" do
expect(result).to be_success
expect(result.invoices.pluck(:id)).to match_array matching_invoices.pluck(:id)
end
end
end

context "when multiple filters provided" do
let(:metadata) do
{
red: 5,
orange: 3
orange: 3,
green: ""
}
end

let!(:matching_invoices) { create_pair(:invoice, organization:) }

before do
matching_invoices.each do |invoice|
metadata.each do |key, value|
create(:invoice_metadata, invoice:, key:, value:)
end
create(:invoice_metadata, invoice:, key: :red, value: 5)
create(:invoice_metadata, invoice:, key: :orange, value: 3)
end

create(:invoice, organization:) do |invoice|
create(:invoice_metadata, invoice:, key: :red, value: 5)
create(:invoice_metadata, invoice:, key: :pink, value: 7)
end

create(:invoice, organization:)

create(:invoice, organization:) do |invoice|
create(:invoice_metadata, invoice:, key: :red, value: 5)
create(:invoice_metadata, invoice:, key: :orange, value: 3)
create(:invoice_metadata, invoice:, key: :green, value: 1)
end
end

Expand Down

0 comments on commit 10a9e25

Please sign in to comment.