Skip to content

Partial or Custom Entity Validation #70

@dalssoft

Description

@dalssoft

Problem

When defining a entity, the validation rules are defined for all the fields, but sometimes we want to have different validation rules for different scenarios.

Partial or Custom Validation

for this use case we can think in several approaches:

(1) "Manual" Check for error return

Without extra implementation, it would be possible to check the returned error code:

if(!user.isValid()){ 
	if (user.error...) {
		// check if the only error came from `createdAt`, regarding `presence`.
		// we could create a helper function to do that
	}
}

This can be a bit verbose, but feasible.

(2) Validation Ignore or Override - Specific

This would make the caller to be able to ignore or override the validation rules for a specific field.

This is important because the validation are defined in the context of the caller and might be very specific to be generalized.

if(!user.isValid({ ignore: { createdAt: "presence"} })){ 

or

if(!user.isValid({ override: { createdAt: { presence: false } } })){ 

(3) Validation Contexts - Generalization

Let´s say there is a context validation that can be reused in several scenarios. For instance, the dev started with a specific validation for a specific scenario, but then realized that the same validation can be used in other scenarios.

There could have be a extra metadata on the entity for validation scenarios, like:

const User = entity('User', {
	id: id(Number, { presence: true }),
	name: field(String, { presence: true }),
	email: field(String, { presence: true }),
	createdAt: field(Date, { presence: true }),

    // this 
	create: validation({
            override: { createdAt: { presence: false } },
            ignore: { email: "presence" } 
	}),

    // or
    validations: {
        create: {
            override: { createdAt: { presence: false } },
            ignore: { email: "presence" } 
        }
    }    
})

to use it:

if(!user.isValid({ context: 'create' })){ 

Of course, the suggested code and names are just that, suggestions. Ideas on how to improve this are welcome.

(4) Conditional Validation

This is a more complex approach, but it would allow to define a validation rule that would be executed only if a condition is met.

const User = entity('User', {
    id: id(Number, { presence: true }),
    name: field(String, { presence: true }),
    email: field(String, { presence: true }),
    createdAt: field(Date, { presence: true })

    validations: {
        create: {
            condition: (user) => user.createdAt === null, // or a function that returns a boolean
            override: { createdAt: { presence: false } },
        }
    }    
})

To use it:

if(!user.isValid()) { // no need to pass the context here - if the condition is met, the validation will be executed 

I feel this could be polished a bit more. Ideas are welcome.

Error return

The error returned should be the same, regardless of the approach (1), (2), (3) or (4)

user.errors // {"password":[{"cantBeNull":true}]}

Conclusion

I would suggest start with (2), since it can help in many scenarios, and then move to (3) and (4) if needed.

Benchmarks

    class User < ApplicationRecord
        with_options if: :is_admin? do |admin|
            admin.validates :password, length: { minimum: 10 }
            admin.validates :email, presence: true
        end
    end

Reference

This issue is a spin off from #49

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions