Classes de dados

Afinal de contas, o que é mesmo Classe de Dados? O conceito de Classe de Dados e Hierarquia de Classe será melhor compreendido através de um exemplo.

Pense em Classe de Dados como o campo TIPO de uma tabela chamada ENTIDADE, onde o tipo pode ser: PESSOA_FISICA, PESSOA_JURIDICA, CLIENTE, FORNECEDOR, VENDEDOR, FUNCIONARIO etc. Graficamente seria algo assim:

Data Class

Note que entre os possíveis valores para o campo TIPO há intuitivamente uma hierarquia. Veja:

Class Hierarchy

O conceito de Classe de dados do sistema é bem semelhante ao exemplo apresentado acima, só que o campo TIPO da tabela ENTIDADE se chama CLASSE.

Há no sistema, portanto, uma tabela chamada CLASSE que armazena todas as Classe de Dados. A tabela CLASSE é referenciada em todos as outras tabelas cadastrais por um campo chamado CLASSE ou iClass. Nesta tabela existe um campo chamado MAE que é usado para definir o relacionamento de hierarquia entre as Classes de Dados.

É comum desenvolvedores acharem estranho a seguinte afirmação: “Os registros no sistema são gravados em uma Classe de Dados”. Geralmente eles ficam se perguntando: “Mas os registros não são gravados em tabelas?”.

Bem, o fato é que as duas afirmações estão corretas. Logicamente o sistema grava registros em classes, mas fisicamente, estes registros são gravados em tabelas.

Criando a tabela da agenda de contatos

No exemplo hipotético, criaremos uma classe chamada Contatos que na hierarquia de classes será filha de Raiz\Dados\Cadastrais\Entidades\Pessoas. Para isso basta clicar com o botão direito na classe Pessoa e depois em Insert, na IDE do Engine.

Após clicar Insert, aparecerá uma nova classe chamada Nova Classe <Chave da Classe> a qual o desenvolvedor poderá renomear para Contatos. Como a classe contatos é filha da classe Pessoas ela herda os campos definidos em Pessoas e inclusive o desenvolvedor já poderá ver se há registros na classe Contatos.

Veja que a tabela onde ficam os registros da Classe de Dados Contatos é a tabela ENTIDADE.

Neste ponto você pode estar se perguntando: Como é possível saber qual o nome da tabela que guarda os registro de uma classe que criamos?

Bem, para responder este questionamento é necessário introduzir o conceito de x-class e x-model.

X-Class

Atenção: Os arquivos do tipo x-class são deprecated e não devem ser mais utilizados, prefira utilizar x-model e x-view.

Os X-Classes no sistema são arquivos do tipo application/x-class que possuem a extensão “.ic”. São usados basicamente para configurar o software e definir telas de processo do Framework HTML.

No X-Class são definidos, por exemplo:

  • O nome da tabela que irá armazenar os registros de uma classe;
  • Quais serão os campo da classe;

Como podemos usar o X-Class para configurar a tabela que irá guardar os registros de uma classe? Bem, basta criar um arquivo X-Class dentro da classe em questão e colocar no nome da tabela na propriedade this.tableName. O this no contexto do x-class se refere à própria classe.

Mais de um x-class pode ser definido para uma classe, e a ordem lexicográfica deles importa. Arquivos anteriores podem ter configurações sobrescritas por arquivos posteriores.

Configurações definidas em x-class para classes mãe são herdadas pelas classes filhas.

X-Model

Arquivos x-model são do tipo de arquivo application/x-model e possuem a extensão “.model”. Contém as definições do esquema de uma tabela representada por uma classe, seguindo o conceito de ORM (Object Relational Mapping). Essas definições são responsáveis pela estrutura de tabelas, configurações de campos e regras de negócio que independem da interface.

Existe a divisão de definições e portanto tudo que diz respeito a esquema de tabelas do banco de dados deve estar somente no arquivo x-model.

Como poderemos usar os arquivos de definição para configurar uma tabela que irá guardar os registros de uma classe?

Para isso é necessário criarmos um arquivo x-model dentro da classe em questão e colocar no nome da tabela na propriedade this.tableName.

Deve ser definido também a propriedade this.lookupDisplayFieldName. Apesar do nome dessa propriedade remeter uma definição visual, ela trata o valor que será derivado do registro de uma tabela, ou seja, qualquer consulta utilizando as definições de classe sobre o dado de uma classe A que possui lookup para outra classe B irá retornar o valor do campo de mesmo nome que estiver definido em this.lookupDisplayField para o mesmo registro da tabela da classe B.

