hanami / validations

Validation mixin for Ruby objects
http://hanamirb.org
MIT License
213 stars 49 forks source link

Conditional validations #80

Closed darkbaby123 closed 8 years ago

darkbaby123 commented 8 years ago

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?

runlevel5 commented 8 years ago

@darkbaby123 atm there is a PR #71 that allows users to define custom validator which can work around this issue.

I like your suggestion and open for the PR submission. The validator should take in a proc/lambda OR the symbol name of a private condition method

jodosha commented 8 years ago

@darkbaby123 Hello and thanks for this suggestion. We won't to support conditionals for action callbacks, validators, because they increase the complexity both of the frameworks and applications.

If you use validations around use cases, and not in entities/models, you'll almost eliminate this need. Now, I don't know your specific use case, so please share with us, and we can find a solution for you of for the framework.

As @joneslee85 we'll offer soon a more flexible approach for validations, so it could be helpful for you. But my opinion on conditionals is :-1:

darkbaby123 commented 8 years ago

@jodosha I read your blog post before I submit this issue, and I'm not use validations in entities/model level in my recent project (all plain Ruby object + validation mixin, it's a Rails project). I admit that most of time I don't need the conditional validations, but they're still useful in some of my cases:

Case 1

I have an API that responsible to connect some device (hardware) to current user, and I use a single API endpoint to handle all 3 connection types. It's like this:

{
  conn_type: 'a/b/c',
  quick_code: 'xxx', // should be present in type a
  uuid: 'xxx', // should be present in type b
}

In this case I can use 2 different validators, but a conditional validation can make it simpler.

Case 2

In my another project I have a "sign contract" page, the user need to choose a "sign via" radio button to select sign type, he should either enter:

  1. Driver's licence and state
  2. SSN

The validation in this logic is depend on what "sign via" he choose. So I did something like this:

validates :signer_driver_lic, :signer_state, presence: true, if: :sign_via_driver_lic?
validates :signer_ssn, presence: true, if: :sign_via_ssn?

I think conditional validations are not used frequently (if so then it's wrong way to use validations), but in some cases it make sense. A simple "if" is better than an extra abstraction (e.g. a different validator object).

darkbaby123 commented 8 years ago

BTW maybe out of the topic, but in these days I'm also thinking about what is a good validation system. I checked Ruby/Node/PHP implementations and I really like Laravel's validation because of 2 reasons:

  1. It separates validation rules and validator. A rule (e.g. required/digits) just validate a value and does not care about what object attribute it applies and how to set error messages. A validator does that.
  2. It's code. So it's flexible to composite different validation rules. I found most of Ruby validation libraries are using DSLs (maybe affected by Rails). It's clean but not flexible enough. For example to support conditional validations they need to create more syntax like if: :some_condition?
jodosha commented 8 years ago

@darkbaby123 I made a mistake by evaluating this feature. My apologize.

Now, we could think of a better design that uses composition rather than a DSL. I still believe that :if/:unless once introduced, will go soon out of hand.

I'm reopening this, and inviting other devs who asked for the same feature, to better explain their needs and discuss together.

darkbaby123 commented 8 years ago

@jodosha Thank you, you're really open minded :smile:

pascalbetz commented 8 years ago

I understand the concern about complexity. And most of the times it can be modeled in a different way. But sometimes the design decision is not up to the developer. So i guess this might be useful.

jodosha commented 8 years ago

@pascalbetz Thank you. In my understanding from chat conversation, you had a concrete use case. Is it correct? Would you love to bring into this thread? Thank you!

solnic commented 8 years ago

:+1: for rethinking validation DSL and reconsidering. I'm absolutely convinced the DSL introduced in Rails was never-ever a good idea. Validations can be very complex, a DSL is limited, trying to make it flexible by introducing tons of options is not the way to go. Which is actually a general issue with any DSL. It's interesting that @darkbaby123 mentioned Laravel's validation with validators and rules, coincidently I've been thinking about a very similar approach, with lightweight validations that work with "rules" (I wanted to call them predicates) that are composable.

Validation DSL is a dead end, especially when you start to introduce more and more options.

I love Jose Valim's quote from one of his elixir talks where he mentioned that he gave a talk in 2010 titled "DSL or NoDSL" and then he says "I can give you a short digest of this talk: "NoDSL". Brilliant and very true.

And I agree. We should have less DSLs, in most cases we shouldn't have them at all.

pascalbetz commented 8 years ago

@jodosha I can not point out one single project. I just had the requirements of conditional validations in various projects.

@solnic i don't know Laravel's validation. Can you explain?

hlegius commented 8 years ago

Spoiler :+1: for predicates within validators :-1: for attribute :bar, if :foo?

Laravel uses php's lambda to build conditional/complex validations, some kind of:

attribute :driver_lic do
  presence: true if age > 18
  size: 14
  type: Integer
end

Those predicates are composed within main attribute and does not rely on DSL's to build it.

solnic commented 8 years ago

@pascalbetz boils down to what @hlegius just wrote :)

