Skip to content

Commit

Permalink
Merge pull request #119 from wadetandy/master
Browse files Browse the repository at this point in the history
Add "enum" type
  • Loading branch information
wadetandy committed Mar 27, 2019
2 parents 8eb0363 + 5ea938f commit 621f947
Show file tree
Hide file tree
Showing 9 changed files with 152 additions and 5 deletions.
1 change: 1 addition & 0 deletions lib/graphiti/adapters/abstract.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ def self.default_operators
:not_match,
],
uuid: [:eq, :not_eq],
enum: [:eq, :not_eq],
integer_id: numerical_operators,
integer: numerical_operators,
big_decimal: numerical_operators,
Expand Down
2 changes: 2 additions & 0 deletions lib/graphiti/adapters/active_record.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ def filter_eq(scope, attribute, value)
alias filter_date_eq filter_eq
alias filter_boolean_eq filter_eq
alias filter_uuid_eq filter_eq
alias filter_enum_eq filter_eq

def filter_not_eq(scope, attribute, value)
scope.where.not(attribute => value)
Expand All @@ -35,6 +36,7 @@ def filter_not_eq(scope, attribute, value)
alias filter_date_not_eq filter_not_eq
alias filter_boolean_not_eq filter_not_eq
alias filter_uuid_not_eq filter_not_eq
alias filter_enum_not_eq filter_not_eq

def filter_string_eq(scope, attribute, value, is_not: false)
column = column_for(scope, attribute)
Expand Down
24 changes: 24 additions & 0 deletions lib/graphiti/errors.rb
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,30 @@ def message
end
end

class MissingEnumAllowList < Base
def initialize(resource_class, filter_name, enum_type)
@resource_class = resource_class
@filter_name = filter_name
@enum_type = enum_type
end

def message
<<-MSG
#{@resource_class.name} You declared an attribute or filter of type "#{@enum_type}" without providing a list of permitted values, which is required.
When declaring an attribute:
attribute :status, :#{@enum_type}, allow: ['published', 'draft']
When declaring a filter:
filter :status, :#{@enum_type}, allow: ['published', 'draft'] do
# ...
end
MSG
end
end

class InvalidLink < Base
def initialize(resource_class, sideload, action)
@resource_class = resource_class
Expand Down
16 changes: 13 additions & 3 deletions lib/graphiti/resource/dsl.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,13 @@ def filter(name, *args, &blk)
aliases = [name, opts[:aliases]].flatten.compact
operators = FilterOperators.build(self, att[:type], opts, &blk)

if Graphiti::Types[att[:type]][:canonical_name] == :boolean
case Graphiti::Types[att[:type]][:canonical_name]
when :boolean
opts[:single] = true
when :enum
if opts[:allow].blank?
raise Errors::MissingEnumAllowList.new(self, name, att[:type])
end
end

required = att[:filterable] == :required || !!opts[:required]
Expand All @@ -29,7 +34,7 @@ def filter(name, *args, &blk)
operators: operators.to_hash,
}
elsif (type = args[0])
attribute name, type, only: [:filterable]
attribute name, type, only: [:filterable], allow: opts[:allow]
filter(name, opts, &blk)
else
raise Errors::ImplicitFilterTypeMissing.new(self, name)
Expand Down Expand Up @@ -100,8 +105,13 @@ def attribute(name, type, options = {}, &blk)
options[:proc] = blk
config[:attributes][name] = options
apply_attributes_to_serializer
options[:filterable] ? filter(name) : config[:filters].delete(name)
options[:sortable] ? sort(name) : config[:sorts].delete(name)

if options[:filterable]
filter(name, allow: options[:allow])
else
config[:filters].delete(name)
end
end

