Se ha denunciado esta presentación.
Utilizamos tu perfil de LinkedIn y tus datos de actividad para personalizar los anuncios y mostrarte publicidad más relevante. Puedes cambiar tus preferencias de publicidad en cualquier momento.

Dry-validation update. Dry-validation vs Dry-schema 1.0 - Aleksandra Stolyar | Ruby Meditation 29

570 visualizaciones

Publicado el

Talk of Aleksandra Stolyar, Ruby developer at DataArt, at Ruby Meditation #29 Kyiv 14.12.2019
Next conference - http://www.rubymeditation.com/

Description:
I will talk about dry-rb ecosystem and it’s major components - dry-validation and dry-schema which are very helpful for validations. This year dry-rb introduced a major update to its’ gems and I faced some problems when decided to marry these updates with a project. This talk will cover some of differences and specifics of dry-validation and dry-schema updates.


Announcements and conference materials https://www.fb.me/RubyMeditation
News https://twitter.com/RubyMeditation
Photos https://www.instagram.com/RubyMeditation
The stream of Ruby conferences (not just ours) https://t.me/RubyMeditation

Publicado en: Tecnología
  • Sé el primero en comentar

Dry-validation update. Dry-validation vs Dry-schema 1.0 - Aleksandra Stolyar | Ruby Meditation 29

  1. 1. Oleksandra Stolyar (Tomilina) dry-validation vs dry-schema 1.*.*
  2. 2. Harry Potter and… the Very Important Gem Update to the Major Version
  3. 3. “a set of gems that bring solutions to common problems” dry-struct dry-logic dry-container dry-validation dry-transaction dry-schema … dry-rb
  4. 4. Backstory… ME dry-validation
  5. 5. Yeah, that feeling…
  6. 6. 2017, February • Plans for dry-validation + dry-schema (a new gem!) by Piotr Solnica 2019, January • dry-schema introduced 2019, May • dry-schema v1.0.0 released 2019, June • dry-validation v1.0.0 released gems update timeline
  7. 7. exceptionally about my experience how I structured knowledge about these gems features that were crucial for me workarounds successes and failures conclusions, decisions and their consequences What this talk will be about?
  8. 8. bugs that were fixed since dry-validation 0.13 all great features that dry-validation or dry-schema can provide What I won’t cover?
  9. 9. The Challenge
  10. 10. Operations gem ‘trailblazer-operation’ Params schema and Validation Macro in operations gem ‘dry-validation’ Error normalization Macro for errors in operations gem ‘error_normalizer’ I had:
  11. 11. Trailblazer Operation v2.0 Contract documentation mentions the ability of validation using: Dry::Schema (dry-validation < v0.13.3 Schema) Reform Trailblazer Macro Contract has dry-validation v0.11.1 dependency Reform status: Since September 2019 Emanuele Magliozzi started pushing commits for old and new API compatibility which use dry-validation < v0.13.3 and dry-validation > v1.0.0 respectively. In November 2019 he pushed into Trailblazer Reform v2.3.0 with new api which uses Dry::Validation::Contract Yet docs are still not updated and 🤔 Trailblazer usage
  12. 12. For same purposes you can use: dry-transaction (active v0.13.0, release of v1.0.0 is planned but further development will be stopped) dry-monads gem ‘interactor’ Other approaches
  13. 13. That is the question… 🤔
  14. 14. Reusing schemas Rules Custom Predicates Custom Error Messages Schema/Contract Configuration Features to migrate:
  15. 15. dry-validation < 0.13 dry-validation > 1.0 (business validations) dry-schema > 1.0 (shape & type validations) Main changes
  16. 16. Reusing schemas Rules Custom predicates Custom Error Messages Configuration Parameters dry-validation (Contract) dry-schema
  17. 17. Reusing Schemas
  18. 18. { "sender": { "first_name": "Draco", "last_name": "Malfoy" }, "receiver": { "first_name": "Lord", "last_name": "Voldemort" }, "receiver_address": "Castle", "text": "I'm scared", "asap": true } it needs to be validated:
  19. 19. Reusing Schemas dry-validation 0.13 optional(:sender).schema(PersonalInfo::FormValidation) required(:receiver).schema(PersonalInfo::FormValidation) required(:receiver_address).filled(:str?) required(:text).filled(:str?) optional(:asap).filled(:bool?) module PersonalInfo FormValidation = Dry::Validation.Schema required(:first_name).filled(:str?) required(:last_name).filled(:str?) end end
  20. 20. Reusing Schemas dry-validation 1.3.1 params do optional(:sender).hash(PersonalInfo::FormValidation) required(:receiver).hash(PersonalInfo::FormValidation) required(:receiver_address).filled(:string) required(:text).filled(:string) optional(:asap).filled(:bool) end module PersonalInfo FormValidation = Dry::Schema.Params do required(:first_name).filled(:string) required(:last_name).filled(:string) end end
  21. 21. Reusing Schemas dry-schema 1.3.4 optional(:sender).hash(PersonalInfo::FormValidation) required(:receiver).hash(PersonalInfo::FormValidation) required(:receiver_address).filled(:string) required(:text).filled(:string) optional(:asap).filled(:bool) module PersonalInfo FormValidation = Dry::Schema.Params do required(:first_name) { filled? > str? } required(:last_name).filled(:string) end end
  22. 22. Reusing schemas Rules Custom predicates Custom Error Messages Configuration Parameters dry-validation (Contract) dry-schema Achieved with defining Schema
  23. 23. Rules
  24. 24. { "hogwarts_student": { "name": "Sasha", "age": 18, "parents_owl_id": "hedwig_was_the_best@owail.com", "has_owl": true, "has_cat_or_toad": true } } it needs to be validated:
  25. 25. required(:hogwarts_student).schema do required(:name).filled(:str?) required(:age).filled(:int?, gt?: 11, lteq?: 18) required(:parents_owl_id).filled(:str?) optional(:has_owl).filled(:bool?) optional(:has_cat_or_toad).filled(:bool?) rule(inadmissible_animal_quantity: [:has_owl, :has_cat_or_toad]) do |owl, not_owl| owl.true? ^ not_owl.true? end end Rules dry-validation 0.13
  26. 26. params do required(:hogwarts_student).schema do required(:name).filled(:string) required(:age).filled(:integer) required(:parents_owl_id).filled(:string) required(:has_owl).filled(:bool) required(:has_cat_or_toad).filled(:bool) end end rule('hogwarts_student.age').validate(gteq?: 11) rule('hogwarts_student.age').validate(lteq?: 18) rule(hogwarts_student: [:has_owl, :has_cat_or_toad]) do unless values[:hogwarts_student][:has_owl] ^ values[:hogwarts_student][:has_cat_or_toad] key(:owl_errors).failure(:inadmissible_animal_quantity) end end Rules dry-validation 1.3.1
  27. 27. required(:hogwarts_student).schema do required(:name).filled(:string) required(:age).filled(:integer, gt?: 11, lteq?: 18) required(:parents_owl_id).filled(:string) required(:has_owl).filled(:bool) required(:has_cat_or_toad).filled(:bool) end step :check_inadmissible_animal_quantity fail AddError('inadmissible_animal_quantity', path: 'has_owl'), fail_fast: true def check_inadmissible_animal_quantity(opts, output:, **) hogwarts_student = output[:hogwarts_student] hogwarts_student[:has_owl] ^ hogwarts_student[:has_cat_or_toad] end Rules dry-schema 1.3.4
  28. 28. { "hogwarts_house": { "name": "Slotherin", "head": "Snape", "ghost": "Bloody Baron", "immutable_info": { "founder_name": "Salazar Slytherin", "element": "Water", "flag": "flag_img", "animal": "Serpent", "traits": [ "Resourcefulness", ... "Lineage" ] }, "common_room": { "name": "Slytherin Dungeon", "location": "hell" } } } it needs to be validated:
  29. 29. required(:hogwarts_house).schema do # ... required(:head).filled(:str?, size?: 1..255, format?: SOME_MAGIC_REGEX) # ... required(:common_room).schema do required(:name).filled(:str?) required(:location).filled(:str?, included_in?: %w[tower underground]) end end Rules dry-validation 0.13
  30. 30. params do required(:hogwarts_house).schema do # ... required(:head).filled(:string) # ... required(:common_room).schema do required(:name).filled(:string) required(:location).filled(:string) end end end rule('hogwarts_house.head') do key.failure(:invalid_format) unless SOME_MAGIC_REGEX.match?(value) key.failure(:invalid_size, range: 1..255) unless (1..255).cover?(value.length) end rule('hogwarts_house.common_room.location') do Rules dry-validation 1.3.1
  31. 31. Reusing schemas Rules Custom predicates Custom Error Messages Configuration Parameters dry-validation (Contract) dry-schema
  32. 32. Custom predicates
  33. 33. it needs to be validated: { "hogwarts_student": { "name": "Sasha", "age": 18, "parents_owl_id": "hedwig_was_the_best@owail.com", "has_owl": true, "has_cat_or_toad": true } }
  34. 34. required(:hogwarts_student).schema do # ... required(:parents_owl_id).filled(:str?, :owl_id?) optional(:has_owl).filled(:bool?) optional(:has_cat_or_toad).filled(:bool?) end class CommonSchema < Dry::Validation::Schema::Params configure do # ... end def owl_id?(value) OwlLib.valid?(value) end end Dry::Validation.Schema(CommonSchema, {}, &block) Custom Predicates dry-validation 0.13
  35. 35. params do required(:hogwarts_student).schema do # ... required(:parents_owl_id).filled(:string) required(:has_owl).filled(:bool) required(:has_cat_or_toad).filled(:bool) end end ## 1 rule(hogwarts_student: :parents_owl_id) do key.failure(:invalid_owl_id) unless OwlLib.valid?(value) end Custom Predicates dry-validation 1.3.0
  36. 36. params do required(:hogwarts_student).schema do # ... required(:parents_owl_id).filled(:string) required(:has_owl).filled(:bool) required(:has_cat_or_toad).filled(:bool) end end ## 2 rule('hogwarts_student.parents_owl_id') do unless owl_validator.valid?(value) key.failure('invalid_owl_id') end end Custom Predicates dry-validation 1.3.0
  37. 37. ## 2 class CommonContract < Dry::Validation::Contract option :owl_validator end class OwlValidator def self.valid?(value) OwlLib.valid?(value) end end MyContract.new(owl_validator: OwlValidator) Custom Predicates dry-validation 1.3.0
  38. 38. params do required(:hogwarts_student).schema do # ... required(:parents_owl_id).filled(:string) required(:has_owl).filled(:bool) required(:has_cat_or_toad).filled(:bool) end end ## 3 rule(hogwarts_student: :parents_owl_id).validate(:owl_id_format) Custom Predicates dry-validation 1.3.0
  39. 39. class CommonContract < Dry::Validation::Contract register_macro(:owl_id_format) do unless OwlLib.valid?(value) key.failure('not a valid owl id format') end end end MyContract.new Custom Predicates dry-validation 1.3.0
  40. 40. Custom Predicates dry-schema 1.3.4
  41. 41. required(:hogwarts_student).schema do # ... required(:parents_owl_id).filled(:string) required(:has_owl).filled(:bool) required(:has_cat_or_toad).filled(:bool) end step Validate() fail NormalizeErrors(), fail_fast: true step ValidateOwl(:hogwarts_student, :parents_owl_id) fail AddError('invalid_parents_owl_id', path: 'parents_owl_id'), fail_fast: true def ValidateOwl(*args) step = lambda do |_input, options| value = options[:output].dig(*args) OwlLib.valid?(value) end end Custom Predicates dry-schema 1.3.4
  42. 42. Reusing schemas Rules Custom predicates Custom Error Messages Configuration Parameters dry-validation (Contract) dry-schema Rules Achieved with Macro
  43. 43. Custom Error Messages
  44. 44. Custom Error Messages dry-validation 0.13 class CommonSchema < Dry::Validation::Schema::Params configure do I18n.config.backend.load_translations('somewhere/custom_error_messages.yml') config.messages = :i18n end end en: errors: bool?: "must be bla bla bla" owl_number?: "must be in owl number international format" rules: inadmissible_animal_quantity: "either owl or something else"
  45. 45. Custom Error Messages dry-validation 1.3.1 class CommonContract < Dry::Validation::Contract config.messages.load_paths << 'somewhere/custom_error_messages.yml' end en: dry_validation: errors: bool?: "must be bla bla bla" rules: hogwarts_house: common_room: location: invalid: "must be somewhere in: %{list}" head: invalid_format: "must not contain magic" invalid_size: "length must be within %{range}" hogwarts_student: parents_owl_number: invalid_owl_number: "must be in owl number international format" owl_errors: inadmissible_animal_quantity: "either owl or something else"
  46. 46. Custom Error Messages dry-schema 1.3.4 en: dry_schema: errors: bool?: "must be bla bla bla" CommonConfig = Dry::Schema.Params do config.messages.load_paths << 'somewhere/custom_error_messages.yml' end
  47. 47. Reusing schemas Rules Custom predicates Custom Error Messages Configuration Parameters dry-validation (Contract) dry-schema
  48. 48. Configuration parameters
  49. 49. class CommonSchema < Dry::Validation::Schema::Params configure do # custom errors files and I18n configs end # predicates # custom validation blocks end Dry::Validation.Schema(CommonSchema, {}, &block) Configuration parameters dry-validation 0.13
  50. 50. Configuration parameters dry-validation 1.3.1 class CommonContract < Dry::Validation::Contract # custom errors files and I18n configs # external dependencies # predicates as macros end MyContract.new( # list of validators )
  51. 51. Configuration parameters dry-schema 1.3.4 CommonConfig = Dry::Schema.Params do # custom errors files and I18n configs # custom types end Dry::Schema.Params( processor: 'Params', config: CommonConfig.config, &block )
  52. 52. Reusing schemas Rules Custom predicates Custom Error Messages Configuration Parameters dry-validation (Contract) dry-schema Reusing schemas Rules Custom predicates Custom Error Messages Configuration Parameters dry-validation (Contract) dry-schema
  53. 53. Totals Reusing schemas Rules Custom predicates Custom Error Messages Configuration Parameters dry-validation (Contract) dry-schema Reusing schemas Rules Custom predicates Custom Error Messages Configuration Parameters dry-validation (Contract) dry-schema Reusing schemas Rules Custom predicates Custom Error Messages Configuration Parameters dry-validation (Contract) dry-schema
  54. 54. This separation will not only make dry-v much simpler internally, but also allow us to have schemas for processing/validating input at the HTTP boundary, and then having domain validators that can be called in the application layer. Schemas and validators can be composed, it means that you’ll be able to specify schemas and reuse them in validators. This way your application’s domain validation will live in the app layer, and hairy HTTP processing/validation will be in the HTTP layer (ie controllers, roda routes, etc.) and this will be possible with 0 code duplication (ie you won’t have to define same attributes in two places). The main idea behind dry-schema is to be a fast type checker and a coercion mechanism. It does support lots of predicates OOTB through dry-logic but it’s important to understand that for “domain validation” it is not a good fit. Good use cases for dry-schema with additional predicates (as in, other than type checks) may include things like processing and validating application configuration or HTTP params pre-processing (before it is passed down to domain layer where further processing may take place). Proving the idea by Piotr Solnica
  55. 55. How can we draw a line between dry-validation and dry-schema? Use dry-validation or use both. dry-schema for high-level http params validation dry-validation for specific and complex validations, business logic Ex.: dry-schema in Controllers dry-validation in Operations or whatever you use to process data Figuring it out
  56. 56. About dry-rb gems https://www.rubyguides.com/2019/01/what-is-dry-rb/ Piotr Solnica about dry-schema 1.0 release https://solnic.codes/2019/01/31/introducing-dry-schema/ Piotr Solnica about dry-validation 1.0 release https://dry-rb.org/news/2019/06/10/dry-validation-1-0-0-released/ Tim Riley “A tour of dry-schema and dry-validation 1.0” https://speakerdeck.com/timriley/a-tour-of-dry-schema-and-dry-validation-1-dot-0 Igor Morozov upgrading dry-gems https://www.morozov.is/2019/05/31/upgrading-dry-gems.html Helpful links:
  57. 57. Questions?
  58. 58. Thanks for listening 🎉🐱

×