[web-devel] real Models in Yesod

Greg Weber greg at gregweber.info
Wed Jul 13 08:39:50 CEST 2011

Yesod (and AFAIK other haskell frameworks) do not have strong models. In my
experience, the heart of an individual model is validation logic.
One can achieve validation by writing their own save function, however this
allows multiple possible entries into saving a model because they can write
multiple different save functions.
Real enforcement of a single entry point with triggers is possible in TCache
[1] where triggers are tied into the persistence layer.
The main other issue I see causing weak models is the separation of forms
from models. So I want to propose a design for stronger models and see what
critiques and better ideas there are.

Lets start with a data structure for a model:

Person { personAge :: Int, personSex :: Maybe String }

The main aspect is the addition of triggers, which are callbacks ran before
a save to the database. A trigger is either a validator or a modifier.
A validator is run as a callback before a save to the database.

   myTrigger :: Person -> Either ValidateError Person

A ValidationError will be associated with a particular model, and possibly
with a particular field of that model. We may want a validation errors to
stop any more validations from being ran, or we may want to run all
validations (just as a form returns all errors, not just one).

ValidationError = FieldInvalid EntityField Text
                       | BaseInvalid Text
                       | ShortCircuitValidation ValidationError

verifySex :: Person -> Either ValidationError Person
verifySex p =
  when ((personAge p) > 18 && isNothing (personSex p)) FieldInvalid
PersonSex "is required"
  return p

Note that this is a non-monadic trigger, but triggers do need the ability to
run database queries.
One approach to registering callbacks would be to place them in the schema.

Person triggers=verifySex, trigger2, trigger3
  age Int
  sex String Maybe

So now we can have a save function.

ModelSave val = ModelValid val | ModelInvalid val [ValidationError]
save :: val -> ModelSave val

sav <- savePerson person
case sav of
  ModelValid person ->
  ModelInvalid (person, errors) ->

Note that both Constructors have a person. This is to allow triggers to
modify the person.
Now we need a technique for tieing the model to the form. I will use some
magical functions.

getNewPerson = do
  f <- newPersonForm
  [|hamlet| ^{f} |]

postCreatePerson = do
  res <- saveForm personForm
  case res of
    ModelValid person -> ...
    ModelInvalid (person, errors) -> editPersonFrom person errors

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://www.haskell.org/pipermail/web-devel/attachments/20110712/97516860/attachment.htm>

More information about the web-devel mailing list