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.

Avatar for Tim Riley

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)