Skip to content

Instantly share code, notes, and snippets.

@nabinno
Created September 20, 2019 08:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nabinno/fae5b6a508f8865cafaeeab8b1fed0a7 to your computer and use it in GitHub Desktop.
Save nabinno/fae5b6a508f8865cafaeeab8b1fed0a7 to your computer and use it in GitHub Desktop.

背景

  • 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>}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment