Hello,
We have some hacks and plasters on top of standard Seaside(3.1.4)/Magritte(3.3.1), which we use in development on Pharo and in production on GemStone. I think it is hurting us to be so far removed from what everyone else uses and talks about out there. I also think we could add value to the community if, instead of making hasty hacks to get something to work, we provided some feedback about our needs and also contribute some of what we have done. Ever present pressure permitting, of course... This longish post and associated code is an attempt in that direction. Please let us know what you think. The code is at github[1] . I am specifically talking about the packages: - UserErrorSpike-Core - UserErrorSpike-Pharo (not everything will work in Pharo though...) - UserErrorSpike-Tests One of the ways in which I think GemStone can add value to Seaside/Magritte is GemStone's ability to roll back a transaction. Our fundamental use case goes as follows: We have a complex domain, with a lot of behaviour implemented on complex domain objects. We have a lot of validations written for Magritte using addCondition: on MAElementDescriptions or MAContainers. For example: We have a screen where you can capture an investment you want to make. On it, you specify the amount you want to invest, and you pick several funds you want to invest into, specifying how much of your investment amount must go where. A simplified example ([] denotes an input field, <> denotes a button): Investment amount: [ 20000 ] Funds: +-------+------------+--------+--------+ | Fund | Percentage | Amount | | +-------+------------+--------+--------+ | A | [10 %] | 2000 | remove | | B | [50 %] | 10000 | remove | +-------+------------+--------+--------+ <add fund> We have to validate simple things like: - You are only allowed to type a number into Investment amount, and the Percentage column for each fund. And more complex things like: - All the percentages you typed add up to 100% And even more complex things like: - How much you are allowed to invest into which kinds of funds as dictated by local laws. This screen is built on top of a little tree of domain objects, and each of them contains logic to do various related calculations. If you do all of the validations using Magritte conditions, you have a problem: Inside a condition block, you only have the memento, and you cannot call methods on the actual domain object instance involved. Yet, the things we want to validate are quite complicated to calculate, and we really need to be able to implement such calculation on our domain objects themselves. We thought that complex validations would work better if we can execute methods on the underlying domain objects to check them. For this to work, one would need to validate simple user input (ie, did you type only numbers), write the (now valid ito user input) mementos to the domain and THEN have a second step that "validates" the domain according to its own logic. The trouble with this is that at this point you have now already modified the domain... and this will be (database) committed to GemStone by default. We would have liked to commit the mementos to the domain, do validations on the domain, and then roll back the GemStone database transaction if the second validation step fails. At this point we also realised that "valid" means different things to the domain, depending on what you want to do with the domain. As a result, the second step of "domain"validation can just happen inside domain code during a normal action callback linked to a Button, say. For example: InvestmentInstruction>>submit can check that all its parts add up to 100% and are compliant with laws. And if such an action inside the domain picks up a problem, it can just raise an exception. The job then would be to catch this exception, abort just the side-effects of having done that action, redirect the user to stay on the same page/component, but also display the error message like you would a validation error message. This way we can also test domain-level validations as part of domain-only tests, without the need for dealing with UI overheads. The example code here[1] may very likely not be the nicest way to accomplish this... but that is what I could come up with at present. Here's what it does: 1) UserError is the exception you want to raise in the domain. 2) TransactionBoundaryFilter should be added (first) to your application to ensure seaside state is committed before handling the rest of the request. (This allows one to abort deeper in the stack.) 3) AbortableAction is then a wrapper around the block one passes to addValidatedForm: (for example), which takes care of catching any UserErrors and dealing appropriately with transactions in response. It does a little more though: it always validates (as in Magritte condition validate), and then writes mementos to the domain BEFORE the wrapped block is executed. 4) UserErrorExampleApp shows this in action [1] https://github.com/finworks/contrib Regards - Iwan -- Reahl, the Python only web framework: http://www.reahl.org _______________________________________________ Glass mailing list [hidden email] http://lists.gemtalksystems.com/mailman/listinfo/glass |
Free forum by Nabble | Edit this page |