Uma prática que deve ser adotada é o uso de Resource Strings na propriedade help, tendo como principal objetivo melhorar o uso da memória, uma vez que a informação só é carregada quando for necessária.

Assim como no x-class, uma configuração definida em um arquivo x-model pode ser sobrescrita por outro x-model que vier depois na ordem lexicográfica.

Para exemplificar este sistema de prioridades dos arquivos, suponhamos que existam os seguintes arquivos numa classe do sistema:

0100 Engine.model 0150 Engine.model 0200 Engine.model

O caso acima mostra que as configurações que forem definidas no arquivo 0200 Engine.model irão sobrescrever as configurações definidas no arquivo 0150 Engine.model, que por sua vez, também sobrescreverão o arquivo 0100 Engine.model. Isso também acontece na hierarquia de classes, ou seja, as definições nas classes filhas sobrescrevem as configurações da classe mãe.

Abaixo está definido o padrão numérico utilizado para os intervalos por tipo de arquivo:

  • x-config - 0000 até 0099.
  • x-model - 0100 até 4999.
  • x-view - 5000 até 8999.
  • definições de configuração para produto custom - 9000 até 9099.
  • definições de modelo para produto custom - 9100 até 9499.
  • definições de visão para produto custom - 9500 até 9999.

Obs: Entende-se por produto custom chaves positivas também.

Abaixo temos um trecho de um arquivo x-model como exemplo:

__includeOnce(-1898144572) /* NetworkUtilities.ijs */

// Definição do nome da tabela que irá guardar os registros da classe Engines e das suas filhas.
this.tableName = 'iHost';
this.upgradeChangesTableStructure = true;
this.cachedData = true;
this.lookupDisplayFieldName = 'iName';

this.on('lookupDisplay', function (evt){
    evt.displayValue = evt.key.iname;
})

// Definição do campo iKey
let fld = this.field('iKey', 'integer');
fld.label = 'Chave';
fld.required = true;
fld.readOnly = true;
fld.order = -1000;
fld.help = $R(-1898140920);

// Definição do campo iClass
fld = this.field('iClass', 'integer');
fld.label = 'Classe';
fld.required = true;
fld.order = 20;
fld.classKey = this.key;
fld.lookupType = LookupType.CLASS;
fld.help = $R(-1898140918);


// Definição do campo iIPAddresses
// Eventos responsáveis pela manipulação e validação dos dados pertencem ao model.
let fld = this.field('iIPAddresses', 'string', 100);
fld.label = 'Endereços IP';
fld.required = true;
fld.order = 50;
fld.on('beforeChange', function (evt){
  const value = evt.newValue;
  if (value) {
    const ar = value.split(",");
    for (let i = 0; i < ar.length; ++i){
      if (ar[i] == 'dhcp' || NetworkUtilities.isIPv4Address(ar[i])){
        ar[i] = ar[i].trim();
      } else {
        throw new Error("O valor \"" + ar[i] + "\" não é um endereço IP válido.");
      }
    }
  evt.newValue = ar.join(',');
}
});
fld.help = $R(-1898140912);

fld = this.field('xServices', 'masterdetail');
fld.order = 500;
fld.label = 'Serviços';
fld.classKey = -1898146242; /* Services */
fld.masterFieldNames = 'iKey';
fld.detailFieldNames = 'iServer';
fld.help = $R(-1898140898);

X-View

Arquivos x-view são do tipo de arquivo application/x-view, possuem a extensão “.view” e são usados para definir a interface com o usuário. Nele temos:

  • As configurações dos campos que irão exibir os dados no WebFramework.
  • As validações de exibição dos dados devem ser feitas no próprio x-view. Como por exemplo a regras de visão que são definidas no evento onCalculate.

Obs: Os arquivos x-view definem uma interface para visualização dos dados. É importante ressaltar que NÃO devem ser inseridas regras de negócio no x-view.

Para exemplificar o uso do arquivo x-view, segue abaixo um arquivo x-view:

this.lookupTableViewWidth = 20;

let fld = this.field('iKey');
fld.visible = false

fld = this.field('iClass');
fld.tableViewWidth = 10;

fld = this.field('iIPAddresses');
fld.tableViewWidth = 20;

fld = this.field('iServices');
fld.detailIndexFieldNames = 'iName';
fld.on('defineGrid', function (evt){
  const grid = evt.field.grid;
  grid.on('defineFields', function (evt){
    let fld = evt.grid.field("iHost");
    fld.visible = false;
  });

  grid.on('afterInsert', function (evt){
    const ds = evt.grid.ds;
    const masterGrid = evt.grid.parent.parent;
    ds.iserver = masterGrid.ds.ikey;
  });
})