WILLIAM WOODMAN
WILLIAM WOODMAN

Reputation: 1231

grails v3.2.6 /gorm embedded class in same domain class.groovy creates dummy DB table

in the grails/gorms docs it says you can put the embedded class in the same domain class file as the top level parent domain class - this works from code perspective but it still generates a GeoAddress table, as well as embedding the columns into the source Venue table. test data is input in venue - geoAddress table stays empty.

documentation implies this embedded table shouldnt be generated. I can try and move the GeoAddress into its own src/groovy file, so its out of the grails-app/domain folder, but then i have to 'remember i have done this'. it would be much 'cleaner' to keep in same file as the containing class.

other than promoting the GeoAddress back to full domain class in its own right - how can i tell gorm not to generate the table for it when its use is embedded ?

my venue.groovy in grails-app/domain folder

class Venue {

    String name
    LocalDate dateCreated
    LocalDate lastVisited
    LocalDate lastUpdated
    GeoAddress location
    Collection posts


    static hasMany = [posts:Post]           
    static embedded =['location']

    static constraints = {
        lastVisited nullable:true
        location    nullable:true, unique:true
        posts       nullable:true
    }
    static mapping = {
        location cascade: "all-delete-orphan", lazy:false, unique:true 
         posts    sorted: "desc", cascade:"save-update"


    }
}


class   GeoAddress {

    String addressLine1
    String addressLine2
    String addressLine3
    String town
    String county
    String country = "UK"
    String postcode

    //adds addTo/removeFrom methods to venue
    static belongsTo = Venue

    static constraints = {
        addressLine1 nullable:true
        addressLine2 nullable:true
        addressLine3 nullable:true
        town         nullable:true
        county       nullable:true
        country      nullable:true
        postcode     nullable:true
    }
}

Upvotes: 0

Views: 626

Answers (3)

WILLIAM WOODMAN
WILLIAM WOODMAN

Reputation: 1231

I appear to have put postings on what is essentially same problem - careless. See also Can't get cascade save nor delete on embedded class ref to work

However to reprise: having the embedded class sitting below the main class in the grails-app/domain folders does not stop the embedded class from being generated (as it implies in the docs).

So Gorm goes ahead and creates you a Venue and a GeoLocation (my example here). However the usage is not what I expected either, which is why the test failed.

If you add the new GeoLocation as constructor arg to the venue, the transient instance is saved in the venue table in the embedded location columns in same table. If you delete the venue the entire row is deleted from venue table.

However, if you create a GeoLoaction first and save() it - it's actually stored in the GeoLocation table instead. Then adding it to the venue updates the columns the venue tables.

So when the test does a GeoLocation.list()/get(), it finds the row in that table rather than looking in the venue embedded columns.

I can't find a nice way out of this. The only way to stop geoLocation going into the DB is to move the the definition out of domain folders and put into src/groovy. But then your domain model is split across two subtrees, and getting the gorm validations to fire outside of the domains folder is more fiddly to achieve.

I think I have just had to accept using GeoLocation as full domain class rather than use embedded - and accept the join cost. At least that way the model/tests work as you might expect.

The outcome is: I am not sure embedded was a wise modelling decision and I'd recommend not using unless you really have to.

Upvotes: 0

WILLIAM WOODMAN
WILLIAM WOODMAN

Reputation: 1231

ok - i did find a way to use the geoLocation as embedded and stop it creating a table .

I had to setup GeoLoction as static inner class in side venue. When you do that and run the app you dont get the extra spurious domain table in in the DB. It has to be static inner class as the GeoLocation has a static declaration for constraints.

I'm not sure if this is a bug or what - but the grails documentation is incorrect in the suggestion in section 5.2 - composition in gorm, which says

If you define the Address class in a separate Groovy file in the grails-app/domain directory you will also get an address table. If you don’t want this to happen use Groovy’s ability to define multiple classes per file and include the Address class below the Person class in the grails-app/domain/Person.groovy file

modified class shown below. when you want to create one of these the format (for bootstrap/tests ) would look like new Venue.GeoLocation (...)

class Venue implements Serializable {

    String name
    LocalDate dateCreated
    LocalDate lastVisited
    LocalDate lastUpdated
    GeoAddress location
    Collection posts

    //static hasOne = [location:GeoAddress]   //, temp:TempLocation
    static hasMany = [posts:Post]           //but doesn't really own thats with user
    static embedded =['location']

    static constraints = {
        lastVisited nullable:true
        location    nullable:true, unique:true
        posts       nullable:true
    }
    static mapping = {
        location cascade: "all-delete-orphan", lazy:false, unique:true  //eager fetch strategy
        posts    sorted: "desc", cascade:"save-update"

        //comment out for now
        //posts joinTable: [name:"venue_posts", key:"venue_id", column:"posts", type:Post]
    }

    static class GeoAddress {

        String addressLine1
        String addressLine2
        String addressLine3
        String town
        String county
        String country = "UK"
        String postcode

        //adds addTo/removeFrom methods to venue
        static belongsTo = Venue

        static constraints = {
            addressLine1 nullable:true
            addressLine2 nullable:true
            addressLine3 nullable:true
            town         nullable:true
            county       nullable:true
            country      nullable:true
            postcode     nullable:true,
                    matches: /^([gG][iI][rR] {0,}0[aA]{2})|([a-pr-uwyzA-PR-UWYZ](([0-9](([0-9]|[a-hjkstuwA-HJKSTUW])?)?)|([a-hk-yA-HK-Y][0-9]([0-9]|[abehmnprvwxyABEHMNPRVWXY])?)) ?[0-9][abd-hjlnp-uw-zABD-HJLNP-UW-Z]{2})$/
        }
    }
}

Upvotes: 1

Djamware
Djamware

Reputation: 126

Grails table generation base on Class name, try put embedded as Collection rather than Class.

Upvotes: 0

Related Questions