Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Grails dynamic scaffold with hasMany: is it a bug or am I misconfiguring?

I'm a Grails noob and running into something that seems to be a bug, but it is entirely possible I'm not configuring everything correctly.

I've got two simple Domain Classes:

   class Player {

        String firstName
        String lastName

        static constraints = {
            firstName(blank:false)
            lastName(blank:false)
        }
        String toString() { lastName + ", " + firstName }
    }

and

class Team {

    String mascot;
    static hasMany = [players:Player]

    static constraints = {
        mascot(blank:false)
    }
}

I have controllers for both that do nothing beyond dynamic scaffold these two Domain Classes.

But even when I have a list of Players in my DB, I don't get a multi-select box for them when creating a new Team.

However, the multi-select shows up when I go to edit a Team

Is this a bug in the dynamic scaffolding for new items, do I misunderstand how this is supposed to work, or is there something else I need to declare here?

Any help is hugely appreciated! I've got screenshots that StackOverflow won't let me add because of my newness, but I'd be happy to show them another way if that'll help.

like image 970
Pete Avatar asked May 05 '09 03:05

Pete


2 Answers

I finally figured this out and wanted to pass on what I did just in case someone else runs into it.

When I generated the views for Team, the form block in edit.gsp looks like this:

    <input type="hidden" name="id" value="${teamInstance?.id}" />
                <input type="hidden" name="version" value="${teamInstance?.version}" />
                <div class="dialog">
                    <table>
                        <tbody>

                            <tr class="prop">
                                <td valign="top" class="name">
                                    <label for="mascot">Mascot:</label>
                                </td>
                                <td valign="top" class="value ${hasErrors(bean:teamInstance,field:'mascot','errors')}">
                                    <input type="text" id="mascot" name="mascot" value="${fieldValue(bean:teamInstance,field:'mascot')}"/>
                                </td>
                            </tr> 

                            <tr class="prop">
                                <td valign="top" class="name">
                                    <label for="players">Players:</label>
                                </td>
                                <td valign="top" class="value ${hasErrors(bean:teamInstance,field:'players','errors')}">
                                    <g:select name="players"
from="${Player.list()}"
size="5" multiple="yes" optionKey="id"
value="${teamInstance?.players}" />

                                </td>
                            </tr> 

                        </tbody>
                    </table>
                </div>
                <div class="buttons">
                    <span class="button"><g:actionSubmit class="save" value="Update" /></span>
                    <span class="button"><g:actionSubmit class="delete" onclick="return confirm('Are you sure?');" value="Delete" /></span>
                </div>
            </g:form>

but the form block in create.gsp looks like this:

<g:form action="save" method="post" >
                <div class="dialog">
                    <table>
                        <tbody>

                            <tr class="prop">
                                <td valign="top" class="name">
                                    <label for="mascot">Mascot:</label>
                                </td>
                                <td valign="top" class="value ${hasErrors(bean:teamInstance,field:'mascot','errors')}">
                                    <input type="text" id="mascot" name="mascot" value="${fieldValue(bean:teamInstance,field:'mascot')}"/>
                                </td>
                            </tr> 

                        </tbody>
                    </table>
                </div>
                <div class="buttons">
                    <span class="button"><input class="save" type="submit" value="Create" /></span>
                </div>
        </g:form>

In other words, for this corner case, the default Create view omits the widget to properly display the multi-select list. When I did a copy and paste of the missing code, the dynamically scaffolded controller picked it up and persisted it as expected. So, it's definitely a bug in the view generation code.

like image 59
Pete Avatar answered Sep 23 '22 08:09

Pete


Yes, the default scaffolding puts a parent selector in the child class' create/edit page.

I'm guessing it was just easier for them this way. It shouldn't be a multi-select though, just a pull-down single-select, as it's a One-to-Many.

As you've explained you wanted more of a Many-to-Many relationship, you might try adding:

static hasMany = [teams:Team]

to your Player class. I've found that Grails does better with bi-directional relationships. It's also useful to have when building search queries, and shouldn't require more than the one relationship table you'd already need.

If you're using Grails pre-v1.1, Many-to-Many relationships aren't directly supported, so even adding the static hasMany above won't be the complete solution, as you'll need to manage adding to the other list when you add to one direction. I haven't used v1.1 yet, so I can't talk about what is needed to specify the Many-to-Many in it.

like image 20
billjamesdev Avatar answered Sep 22 '22 08:09

billjamesdev