Skip to content

Commit 054685c

Browse files
authored
Merge pull request #336 from chiliburger/add-filter-group
Added filter_group DSL:
2 parents ced743e + 6af36dc commit 054685c

File tree

9 files changed

+341
-1
lines changed

9 files changed

+341
-1
lines changed

lib/graphiti.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ def self.setup!
142142
require "graphiti/scoping/paginate"
143143
require "graphiti/scoping/extra_attributes"
144144
require "graphiti/scoping/filterable"
145+
require "graphiti/scoping/filter_group_validator"
145146
require "graphiti/scoping/default_filter"
146147
require "graphiti/scoping/filter"
147148
require "graphiti/stats/dsl"

lib/graphiti/errors.rb

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -816,5 +816,34 @@ def message
816816

817817
class ConflictRequest < InvalidRequest
818818
end
819+
820+
class FilterGroupInvalidRequirement < Base
821+
def initialize(resource, valid_required_values)
822+
@resource = resource
823+
@valid_required_values = valid_required_values
824+
end
825+
826+
def message
827+
<<-MSG.gsub(/\s+/, " ").strip
828+
The filter group required: value on resource #{@resource.class} must be one of the following:
829+
#{@valid_required_values.join(", ")}
830+
MSG
831+
end
832+
end
833+
834+
class FilterGroupMissingRequiredFilters < Base
835+
def initialize(resource, filter_names, required)
836+
@resource = resource
837+
@filter_names = filter_names
838+
@required_label = required == :all ? "All" : "One"
839+
end
840+
841+
def message
842+
<<-MSG.gsub(/\s+/, " ").strip
843+
#{@required_label} of the following filters must be provided on resource #{@resource.type}:
844+
#{@filter_names.join(", ")}
845+
MSG
846+
end
847+
end
819848
end
820849
end

