GithubHelp home page GithubHelp logo

hanami / validations Goto Github PK

View Code? Open in Web Editor NEW
213.0 23.0 49.0 809 KB

Validation mixin for Ruby objects

Home Page: http://hanamirb.org

License: MIT License

Ruby 95.79% Shell 4.21%
ruby whitelisting validations coercion

validations's Introduction

Hanami::Validations

Internal support gem for Hanami::Action params validation.

Status

Gem Version CI Test Coverage Depfu Inline Docs

Version

This branch contains the code for hanami-validations 2.x.

Contact

Rubies

Hanami::Validations supports Ruby (MRI) 3.0+

Installation

Add this line to your application's Gemfile:

gem "hanami-validations"

And then execute:

$ bundle

Or install it yourself as:

$ gem install hanami-validations

Usage

Installing hanami-validations enables support for params validation in hanami-controller’s Hanami::Action classes.

class Signup < Hanami::Action
  params do
    required(:first_name)
    required(:last_name)
    required(:email)
  end

  def handle(req, *)
    puts req.params.class            # => Signup::Params
    puts req.params.class.superclass # => Hanami::Action::Params

    puts req.params[:first_name]     # => "Luca"
    puts req.params[:admin]          # => nil
  end
end

See hanami-controller for more detail on params validation.

