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. É obCommitqueMPFormModeljá traz por padrão. - Erro comum
- passar a estrutura de view (
FWFormStruct(2,...)) paraAddFields— oPARAMTYPErejeita, é 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 é:
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:
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:
METHOD New( cID, bPre, bPost, bCommit, bCancel ) CLASS FWFormModelMPFormModel herda de FWFormModel e redeclara New só para trocar os
valores padrão:
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:
METHOD AddFields(cId, cOwner, oModelStruct, bPre, bPost, bLoad) CLASS FWFormModelcId é 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:
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:
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 oModelTire 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
TDNtdn.totvs.com
TDN — FWFormModelTDNtdn.totvs.com
TDN — MPFormModelTDNtdn.totvs.com
TDN — FWFormStruct
Continue por aqui
- Vídeo
ProtheusGuia de processo
MVC no Protheus: o que é e por que ele existe
MVC separa modelo, tela e menu, e é por isso que ele grava sem RecLock. O esqueleto, o nome obrigatório de cada função e o que o framework custa em troca.