背景
- TyepScript読みやすいのでRubyにも型がほしい
- というか、すでに型付けライブラリをつかっていた、virtus、dry-rb
- virtusは枯れているが拡張性がなく、後継として推薦されているdry-rb(dry-validation)を試すことに
試した
結論: まあまあいいかも
- Pros
- Grapeのdeclaredみたいなサニタイズができる
- エラーがわかりやすい
- クラスでないので構造体っぽくカジュアルにスキーマをかける
- 型のコメントがらく
- Cons
- サニタイズつきのネスト構造の書き方が直感的でない
require 'dry-validation'
require 'active_support'
require 'active_support/core_ext'
module SchemaHelpers
module DrySchema
def dry_schema(&block)
rc = Dry::Validation.Schema(build: false, &block)
rc.configure(&lambda { |config| config.input_processor = :sanitizer })
rc.new
end
end
def validset(schema, attrs, options = {})
schema = schema.with(options) if options.present?
case (result = schema.call(attrs)).success?
when true then result.output
when false then raise result.errors
end
rescue TypeError => _e
[:error, result.errors]
rescue Dry::Validation::MissingMessageError => _e
[:error, 'invalid values']
end
end
class Foo
extend SchemaHelpers::DrySchema
_NestedDataSchema = dry_schema do
optional(:a) { int? }
end
FooSchema = dry_schema do
configure do
Dry::Logic::Predicates.predicate(:bar?) { |v| v.is_a?(Bar) }
def self.messages; super.merge(en: { errors: { :bar? => 'is not Bar class' } }); end
end
required(:name) { str? & size?(4) }
required(:nested_data).each { schema(_NestedDataSchema) }
required(:bar) { filled? & bar? }
end
class << self
include SchemaHelpers
# @return {FooSchema}
def call(attrs)
validset(FooSchema, attrs)
end
end
end
class Bar
end
class Buzz
end
p Foo.call(bar: Buzz.new)
#=> [:error, {:name=>["is missing"], :nested_data=>["is missing"], :bar=>["is not Bar class"]}]
p Foo.call(
name: 'helo',
nested_data: [
a: 1,
unknown: 2
],
bar: Bar.new,
unknown: 'blah'
)
#=> {:name=>"helo", :nested_data=>[{:a=>1}], :bar=>#<Bar:0x007f7f84e38180>}