Contributing

  1. Fork it ( https://github.com/hanami/validations/fork )
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request

Copyright

Copyright © 2014 Hanami Team – Released under MIT License

validations's People

Contributors

alfonsouceda avatar artofhuman avatar cllns avatar davydovanton avatar deepj avatar depfu[bot] avatar dsiw avatar dsnipe avatar ernestns avatar hlegius avatar ippachi avatar jc00ke avatar jeremyf avatar jodosha avatar linuus avatar lucasas avatar nickgnd avatar oana-sipos avatar pmatsinopoulos avatar pragtob avatar rail44 avatar rhynix avatar rowlando avatar solnic avatar stevehodgkiss avatar timriley avatar tomkadwill avatar tsorencraig avatar viking avatar vyper avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

validations's Issues

Format with empty/blank values does not coerce to nil

Hello,

As described by @wafcio in his gist, he was trying to validate a value with format attribute. If pass an empty string, format returns valid true (which is correct), but does not coerce into nil. If we provide type: Integer attribute, it will return 0, which isn't the same of nil of blank.

class Person
  include Hanami::Validations

  attribute :number, format: /\A\d+\z/
end

person = Person.new(number: '')
p.valid? # => true

I was wondering if we should introduce blank/empty string to nil conversion in all occasions as well.

Enforce validations at Repository-level

I'm working on converting a Rails app to Lotus piece-by-piece. I'm starting with lotus-model, and in the process, I've included lotus-validations as well.

The problem is that I can create a new Article entity, and persist it via the ArticleRepository, even if the entity is invalid. I have to remember to always manually check whether the Entity is valid? before persisting it.

What I did to get around it was add the following method to ArticleRepository:

  def self.create(article)
    if article.valid?
      super(article)
    else
      article
    end
  end

It'd be nice if this functionality were included in some module (and also included similar methods for update and persist). Either in Lotus::Validations, or perhaps as a separate module called Lotus::EnforceValidations?

I understand the validations generally occur at the Action level in a full Lotus app but in this case, I haven't converted the Controller yet.

Of course this requires that the corresponding Entity responds to .valid?, so that should be checked (and a descriptive error should be raised if it doesn't).

This lack of validations at the Repository might be by design. I could be missing some key insight about separation of concerns, but I feel like there should be some way to ensure data integrity at the Repository level.

Empty parameter values with defined size attribute are not valid

Hello there!

I've found a bug within size and blank values. If I define size attribute and try to pass the attribute as blank/empty, it will fail.

class User
  attribute :name, size: 8..10
end

User.new({name: '', other: 'foo'}).valid? # <= false
User.new({name: '12345678', other: 'foo'}).valid? # <= true
User.new({other: 'foo'}).valid? # <= true
class User
  attribute :name
  attribute :other, size: 2..10
end

User.new({name: '', other: 'foo'}).valid? # <= true

How should it be, IMO:

class User
  attribute :name, size: 3..10
  attribute :other_param, presence: true, size: 2..10
end

User.new({name: '', other_param: 'abc'}).valid? # <= true
User.new({name: 'a', other_param: 'abc'}).valid? # <= false
User.new({name: 'abc', other_param: 'abc'}).valid? # <= true
User.new({name: 'abc'}).valid? # <= false
User.new({other_param: 'abc'}).valid? # <= true

If you guys agrees, I'd like to fix it 👍

Validator for `size_at_least`

Right now, if you want to validate a size is greater than a value, (it seems) you have to pick an arbitrary upper-bound for the Range.

attribute :text, presence: true, size: 5..99

In most cases this will be fine. But let's say you want to make sure a blog post is at least 5 characters long, and during development you pick an arbitrary upper-bound of 1000, let's say. It could be a while before you realize that you're limiting the size of the blog text.

Instead, it'd be better if you could do:

attribute :text, presence: true, size_at_least: 5

(Another name could be size_greater_than)

Note: size_at_most / size_less_than is less important, since it can be handled with a Range starting at 0. It'd probably make sense to implement though, to be consistent.

I'd be happy to implement it, if this is something we want to do!

Documentation: Acceptance

IMHO the documentation/example of acceptance validation has a small error.

It states that truthy values will make this validation pass. Empty-string ""is true, in ruby.
Since acceptance forwards to Lotus::Kernel::Boolean empty String is considered false.

In other words: acceptance does not evaluate according to ruby truthiness but according to Lotus::Utils::Kernel.Boolean

Improve error reporting

Consider the following case:

class Signup
  include Lotus::Validations

  # ...
  param :age, type: Integer, size: 18..65
end

signup = Signup.new(age: 15)

signup.valid? # => false

# This should be improved
# from:
signup.errors # => { :age => [:size] }

# to:
signup.errors # => { :age => { :size => [18..55, 15] } }
#                    1         2        3        4

Where:

  1. Attribute
  2. Validation
  3. Expectation
  4. Actual value

Bonus point: Because this is a quite complex data structure, consider to introduce Lotus::Validation::Errors.

Migrate from Minitest to RSpec

A few rules to follow:

  • If you want to work on this, please leave a comment here. We want to avoid many people to work on the same task.

  • We expect this migration to happen within a week from now.

  • Do not change the tests, only migrate from Minitest to RSpec syntax. If not sure, please ask here.

  • Do not remove the old tests, only add the new ones.

  • Make CI build to use RSpec tests.

  • Target develop branch, instead of master.

  • All the tests artefacts like fixtures, goes under spec/support.

  • Try to reflect the structure under lib/. Eg. lib/hanami/view.rb should be tested under spec/hanami/view_spec.rb, while test/rendering_test.rb should go under spec/integration/rendering_spec.rb.

If you want to take inspiration, please have a look at: hanami/mailer#67

base class inheritance for validations

dry-validation has the ability to inherit a base schema as documented here:

http://dry-rb.org/gems/dry-validation/basics/working-with-schemas/

To allow for validations from a base schema to apply to the current and then in the block you can describe any additional validations. It doesn't seem like we have the ability to take advantage of this feature. It would probably involve doing something like:

class SpecialUser
  include Hanami::Validations::Form
  validations(BaseUser) do
    required(:extra_field)
  end
end

So basically being able to pass a schema (or ideally an entire other form much like you have support for composition in nested attributes)

Conditional validations

I found there's no conditional validations like validates :name, presence, if: :some_condition. IMO it's used very frequently. Curious the reason it's not implemented in Lotus validations. Is it already in plan? Or there's other way you recommend more to do the same thing?

Fail on incomplete expression?

In order to check for a field to be always be present, but with any possible value (including nil), please check the following example:

require 'hanami-validations'

class Test
  include Hanami::Validations

  validations do
    required(:present)
    required(:present2).maybe
  end
end

p Test.new.validate

It returns:

<Dry::Validation::Result output={} errors={:present2=>["is missing"]}>

Either required(:present) is an incomplete expression, in which case Hanami should not silently ignore it, or it is equivalent to required(:present).maybe, in which case it should display the same behaviour.

Make validations a callable object

At his talk at railsclub(https://www.youtube.com/watch?v=SRQVhHzW-Eo) @jodosha talked about
making changes to Hanami::Validations to make it a callable object.

I am happy to start working on this, would just like some guidance so I can move in the right direction.

Would we have to make this backwards compatible still allowing for params to be set on initialization?

I would also like to work on moving it from being a Mixin to being an "inheritable" class as also mentioned in the talk, but that would be a separate Issue/PR.

Validations from Initializer

I recently tried updating my gem to 0.6.0 and noticed that I started receiving "undefined method `validates'". I've had no success trying to update my Class to validate inside a constructor with a validations block as documented in the README. Is this functionality no longer supported?

Here's the code that works fine with version 0.5.0.

require 'hanami/validations'
class Name
  include Hanami::Validations
  MAX_NAME_LENGTH = 64
  attr_reader :name
  validates :name, type: String, size: 1..MAX_NAME_LENGTH, presence: true, format: /\A[\-_a-zA-Z1234567890]+\z/

  def initialize(name)
    @name = name.to_s
    raise "Invalid Name: #{name}" unless valid?
  end
end

n = Name.new('Goodname')
m = Name.new('Invalidname%%%%%%')

Retrieve only whitelisted attributes after validation

#21 introduced the ability to retrieve coerced attributes (.attributes or .to_h) after validation, however, it also carried over extra attributes that weren't validated or coerced.

Example:

class NewUser
  include Lotus::Validations

  attribute :name, type: String,  presence: true
  attribute :age,  type: Integer, presence: true
end

luis = NewUser.new(name: "Luis", age: "34", garbage: "foo")
luis.valid? # => true

luis.attributes # => {:name=>"Luis", :age=>34, :garbage=>"foo"}

# Expected:

luis.attributes # => {:name=>"Luis", :age=>34}

Having attributes return only the whitelisted and coerced values avoids the potential issue of sending parameters down to other objects that might have undesired effects.

While usage of .attributes.slice is possible, it leaks details about which fields are desired to the invoker.

Cheers

Make validator to act like a Hash

With #21 we introduced #attributes as a public method for obtain the set of all the validated/coerced attributes. That API is truly useful, but it leads to some weird usage. Think of the following use case:

class Signin
  include Lotus::Validations

  attribute :email
  attribute :password
end

signin = Signin.new(email: '[email protected]', password: 'shhh')

If we want to introspect all the data that a validator hides, we should always refer to #attributes and then manipulate it. For instance signup.attributes.values or signup.attributes.each {|attr, val| ... }.

Because this gem powers Lotus::Action params, think of this other case:

module Books
  class Create
    params do
      param :title
    end

    def call(params)
      @result = CreateBook.new(params.attributes).call
    end
  end
end

That params.attributes looks weird, it leaks the internal state of the object, it's a Primitive Obsession and leads developers to violate the Law of Demeter.

The new API should remove the need of using #attributes for bulk data operations.
Lotus::Validations uses a Hash to store the data, and Ruby community is used to Hash semantic when dealing with data.

Right now, a validator creates getters for each defined attribute. In the case above Signin, responds to #email. Alongside with that, we could expose #[] and #each(&blk).

Obtain coerced and whitelisted attributes

Take the following example:

class Signup
  include Lotus::Validations

  attribute :email, type: String # format....
  attribute :password, type: String, confirmation: true
end
signup = Signup.new(email: "[email protected]",
                    password: "abc123",
                    password_confirmation: "abc123",
                    garbage: "garbage")

signup.valid? # => true

There is no easy way, without repetition, to pass the coerced and whitelisted attributes down to the model/entity.

Being attributes a protected method and not supporting slice to retrieve a defined set of attributes, end doing signup.email and signup.password multiple times (and that is just an example)

It should be possible obtain the coerced and whitelisted attributes more easily.

Thank you!

param not required is still validated

In controller I have following code:

params do
  param :path, format: /^\S+$/
end

Sending request without :path param, in fact sends an empty :path:

params.to_h returns { "path"=>"" }

Validation fails due to format constraint.

Check validations names

Check validation names when .attribute is used. This should protect developers against typos.

Example:

class Signup
  include Lotus::Validations

  # ...
  attribute :email, comfirmation: true
end

signup = Signup.new({})
signup.valid? # => true ??? It supposed to be false

If you look at the code above, there was a typo ;)

+required+ is not a valid predicate name (ArgumentError)

Gemfile

source 'https://rubygems.org'

gem 'hanami-utils',       github: 'hanami/utils',       branch: 'master'
gem 'hanami-validations', github: 'hanami/validations', branch: 'master'

testing.rb

require 'hanami-validations'

class Testing
  include Hanami::Validations

  validations do
    required(:email) { format?(/@/) }
  end
end

result = Testing.new.validate
puts result.messages

But, when I ran command bundle exec ruby testing.rb:

/Users/vyper/.rvm/gems/ruby-2.3.0@hanami/gems/dry-validation-0.7.4/lib/dry/validation/schema.rb:195:in `[]': +required+ is not a valid predicate name (ArgumentError)
    from /Users/vyper/.rvm/gems/ruby-2.3.0@hanami/gems/dry-logic-0.2.3/lib/dry/logic/rule_compiler.rb:54:in `visit_predicate'
    from /Users/vyper/.rvm/gems/ruby-2.3.0@hanami/gems/dry-logic-0.2.3/lib/dry/logic/rule_compiler.rb:18:in `visit'
    from /Users/vyper/.rvm/gems/ruby-2.3.0@hanami/gems/dry-logic-0.2.3/lib/dry/logic/rule_compiler.rb:41:in `visit_val'
    from /Users/vyper/.rvm/gems/ruby-2.3.0@hanami/gems/dry-logic-0.2.3/lib/dry/logic/rule_compiler.rb:18:in `visit'
    from /Users/vyper/.rvm/gems/ruby-2.3.0@hanami/gems/dry-logic-0.2.3/lib/dry/logic/rule_compiler.rb:59:in `visit_and'
    from /Users/vyper/.rvm/gems/ruby-2.3.0@hanami/gems/dry-logic-0.2.3/lib/dry/logic/rule_compiler.rb:18:in `visit'
    from /Users/vyper/.rvm/gems/ruby-2.3.0@hanami/gems/dry-validation-0.7.4/lib/dry/validation/schema.rb:254:in `block in initialize_rules'
    from /Users/vyper/.rvm/gems/ruby-2.3.0@hanami/gems/dry-validation-0.7.4/lib/dry/validation/schema.rb:253:in `each'
    from /Users/vyper/.rvm/gems/ruby-2.3.0@hanami/gems/dry-validation-0.7.4/lib/dry/validation/schema.rb:253:in `each_with_object'
    from /Users/vyper/.rvm/gems/ruby-2.3.0@hanami/gems/dry-validation-0.7.4/lib/dry/validation/schema.rb:253:in `initialize_rules'
    from /Users/vyper/.rvm/gems/ruby-2.3.0@hanami/gems/dry-validation-0.7.4/lib/dry/validation/schema.rb:174:in `initialize'
    from /Users/vyper/.rvm/gems/ruby-2.3.0@hanami/gems/dry-validation-0.7.4/lib/dry/validation/schema.rb:46:in `new'
    from /Users/vyper/.rvm/gems/ruby-2.3.0@hanami/gems/dry-validation-0.7.4/lib/dry/validation/schema.rb:46:in `new'
    from /Users/vyper/.rvm/gems/ruby-2.3.0@hanami/bundler/gems/validations-34c34dfa416a/lib/hanami/validations.rb:57:in `validations'
    from testing.rb:6:in `<class:Testing>'
    from testing.rb:3:in `<main>'

How can I custom keys in validation?

Hi, I read README and understood to custom my error messages.

But I have a question... how to custom keys (in this case, like "age".) in validation?

en:
  errors:
    email?: "must be an email"

    rules:
      signup:
        age: # want to set custom value here.
          gt?: "must be an adult"

It is serious problem for me, because I'm Japanese and I work in Japan. I have to use Japanese error messages at all to use Hanami in our products.

I checked issues, but I am sorry if I duplicated.

Thanks.

Extracting nested attributes values as a nested hash

I guess this may be expected behaviour, but thought I'd raise it as I've come against it a few times when trying to use Lotus::Validations to create form objects.

Given a Form Object e.g.

module Admin::Forms
  module Job
    class Search
      include Lotus::Validations
      attribute :filter do
        attribute :id,              type: String
        attribute :customer_name,   type: String
        attribute :customer_email,  type: String
        attribute :vendor_name,     type: String
        attribute :state,           type: String
      end
    end
  end
end

Running the following

form = Admin::Forms::Job::Search.new(filter: {customer_name: 'Buster'})
form.to_h

Nets

{:filter=>#<#<Class:0x007fb4fab8de70>:0x007fb4f9629908 @attributes=#<Lotus::Utils::Attributes:0x007fb4fac25658 @attributes={\"customer_name\"=>\"Buster\"}>>}

This output is quite hard to use again to do something such as initialise a new object or merge the new data with a hash. I've ended up creating methods to iteratively cast the nested attributes as hashes before using the output.

Wondering if is worth looking into?

Allow both strings and symbols as attribute keys

We have recently introduced a new feature in Lotus::Router: JSON body parsing. The body of the incoming JSON requests gets parsed and transformed into a Ruby Hash. For security reasons, we don't symbolize the keys of that Hash.

Lotus::Validations powers the internals of the Lotus::Controller params management. This gem expects symbols as attribute keys, but it may happen that those keys are strings instead. Like we have just seen in the case above.

Allow both string and symbols as attribute keys in Lotus::Validations#initialize

Presence validation accepts empty values

Consider:

class Foo
  include Lotus::Validations

  attribute :bar, presence: true
end
Foo.new(bar: '').valid? #=> true

In the context of a Lotus::Application, input tags in an HTML form that are left blank are empty strings and are never nil. Should the presence validation treat empty values as missing by default?

undefined method `merge' for #<Dry::Validation::Messages::Namespaced

So, i'm trying to use hanami-validations with Rails 5 app. I have a custom predicate and i need a custom message error for this.

This code:

predicate :unique?, message: 'error test message' do |current|
    MyModel.find_by(name: current).nil?
  end

generate this error:

/Users/eduardo/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/bundler/gems/validations-74276a8dd4b5/lib/hanami/validations.rb:148:in `messages': undefined method `merge' for #<Dry::Validation::Messages::Namespaced:0x007f9a5f3dd5f0> (NoMethodError)

Is this a bug?

Update: The problem above occurs with inline custom predicates only. The same predicate with global custom predicate works.

Custom validation plugin API

Firstly I would like to say that I love Lotus validations. I have actually almost entirely replaced my ActiveModel validations with it, and it has been a dream to work with.

Since I use Lotus separately from the rest of the Lotus framework, there is one feature I really would love to have: the ability to create custom validations. Looking in the source, there are a finite number of accepted validation types in the API, specifically:

      VALIDATIONS = [
        :presence,
        :acceptance,
        :format,
        :inclusion,
        :exclusion,
        :confirmation,
        :size,
        :type,
        :nested
      ].freeze

I would like to be able to create my own validations, and append to this list. For example, say I wanted to validate the authenticity of a token, it would nice to be able to create my own validator for this:

attribute :jwt_token, authenticity: true

This would allow me to create my own logic to verify the authenticity of a token, and have it validated by Lotus.

Currently, to achieve this, I can do some somewhat nasty monkey-patching by overriding #valid?

def valid?
  super()
  validate_jwt_token_authenticity
  errors.empty?
end

def validate_jwt_token_authenticity
  if external_jwt_validation
    errors.add :jwt_token, Lotus::Validations::Error.new(:jwt_token, :presence, true, false)
  end
end

Would you guys accept a PR which created a way in which users could add custom validations to the validation set? I would like to add this functionality if possible 😄

Thoughts? Concerns? Would love to hear what you guys think about this!

  • Ian

Rule is not working for some reason

Hanami Validations are great! I played around with following code and cannot figure out, why the rule location_precence is not working. The rule is broken by data (remote is true and location is set), but success returns true. What have I made wrong?

class CreateJob
  include Hanami::Validations::Form

  validations do
    required(:type).filled(:int?, included_in?: [1, 2, 3])

    optional(:remote).maybe(:bool?)

    required(:title).filled(:str?)
    required(:description).filled(:str?)
    required(:company).filled(:str?)

    optional(:website).filled(:str?, format?: URI.regexp(%w(http https)))

    rule(location_presence: [:location, :remote]) do |location, remote|
      (remote.none? | remote.false?).then(location.filled?) & remote.true?.then(location.none?)
    end
  end
end

result = CreateJob.new(type: 1, title: "test", description: "desc", company: "comp", remote: true, location: "loc").validate
ap result.success? # => true
ap result.messages # {}

Thx

PORO validator

This would be a feature request but would be nice if we could implement this to be just a PORO validator as well.

Perhaps do something like Virtus where you can set the constructor or the mass assignments.

Default values for attributes?

I was using lotus/validations in my gem, I liked the syntax sugar of attribute, with their validations. But I found out that there's no way to include default data. It seems (to me at least) something basic, so maybe I'm just missing something obvious (I read the verbose explanation on how to use only the validations, but using that I have to sacrifice almost all the sugar).

Is there already a way to do it?

Custom message gets wiped out when inline predicate in use

The following code outputs the custom message for name:

# config/messages.rb
en:
  errors:
    name: "must be frank"
# customer_validator.rb
class CustomerValidator
  include Hanami::Validations
  messages_path 'config/messages.yml'

  validations do
    required(:name) { format?(/Frank/) }
    required(:age) { gt?(18) }
  end
end

customer = {:name => 'John', age: 15}
validation = CustomerValidator.new(customer).validate
p validation.messages # {:name=>["must be frank"], :age=>["must be greater than 18"]}

As soon as I introduce an inline predicate, the custom message isn't honoured, with the output as follows:

class CustomerValidator
  include Hanami::Validations
  messages_path 'config/messages.yml'

  predicate(:adult?, message: 'Must be adult') do |current|
    current > 18
  end

  validations do
    required(:name) { format?(/Frank/) }
    required(:age) { adult? }
  end
end

customer = {:name => 'John', age: 15}
validation = CustomerValidator.new(customer).validate
p validation.messages # {:name=>["is in invalid format"], :age=>["Must be adult"]}

I have found a way around this for the time being:

class CustomerValidator
  include Hanami::Validations
  
  predicate(:adult?, message: 'Must be adult') do |current|
    current > 18
  end

  validations do
    configure do
      def self.messages
        super.merge(
            en: {
                errors: {
                    name: 'must be frank'
                }
            }
        )
      end
    end
    required(:name) { format?(/Frank/) }
    required(:age) { adult? }
  end
end

customer = {:name => 'John', age: 15}
validation = CustomerValidator.new(customer).validate
p validation.messages # {:name=>["must be frank"], :age=>["Must be adult"]}

Does anything spring to mind in terms of a fix?

Nested validation of arrays

Lotus allows validating nested objects, but it would be very nice if arrays allow nested validation too. Often, the entries of an array have a specific object structure too.

Error if each macro is used with a single check

Hello,

Following Params-Validation works

# incoming hash
{
  :costUnit => "details"
  :areas => [
   {
     :id => "1203",
     :actual => false
   },
   {
     :id => "1204",
     :actual => true,
   }
  ]
}
# validation-class
class Params
  include Hanami::Validations::Form

  validations do
    required(:costUnit).filled(:str?)
    required(:areas).each do
      required(:id).filled(:str?)
      required(:actual).filled(:bool?)
    end
  end
end

But if I remove the actual-check at areas, because I don't care if it is present or not,

# validation-class
class Params
  include Hanami::Validations::Form

  validations do
    required(:costUnit).filled(:str?)
    required(:areas).each do
      required(:id).filled(:str?)
    end
  end
end

the validation fails with following error:

gems/dry-types-0.11.1/lib/dry/types/compiler.rb:16:in `visit': undefined method `visit_id' for #<Dry::Types::Compiler:0x005609492a0438 @registry=Dry::Types> (NoMethodError)

Thx

Nested parameter whitelisting

If I have the following configuration for my parameters in an action:

required(:id).filled(:int?)
required(:user).filled.schema do
  optional(:first_name)
  optional(:last_name)
end

I observed that I cannot filter a nested parameter (i.e. anything inside :user).

def call(params)
  puts params[:email]                 # => nil
  puts params[:user]                  # => { first_name: "John", email: "[email protected]" }
  puts params[:user][:email]          # => "[email protected]"
end

I believe that the params[:user][:email] should be filtered just as params[:email].

[Question] Validation Object receiving key arguments as string

I was playing with Validations within a Rails app when got something interesting:

class FooController
  def create
    # params = {title: => ‘Awesome Task’, due_date: => ‘2015-10-20’, non_used_param: => ‘I am garbage!'}
    task_validation = TaskValidation.new(params)
    task_validation.valid? # < = true

    # params = {‘title’ => ‘Awesome Task’, ‘due_date’ => ‘2015-10-20’, ‘non_used_param’ => ‘I am garbage!'}
    task_validation = TaskValidation.new(params)
    task_validation.valid? # <= false
    task_validation.errors # <= #<Lotus::Validations::Errors:0x007ff86b86b1c0 @errors={}>
  end
end

class TaskValidation
  include Lotus::Validations

  attribute :title
  attribute :due_date

  validates :title, presence: true
end

In other words: if my params has keys as symbol, it works fine; if keys are strings, won't work and also, .errors returns an empty @errors.

I don't know if is an expected behavior or a non-implemented and could be a feature to be added.

Any thoughts?
Thanks in advance!

Does not validate when attribute is set using accessor

class MyClass
  include Hanami::Entity
  include Hanami::Validations

  attributes :email

  validations do
    required(:email) { format?(/@/) }
  end
end

my_class = MyClass.new
my_class.email = '[email protected]'
my_class.validate
# => #<Dry::Validation::Result output={} messages={:email=>["is missing", "is in invalid format"]}>

my_class = MyClass.new(email: '[email protected]')
my_class.validate
# => #<Dry::Validation::Result output={:email=>"[email protected]"} messages={}>

Gemfile

gem 'dry-types',          github: 'dry-rb/dry-types'
gem 'dry-logic',          github: 'dry-rb/dry-logic'
gem 'dry-validation',     github: 'dry-rb/dry-validation'
gem 'hanami-utils',       github: 'hanami/utils',       branch: '0.8.x'
gem 'hanami-model',       github: 'hanami/model',       branch: '0.7.x'
gem 'hanami-validations', github: 'hanami/validations', branch: 'master'

custom predicates do not work in conjunction with schema

Greetings,

I've been tinkering with the validations gem since I've been looking for a way to get custom predicates to work with Hanami::Action::Params (see the corresponding issue).
When I thought I had found a solution it instead blew up in my face saying NoMethodError: undefined method 'messages' for #<Dry::Validation::Messages::YAML:0x007fec9b10cfb8>. Don't worry, I'm unharmed 😁

However, I do believe I may have discovered a bug in the Wrapper around the dry-validation gem.
I have managed to construct a small example to reproduce this:

require 'hanami/validations'

class Signup
  include Hanami::Validations
  
  predicate(:boom?, message: 'boom!') { |bomb| bomb.nil? }
  validations do
    required(:bomb_site).schema do
      required(:bomb) { boom? }
    end
  end
end

When you do not use schema it works as expected. So this is only a problem when one wants to use a custom predicate in conjunction with nested input data.

Best regards,
Kai

Is the conjunction of the array? predicate and another predicate expected to work?

The following code outputs true:

# customer_validator.rb
class CustomerValidator
  include Hanami::Validations

  predicate :address?, message: 'must be address' do |current|
    current.key?(:street) && current.key?(:city)
  end

  validations do
    required(:name).filled(:str?)
    required(:addresses) { min_size?(2) }
  end

end

address = {
    street: '2 Chicken Road',
    city: 'Glasgow'
}

customer = {
    name: 'John Doe',
    addresses: [address,address]
}
puts CustomerValidator.new(customer).validate.success?

When I change the addresses validation to:

required(:addresses) { array? { each { address?} } }

the output is true.

However, when combine the predicates like this:

required(:addresses) { min_size?(2) & array? { each { address?} } }

I get the following error:

/Users/nick/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/dry-logic-0.4.0/lib/dry/logic/evaluator.rb:32:in `[]': no implicit conversion of Symbol into Integer (TypeError)

Is a conjunction of the array? predicate and another predicate expected to work?

Coercion is not run without validation

This took me some time to figure out. Not sure if it's unintended or by design.

I have this lotus controller:

module MyApp
  module Controllers
    module Items
      class Show
        include MyApp::Action

        expose :item

        params do
          param :id, type: Integer
        end

        def initialize(options = {})
          @repo = options.fetch(:repo) { ItemsRepo.new }
        end

        def call(params)
          @item = @repo.find(params.id)
        end
      end
    end
  end
end

When calling this with a string:

action.call(id: '42')

The output from params.id is not coerced to 42 but is the original "42".

I found out that I needed to run validations (as coercion is a validation in Lotus::Validations) using valid?. After that the params output is as expected (42).

Would it be wrong to treat coercion as a 'special' validation that is run on initialization of the attribute? Or alternatively, run the first time the attribute is requested using either #[] or #name.

I will be happy to implement it :)

Validation inside model do not work

Using lotus-utils 0.3.3
Using sequel 4.18.0
Using lotus-model 0.2.0
Using lotus-validations 0.2.2

require 'lotus/model'
require 'lotus/validations'

class Product
  include Lotus::Entity
  include Lotus::Validations

  attributes :name
  validates :name, type: String, presence: true
end

class ProductRepository
  include Lotus::Repository
end

Lotus::Model.configure do
  adapter type: :memory, uri: 'memory://localhost/test'

  mapping do
    collection :products do
      entity     Product
      repository ProductRepository

      attribute :id,   Integer
      attribute :name, String
    end
  end
end

Mutex.new.synchronize do
  Lotus::Model.load!
end

p = ProductRepository.create(Product.new(name: 'foo'))
r = ProductRepository.find(p.id)
ProductRepository.delete(r)

bundle exec ruby test.rb
/Users/gotar/.rbenv/versions/2.2.0/lib/ruby/gems/2.2.0/gems/lotus-validations-0.2.2/lib/lotus/validations/validation_set.rb:61:in `validate_options!': Unknown validation(s): type for "name" attribute (ArgumentError)
    from /Users/gotar/.rbenv/versions/2.2.0/lib/ruby/gems/2.2.0/gems/lotus-validations-0.2.2/lib/lotus/validations/validation_set.rb:32:in `add'
    from /Users/gotar/.rbenv/versions/2.2.0/lib/ruby/gems/2.2.0/gems/lotus-validations-0.2.2/lib/lotus/validations.rb:116:in `validates'
    from test.rb:9:in `<class:Product>'
    from test.rb:4:in `<main>'

Compatability with Lotus::Model/Entity

Seeing consistency break down when including/using validations with Lotus::Model's Entity mixin. The interface to a defined entity changes subtly so as to break expectations. See the following example:

class DummyModel
  include Lotus::Entity
  attributes :name, :email
end
# => [:name, :email]

DummyModel.attributes
# => #<Set: {:id, :name, :email}>

m = DummyModel.new name: "Luke", email: "[email protected]"
# => #<DummyModel:0x007fdc8d135b48 @email="[email protected]", @name="Luke">

m.to_h
# => {:id=>nil, :name=>"Luke", :email=>"[email protected]"}

Vs the output of:

class OtherModel
  include Lotus::Entity
  include Lotus::Validations
  attribute :email, presence: true
  attribute :name, presence: true
end
#=> [:name]

OtherModel.attributes
#=> #<Set: {:id, :email, :name}>
# Note, attributes includes the id here...

o = OtherModel.new name: "Luke", email: "[email protected]"
#=> #<OtherModel:0x007fc03ba9c1f0 @attributes=#<Lotus::Utils::Attributes:0x007fc03ba9c1c8 @attributes={"name"=>"Luke", "email"=>"[email protected]"}>>
# The internal structure (or at least the output of the inspect method) has changed

o.to_h
#=> {:email=>"[email protected]", :name=>"Luke"}
# What happened to the id field? `.attributes` tells me I should be able to expect it returned...

I can note that if I specify attribute :id at the same time as the validators, I can get it to return the id as well, but this is not consistent with how Lotus::Model acts.

Make validations composable

Example:

module EmailValidations
  include Lotus::Validations

  attribute :email, presence: true, format: /.../
end

module PasswordValidations
  attribute :password, presence: true, confirmation: true
end

class Signup
  include EmailValidations
  include PasswordValidations

  attribute :name, presence: true
end

Error messages, I18N and Namespaces

Reform 2.0 does support lotus-validations and I will push towards making this the default implementation. Thanks for this. ❤️

As discussed with Luca, I'm currently designing the process of generating an (i18nized) message string from an Error object. I do this in Reform first, and will then extract it to this gem.

Consider the following object for a nested field band.label.name.

#<Lotus::Validations::Error:0x007fb6d5b48aa0 @attribute_name="name", 
  @validation=:presence, @expected=true, @actual=nil, 
  @namespace="band.label", @attribute="name">]}

