Pragiva — Tecnologia na prática

ModelDef: o modelo de dados de uma rotina MVC

A sequência real de FWFormStruct, MPFormModel e AddFields que monta o modelo — e por que instanciar FWFormModel direto não grava nada.

Protheus Referência técnica 5 min de leitura

Assista o vídeo

Resposta rápida

O que monta
a estrutura de campos (do dicionário), o objeto que guarda valores e decide como grava, e a chave primária.
Sequência real
FWFormStruct(1, cAlias) → MPFormModel():New(cId) → AddFields(...) → SetPrimaryKey(aPK) → Return oModel.
Quem grava
não é ModelDef. É o bCommit que MPFormModel já traz por padrão.
Erro comum
passar a estrutura de view (FWFormStruct(2,...)) para AddFields — o PARAMTYPE rejeita, é erro de tipo.
Onde fica
a Static Function ModelDef(), de nome obrigatório, dentro do fonte MVC.
Consulta rápida5 seções

ModelDef é a segunda das três funções que MenuDef, ModelDef e ViewDef formam num fonte MVC. Ela não desenha nada — isso é trabalho da view. O que ela monta é o contrato de dados: quais campos existem, de onde vêm os valores, o que precisa estar preenchido e o que identifica um registro. PGV010.prw tem o ModelDef mais curto que roda; comparado a ele, o resto deste artigo é só o que falta explicar.

FWFormStruct: a leitura do dicionário

Tudo começa lendo a estrutura da tabela no SX3. É a função mais chamada de todo o MVC — não há ModelDef nem ViewDef que dispense essa leitura — e a assinatura é:

ADVPL / TLPP
FWFormStruct(nType, cAliasSX2, bSX3, lViewUsado)

nType decide o que ela devolve, e não é só um rótulo — muda o tipo do retorno: 1 (modelo) devolve um FWFormModelStruct; 2 (view) devolve um FWFormViewStruct. Em PGV010.prw, ModelDef chama FWFormStruct(1, "Z50") e ViewDef chama FWFormStruct(2, "Z50") — o mesmo alias, duas leituras separadas, cada função para si.

O terceiro parâmetro, bSX3, é um code-block que recebe o nome do campo e devolve lógico — o filtro de quais campos entram na estrutura. A forma real, de um fonte padrão:

ADVPL / TLPP
FWFormStruct(1,'SA1',{|cCampo| AllTrim(cCampo)+"|" $ "A1_FILIAL|A1_COD|A1_LOJA|A1_NOME|"})

Sem esse terceiro argumento — como em PGV010.prw — a estrutura carrega todos os campos do alias que o dicionário expõe.

MPFormModel contra FWFormModel: por que um MVC grava sozinho

Este é o ponto que separa quem só copiou um exemplo de quem entende o que está copiando. As duas classes têm a mesma assinatura de construtor:

ADVPL / TLPP
METHOD New( cID, bPre, bPost, bCommit, bCancel ) CLASS FWFormModel

MPFormModel herda de FWFormModel e redeclara New só para trocar os valores padrão:

ADVPL / TLPP
DEFAULT bPre := {||.T.}
DEFAULT bPost := {|| FWFormCanDel(Self) }
DEFAULT bCommit := {|| FWFormCommit(Self)}
DEFAULT bCancel := {|| FWFormCancel(Self)}

FWFormCommit é o bloco que grava — o mesmo que faria, em ADVPL comum, um RecLock seguido de atribuição de campo a campo e MsUnlock. Instanciar MPFormModel():New(cId) sem mais nada já entrega gravação, cancelamento e pós-validação de exclusão prontos. É por isso que PGV010.prw não tem uma linha de RecLock: o construtor já resolveu isso. Quem instancia FWFormModel direto não herda esse default — sem passar o próprio bCommit, a rotina não grava nada, e o sintoma costuma parecer bug de tela, não de modelo.

A diferença de uso confirma qual é o caminho normal: MPFormModel():New é a escolha default de quase todo ModelDef; FWFormModel():New direto é raro o bastante para merecer justificativa quando aparece — e para lembrar quem o escreveu de passar o próprio bCommit na mão.

AddFields: o formulário de edição por campo

