Skip to content

Commit

Permalink
Support custom types.
Browse files Browse the repository at this point in the history
Any class can now be used as a type for a parameter, so long
as it defines a class-level `from_param` method. This method
should raise on invalid values and otherwise return the coerced
value.
  • Loading branch information
rnubel authored and Robert Nubel committed Jun 19, 2015
1 parent 7c9623e commit bb2d67c
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 1 deletion.
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
- [Declared](#declared)
- [Include Missing](#include-missing)
- [Parameter Validation and Coercion](#parameter-validation-and-coercion)
- [Custom Types](#custom-types)
- [Built-in Validators](#built-in-validators)
- [Namespace Validation and Coercion](#namespace-validation-and-coercion)
- [Custom Validators](#custom-validators)
Expand Down Expand Up @@ -730,6 +731,36 @@ params do
end
```

#### Custom Types

Aside from the default set of accepted types (`String`, `Integer`, `Boolean`, etc.),
any class can be used as a type so long as it defines a class-level `from\_param` method.
This method must return an instance of the same type, or raise an exception to indicate
the value was invalid. E.g.,

```ruby
class Color
attr_reader :value
def initialize(color)
@value = color
end
def self.from_param(value)
raise "Invalid color" unless %w(blue red green).include?(value)
new(value)
end
end

# ...

params do
required :color, type: Color, default: Color.new('blue')
end
get '/stuff' do
# params[:color] is already a Color.
params[:color].value
end
```

#### Validation of Nested Parameters

Parameters can be nested using `group` or by calling `requires` or `optional` with a block.
Expand Down
10 changes: 9 additions & 1 deletion lib/grape/validations/validators/coerce.rb
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,15 @@ def coerce_value(type, val)
return val || Set.new if type == Set
return val || {} if type == Hash

converter = Virtus::Attribute.build(type)
# To support custom types that Virtus can't easily coerce, pass in an
# explicit coercer. Custom types must implement a `from_param` class
# method.
converter_options = {}
if type.respond_to?(:from_param)
converter_options[:coercer] = type.method(:from_param)
end

converter = Virtus::Attribute.build(type, converter_options)
converter.coerce(val)

# not the prettiest but some invalid coercion can currently trigger
Expand Down
29 changes: 29 additions & 0 deletions spec/grape/validations/params_scope_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,35 @@ def app
end
end

context 'when using custom types' do
class CustomType
attr_reader :value
def self.from_param(value)
raise if value == "invalid"
new(value)
end

def initialize(value)
@value = value
end
end

it 'coerces the parameter via the type\'s from_param method' do
subject.params do
requires :foo, type: CustomType
end
subject.get('/types') { params[:foo].value }

get '/types', foo: "valid"
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('valid')

get '/types', foo: "invalid"
expect(last_response.status).to eq(400)
expect(last_response.body).to match(/foo is invalid/)
end
end

context 'array without coerce type explicitly given' do
it 'sets the type based on first element' do
subject.params do
Expand Down

0 comments on commit bb2d67c

Please sign in to comment.