0

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
    }
}
Ross
  • 1,313
  • 4
  • 16
  • 24
WILLIAM WOODMAN
  • 1,185
  • 5
  • 19
  • 36
  • wonder if you tried it as abstract class and whether that would work and keep things the same try declaring it as `abstract class GeoAddress ` – V H Feb 14 '17 at 20:18
  • it surely cant be an abstract class - as i have to create an instance of it to add to the venue constructor. you cant have new () so i don think this works. – WILLIAM WOODMAN Feb 15 '17 at 15:36
  • I don't think you are going to find the answer you are looking for, if you are that concerned about table creation set dbCreate - Whether to auto-generate the database from the domain model - one of 'create-drop', 'create', 'update' or 'validate' i presume update or maybe even validate to not create then run `grails export-schema` and manually create tables yourself – V H Feb 15 '17 at 15:51
  • i looked on the train, i'm using dev in memory H2 db at the mo. heres what i found. Despite the docs suggesting that putting the embedding class in the same groovy file as the parent (ie i define Venue with embedded location, GeoLocation follows the venue definition) this creates a table for GeoLocation. – WILLIAM WOODMAN Feb 16 '17 at 14:26
  • found out why the tests fail. if you add a new GeoLocation in venue constructor and save, the entries are written with the venue as youd expect all in the same table. however if you create the location first - and save it, it writes a row in the GeoLocation table. so when you delete the venue, it (and the embedded location ) goes, however if you saved a location by itself - its written to the GeoLocation table. so the GeoLocation.list() finds an entry and the test fails – WILLIAM WOODMAN Feb 16 '17 at 14:27
  • the only way i could get it not to create a GeoLocation table was to remove the definition from the grails-app/domain folders and create in src/groovy. however you then have code split across two areas for whats part of your domain model. also getting domain validations is now harder as the src file isnt in domain folders and you have have to do more fiddling to try and get the validators to be fired – WILLIAM WOODMAN Feb 16 '17 at 14:29
  • there may be something else to stop this unwanted table creation in the domain folders, but i think the easiest option appears to be quit trying to use embedded - and just live with it as a first class domain and accept the join that goes with that. – WILLIAM WOODMAN Feb 16 '17 at 14:31
  • as i suggested on the previous question just create the entry as `GeoAddress location` this then creates an object reference in the current table for `location_id` and is then accessible through your db query as venue.location.{object_you_require} and all the delete functions work smoothly with no additional work needed. I do this all over the place in my code and works really well – V H Feb 16 '17 at 15:20
  • vahid - but it is already doing that - 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'] – WILLIAM WOODMAN Feb 16 '17 at 16:10
  • accessing wasnt the problem - if a created an address (and saveed) and then passed a reference into venue cosntructor - thats my problem. The first save writes a row to GeoLocation table (which i kind of wasnt expecting to see reading the docs) and then the venue.save() saves the data in the venue table as well - so now deleting the venue did wat it said - but the first location.save() wrote seperate row by itself to GeoLocation table - thats been my issue – WILLIAM WOODMAN Feb 16 '17 at 16:13
  • posted the project here to git https://github.com/woodmawa/coffeeCoffeeShopApp so you can see it as code – WILLIAM WOODMAN Feb 16 '17 at 16:14

3 Answers3

1

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})$/
        }
    }
}
WILLIAM WOODMAN
  • 1,185
  • 5
  • 19
  • 36
0

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

Djamware
  • 126
  • 1
  • 7
0

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.

halfer
  • 19,824
  • 17
  • 99
  • 186
WILLIAM WOODMAN
  • 1,185
  • 5
  • 19
  • 36
  • `the join cost` vs the process to try achieve a similar effect but embedding a domain class still unsure what you feel the cost is if you introduced cache, if you used things such as `String getAddressLine1() { return location.addressLine1 } ` in the `Venue` class for each object you want to have quick access to - then used hql maps to clearly grab specific elements from the venue join location so select new map(v.name, l.addessLine1) from Venue v join location l ` this way costs are at minimal - as i say these type of joins are far too common vs embedded a rare scenario – V H Feb 16 '17 at 18:17
  • may have stressed the wrong concern - i can live with a join cost. my real concern was that having read the docs i'd understand the second class (embedded one) in the same class file would stop the generation of the table, and then i wouldnt have had this issue of an location.save() by itself going into the GeoLocation table, but the location in the venue constructor getting stored properly as embedded in the venue table. This was just too confusing and gets in the way of focusing on the app – WILLIAM WOODMAN Feb 16 '17 at 22:23
  • instead you end up spending the time trying to understand what gorm is doing underneath - i have no objection to doing some learning but this just didnt work as i'd read it and i lost time trying to figure out why. Hence try and keep it as simple as you can and avoid some of the 'nicer' features where it increases confusion for the developer – WILLIAM WOODMAN Feb 16 '17 at 22:25
  • indeed and anyway when you embed the entire object you end up bloating the db and in effect reducing db efficiency / increasing size with duplicate info.. so it is cleaner and overall the cost of joining is so much less than your experiment. But hey it is good to experiment lots of new things are picked up learnt in the processs – V H Feb 16 '17 at 23:03