Validações concisas para VBA

Algumas coisas são melhores sujas, mas o código não é uma delas.
Recentemente, mergulhei em um antigo banco de dados do Access para ver como as validações de Form foram escritas apenas para encontrar uma bagunça confusa de If..ElseIF … condicionais com loops misturados e instruções Goto que fariam sua cabeça girar. Isso me lembrou da versão Disney de Alice no país das maravilhas, onde o Gato de Cheshire diz: “Algumas pessoas vão para os dois lados”. Depois de seguir a lógica pela toca do coelho, percebi que o Access e o VBA, nesse caso, parecem não ter uma forma concisa de agrupar validações, o que me inspirou a escrever novas validações para qualquer coisa que eu pudesse pensar.

Comecei com algumas validações muito básicas para números e strings, mas como acontece com todo o código (pelo menos para mim), uma vez que o redemoinho comece a girar, você pode muito bem nadar até o fundo. Então pensei comigo mesmo onde estão as validações mais limpas e transparentes dos RAILS! (com preconceito estrito devido ao meu caso de amor com Ruby). Então, parti em uma aventura para criar uma classe de validação de estilo rails para VBA.

Entrar no validador

Validator atua como um mecanismo de validação para aplicativos VBA

A formatação do Validator foi inspirada nas validações Rails e usa o estilo de formatação mais semelhante possível

Deve ser usado em conjunto com ValidatorError, que conterá o nome e a mensagem de erro para cada objeto que falhar na validação

O Validator inclui vários métodos para recuperar esses erros, incluindo

erros – que retorna a coleção completa de erros

error_messages – que retorna apenas as mensagens para objetos que falharam na validação

error_keys – retorna apenas o nome para objetos que falharam na validação

uniq_keys – retorna uma coleção única de erros com base no nome, então apenas a primeira validação com falha para um objeto nomeado será incluída nesta coleção

Uso:

Dim v As New Validator
With v
.validates 123, "Number", numericalityOf:=True, only_integer:=True, greater_than_or_equal_to:=11
.validates "String", "String", stringnessOf:=True, length:=6, contains:="ing", begins_with:="S"
.validates "12345","Number2",numericalityOf:=True, greater_than:=12344, force:=vbInteger, stringnessOf:=True, min_length:=3, begins_with:="1"
End With


If v.is_valid Then
Do Something
Else
Do something Else
End If

Uso alternativo:

   Dim v As New Validator
v
.value = 123
v
.name = "Number"

^^^ is the same as v.validates 123,"Number"

v
.numericality only_integer:=True, greater_than_or_equal_to:=11
v
.stringness length:=6, contains:="ing", begins_with:="S"
v
.dateness

Cada chamada de validação retorna um booleano, portanto, você também pode usar isso em condicionais.

If v.stringness(length:=2) then
Msgbox "String is 2 Characters Long."
Else
Msgbox "String is not 2 Characters Long."
End If

Validação personalizada: se você achar que os métodos embutidos não são suficientes para lidar com a sua validação, ela vem com uma função de validação .custom que permite que você especifique uma instrução booleana com um nome opcional e mensagem de erro

.validates allows you to set a values and an optional name for each validation Options are:

Shared:
allow_null
(Boolean) - returns true for validation even if value is null * Does not apply to presence
other_than
(Variant) - returns true if the value is something other than what is specified * Shared By numericality and dateness
presenceOf
:
returns
true is object has a value
numericalityOf options
:(Default=False)
only_integer
(Boolean) - checks to see if val is a vbInteger
allow_null
(Boolean) - this is the only option that does not require a number as it will return its own value if val is Null
is_equal_to
(Variant) - checks to see if test_val = is_equal_to
greater_than
(Variant) - will check to see if val is greater than this value and return (takes presidence over _or_equal_to)
greater_than_or_equal_to
(Variant) - same a greater than but with an equality check
less_than
(Variant) - will check to see if a value is less than this value and return (takes presidence over _or_equal_to)
less_than_or_equal_to
(Variant) - same as less than with an equality check
odd
(Boolean) - checks to see if val is odd if True
even
(Boolean) - checks to see if a val is even if True
is_type
(VBA.vbVarType) - checks to see if val is of a specific data_type
force
(VBA.VbVarType) - attempts to force test_val to a specified data-type prior to testing
* This value can be retrieved afterwards with .test_value (unless used with stringness strict:=False(Default))

stringnessOf options
: (Default=False)
allow_blank
(Boolean) - this is similar to allow_null only it will fail if the sting is null but pass if it is an empty string ""
length
(Integer) - check if the string is a specified length
min_length
(Integer) - check if the length of a string is greater than or equal to min length
max_length
(Integer) - check is a string is short than or equal to max length
begins_with
(String) - Check is a string begins with a specified string
ends_with
(String) - Check is a string ends with a specified string (can be used with case_sensitive)
contains
(String) - Check is a string contains a specified string (can be used with case_sensitive)
matches
(String) - Check is a string matches a given regex pattern (case sensitive has no effect on this method)
case_sensitive
(Boolean) - to be used in conjunction with begins_with, ends_with, and contains
strict
(Boolean) - if strict it validates test_val as is otherwise it attempts to make it a string before testing
*This value can be retrieved afterwards with .test_value (overrides numericality force)

datenessOf options
:(Default=False)
allow_null
(Boolean) - this is the only option that does not require a date as it will return its own value if test_val is Null
after
(Date) - will check to see if test_val is after this value and return (takes presidence over on_or_)
on_or_after
(Date) - same a after but with an equality check
before
(Date) - will check to see if a value is before this value and return (takes presidence over on_or_)
on_or_before
(Date) - same as before with an equality check

example
: validates 123.45, "Number", [Options]

Então, pude usar isso em conjunto com funções para percorrer a validação nomeada e destacar os campos (Access) ou Células (Excel), adicionar rótulos de erro, comentários e muito mais, tudo em um formato fácil de ler.

Ainda é um trabalho em andamento, mas estou bastante satisfeito com a direção que está tomando. Quem tiver sugestões, fique à vontade. O código também está disponível no meu repositório github