grailscommand-objects

Grails: Command objects, view patterns and conditional form fields?


I'm trying to get the usage of Command Objects right. And I've got some questions.

Given a generic domain, for instance:

package com.foo

class Ticket {

  Customer customer
  Product product
  Defect defect
  Solution solution
  String comment

  static constraints = {
    customer nullable:false
    product nullable:false
    defect nullable:true
    solution nullable:true
    comment nullable:true
  }
}

Then, consider the following rules of the Ticket's form:

Now, what I wanna know is:

  1. What would be the best approach to using CommandObject to handle this scenario?

    • 1 single CommandObject for both actions?
    • 1 CommandObject specific for each action?
    • In the case of a single CommandObject, how to prevent users from hacking the program, passing a forbidden parameter, for instance?
  2. What would be the best approach to implementing the form's rules? That is, which field is shown/enabled/disabled in each case.

    • Is there any pattern or recommendation for this case?
    • Should such rules be written in the actual form?
    • Or should the form "ask" somebody? The CommandObject perhaps? Or the Domain instance itself?

For example, consider this form's gist:

<div class="fieldcontain ${hasErrors(bean:ticketInstance, field:'customer', 'error')} required">
    <label for="customer">
        <g:message code="ticket.customer.label" default="Customer" />
        <span class="required-indicator">*</span>
    </label>
    <g:if test="${ticketInstance.id}">
        <span class="label read-only">${ticketInstance.customer.name}</span>
    </g:if>
    <g:else>
        <g:select id="customer" name="customer.id" from="${Customer.list()}" optionKey="id"
                  required="" disabled="" value="${ticketInstance?.customer?.id}" class="many-to-one"/>
    </g:else>
</div>

In this case, there is no much a problem, because the check is rather simple. That is:

<g:if test="${ticketInstance.id}">
...

However, consider a more complex rule. Something like:

<g:if test="${ticketInstance.id && currentUser.granted('SOME_RULE') && ticketInstance.someField != null}">
    ...

And so on.

Now, there are a few problems with this approach:

And thus, I was wondering if there is any pattern or recommendation that can be used to improve these scenarios. That is, something that would encapsulate such a complexity. For instance:

<g:if test="${cmd.customerAllowed}">
...

Where the CommandObject could be something along the lines:

@Validateable
class TicketCreateCommand {

  def currentUser //injected somehow..      

  Customer customer
  Product product
  Defect defect
  String comment

  static constraints = {
      importFrom Ticket
  }

  boolean isNew() {
      true
  }

  boolean isCustomerAllowed() {
    this.new && currentUser.granted('SOME_RULE') && this.someField != null
    //some more rules if necessary..
  }

  boolean isSomeFieldAllowed() {
    //rules for creating
  }
}

And a CommandObject for editing:

@Validateable
class TicketEditCommand {

  def currentUser //injected somehow..      

  Customer customer
  Product product
  Defect defect
  String comment

  static constraints = {
      importFrom Ticket
  }

  boolean isNew() {
      false
  }

  boolean isCustomerAllowed() {
    this.new && currentUser.granted('SOME_RULE') && this.someField != null
    //some more rules if necessary..
  }

  boolean isSomeFieldAllowed() {
    //rules for editing
  }
}

Can a CommndObject hold such responsibilities? If not, is there any other better way to centralise such complexities? Plus, as I've said before, the property customer cannot be set on updating. How to deal with this?

Well, I reckon that this is pretty much it.

I'd appreciate any opinions and suggestions. Any link for some tutorial would be brilliant as well.

PS: For those who wanna have a look, the full project is available on github.


Solution

  • What would be the best approach to using a command object to handle this scenario?

    For the View, Edit and Remove actions a simple ID of the Ticket will be needed. I feel command objects at that stage are overkill.

    As there is logic behind the forms used in Update (edit submit action) and Save (create submit action) you should try have a command object for each.

    My personal preference regarding structure of a command object is to have it reflect the data provided by the form. You can then have a utility method within the command object/controller to construct/get a Ticket from the data provided.

    What would be the best approach to implementing the form's rules?

    The rules for the form can be written in the gsp as a series of conditional statements. If there are large conditional statements, or any which are reused regularly across the application then you could add methods to your command object which centralises this.

    As the command object lays between the view and the controllers I don't see why the form logic cannot be stored here.

    If a field should be disabled then you can trivially add the disabled="disabled" attribute to the form field. Or using a command object method:

    disabled="${cmd.isFooFieldDisabled() ? 'disabled' : ''}"
    

    If the field should be hidden then you can use a hidden input like so, this value will not be visible to the user but will be passed into the command object on form submit.

    <input type="hidden" name="foo" value="${ticket.foo}"/>
    

    I believe there are tags provided by the spring security plugin to show/hide gsp elements depending on the user's role.

    To prevent users passing in invalid data you can validate this in your controller using the following code.

    def foo(FooCommand cmd)
    {
        if(cmd.hasErrors())
        {
            // Handle validation errors
        }
    }
    

    The method hasErrors validates the command object using the constraints you have defined. From this point you can either return an error or recover from the validation error and continue with the action flow.

    If the user alters a hidden form input, E.G., the ID of an object then the controller code should check that the user has permission to edit the object with the given ID. If they do then the security policy hasn't been broken. If not then you can return some error of your choice. See this post regarding protection of hidden fields.