Groovy: Efficiency Oriented Programming
Lecture 11
Master Proteomics & Bioinformatics - University of Geneva
Alexandre Masselot - summer 2011
Agenda

‣ CRUD
‣ Integration tests
‣ Domain relationships
‣ Application configuration
One domain based app ↔ one database
One domain class ↔ one table
One domain bean ↔ one table entry
One bean operations: CRUD
One bean operations: CRUD


         Action

        Create

         Read

        Update

         Delete
One bean operations: CRUD


         Action

        Create

         Read

        Update

         Delete
One bean operations: CRUD


         Action

        Create

         Read

        Update

         Delete
One bean operations: CRUD


         Action

        Create

         Read

        Update

         Delete
One bean operations: CRUD


         Action       SQL    Grails url

        Create      INSERT   create

         Read       SELECT    show

        Update      UPDATE   update

         Delete     DELETE   delete
One bean operations: CRUD


         Action       SQL    Grails url

        Create      INSERT   create

         Read       SELECT    show

        Update      UPDATE   update

         Delete     DELETE   delete
One bean operations: CRUD


         Action       SQL    Grails url

        Create      INSERT   create

         Read       SELECT    show

        Update      UPDATE   update

         Delete     DELETE   delete
Person joe = new Person(params)
   ➙ bean but no database entry creation
joe.save()
     ➙ insertion into table
(only if valid bean - constraints)
Validation

‣ Create only a valid bean
‣ 3 validation ways
Validation

‣ Create only a valid bean
‣ 3 validation ways
‣ Check explicitly for validation
 joe.validate()
Validation

‣ Create only a valid bean
‣ 3 validation ways
‣ Check explicitly for validation
 joe.validate()

‣ Save a catch exception
 joe.save(failOnError:true)
Validation

‣ Create only a valid bean
‣ 3 validation ways
‣ Check explicitly for validation
 joe.validate()

‣ Save a catch exception
 joe.save(failOnError:true)

‣ Save a check for non-null return
 assert joe.save()
Registered bean ⇔ joe.id != null
Reading a bean from the database
joe = Person.get(beanId)
Dynamic finders: retrieve from constraints
Dynamic finders (for single return)

‣ Domain class definition generate static methods
 def p = Person.findByUsername(‘lucky_luke’)
 def p = Person.findByFirstName(‘Lucky’)
 def p = Person.findByFirstNameAndLastName(‘Joe’, ‘Dalton’)
Dynamic finders (for single return)

‣ Domain class definition generate static methods
 def p = Person.findByUsername(‘lucky_luke’)
 def p = Person.findByFirstName(‘Lucky’)
 def p = Person.findByFirstNameAndLastName(‘Joe’, ‘Dalton’)

‣ Multiple results => returns first (sorted on id)
 def p = Person.findByLastName(‘Dalton’)
Dynamic finders (for single return)

‣ Domain class definition generate static methods
 def p = Person.findByUsername(‘lucky_luke’)
 def p = Person.findByFirstName(‘Lucky’)
 def p = Person.findByFirstNameAndLastName(‘Joe’, ‘Dalton’)

‣ Multiple results => returns first (sorted on id)
 def p = Person.findByLastName(‘Dalton’)

‣ findByXxxx efficient with unique:true fields
Update



  ‣ Update: change fields values and save into database
Update



  ‣ Update: change fields values and save into database
  1. modify bean as usual
Update



  ‣ Update: change fields values and save into database
  1. modify bean as usual
  2. validate/save as for creation
joe.delete() removes entry from table
Scaffolded controller hides CRUD operations
Explicit controller

‣ It is possible to generate scaffold controller code
 generate-controller eop.lec11.twitter.Person
Explicit controller

‣ It is possible to generate scaffold controller code
 generate-controller eop.lec11.twitter.Person

‣ PersonController.groovy write operation & test
Explicit controller

‣ It is possible to generate scaffold controller code
 generate-controller eop.lec11.twitter.Person

