[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
[1]
http://hackage.haskell.org/packages/archive/TCache/0.8.0.2/doc/html/Data-TCache.html
-------------- 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