Upgrade to Pro — share decks privately, control downloads, hide ads and more …

A tour of dry-schema and dry-validation 1.0

A tour of dry-schema and dry-validation 1.0

The release of dry-validation 1.0 is just around the corner, so now is the perfect time to learn about its powerful approach to data validation.

If your Ruby app accepts external input (hint: yes, it does!), then this talk is for you. We’ll:

- Take a fresh look at why we validate in the first place
- Learn how it works (along with its new companion, dry-schema)
- Discover how it can improve our app architecture

You’ll leave with a couple of new (and newly API stable!) tools to use, as well as a new appreciation for the importance of data validation in any kind of application.

Tim Riley

June 11, 2019
Tweet

More Decks by Tim Riley

Other Decks in Technology

Transcript

  1. !

  2. !

  3. ! "

  4. result = schema.call( "email" => "[email protected]", "age" => "35", )

    # => #<Dry::Schema::Result{ # :email=>"[email protected]", :age=>35 # } errors={}>
  5. result = schema.call( "email" => "[email protected]", "age" => "35", )

    # => #<Dry::Schema::Result{ # :email=>"[email protected]", :age=>35 # } errors={}>
  6. result = schema.call( "email" => "[email protected]", "age" => "35", )

    # => #<Dry::Schema::Result{ # :email=>"[email protected]", :age=>35 # } errors={}>
  7. result = schema.call( "email" => "[email protected]", "age" => "35", )

    # => #<Dry::Schema::Result{ # :email=>"[email protected]", :age=>35 # } errors={}>
  8. result = schema.call( "email" => "[email protected]", "age" => "35", )

    # => #<Dry::Schema::Result{ # :email=>"[email protected]", :age=>35 # } errors={}>
  9. result = schema.call( "email" => "[email protected]", "age" => "35", )

    # => #<Dry::Schema::Result{ # :email=>"[email protected]", :age=>35 # } errors={}>
  10. result = schema.call( "email" => "[email protected]", "age" => "35", )

    # => #<Dry::Schema::Result{ # :email=>"[email protected]", :age=>35 # } errors={}>
  11. result = schema.call( "email" => "[email protected]", "age" => "35", )

    # => #<Dry::Schema::Result{ # :email=>"[email protected]", :age=>35 # } errors={}>
  12. result = schema.call( "email" => "[email protected]", "age" => "35", )

    # => #<Dry::Schema::Result{ # :email=>"[email protected]", :age=>35 # } errors={}>
  13. result = schema.call( "email" => "", "age" => "35", )

    # => #<Dry::Schema::Result{ # :email=>"", :age=>35 # } # errors={ # :email=>["must be filled"] # }>
  14. result = schema.call( "email" => "", "age" => "35", )

    # => #<Dry::Schema::Result{ # :email=>"", :age=>35 # } # errors={ # :email=>["must be filled"] # }>
  15. result = schema.call( "email" => "", "age" => "35", )

    # => #<Dry::Schema::Result{ # :email=>"", :age=>35 # } # errors={ # :email=>["must be filled"] # }>
  16. module Types include Dry.Types StrippedString = Types::String.constructor(&:strip) end schema =

    Dry::Schema.Params do optional(:name).filled(Types::StrippedString) end schema.("name" => " Clover ") # => #<Dry::Schema::Result{:name=>"Clover"} errors={}>
  17. module Types include Dry.Types StrippedString = Types::String.constructor(&:strip) end schema =

    Dry::Schema.Params do optional(:name).filled(Types::StrippedString) end schema.("name" => " Clover ") # => #<Dry::Schema::Result{:name=>"Clover"} errors={}>
  18. module Types include Dry.Types StrippedString = Types::String.constructor(&:strip) end schema =

    Dry::Schema.Params do optional(:name).filled(Types::StrippedString) end schema.("name" => " Clover ") # => #<Dry::Schema::Result{:name=>"Clover"} errors={}>
  19. module Types include Dry.Types StrippedString = Types::String.constructor(&:strip) end schema =

    Dry::Schema.Params do optional(:name).filled(Types::StrippedString) end schema.("name" => " Clover ") # => #<Dry::Schema::Result{:name=>"Clover"} errors={}>
  20. module Types include Dry.Types StrippedString = Types::String.constructor(&:strip) end schema =

    Dry::Schema.Params do optional(:name).filled(Types::StrippedString) end schema.("name" => " Clover ") # => #<Dry::Schema::Result{:name=>"Clover"} errors={}>
  21. module Types include Dry.Types StrippedString = Types::String.constructor(&:strip) end schema =

    Dry::Schema.Params do optional(:name).filled(Types::StrippedString) end schema.("name" => " Clover ") # => #<Dry::Schema::Result{:name=>"Clover"} errors={}>
  22. contract = EventContract.new contract.( "name" => "ROROSyd", "starts_at" => "2019-06-11

    18:00", "ends_at" => "2019-06-11 22:00", ) # => #<Dry::Validation::Result{ # :name=>"ROROSyd", # :starts_at=>2019-06-11 18:00:00 +1000, # :ends_at=>2019-06-11 22:00:00 +1000 # } errors={}>
  23. contract = EventContract.new contract.call( "name" => "ROROSyd", "starts_at" => "2019-06-11

    18:00", "ends_at" => "2019-06-11 22:00", ) # => #<Dry::Validation::Result{ # :name=>"ROROSyd", # :starts_at=>2019-06-11 18:00:00 +1000, # :ends_at=>2019-06-11 22:00:00 +1000 # } errors={}>
  24. contract = EventContract.new contract.call( "name" => "ROROSyd", "starts_at" => "2019-06-11

    18:00", "ends_at" => "2019-06-11 22:00", ) # => #<Dry::Validation::Result{ # :name=>"ROROSyd", # :starts_at=>2019-06-11 18:00:00 +1000, # :ends_at=>2019-06-11 22:00:00 +1000 # } errors={}>
  25. class EventContract < Dry::Validation::Contract params do required(:name).filled(:string) required(:starts_at).filled(:time) required(:ends_at).maybe(:time) end

    rule(:ends_at, :starts_at) do if value && value <= values[:starts_at] key.failure("must be after start time") end end end
  26. class EventContract < Dry::Validation::Contract params do required(:name).filled(:string) required(:starts_at).filled(:time) required(:ends_at).maybe(:time) end

    rule(:ends_at, :starts_at) do if value && value <= values[:starts_at] key.failure("must be after start time") end end end
  27. class EventContract < Dry::Validation::Contract params do required(:name).filled(:string) required(:starts_at).filled(:time) required(:ends_at).maybe(:time) end

    rule(:ends_at, :starts_at) do if value && value <= values[:starts_at] key.failure("must be after start time") end end end
  28. contract = EventContract.new contract.call( "name" => "ROROSyd", "starts_at" => "2019-06-11

    18:00", "ends_at" => "2019-06-11 17:00", ).errors.to_h # => {:ends_at=>["must be after start time”]}
  29. contract = EventContract.new contract.( "name" => "ROROSyd", "starts_at" => "2019-06-11

    18:00", "ends_at" => "2019-06-11 17:00", ).errors.to_h # => {:ends_at=>["must be after start time”]}
  30. contract = EventContract.new contract.( "name" => "ROROSyd", "starts_at" => "2019-06-11

    18:00", "ends_at" => "2019-06-11 17:00", ).errors.to_h # => {:ends_at=>["must be after start time"]}
  31. class EventContract < Dry::Validation::Contract params do required(:name).filled(:string) required(:starts_at).filled(:time) required(:ends_at).maybe(:time) end

    rule(:ends_at, :starts_at) do if value && value <= values[:starts_at] key.failure("must be after start time") end end end
  32. class EventContract < Dry::Validation::Contract params do required(:name).filled(:string) required(:starts_at).filled(:time) required(:ends_at).maybe(:time) end

    rule(:ends_at, :starts_at) do if value && value <= values[:starts_at] key.failure("must be after start time") end end end
  33. class EventContract < Dry::Validation::Contract params do required(:name).filled(:string) required(:starts_at).filled(:time) required(:ends_at).maybe(:time) end

    rule(:ends_at, :starts_at) do if value && value <= values[:starts_at] key.failure("must be after start time") end end end
  34. class EventContract < Dry::Validation::Contract params do required(:name).filled(:string) required(:starts_at).filled(:time) required(:ends_at).maybe(:time) end

    rule(:ends_at, :starts_at) do if value && value <= values[:starts_at] key(:ends_at).failure("must be…") end end end
  35. class EventContract < Dry::Validation::Contract params do required(:name).filled(:string) required(:starts_at).filled(:time) required(:ends_at).maybe(:time) end

    rule(:ends_at, :starts_at) do if value && value <= values[:starts_at] key(:ends_at).failure("must be…") end end end
  36. class EventContract < Dry::Validation::Contract params do required(:name).filled(:string) required(:starts_at).filled(:time) required(:ends_at).maybe(:time) end

    rule(:ends_at, :starts_at) do if value && value <= values[:starts_at] key(:times).failure("must be…") end end end
  37. class EventContract < Dry::Validation::Contract params do required(:name).filled(:string) required(:starts_at).filled(:time) required(:ends_at).maybe(:time) end

    rule(:ends_at, :starts_at) do if value && value <= values[:starts_at] key(:times).failure("must be…") end end end
  38. class EventContract < Dry::Validation::Contract params do required(:name).filled(:string) required(:starts_at).filled(:time) required(:ends_at).maybe(:time) end

    rule(:ends_at, :starts_at) do if value && value <= values[:starts_at] base.failure("times must be…") end end end
  39. class EventContract < Dry::Validation::Contract params do required(:name).filled(:string) required(:starts_at).filled(:time) required(:ends_at).maybe(:time) end

    rule(:ends_at, :starts_at) do if value && value <= values[:starts_at] base.failure("times must be…") end end end
  40. class EventContract < Dry::Validation::Contract params do required(:name).filled(:string) required(:starts_at).filled(:time) required(:ends_at).maybe(:time) end

    rule(:ends_at, :starts_at) do if value && value <= values[:starts_at] key.failure("must be after start time") end end end
  41. class EventContract < Dry::Validation::Contract params do required(:name).filled(:string) required(:starts_at).filled(:time) required(:ends_at).maybe(:time) end

    rule(:ends_at, :starts_at) do if value && value <= values[:starts_at] key.failure("must be after start time") end end end
  42. contract = EventContract.new contract.( "name" => "ROROSyd", "starts_at" => "2019-06-11

    18:00", "ends_at" => "last drinks", ).errors.to_h # => {:ends_at=>["must be a time”]}
  43. contract = EventContract.new contract.( "name" => "ROROSyd", "starts_at" => "2019-06-11

    18:00", "ends_at" => "last drinks", ).errors.to_h # => {:ends_at=>["must be a time"]}
  44. # => {:ends_at=>["must be a time"]} class EventContract params do

    # … required(:ends_at).maybe(:time) end rule(:ends_at, :starts_at) do # … end end
  45. # => {:ends_at=>["must be a time"]} class EventContract params do

    # … required(:ends_at).maybe(:time) end rule(:ends_at, :starts_at) do # … end end
  46. # => {:ends_at=>["must be a time"]} class EventContract params do

    # … required(:ends_at).maybe(:time) end rule(:ends_at, :starts_at) do # … end end
  47. class EventContract < Dry::Validation::Contract option :repo params do required(:slug).filled(:string) end

    rule(:slug) do unless repo.unique?(:slug, slug) key.failure("is already used") end end end contract = EventContract.new(repo: repo)
  48. class EventContract < Dry::Validation::Contract option :event_repo params do required(:slug).filled(:string) end

    rule(:slug) do unless repo.unique?(:slug, slug) key.failure("is already used") end end end contract = EventContract.new(repo: repo)
  49. class EventContract < Dry::Validation::Contract option :event_repo params do required(:slug).filled(:string) end

    rule(:slug) do unless event_repo.unique?(:slug, value) key.failure("is already used") end end end contract = EventContract.new(repo: repo)
  50. class EventContract < Dry::Validation::Contract option :event_repo params do required(:slug).filled(:string) end

    rule(:slug) do unless event_repo.unique?(:slug, value) key.failure("is already used") end end end contract = EventContract.new(repo: repo)
  51. class EventContract < Dry::Validation::Contract option :event_repo params do required(:slug).filled(:string) end

    rule(:slug) do unless event_repo.unique?(:slug, slug) key.failure("is already used") end end end contract = EventContract.new(event_repo: repo)