‣ PersonController.groovy write operation & test
‣ For example, read:
     def show = {
         def personInstance = Person.get(params.id)
         if (!personInstance) {
             flash.message = "${message(code:
 'default.not.found.message', .....)}"
             redirect(action: "list")
         }
         else {
             [personInstance: personInstance]
         }
     }
Time to go back to test!
Unit testing ↔ no dependency
Integration testing ↔ more complex biotope
Integration tests

‣ Resides under test/integration/
PersonIntegrationTests.groovy
Integration tests

‣ Resides under test/integration/
PersonIntegrationTests.groovy

‣ Launched with
test-app -integration
Integration tests

‣ Resides under test/integration/
 PersonIntegrationTests.groovy

‣ Launched with
 test-app -integration

‣ Results:
  - summary on the console output (count success/failures)
  - html files under target/tests-reports/html

  - plain text files under target/tests-reports/html
  - failure summary available
  - stdout/stderr accessible for each test case
Faster grails command

‣ Launch command (<alt><ctrl>G) interactive
Faster grails command

‣ Launch command (<alt><ctrl>G) interactive
‣ On the console, enter command
test-app -integration
Faster grails command

‣ Launch command (<alt><ctrl>G) interactive
‣ On the console, enter command
 test-app -integration

‣ Hit enter to relaunch last command
Faster grails command

‣ Launch command (<alt><ctrl>G) interactive
‣ On the console, enter command
 test-app -integration

‣ Hit enter to relaunch last command
‣ After several commands, PermGenException can occur
  - terminate
  - relaunch interactive
Grails integration testing cons




         ‣ Slower to execute than unit
Grails integration testing cons




         ‣ Slower to execute than unit
         ‣ Test report is not integrated into eclipse
Grails integration testing cons




         ‣ Slower to execute than unit
         ‣ Test report is not integrated into eclipse
         ‣ Use only when unit test not possible
mockDomain: unit testing with domain class
mockDomain

‣ It is possible to make some unit testing with domain
mockDomain

‣ It is possible to make some unit testing with domain
‣ No real database is connected, but a fake layer
mockDomain

‣ It is possible to make some unit testing with domain
‣ No real database is connected, but a fake layer
‣ In each method (not setup())
 mockDomain(Person)
 mockDomain(Person, initialBeanList)
mockDomain

‣ It is possible to make some unit testing with domain
‣ No real database is connected, but a fake layer
‣ In each method (not setup())
 mockDomain(Person)
 mockDomain(Person, initialBeanList)

‣ All single domain CRUD (and more) operations possible
mockDomain example


 void testDelete(){
     //buildDaltonFamily() return a list of 4 Person

 
 mockDomain(Person, buildDaltonFamily())


 
     assert Person.count() == 4

 
     Person p=Person.findByUsername('joe_dalton')

 
     assertNotNull p


 
     p.delete()


   
   // we should only have 3 members left

   
   assert Person.count() == 3

   
   p=Person.findByUsername('joe_dalton')

   
   assertNull p

   }
mockDomain limits



‣ No explicit database operation (hibernate criteria, HQL) are
  possible
mockDomain limits



‣ No explicit database operation (hibernate criteria, HQL) are
  possible
‣ Multiple domain class interaction are fully possible (cf.
  relationships)
mockDomain limits



‣ No explicit database operation (hibernate criteria, HQL) are
  possible
‣ Multiple domain class interaction are fully possible (cf.
  relationships)
‣ Connection with data already entered in a database
Hermit domain not very useful
Need for relationships
Twitter: Person ↔ Message
Message domain

create-domain-class Domain
Message domain

create-domain-class Domain

‣ Just a text (String) and a commiter (Person)
class Message {
    String text
    Person commiter

    static constraints = {
        text(size:1..140, blank:false)
        commiter(nullable:false)
    }
}
Message + Person

‣ Attach two messages to a user
 Person joe=Person.findByUsername('joe_dalton')
 new Message(text:'hello', commiter:joe).save()
 new Message(text:'world', commiter:joe).save()
