Grails command validation
Groovy & Grails April 13th, 2009
I have setup a small example that demonstrates how to use grails validation on non domain objects using the command pattern.
Collecting all required information was not that easy. Forums do not yet cover many G&G related topics, at least if you want to look under the hood of it
In general, my blogs target G&G developers. You should have at least an idea of how to use it, how to setup a project and what a controller is. My code is tested against Grails 1.1
Short summary
We are going to setup a simple controller with two closures. The one we use is responsible for logging a user into any platform.
Our controller uses a command to gather all params and to validate its values.
The controller
First thing you should create is the controller.
grails create-controller my
You might now copy and paste the following code into your controller class.
grails-app/controllers/MyController.groovy class MyController { def index = { } def run = { MyCommand cmd -> if(cmd.hasErrors()){ render(view:"error") }else{ render(view:"ok") } } } class MyCommand { String login String passwd1 String passwd2 static constraints = { login(blank:false, size:5..15, validator: { it=="foobar" }) passwd1(blank:false, validator: { val, obj -> obj.properties['passwd2']==val }) } }
The important closure for the upcoming test is called “run”.
As an argument it takes a command object.
The springframework behind the scenes is taking care, that all request params are properly mapped to the command object (MyCommand).
Furthermore the command object’s constraints are executed prior any other line of code in the “run” closure.
A powerful feature if you ask me :O)
The command class itself is in charge of all the validation work. As outlined in the grails docs (very good ones indeed) all domain oriented constraints
can be applied to non domain validation objects as well.
The contraint:
login(blank:false, size:5..15, validator: { it=="foobar" })
for example adds an error if the param login is null, if its length is smaller 5 chars or greate 15, or if the validator is returning anything else than null.
Adding an error ?
Grails is making extensive use of spring’s error handling. Have a look at their excellent docs.
For each error, an error object is added to the command object. The controller checks for errors in the command validation and can redirect back to the form if needed.
The test class
test/integration/MyControllerTests.groovy import grails.test.* class MyControllerTests extends ControllerUnitTestCase { protected void setUp() { super.setUp() } protected void tearDown() { super.tearDown() } void testValidCommand() { def cmd = new MyCommand(login:"foobar", passwd1:"password", passwd2:"password") def cntr = new MyController() cntr.run(cmd) assert "ok" == cntr.modelAndView.view } void testInvalidCommand() { def cmd = new MyCommand(login:"foobar", passwd1:"password", passwd2:"not-equals") def cntr = new MyController() cntr.run(cmd) //println cmd.errors.allErrors println "field error " + cmd.errors.fieldError assert 'passwd1' == cmd.errors.getFieldError('passwd1').field assert 'validator.invalid' == cmd.errors.getFieldError('passwd1').code } }
The test itself is simple. You have to create a command object that you use as the request object when calling the “run” closure within the controller.
The way I evaluate an error is ok, but not as good as I wanted it to be.
Grails ships with an enhanced way to handle message codes. Instead of letting the developer create a message key for each error, Grails makes strong usage of their convention over configuration approach by automatically assigning errors to dedicated message keys.
The error key for the login:blank constraint would be mapped in grails-app/i18n/messages.properties as:myCommand.login.blank.
Again the time consuming job of assigning messages keys to errors is omitted.
What I was not yet able to achieve is to fetch the associated error key for a given constraint within a test case.
So, what I am looking for is a line like:
assert 'myCommand.login.blank' == cmd.errors.getField('login').???
Well, I hope that this blog helped you a bit. Yo may leave a comment or try to reach my directly using the steademy plugin and invite me (tmaus@steademy.com) from within the firefox plugin.
Have a nice day
Thorsten
Tags: command pattern, grails, validator
Login user against Steademy API
Groovy & Grails March 20th, 2009
Steademy exposes a public API that allows any third party tool to directly connect to steademy.
My first blog outlines the groovy & grails way to login against the steademy platform and to receive a valid sessionKey.
The steademy sessionKey uniquely identifies a user at our platform. You have to deliver it with every consequent request.
We accept simple POST based requests and return JSON strings.
Setup:
You basically need to setup a grails project (grails create-app std), and create your service (grails create-service steademy)
Furthermore the xfire plugin needs to be nstalled into your grails project.
I downloaded release 0.8.1 from here
Adjust config
Getting rid of static properties in our class is our first goal
We therefore add a couple of new lines into grails-conf/Config.groovy
Grails provides a very elegant way to read properties from the config file.
grails-app/conf/Config.groovy std{ msapi{ url = "https://steademy.com login.path="/msapi/Login" } }
SteademyService
Our groovy driven service only needs a couple of code-lines for proper execution.
grails-service/SteademyService.groovy ... class SteademyService implements InitializingBean{ boolean transactional = true def grailsApplication def msapi def crs void afterPropertiesSet(){ msapi = grailsApplication.config.std.msapi crs = grailsApplication.config.std.crs } ... String loginUser(String login, String password){ log.debug "remote login to steademy using [login:${login}] [password:${password}]" def builder = new HTTPBuilder(msapi.url+msapi.login.path) builder.request(POST, JSON){ req -> send URLENC,[login:login, password:password, appId:"12345678", appversion:"1.0"] response.success = {resp,json -> if(json.error){ log.debug "ERROR: ${json.error}" return null } return json.steademy.sessionKey } response.failure = { log.debug "url ${msapi.url}/${msapi.login.path} not valid" return null } } } }
- The service implements the Initalizing Bean. Thus the method afterPropertiesSet is called once all properties have been initialized by spring.
- We directly reference the properties set in the grails-app/conf/Config.groovy file by defining the grailsApplication object. Grails/Spring in the backend takes care for its proper binding. The command grailsApplication.config.std.crs finally reads the corresponding property from with the config file.
The actual login method is quite simple. Due to grails perfect support for JSON and thanks to the http-builder project, some simple lines of code connect to the steademy api and process the user login.
SteademyServiceTest
Lets step forward to our test. If you created the service in the grails way, there should exist a groovy class called. test/integration/SteademyServiceTest.
SteademyServicetest ... def steademyService void testSteademyLogin(){ steademyService.loginUser("foo", "bar") }
Thats it basically.
The next blog will outline a couple of more API calls that allow us to send messages through steademy.
Thorsten Maus
Groovy and Grails : My weekly blog
Groovy & Grails March 15th, 2009
I have started to investigate in Groovy & Grails more by accident than by practical means. A Berlin company was looking for a freelancer with knowhow in G&G that I was not able to deliver.
I consequently used my spare time to dive into G&G for a couple of months.
The upcoming blogs will outline topics that might be interesting for others to follow.
At steademy, we use G&G for chatroom administration and chatroom communication where as the rest of our platform runs on s2ap from springsource.
Thorsten Maus
About