Com a estrutura e o modelo em mãos, o próximo passo é ligar um ao outro:

ADVPL / TLPP
METHOD AddFields(cId, cOwner, oModelStruct, bPre, bPost, bLoad) CLASS FWFormModel

cId é o identificador que o resto do fonte vai usar para se referir a esse submodelo — em PGV010.prw, "Z50MASTER". cOwner é o dono, vazio quando o componente é o principal do modelo. oModelStruct é a estrutura que FWFormStruct(1,...) produziu.

A documentação do método é direta sobre o papel dele: o modelo precisa começar, obrigatoriamente, por um formulário de edição por campo — é AddFields quem cria esse componente. Um cadastro pai-filho ainda vai ter grade depois (AddGrid), mas o primeiro componente do modelo é sempre este.

cOwner só fica vazio no componente principal. Num cadastro pai-filho, o submodelo de grade informa, nesse mesmo parâmetro, qual componente é o dono — e é essa cadeia de donos que a view vai usar depois para ligar cada peça ao lugar certo na tela. ModelDef não decide onde algo aparece; decide apenas que existe e a quem pertence dentro do modelo. Esse encadeamento com uma segunda tabela — AddGrid, SetRelation e a chave do filho no lugar da chave do pai em SetPrimaryKey — é assunto de MVC pai e filho, mais adiante na série; aqui fica só a peça que toda rotina MVC usa, com uma tabela ou com dez.

SetPrimaryKey fecha a parte estrutural, recebendo um array:

ADVPL / TLPP
oModel:SetPrimaryKey({"Z50_FILIAL", "Z50_COD"})

Vale saber que ela nem sempre é obrigatória: no primeiro componente adicionado, AddFields já chama SetDescription e SetPrimaryKey por conta própria, a partir do que o dicionário registra para o alias. Declarar no fonte é explicitar o contrato — e passa a ser necessário quando a chave do modelo não é a da tabela do primeiro componente, caso que aparece no cadastro pai-filho.

SetDescription, no modelo geral e em cada submodelo (oModel:GetModel("Z50MASTER"):SetDescription(...)), é o texto que aparece como título nas telas de edição — não influencia gravação nem validação. GetModel(cId) é como qualquer trecho do fonte volta a alcançar um submodelo já criado, depois que ele foi adicionado.

O que você nunca instancia direto

AddFields cria internamente um submodelo da classe FWFormFieldsModel, e AddGrid um FWFormGridModel — nenhum dos dois devolve esse objeto: o método termina em Return seco, e quem quiser a instância pede depois, com GetModel(cId). Mas você não escreve FWFormFieldsModel():New() em lugar nenhum. Quem cria essas instâncias é o próprio AddFields/AddGrid; o que se instancia à mão é só a estrutura (FWFormStruct) e o modelo (MPFormModel). E não existe classe chamada FWFormFields — o nome de quatro sílabas some no meio: é FWFormFieldsModel.

O ModelDef mais enxuto possível

PGV010.prw, entidade única:

ADVPL / TLPP
Static Function ModelDef()

 Local oModel := Nil
 Local oStruct := FWFormStruct(1, "Z50")

 oModel := MPFormModel():New("PGV010M")
 oModel:AddFields("Z50MASTER", /*cOwner*/, oStruct)

 oModel:SetDescription("Tipos de contrato")
 oModel:GetModel("Z50MASTER"):SetDescription("Dados do tipo de contrato")

 oModel:SetPrimaryKey({"Z50_FILIAL", "Z50_COD"})

Return oModel

Tire as duas linhas de SetDescription — são cosméticas, não estruturais, só o texto que aparece como título da tela — e sobra a forma mínima que qualquer ModelDef de entidade única precisa ter: estrutura, modelo, campos, chave. Quatro linhas de corpo é o piso; o que cresce a partir daí é regra de negócio, nos blocos bPre/bPost, ou uma segunda tabela, com AddGrid — nunca a parte que já está aqui.


Se um modelo seu não grava e o erro não diz onde, fale com a gente.

Referências

  1. TDNtdn.totvs.com

    TDN — FWFormModel
  2. TDNtdn.totvs.com

    TDN — MPFormModel
  3. TDNtdn.totvs.com

    TDN — FWFormStruct

Continue por aqui