Message + Person

‣ Attach two messages to a user
 Person joe=Person.findByUsername('joe_dalton')
 new Message(text:'hello', commiter:joe).save()
 new Message(text:'world', commiter:joe).save()

‣ Look for message from joe
 Message.findAllByCommiter(joe)
Message + Person

‣ Attach two messages to a user
 Person joe=Person.findByUsername('joe_dalton')
 new Message(text:'hello', commiter:joe).save()
 new Message(text:'world', commiter:joe).save()

‣ Look for message from joe
 Message.findAllByCommiter(joe)

‣ Not possible to access to message directly from joe bean
  - one solution: explicitly declare setCommiter(Person p) in
    Message.groovy that would add the message to a list in joe;
  - problem for deletion, save inconsistency...
Define a one-to-many relationship
One-to-many relationship



‣Message.groovy
//Person commiter
static belongsTo = [commiter:Person]
One-to-many relationship



‣Message.groovy
//Person commiter
static belongsTo = [commiter:Person]




‣Person.groovy
static hasMany = [messages: Message]
One-to-many relationship                        (cont’d)



‣ Add a message:
joe.addToMessages(new Message(text:‘hello world’)).save()
One-to-many relationship                         (cont’d)



‣ Add a message:
 joe.addToMessages(new Message(text:‘hello world’)).save()

‣ Will execute the following actions
  - create a message with joe as commiter
  - save the message
  - add the message to joe’s list
One-to-many relationship   (cont’d)

‣ Access to the list
 joe.messages
One-to-many relationship                                     (cont’d)

‣ Access to the list
 joe.messages

‣ Deleting will cascade
 joe.delete()
  - all messages with joe as commiter will also be deleted
Testing

