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:
Note que entre os possíveis valores para o campo TIPO há intuitivamente uma hierarquia. Veja:
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 ? DBKey.from(evt.key).str('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;
});
})