def extra_attribute(name, type, options = {}, &blk)
Expand Down
16 changes: 16 additions & 0 deletions lib/graphiti/types.rb
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,22 @@ def self.map
kind: "scalar",
description: "Base Type. Like a normal string, but by default only eq/!eq and case-sensitive.",
},
string_enum: {
canonical_name: :enum,
params: Dry::Types["coercible.string"],
read: Dry::Types["coercible.string"],
write: Dry::Types["coercible.string"],
kind: "scalar",
description: "String enum type. Like a normal string, but only eq/!eq and case-sensitive. Limited to only the allowed values.",
},
integer_enum: {
canonical_name: :enum,
params: Dry::Types["coercible.integer"],
read: Dry::Types["coercible.integer"],
write: Dry::Types["coercible.integer"],
kind: "scalar",
description: "Integer enum type. Like a normal integer, but only eq/!eq filters. Limited to only the allowed values.",
},
string: {
params: Dry::Types["coercible.string"],
read: Dry::Types["coercible.string"],
Expand Down
48 changes: 48 additions & 0 deletions spec/filtering_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,54 @@ def self.name
end
end

context "when filtering on an enum field" do
context "when allowed values are provided" do
before do
resource.filter :enum_age, :integer_enum, allow: [1, 3, 5] do
eq do |scope, value|
scope[:conditions][:age] = value
scope
end
end
end

it "rejects values not in the allowlist" do
params[:filter] = {enum_age: {eq: 2}}
expect {
records
}.to raise_error(Graphiti::Errors::InvalidFilterValue, /Allowlist: \[1, 3, 5]/)
end
end

context "when allow list is omitted" do
context 'when using a string_enum field' do
it "raises an error at load time" do
expect {
resource.filter :enum_first_name, :string_enum do
eq do |scope, value|
scope[:conditions][:first_name] = value
scope
end
end
}.to raise_error(Graphiti::Errors::MissingEnumAllowList, /string_enum/)
end
end

context 'when using an integer_enum field' do
it "raises an error at load time" do
expect {
resource.filter :enum_age, :integer_enum do
eq do |scope, value|
scope[:conditions][:age] = value
scope
end
end
}.to raise_error(Graphiti::Errors::MissingEnumAllowList, /integer_enum/)
end
end
end
end

context "when only allowing single values" do
before do
resource.filter :first_name, :string, single: true do
Expand Down
17 changes: 17 additions & 0 deletions spec/resource_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -671,6 +671,23 @@ def apply_attribute
}.to(change { klass.filters[:foo] })
end
end

context 'when the attribute is of type "enum"' do
context "and an allow list is passed in" do
it "correctly adds to the filter allowlist" do
klass.attribute :foo, :string_enum, allow: ["bar", "baz"]
expect(klass.filters[:foo][:allow]).to eq ["bar", "baz"]
end
end

context "and an allow list omitted" do
it "raises a helpful error" do
expect {
klass.attribute :foo, :string_enum
}.to raise_error(Graphiti::Errors::MissingEnumAllowList, /string_enum/)
end
end
end
end

context "when not filterable" do
Expand Down
29 changes: 29 additions & 0 deletions spec/schema_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,14 @@
description: "Base Type. Like a normal string, but by default only eq/!eq and case-sensitive.",
kind: "scalar",
},
string_enum: {
description: "String enum type. Like a normal string, but only eq/!eq and case-sensitive. Limited to only the allowed values.",
kind: "scalar",
},
integer_enum: {
description: "Integer enum type. Like a normal integer, but only eq/!eq filters. Limited to only the allowed values.",
kind: "scalar"
},
integer: {
kind: "scalar",
description: "Base Type.",
Expand Down Expand Up @@ -129,6 +137,14 @@
description: "Base Type.",
kind: "array",
},
array_of_string_enums: {
description: "Base Type.",
kind: "array",
},
array_of_integer_enums: {
description: "Base Type.",
kind: "array",
},
array_of_integers: {
kind: "array",
description: "Base Type.",
Expand Down Expand Up @@ -437,6 +453,19 @@ def self.name
end
end

context "when the attribute is a string enum" do
before do
employee_resource.class_eval do
attribute :enum_first_name, :string_enum, allow: [:foo]
end
end

it "reflects the values in the filters" do
expect(schema[:resources][0][:filters][:enum_first_name][:allow])
.to eq(["foo"])
end
end

context "when the filter has a denylist" do
before do
employee_resource.class_eval do
Expand Down
4 changes: 2 additions & 2 deletions spec/sideloading_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -419,7 +419,7 @@ def assert_correct_response
end
end

context 'when linking unknown type' do
context "when linking unknown type" do
before do
Graphiti::Resource.autolink = true
params.delete(:include)
Expand All @@ -436,7 +436,7 @@ def assert_correct_response
Graphiti::Resource.autolink = false
end

it 'does not blow up' do
it "does not blow up" do
render
expect(d[0].link(:credit_card, :related)).to be_present
expect(d[1].link(:credit_card, :related)).to be_present
Expand Down

0 comments on commit 621f947

Please # to comment.