‣ Test database consistency with two domain: integration testing
     // taken from MessageIntegrationTests.groovy
     // 4 Person are added in the setup() method
 
   public void testListMessagesUserDeletion(){
 
   
 Person joe=Person.findByUsername('joe_dalton')
 
   
 Person averell=Person.findByUsername('averell_dalton')
 
   
 
   
 joe.addToMessages(new Message(text:'hello world')).save()
 
   
 joe.addToMessages(new Message(text:'i'm running')).save()
 
   
 averell.addToMessages(new Message(text:'i'm eating')).save()
 
   
 
   
 assert Message.count() == 3
 
   
 assert Person.count() == 4
 
   
 
   
 joe.delete()
 
   
 assert Person.count() == 3

 
 
    //having deleted joe should delete all message related to joe
 
 
    assert Message.count() == 1
 
 }
Back to the web: 2 scaffolded controllers
groovy & grails - lecture 11
groovy & grails - lecture 11
groovy & grails - lecture 11

groovy & grails - lecture 11

  • 1.
    Groovy: Efficiency OrientedProgramming Lecture 11 Master Proteomics & Bioinformatics - University of Geneva Alexandre Masselot - summer 2011
  • 2.
    Agenda ‣ CRUD ‣ Integrationtests ‣ Domain relationships ‣ Application configuration
  • 4.
    One domain basedapp ↔ one database
  • 5.
    One domain class↔ one table
  • 6.
    One domain bean↔ one table entry
  • 7.
  • 8.
    One bean operations:CRUD Action Create Read Update Delete
  • 9.
    One bean operations:CRUD Action Create Read Update Delete
  • 10.
    One bean operations:CRUD Action Create Read Update Delete
  • 11.
    One bean operations:CRUD Action Create Read Update Delete
  • 12.
    One bean operations:CRUD Action SQL Grails url Create INSERT create Read SELECT show Update UPDATE update Delete DELETE delete
  • 13.
    One bean operations:CRUD Action SQL Grails url Create INSERT create Read SELECT show Update UPDATE update Delete DELETE delete
  • 14.
    One bean operations:CRUD Action SQL Grails url Create INSERT create Read SELECT show Update UPDATE update Delete DELETE delete
  • 15.
    Person joe =new Person(params) ➙ bean but no database entry creation
  • 16.
    joe.save() ➙ insertion into table (only if valid bean - constraints)
  • 17.
    Validation ‣ Create onlya valid bean ‣ 3 validation ways
  • 18.
    Validation ‣ Create onlya valid bean ‣ 3 validation ways ‣ Check explicitly for validation joe.validate()
  • 19.
    Validation ‣ Create onlya valid bean ‣ 3 validation ways ‣ Check explicitly for validation joe.validate() ‣ Save a catch exception joe.save(failOnError:true)
  • 20.
    Validation ‣ Create onlya valid bean ‣ 3 validation ways ‣ Check explicitly for validation joe.validate() ‣ Save a catch exception joe.save(failOnError:true) ‣ Save a check for non-null return assert joe.save()
  • 21.
    Registered bean ⇔joe.id != null
  • 22.
    Reading a beanfrom the database
  • 23.
  • 24.
    Dynamic finders: retrievefrom constraints
  • 25.
    Dynamic finders (forsingle return) ‣ Domain class definition generate static methods def p = Person.findByUsername(‘lucky_luke’) def p = Person.findByFirstName(‘Lucky’) def p = Person.findByFirstNameAndLastName(‘Joe’, ‘Dalton’)
  • 26.
    Dynamic finders (forsingle return) ‣ Domain class definition generate static methods def p = Person.findByUsername(‘lucky_luke’) def p = Person.findByFirstName(‘Lucky’) def p = Person.findByFirstNameAndLastName(‘Joe’, ‘Dalton’) ‣ Multiple results => returns first (sorted on id) def p = Person.findByLastName(‘Dalton’)
  • 27.
    Dynamic finders (forsingle return) ‣ Domain class definition generate static methods def p = Person.findByUsername(‘lucky_luke’) def p = Person.findByFirstName(‘Lucky’) def p = Person.findByFirstNameAndLastName(‘Joe’, ‘Dalton’) ‣ Multiple results => returns first (sorted on id) def p = Person.findByLastName(‘Dalton’) ‣ findByXxxx efficient with unique:true fields
  • 28.
    Update ‣Update: change fields values and save into database
  • 29.
    Update ‣Update: change fields values and save into database 1. modify bean as usual
  • 30.
    Update ‣Update: change fields values and save into database 1. modify bean as usual 2. validate/save as for creation
  • 31.
  • 32.
  • 33.
    Explicit controller ‣ Itis possible to generate scaffold controller code generate-controller eop.lec11.twitter.Person
  • 34.
    Explicit controller ‣ Itis possible to generate scaffold controller code generate-controller eop.lec11.twitter.Person ‣ PersonController.groovy write operation & test
  • 35.
    Explicit controller ‣ Itis possible to generate scaffold controller code generate-controller eop.lec11.twitter.Person ‣ PersonController.groovy write operation & test ‣ For example, read: def show = { def personInstance = Person.get(params.id) if (!personInstance) { flash.message = "${message(code: 'default.not.found.message', .....)}" redirect(action: "list") } else { [personInstance: personInstance] } }
  • 36.
    Time to goback to test!
  • 37.
    Unit testing ↔no dependency
  • 38.
    Integration testing ↔more complex biotope
  • 39.
    Integration tests ‣ Residesunder test/integration/ PersonIntegrationTests.groovy
  • 40.
    Integration tests ‣ Residesunder test/integration/ PersonIntegrationTests.groovy ‣ Launched with test-app -integration
  • 41.
    Integration tests ‣ Residesunder test/integration/ PersonIntegrationTests.groovy ‣ Launched with test-app -integration ‣ Results: - summary on the console output (count success/failures) - html files under target/tests-reports/html - plain text files under target/tests-reports/html - failure summary available - stdout/stderr accessible for each test case
  • 42.
    Faster grails command ‣Launch command (<alt><ctrl>G) interactive
  • 43.
    Faster grails command ‣Launch command (<alt><ctrl>G) interactive ‣ On the console, enter command test-app -integration
  • 44.
    Faster grails command ‣Launch command (<alt><ctrl>G) interactive ‣ On the console, enter command test-app -integration ‣ Hit enter to relaunch last command
  • 45.
    Faster grails command ‣Launch command (<alt><ctrl>G) interactive ‣ On the console, enter command test-app -integration ‣ Hit enter to relaunch last command ‣ After several commands, PermGenException can occur - terminate - relaunch interactive
  • 46.
    Grails integration testingcons ‣ Slower to execute than unit
  • 47.
    Grails integration testingcons ‣ Slower to execute than unit ‣ Test report is not integrated into eclipse
  • 48.
    Grails integration testingcons ‣ Slower to execute than unit ‣ Test report is not integrated into eclipse ‣ Use only when unit test not possible
  • 49.
    mockDomain: unit testingwith domain class
  • 50.
    mockDomain ‣ It ispossible to make some unit testing with domain
  • 51.
    mockDomain ‣ It ispossible to make some unit testing with domain ‣ No real database is connected, but a fake layer
  • 52.
    mockDomain ‣ It ispossible to make some unit testing with domain ‣ No real database is connected, but a fake layer ‣ In each method (not setup()) mockDomain(Person) mockDomain(Person, initialBeanList)
  • 53.
    mockDomain ‣ It ispossible to make some unit testing with domain ‣ No real database is connected, but a fake layer ‣ In each method (not setup()) mockDomain(Person) mockDomain(Person, initialBeanList) ‣ All single domain CRUD (and more) operations possible
  • 54.
    mockDomain example voidtestDelete(){ //buildDaltonFamily() return a list of 4 Person mockDomain(Person, buildDaltonFamily()) assert Person.count() == 4 Person p=Person.findByUsername('joe_dalton') assertNotNull p p.delete() // we should only have 3 members left assert Person.count() == 3 p=Person.findByUsername('joe_dalton') assertNull p }
  • 55.
    mockDomain limits ‣ Noexplicit database operation (hibernate criteria, HQL) are possible
  • 56.
    mockDomain limits ‣ Noexplicit database operation (hibernate criteria, HQL) are possible ‣ Multiple domain class interaction are fully possible (cf. relationships)
  • 57.
    mockDomain limits ‣ Noexplicit database operation (hibernate criteria, HQL) are possible ‣ Multiple domain class interaction are fully possible (cf. relationships) ‣ Connection with data already entered in a database
  • 58.
    Hermit domain notvery useful
  • 59.
  • 60.
  • 61.
    Message domain create-domain-class Domain ‣Just a text (String) and a commiter (Person) class Message { String text Person commiter static constraints = { text(size:1..140, blank:false) commiter(nullable:false) } }
  • 62.
    Message + Person ‣Attach two messages to a user Person joe=Person.findByUsername('joe_dalton') new Message(text:'hello', commiter:joe).save() new Message(text:'world', commiter:joe).save()
  • 63.
    Message + Person ‣Attach two messages to a user Person joe=Person.findByUsername('joe_dalton') new Message(text:'hello', commiter:joe).save() new Message(text:'world', commiter:joe).save() ‣ Look for message from joe Message.findAllByCommiter(joe)
  • 64.
    Message + Person ‣Attach two messages to a user Person joe=Person.findByUsername('joe_dalton') new Message(text:'hello', commiter:joe).save() new Message(text:'world', commiter:joe).save() ‣ Look for message from joe Message.findAllByCommiter(joe) ‣ Not possible to access to message directly from joe bean - one solution: explicitly declare setCommiter(Person p) in Message.groovy that would add the message to a list in joe; - problem for deletion, save inconsistency...
  • 65.
  • 66.
  • 67.
    One-to-many relationship ‣Message.groovy //Person commiter staticbelongsTo = [commiter:Person] ‣Person.groovy static hasMany = [messages: Message]
  • 68.
    One-to-many relationship (cont’d) ‣ Add a message: joe.addToMessages(new Message(text:‘hello world’)).save()
  • 69.
    One-to-many relationship (cont’d) ‣ Add a message: joe.addToMessages(new Message(text:‘hello world’)).save() ‣ Will execute the following actions - create a message with joe as commiter - save the message - add the message to joe’s list
  • 70.
    One-to-many relationship (cont’d) ‣ Access to the list joe.messages
  • 71.
    One-to-many relationship (cont’d) ‣ Access to the list joe.messages ‣ Deleting will cascade joe.delete() - all messages with joe as commiter will also be deleted
  • 72.
    Testing ‣ Test databaseconsistency with two domain: integration testing // taken from MessageIntegrationTests.groovy // 4 Person are added in the setup() method public void testListMessagesUserDeletion(){ Person joe=Person.findByUsername('joe_dalton') Person averell=Person.findByUsername('averell_dalton') joe.addToMessages(new Message(text:'hello world')).save() joe.addToMessages(new Message(text:'i'm running')).save() averell.addToMessages(new Message(text:'i'm eating')).save() assert Message.count() == 3 assert Person.count() == 4 joe.delete() assert Person.count() == 3 //having deleted joe should delete all message related to joe assert Message.count() == 1 }
  • 73.
    Back to theweb: 2 scaffolded controllers

Editor's Notes

  • #2 \n
  • #3 \n
  • #4 \n
  • #5 run-app generate the database\n
  • #6 Domain bean class generate a table from the member fields\n
  • #7 map one object &lt;-&gt; SQL statements\n
  • #8 \n
  • #9 \n
  • #10 \n
  • #11 \n
  • #12 \n
  • #13 \n
  • #14 \n
  • #15 \n
  • #16 \n
  • #17 \n
  • #18 \n
  • #19 \n
  • #20 \n
  • #21 \n
  • #22 \n
  • #23 \n
  • #24 id property is added by default to domain beans\nsetting the bean joe.id=123 is dangerous\n
  • #25 \n
  • #26 necessitate to know the beanId value\ntypically used with url show/id\n
  • #27 \n
  • #28 possible to configure sort on other condition in domain class definition\nunique:true =&gt; table index\n
  • #29 possible to configure sort on other condition in domain class definition\nunique:true =&gt; table index\n
  • #30 possible to configure sort on other condition in domain class definition\nunique:true =&gt; table index\n
  • #31 if not valid =&gt; bean remains the same in the database\n
  • #32 if not valid =&gt; bean remains the same in the database\n
  • #33 if not valid =&gt; bean remains the same in the database\n
  • #34 \n
  • #35 \n
  • #36 do not call generate-controller too early in development phase\n
  • #37 do not call generate-controller too early in development phase\n
  • #38 do not call generate-controller too early in development phase\n
  • #39 \n
  • #40 \n
  • #41 database\nsame situation ~ than with run-app\n
  • #42 PersonIntegrationTests instead PersonTests to avoid class name conflict\n
  • #43 PersonIntegrationTests instead PersonTests to avoid class name conflict\n
  • #44 PersonIntegrationTests instead PersonTests to avoid class name conflict\n
  • #45 \n
  • #46 \n
  • #47 \n
  • #48 \n
  • #49 \n
  • #50 \n
  • #51 \n
  • #52 \n
  • #53 \n
  • #54 \n
  • #55 \n
  • #56 \n
  • #57 see error groovy 1.6 on the storyboard\n
  • #58 \n
  • #59 \n
  • #60 \n
  • #61 \n
  • #62 \n
  • #63 This is not the correct way!\n
  • #64 This is not the correct way!\n
  • #65 \n
  • #66 \n
  • #67 \n
  • #68 grails will provide the mechanism for consistency\n
  • #69 \n
  • #70 \n
  • #71 addToMessages is implicitely created\n
  • #72 addToMessages is implicitely created\n
  • #73 \n
  • #74 \n
  • #75 \n
  • #76 \n
  • #77 \n
  • #78 \n