Skip to content
Open
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## Added

- Support for Ruby 3.4.
- Added per-filter option validation. Each filter type now validates its own specific options and raises `ArgumentError` when passed unrecognized options.

## Fixed

Expand Down
14 changes: 14 additions & 0 deletions lib/active_interaction/filter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,13 @@ def factory(slug)
CLASSES.fetch(slug) { raise MissingFilterError, slug.inspect }
end

# Returns the list of allowed options for this filter type.
#
# @return [Array<Symbol>]
def allowed_options
Comment on lines +54 to +57
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't replicate this comment on all the subclass methods, but I could if we think that's valuable

%i[desc default]
end

private

# @param slug [Symbol]
Expand All @@ -66,6 +73,7 @@ def register(slug)
#
# @option options [Object] :default Fallback value to use when given `nil`.
def initialize(name, options = {}, &block)
validate_options!(options)
@name = name
@options = options.dup
@filters = {}
Expand Down Expand Up @@ -229,6 +237,12 @@ def describe(value)
"(Object doesn't support #inspect)"
end

def validate_options!(options)
if (invalid_options = options.keys - self.class.allowed_options).any?
raise ArgumentError, "invalid options: #{invalid_options.join(', ')}"
end
end

def raw_default(context)
value = options.fetch(:default)
return value unless value.is_a?(Proc)
Expand Down
4 changes: 4 additions & 0 deletions lib/active_interaction/filters/abstract_date_time_filter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ module ActiveInteraction
#
# @private
class AbstractDateTimeFilter < Filter
def self.allowed_options
super + %i[format]
end

def database_column_type
self.class.slug
end
Expand Down
4 changes: 4 additions & 0 deletions lib/active_interaction/filters/array_filter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ class ArrayFilter < Filter

register :array

def self.allowed_options
super + %i[index_errors]
end

def process(value, context)
input = super

Expand Down
4 changes: 4 additions & 0 deletions lib/active_interaction/filters/decimal_filter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ class Base # rubocop:disable Lint/EmptyClass
class DecimalFilter < AbstractNumericFilter
register :decimal

def self.allowed_options
super + %i[digits]
end

private

def digits
Expand Down
4 changes: 4 additions & 0 deletions lib/active_interaction/filters/hash_filter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ class HashFilter < Filter

register :hash

def self.allowed_options
super + %i[strip]
end

def process(value, context) # rubocop:disable Metrics/AbcSize
input = super

Expand Down
4 changes: 4 additions & 0 deletions lib/active_interaction/filters/integer_filter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ class Base # rubocop:disable Lint/EmptyClass
class IntegerFilter < AbstractNumericFilter
register :integer

def self.allowed_options
super + %i[base]
end

private

def base
Expand Down
4 changes: 4 additions & 0 deletions lib/active_interaction/filters/interface_filter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ class Base # rubocop:disable Lint/EmptyClass
class InterfaceFilter < Filter
register :interface

def self.allowed_options
super + %i[from methods]
end

def initialize(name, options = {}, &block)
if options.key?(:methods) && options.key?(:from)
raise InvalidFilterError,
Expand Down
4 changes: 4 additions & 0 deletions lib/active_interaction/filters/object_filter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ class Base # rubocop:disable Lint/EmptyClass
class ObjectFilter < Filter
register :object

def self.allowed_options
super + %i[class converter]
end

private

def klass
Expand Down
4 changes: 4 additions & 0 deletions lib/active_interaction/filters/record_filter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ class Base # rubocop:disable Lint/EmptyClass
class RecordFilter < Filter
register :record

def self.allowed_options
super + %i[class finder]
end

private

def klass
Expand Down
4 changes: 4 additions & 0 deletions lib/active_interaction/filters/string_filter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ class Base # rubocop:disable Lint/EmptyClass
class StringFilter < Filter
register :string

def self.allowed_options
super + %i[strip]
end

private

def strip?
Expand Down
36 changes: 36 additions & 0 deletions spec/active_interaction/base_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,42 @@ def execute
end.to raise_error NoMethodError
end

it 'raises an error for a non-existent kwarg' do
expect do
Class.new(TestInteraction) do
string :beverage, type: :sparkling_wine, bubbles: true
end
end.to raise_error ArgumentError, /invalid options: type, bubbles/
end

it 'allows extending allowed_options for custom filters' do
Class.new(ActiveInteraction::Filter) do
register :custom

def self.allowed_options
super + %i[custom_option]
end

private

def matches?(value)
value.is_a?(String)
end
end

extended_class = Class.new(TestInteraction) do
custom :test_field, custom_option: true
end

expect { extended_class.new }.not_to raise_error
end

it 'filters validate their own options' do
expect(ActiveInteraction::StringFilter.allowed_options).to include(:desc, :default, :strip)
expect(ActiveInteraction::IntegerFilter.allowed_options).to include(:desc, :default, :base)
expect(ActiveInteraction::Filter.allowed_options).to eq(%i[desc default])
end

it do
expect do
Class.new(TestInteraction) do
Expand Down
4 changes: 2 additions & 2 deletions spec/active_interaction/errors_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@

let(:klass) do
Class.new(ActiveInteraction::Base) do
string :attribute, defualt: nil
array :array, defualt: nil
string :attribute, default: nil
array :array, default: nil

def self.name
@name ||= SecureRandom.hex
Expand Down