Building Grails Plugins
     tips and tricks from the wild
About Me
•   Mike Hugo, Independent Software Developer

    •   http://piragua.com

    •   http://WhenWorksForYou.com

• ...
• Plugins overview
                                  • Build your own plugin
                                   • testing
...
App                                               Plugin




a plugin is just another grails application, with a few extra...
Cool hooks into the runtime environment of a running grails app, plus ability to add new
‘artefacts’ and participate in re...
Existing Plugins




there are a ton of existing plugins from security to Rich UI to searching to...you name it.
common problem - this is one i’ve solved about 6 times. time for a plugin
• grails create-plugin build-status
• cd build-status
• mkdir test/projects
• cd test/projects
• grails create-app statusa...
development
• make a change to the plugin
• grails package-plugin
• cd test/projects/statusapp/
• grails install-plugin .....
In Place Plugins
• In the application just created, modify
   BuildConfig.groovy and add:
• grails.plugin.location."build-s...
Add Controller (test)
import grails.test.*

class BuildInfoControllerTests extends ControllerUnitTestCase {
    void testI...
Add Controller

class BuildInfoController {

!   static final List infoProperties = ['app.version']

     def index = {
! ...
Add the View
<html>
<head>
    <title>Build Info</title>
</head>
<body>
<div>
    <g:each in="${buildInfoProperties}" var=...
Functional Testing
• in the app, install the functional test plugin
• grails create-functional-test
  class BuildInfoPageF...
Introduce i18n
• i18n allows a form of customization
• create a messages.properties in the plugin
  i18n directory for def...
Introducing Config
        • Allow the client application the ability to
            add or remove properties to display

v...
Modularizing Views
      • Put contents of views into templates
      • Allows client to override the default view
//view ...
Events
         • We want to capture the start of WAR file
             being built and log the Date/Time
         • What e...
test it
            • grails.test.AbstractCliTestCase
            • Thank you Peter Ledbrook:
                http://www.c...
import grails.test.AbstractCliTestCase
import java.util.zip.ZipFile

class CreateWarEventTests extends AbstractCliTestCase...
eventCreateWarStart = {warname, stagingDir ->
    Ant.propertyfile(file:
      "${stagingDir}/WEB-INF/classes/application....
return the favor
       • publish your own events to allow clients
           to hook into plugin workflow

eventCreateWarS...
Gradle Build
• http://adhockery.blogspot.com/2010/01/
  gradle-build-for-grails-plugins-with.html
• http://www.cacoethes.c...
in-place plugin caveats
•   Reloading plugin artifacts doesn’t always work

•   Grails 1.1.1

    •   see next slide

•   ...
def watchedResources = ["file:${getPluginLocation()}/web-app/**",
        "file:${getPluginLocation()}/grails-app/controll...
the real deal




           http://plugins.grails.org/grails-build-info/trunk/
source code available in the grails plugin...
Upcoming SlideShare
Loading in...5
×

Building Grails Plugins - Tips And Tricks

12,835

Published on

Published in: Technology, Art & Photos

Transcript of "Building Grails Plugins - Tips And Tricks"

  1. 1. Building Grails Plugins tips and tricks from the wild
  2. 2. About Me • Mike Hugo, Independent Software Developer • http://piragua.com • http://WhenWorksForYou.com • Groovy/Grails since 2007 • Author of several Grails plugins • code-coverage • greenmail • hibernate-stats • build-info • test-template • ????
  3. 3. • Plugins overview • Build your own plugin • testing • modularization • configuration • events • did I mention testing? what’s on the menu for tonight?
  4. 4. App Plugin a plugin is just another grails application, with a few extra files * GrailsPlugin.groovy * Install, Uninstall and Upgrade scripts
  5. 5. Cool hooks into the runtime environment of a running grails app, plus ability to add new ‘artefacts’ and participate in reloading events
  6. 6. Existing Plugins there are a ton of existing plugins from security to Rich UI to searching to...you name it.
  7. 7. common problem - this is one i’ve solved about 6 times. time for a plugin
  8. 8. • grails create-plugin build-status • cd build-status • mkdir test/projects • cd test/projects • grails create-app statusapp gives you a client you can use to test your plugin. why?
  9. 9. development • make a change to the plugin • grails package-plugin • cd test/projects/statusapp/ • grails install-plugin ../../../grails-build-status-0.1.zip • grails run-app • wash, rinse, repeat
  10. 10. In Place Plugins • In the application just created, modify BuildConfig.groovy and add: • grails.plugin.location."build-status" = "../../.." • TADA! You’re now working with an in- place plugin
  11. 11. Add Controller (test) import grails.test.* class BuildInfoControllerTests extends ControllerUnitTestCase { void testIndex() { ! ! controller.index() ! ! ! ! assertEquals('index', renderArgs.view) ! ! assertEquals(['app.version'], renderArgs.model.buildInfoProperties) } }
  12. 12. Add Controller class BuildInfoController { ! static final List infoProperties = ['app.version'] def index = { ! ! render view:'index', model: [buildInfoProperties:infoProperties] ! } }
  13. 13. Add the View <html> <head> <title>Build Info</title> </head> <body> <div> <g:each in="${buildInfoProperties}" var="prop"> <g:if test="${g.meta(name:prop)}"> <tr> <td>${prop}</td><td><g:meta name="${prop}"/></td> </tr> </g:if> </g:each> </div> </body> </html>
  14. 14. Functional Testing • in the app, install the functional test plugin • grails create-functional-test class BuildInfoPageFunctionalTests extends functionaltestplugin.FunctionalTestCase { void testSomeWebsiteFeature() { get('/buildInfo') assertStatus 200 assertContentContains 'app.version' assertContentContains 'app.grails.version' } }
  15. 15. Introduce i18n • i18n allows a form of customization • create a messages.properties in the plugin i18n directory for default values • override it in the app to test to make sure it works
  16. 16. Introducing Config • Allow the client application the ability to add or remove properties to display void testIndex_overrideDefaults(){ mockConfig """ buildInfo.properties.exclude = ['app.version'] buildInfo.properties.add = ['custom.property'] """ controller.index() assertEquals 'index', renderArgs.view def expectedProperties = controller.buildInfoProperties - 'app.version' expectedProperties = expectedProperties + 'custom.property' assertEquals expectedProperties, renderArgs.model.buildInfoProperties }
  17. 17. Modularizing Views • Put contents of views into templates • Allows client to override the default view //view // template <html> <g:each in="${buildInfoProperties}" var=" <head> <g:if test="${g.meta(name:prop)}"> <title>Build Info</title> <tr> </head> <td> <body> <g:message code="${prop}"/> <div> </td> <table> ! ! ! <td> ! <g:render template="info" <g:meta name="${prop}"/> plugin="buildstatus"> </td> </table> </tr> </div> </g:if> </body> </g:each> </html>
  18. 18. Events • We want to capture the start of WAR file being built and log the Date/Time • What events are available? • Search $GRAILS_HOME/scripts for “event” • What variables are available? • binding.variables.each {println it} http://grails.org/doc/latest/guide/4.%20The%20Command%20Line.html#4.3%20Hooking %20into%20Events
  19. 19. test it • grails.test.AbstractCliTestCase • Thank you Peter Ledbrook: http://www.cacoethes.co.uk/blog/ groovyandgrails/testing-your-grails-scripts http://grails.org/doc/latest/guide/4.%20The%20Command%20Line.html#4.3%20Hooking %20into%20Events
  20. 20. import grails.test.AbstractCliTestCase import java.util.zip.ZipFile class CreateWarEventTests extends AbstractCliTestCase { void testCreateWar(){ execute (['war', '-non-interactive']) assertEquals 0, waitForProcess() verifyHeader() Properties props = new Properties() props.load(new ZipFile('target/app-0.1.war'). getInputStream('WEB-INF/classes/application.properties')) assertNotNull props['build.date'] } }
  21. 21. eventCreateWarStart = {warname, stagingDir -> Ant.propertyfile(file: "${stagingDir}/WEB-INF/classes/application.properties") { entry(key: 'build.date', value: new Date()) } }
  22. 22. return the favor • publish your own events to allow clients to hook into plugin workflow eventCreateWarStart = {warname, stagingDir -> event("BuildInfoAddPropertiesStart", [warname, stagingDir]) Ant.propertyfile(file: "${stagingDir}/WEB-INF/classes/application.properties") { entry(key: 'build.date', value: new Date()) } event("BuildInfoAddPropertiesEnd", [warname, stagingDir]) }
  23. 23. Gradle Build • http://adhockery.blogspot.com/2010/01/ gradle-build-for-grails-plugins-with.html • http://www.cacoethes.co.uk/blog/ groovyandgrails/building-a-grails-project- with-gradle • One build file in the plugin that runs tests on all your ‘apps’ in the test/projects directory
  24. 24. in-place plugin caveats • Reloading plugin artifacts doesn’t always work • Grails 1.1.1 • see next slide • Grails 1.2.1 • Plugin controllers lose GrailsPlugin annotation and views cannot be resolved after reloading http://jira.codehaus.org/browse/GRAILS-5869
  25. 25. def watchedResources = ["file:${getPluginLocation()}/web-app/**", "file:${getPluginLocation()}/grails-app/controllers/**/*Controller.groovy", "file:${getPluginLocation()}/grails-app/services/**/*Service.groovy", "file:${getPluginLocation()}/grails-app/taglib/**/*TagLib.groovy" ] def onChange = { event -> if (!isBasePlugin()) { if (event.source instanceof FileSystemResource && event.source?.path?.contains('web-app')) { def ant = new AntBuilder() ant.copy(todir: "./web-app/plugins/PLUGIN_NAME_HERE-${event.plugin.version}") { fileset(dir: "${getPluginLocation()}/web-app") } } else if (application.isArtefactOfType(ControllerArtefactHandler.TYPE, event.source)) { manager?.getGrailsPlugin("controllers")?.notifyOfEvent(event) // this injects the tag library namespaces back into the controller after it is reloaded manager?.getGrailsPlugin("groovyPages")?.notifyOfEvent(event) } else if (application.isArtefactOfType(TagLibArtefactHandler.TYPE, event.source)) { manager?.getGrailsPlugin("groovyPages")?.notifyOfEvent(event) } else if (application.isArtefactOfType(ServiceArtefactHandler.TYPE, event.source)) { manager?.getGrailsPlugin("services")?.notifyOfEvent(event) } } // watching is modified and reloaded. The event contains: event.source, // event.application, event.manager, event.ctx, and event.plugin. } ConfigObject getBuildConfig() { GroovyClassLoader classLoader = new GroovyClassLoader(getClass().getClassLoader()) ConfigObject buildConfig = new ConfigSlurper().parse(classLoader.loadClass('BuildConfig')) return buildConfig } String getPluginLocation() { return getBuildConfig()?.grails?.plugin?.location?.'PLUGIN_NAME_HERE' }
  26. 26. the real deal http://plugins.grails.org/grails-build-info/trunk/ source code available in the grails plugin svn repository, or browse on the web at: http://plugins.grails.org/grails-build-info/trunk/
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×