In order to translate this, I propose the following I18N paths (please comment!).

form.band.label.name.blank
form.band.name.blank
form.name.blank

Don't worry about the prefix form., this could be anything and is just an example.

Another thing is: How to name the suffixes, e.g. blank? This has to be computed from Error by looking at @validation and @actual, so we need to specify translation suffixes for validation/result tuples.

Any input?

[Question] Validation Object receiving key arguments as string or symbol

How to make so there would be no mistake if type key = string or symbol?

require 'hanami-validations'
class Signup
  include Hanami::Validations

  validations do
    required(:name) { filled? }
  end
end

Signup.new('name' => 'Luca').validate.errors # Error: {:name=>["is missing"]}
Signup.new(name: 'Luca').validate.errors # {}

There is a bad decision

module Hanami
  module Validations
    require 'json'

    def initialize(input = {})
      @input = JSON.parse(JSON[input.to_h], symbolize_names: true)
    end
  end
end

Signup.new('name' => 'Luca').validate.errors # {}
Signup.new(name: 'Luca').validate.errors # {}

Lack of flexibility and thoughts on refactorings

Lotus Validations requires ownership of the objects constructor (and the objects attributes) in order to work properly/validate things. This makes Lotus Validations less flexible than other validation libraries. I'd like to be able to do things like the following:

class SignupForm
  include Virtus.model(strict: true)
  include Lotus::Validations

  attribute :name, String # virtus

  validates :name, presence: true # lotus validations
end

class SignupForm
  attr_accessor :name

  include Lotus::Validations

  validates :name, presence: true # lotus validations
end

Some refactoring that would help achieve this:

  • Remove attribute tracking from the Lotus::Validations provided constructor, and only provide a generic accessor setter constructor that can be overwritten easily (like the one in Lotus::Entity [1]).
  • Build the list of attributes and values to be validated lazily, rather than in the constructor.
  • Extract a validate method from attribute, to support using other libraries for type coercion and object construction. Type coercion would only be supported through the attribute method.
  • Move type coercion functionality to setters (run separately from validations).

So, Lotus Validations isn't only about validations, but also object construction and type coercion. Perhaps that functionality could be broken out into separate library?

[1] https://github.com/lotus/model/blob/master/lib/lotus/entity.rb#L178-182

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.