lib/graphiti/resource/configuration.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ def config
199199
@config ||=
200200
{
201201
filters: {},
202+
grouped_filters: {},
202203
default_filters: {},
203204
stats: {},
204205
sort_all: nil,
@@ -235,6 +236,10 @@ def filters
235236
config[:filters]
236237
end
237238

239+
def grouped_filters
240+
config[:grouped_filters]
241+
end
242+
238243
def sorts
239244
config[:sorts]
240245
end
@@ -273,6 +278,10 @@ def filters
273278
self.class.filters
274279
end
275280

281+
def grouped_filters
282+
self.class.grouped_filters
283+
end
284+
276285
def sort_all
277286
self.class.sort_all
278287
end

lib/graphiti/resource/dsl.rb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,17 @@ def filter(name, *args, &blk)
4444
end
4545
end
4646

47+
def filter_group(filter_names, *args)
48+
opts = args.extract_options!
49+
50+
Scoping::FilterGroupValidator.raise_unless_filter_group_requirement_valid!(self, opts[:required])
51+
52+
config[:grouped_filters] = {
53+
names: filter_names,
54+
required: opts[:required]
55+
}
56+
end
57+
4758
def sort_all(&blk)
4859
if block_given?
4960
config[:_sort_all] = blk

lib/graphiti/scoping/filter.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ class Scoping::Filter < Scoping::Base
33
include Scoping::Filterable
44

55
def apply
6+
Graphiti::Scoping::FilterGroupValidator.new(
7+
resource,
8+
query_hash
9+
).raise_unless_filter_group_requirements_met!
10+
611
if missing_required_filters.any? && !@opts[:bypass_required_filters]
712
raise Errors::RequiredFilter.new(resource, missing_required_filters)
813
end
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
module Graphiti
2+
class Scoping::FilterGroupValidator
3+
VALID_REQUIRED_VALUES = %i[all any]
4+
5+
def self.raise_unless_filter_group_requirement_valid!(resource, requirement)
6+
unless VALID_REQUIRED_VALUES.include?(requirement)
7+
raise Errors::FilterGroupInvalidRequirement.new(
8+
resource,
9+
VALID_REQUIRED_VALUES
10+
)
11+
end
12+
13+
true
14+
end
15+
16+
def initialize(resource, query_hash)
17+
@resource = resource
18+
@query_hash = query_hash
19+
end
20+
21+
def raise_unless_filter_group_requirements_met!
22+
return if grouped_filters.empty?
23+
24+
case filter_group_requirement
25+
when :all
26+
raise_unless_all_requirements_met!
27+
when :any
28+
raise_unless_any_requirements_met!
29+
end
30+
31+
true
32+
end
33+
34+
private
35+
36+
attr_reader :resource, :query_hash
37+
38+
def raise_unless_all_requirements_met!
39+
met = filter_group_names.all? { |filter_name| filter_group_filter_param.key?(filter_name) }
40+
41+
unless met
42+
raise Errors::FilterGroupMissingRequiredFilters.new(
43+
resource,
44+
filter_group_names,
45+
filter_group_requirement
46+
)
47+
end
48+
end
49+
50+
def raise_unless_any_requirements_met!
51+
met = filter_group_names.any? { |filter_name| filter_group_filter_param.key?(filter_name) }
52+
53+
unless met
54+
raise Errors::FilterGroupMissingRequiredFilters.new(
55+
resource,
56+
filter_group_names,
57+
filter_group_requirement
58+
)
59+
end
60+
end
61+
62+
def filter_group_names
63+
grouped_filters.fetch(:names, [])
64+
end
65+
66+
def filter_group_requirement
67+
grouped_filters.fetch(:required, :invalid)
68+
end
69+
70+
def grouped_filters
71+
resource.grouped_filters
72+
end
73+
74+
def filter_group_filter_param
75+
query_hash.fetch(:filter, {})
76+
end
77+
end
78+
end

lib/graphiti/version.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
module Graphiti
2-
VERSION = "1.2.33"
2+
VERSION = "1.2.34"
33
end
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
require "spec_helper"
2+
3+
RSpec.describe Graphiti::Scoping::FilterGroupValidator do
4+
let(:resource) { double(:resource, type: :employees) }
5+
let(:query_hash) { {} }
6+
let(:validator) { described_class.new(resource, query_hash) }
7+
8+
describe ".raise_unless_filter_group_requirement_valid!" do
9+
subject { described_class.raise_unless_filter_group_requirement_valid!(resource, required) }
10+
11+
context "when required invalid" do
12+
let(:required) { :invalid }
13+
14+
it "raises an error" do
15+
expect {
16+
subject
17+
}.to raise_error(/The filter group required: value on resource .+ must be one of the following:/)
18+
end
19+
end
20+
21+
context "when required valid" do
22+
let(:required) { :all }
23+
24+
it "works" do
25+
expect(subject).to be true
26+
end
27+
end
28+
end
29+
30+
describe "#raise_unless_filter_group_requirements_met!" do
31+
subject { validator.raise_unless_filter_group_requirements_met! }
32+
33+
before do
34+
allow(resource).to receive(:grouped_filters).and_return(grouped_filters)
35+
end
36+
37+
context "when all are required" do
38+
let(:grouped_filters) do
39+
{
40+
names: [:first_name, :last_name],
41+
required: :all
42+
}
43+
end
44+
45+
context "when all are not given in the request" do
46+
let(:query_hash) do
47+
{
48+
filter: {
49+
first_name: {}
50+
}
51+
}
52+
end
53+
54+
it "raises an error" do
55+
expect {
56+
subject
57+
}.to raise_error(/All of the following filters must be provided on resource/)
58+
end
59+
end
60+
61+
context "when all are given in the request" do
62+
let(:query_hash) do
63+
{
64+
filter: {
65+
first_name: {},
66+
last_name: {}
67+
}
68+
}
69+
end
70+
71+
it "works" do
72+
expect(subject).to be true
73+
end
74+
end
75+
end
76+
77+
context "when any are required" do
78+
let(:grouped_filters) do
79+
{
80+
names: [:first_name, :last_name],
81+
required: :any
82+
}
83+
end
84+
85+
context "when none are given in the request" do
86+
let(:query_hash) do
87+
{
88+
filter: {}
89+
}
90+
end
91+
92+
it "raises an error" do
93+
expect {
94+
subject
95+
}.to raise_error(/One of the following filters must be provided on resource/)
96+
end
97+
end
98+
99+
context "when one is given in the request" do
100+
let(:query_hash) do
101+
{
102+
filter: {
103+
last_name: {}
104+
}
105+
}
106+
end
107+
108+
it "works" do
109+
expect(subject).to be true
110+
end
111+
end
112+
113+
context "when all are given in the request" do
114+
let(:query_hash) do
115+
{
116+
filter: {
117+
first_name: {},
118+
last_name: {}
119+
}
120+
}
121+
end
122+
123+
it "works" do
124+
expect(subject).to be true
125+
end
126+
end
127+
end
128+
end
129+
end

spec/filtering_spec.rb

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1783,4 +1783,82 @@ def after_filtering(scope)
17831783
expect(records.map(&:id)).to eq([employee2.id])
17841784
end
17851785
end
1786+
1787+
context "with filter group" do
1788+
context "when required invalid" do
1789+
it "raises an error" do
1790+
expect {
1791+
resource.filter_group [:first_name, :last_name], required: :foo
1792+
}.to raise_error(/The filter group required: value on resource .+ must be one of the following:/)
1793+
end
1794+
end
1795+
1796+
context "when all are required" do
1797+
before do
1798+
resource.filter_group [:first_name, :last_name], required: :all
1799+
end
1800+
1801+
context "when all are not given in the request" do
1802+
before do
1803+
params[:filter] = {first_name: "Agatha"}
1804+
end
1805+
1806+
it "raises an error" do
1807+
expect {
1808+
records
1809+
}.to raise_error(/All of the following filters must be provided on resource/)
1810+
end
1811+
end
1812+
1813+
context "when all are given in the request" do
1814+
before do
1815+
params[:filter] = {
1816+
first_name: "Agatha",
1817+
last_name: "Christie"
1818+
}
1819+
end
1820+
1821+
it "works" do
1822+
expect(records.map(&:id)).to eq([employee2.id])
1823+
end
1824+
end
1825+
end
1826+
1827+
context "when any are required" do
1828+
before do
1829+
resource.filter_group [:first_name, :last_name], required: :any
1830+
end
1831+
1832+
context "when none are given in the request" do
1833+
it "raises an error" do
1834+
expect {
1835+
records
1836+
}.to raise_error(/One of the following filters must be provided on resource/)
1837+
end
1838+
end
1839+
1840+
context "when one is given in the request" do
1841+
before do
1842+
params[:filter] = {last_name: "Christie"}
1843+
end
1844+
1845+
it "works" do
1846+
expect(records.map(&:id)).to eq([employee2.id])
1847+
end
1848+
end
1849+
1850+
context "when all are given in the request" do
1851+
before do
1852+
params[:filter] = {
1853+
first_name: "Agatha",
1854+
last_name: "Christie"
1855+
}
1856+
end
1857+
1858+
it "works" do
1859+
expect(records.map(&:id)).to eq([employee2.id])
1860+
end
1861+
end
1862+
end
1863+
end
17861864
end

0 commit comments

Comments
 (0)