I'll be experimenting with a validation library soon, I can share some API ideas. The only reason why I may introduce a DSL is to reduce boilerplate required for composing validations, but I'm strongly against AM::V-like validation DSL, it's convoluted, inflexible and introduces ambiguity in many places which is a nonsense in a validation world (see :allow_blank or :presence validation etc.)

pascalbetz commented 8 years ago

@solnic

let me know when you can share something. You've got me interested:-)

@hlegius thanks.

solnic commented 8 years ago

@pascalbetz will do :) the work will be happening in dry-validation repo (current prototype will be replaced)

darkbaby123 commented 8 years ago

@solnic can you tell me the link for Elixir talk? I also checked how validation works in Phoenix & Ecto, but I think it's not the best choice to use the same thing in Ruby land. But I'm very interested in how Jose think about DSLs.

Share some of my thinking about validation rules and validators:

A validation rule just check if a value is valid and nothing more. It doesn't care how to get the value from an data object, or how to set the error type or error message. It can be a pure function without side effect, so it can be used anywhere (even used standalone).

A validator is something like a form object. It includes how to apply validation rules to a data object's attributes. It gets a data object in, validate it, and set errors object. The errors object is maintained by validator but not data object. A validator's validate process has side effect: set errors object.

I found this kind of thinkings are more appeared in Node.js land, maybe it's because it's super easy to construct a plain object in JavaScript world. I saw an NPM library called composable-validator, but I can not find it now. It has really interesting ideas about how to composite validators.

I think to bring a good validator library, we can get ideas from many other languages (JavaScript, Elixir, PHP, etc.). Because validation issue are very common in programming world, and on the other side, many Ruby's validator libraries are very like Rails ActiveModel validator. That means if you don't agree with Rails way, you don't like the rest either.

solnic commented 8 years ago

OK I'm getting close to wrap up the rewrite of dry-validation. You can check out its DSL right here. It's based on simple (and stateless!) predicate logic and rule composition. Error objects are serializable to an AST for further processing, so various error messages can be produced (this needs a bit more work, I literally started a couple of hours ago).

The huge difference is that you're composing validations from simple predicate rules, they obey predicate logic and it's very precise, you can say that I expect key :email to be present, assuming it is, its value must be a string and it must match this regex. This is something that cannot be easily done with huge DSL with lots of options, as it gets hairy very quickly and it's hard to reason about it.

I'm trying to put huge emphasis on preciseness and simple composition. It's not a good idea to have ambiguity and implicit behavior in a validation library. Next thing is that validation objects receive input that they will validate, rather than making them state-full using the input and making assumptions about things like how a value is being extracted from input. This means that in dry-validation you'll be able to validate plain hashes, plain arrays or objects with attr readers, this includes scenarios where things are deeply nested too.

The DSL is only meant to precisely define expected input structure along with the validation logic. I'm planning more advanced features like defining validation "safe-points" where you'll be able to say that if rule A passed then do this or a system for generating error messages where information "from the outside" is required to produce the full error message to the client (which is often needed).

I'm very happy with the progress so far, I believe it's a good direction, definitely better than heavy DSLs with lots of options (it's an anti-pattern in general, regardless of the type of a library).

EDIT: updated the link to the spec

pascalbetz commented 8 years ago

Looks like the spec mentioned above has moved: https://github.com/dryrb/dry-validation/blob/feature/predicate-set/spec/integration/validation_spec.rb

darkbaby123 commented 8 years ago

Just checked the repo and the spec is moved to https://github.com/dryrb/dry-validation/blob/master/spec/integration/validation_spec.rb

jodosha commented 8 years ago

@darkbaby123 We finally coded a solution for your problem.

Please have a look at: #102 - For that PR I used your examples to implement the high level rules feature. https://github.com/hanami/validations/blob/814233a3a45e3fefe12e3d2f28850e59504a1b76/test/rules_test.rb