diff --git a/CHANGELOG.md b/CHANGELOG.md index ff214e64..07aa4340 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # [Master][] +- Add an interface filter. - Add missing translation for symbol filters. # [1.2.1][] (2014-05-02) diff --git a/benchmarks/filters.rb b/benchmarks/filters.rb index abcdc4cc..9a766da5 100644 --- a/benchmarks/filters.rb +++ b/benchmarks/filters.rb @@ -26,6 +26,7 @@ float: [0.0, '0.0', 0], hash: [Hash.new], # TODO integer: [0, '0', 0.0], + interface: [Object.new], model: [Object.new], # TODO: Reconstantizing. string: [''], # TODO: Without strip. symbol: [:'', ''], diff --git a/lib/active_interaction.rb b/lib/active_interaction.rb index 6b42bb42..994bc9a7 100644 --- a/lib/active_interaction.rb +++ b/lib/active_interaction.rb @@ -30,6 +30,7 @@ require 'active_interaction/filters/float_filter' require 'active_interaction/filters/hash_filter' require 'active_interaction/filters/integer_filter' +require 'active_interaction/filters/interface_filter' require 'active_interaction/filters/model_filter' require 'active_interaction/filters/string_filter' require 'active_interaction/filters/symbol_filter' diff --git a/lib/active_interaction/filters/interface_filter.rb b/lib/active_interaction/filters/interface_filter.rb new file mode 100644 index 00000000..1c77db37 --- /dev/null +++ b/lib/active_interaction/filters/interface_filter.rb @@ -0,0 +1,42 @@ +# coding: utf-8 + +module ActiveInteraction + class Base + # @!method self.interface(*attributes, options = {}) + # Creates accessors for the attributes and ensures that values passed to + # the attributes implement an interface. + # + # @!macro filter_method_params + # @option options [Array] :methods ([]) the methods that objects + # conforming to this interface should respond to + # + # @example + # interface :anything + # @example + # interface :serializer, + # methods: [:dump, :load] + end + + # @private + class InterfaceFilter < Filter + def cast(value) + matches?(value) ? value : super + end + + private + + # @param object [Object] + # + # @return [Boolean] + def matches?(object) + methods.all? { |method| object.respond_to?(method) } + rescue NoMethodError + false + end + + # @return [Array] + def methods + options.fetch(:methods, []) + end + end +end diff --git a/lib/active_interaction/locale/en.yml b/lib/active_interaction/locale/en.yml index 02693edf..60ac73d4 100644 --- a/lib/active_interaction/locale/en.yml +++ b/lib/active_interaction/locale/en.yml @@ -16,6 +16,7 @@ en: float: float hash: hash integer: integer + interface: interface model: model string: string symbol: symbol diff --git a/spec/active_interaction/filters/interface_filter_spec.rb b/spec/active_interaction/filters/interface_filter_spec.rb new file mode 100644 index 00000000..584c8097 --- /dev/null +++ b/spec/active_interaction/filters/interface_filter_spec.rb @@ -0,0 +1,46 @@ +# coding: utf-8 + +require 'spec_helper' +require 'json' +require 'yaml' + +describe ActiveInteraction::InterfaceFilter, :filter do + include_context 'filters' + it_behaves_like 'a filter' + + before { options[:methods] = [:dump, :load] } + + describe '#cast' do + let(:result) { filter.cast(value) } + + context 'with an Object' do + let(:value) { Object.new } + + it 'raises an error' do + expect { result }.to raise_error ActiveInteraction::InvalidValueError + end + end + + context 'with JSON' do + let(:value) { JSON } + + it 'returns an Array' do + expect(result).to eql value + end + end + + context 'with YAML' do + let(:value) { YAML } + + it 'returns an Hash' do + expect(result).to eql value + end + end + end + + describe '#database_column_type' do + it 'returns :string' do + expect(filter.database_column_type).to eql :string + end + end +end diff --git a/spec/active_interaction/integration/interface_interaction_spec.rb b/spec/active_interaction/integration/interface_interaction_spec.rb new file mode 100644 index 00000000..ea30b813 --- /dev/null +++ b/spec/active_interaction/integration/interface_interaction_spec.rb @@ -0,0 +1,12 @@ +# coding: utf-8 + +require 'spec_helper' +require 'json' +require 'yaml' + +describe 'InterfaceInteraction' do + it_behaves_like 'an interaction', + :interface, + -> { [JSON, YAML].sample }, + methods: [:dump, :load] +end