Grails Plugins(Console, DB Migration, Asset Pipeline and Remote pagination)
Building Grails Plugins - Tips And Tricks
-
Upload
mike-hugo -
Category
Technology
-
view
12.375 -
download
3
Transcript of Building Grails Plugins - Tips And Tricks
Building Grails Pluginstips and tricks from the wild
About Me• Mike Hugo, Independent Software Developer
• http://piragua.com
• http://WhenWorksForYou.com
• Groovy/Grails since 2007
• Author of several Grails plugins
• code-coverage
• hibernate-stats
• test-template
• greenmail
• build-info
• ????
• Plugins overview
• Build your own plugin
• testing
• modularization
• configuration
• events
• did I mention testing?
what’s on the menu for tonight?
App Plugin
a plugin is just another grails application, with a few extra files* GrailsPlugin.groovy* Install, Uninstall and Upgrade scripts
Cool hooks into the runtime environment of a running grails app, plus ability to add new ‘artefacts’ and participate in reloading events
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 statusapp
gives you a client you can use to test your plugin.
why?
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
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
Add Controller (test)
import grails.test.*
class BuildInfoControllerTests extends ControllerUnitTestCase { void testIndex() {
! ! controller.index()! !! ! assertEquals('index', renderArgs.view)! ! assertEquals(['app.version'],
renderArgs.model.buildInfoProperties) }}
Add Controller
class BuildInfoController {
! static final List infoProperties = ['app.version']
def index = { ! ! render view:'index', model:
[buildInfoProperties:infoProperties]! }}
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>
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' }}
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
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 }
Modularizing Views• Put contents of views into templates
• Allows client to override the default view
//view<html><head> <title>Build Info</title></head><body><div> <table>! <g:render template="info"
plugin="buildstatus"></table></div></body></html>
// template<g:each in="${buildInfoProperties}" var="prop"> <g:if test="${g.meta(name:prop)}"> <tr> <td>
<g:message code="${prop}"/></td>
! ! ! <td><g:meta name="${prop}"/>
</td> </tr> </g:if></g:each>
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
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
import grails.test.AbstractCliTestCaseimport 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'] }
}
eventCreateWarStart = {warname, stagingDir -> Ant.propertyfile(file:
"${stagingDir}/WEB-INF/classes/application.properties") { entry(key: 'build.date', value: new Date()) }}
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])}
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
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 reloadinghttp://jira.codehaus.org/browse/GRAILS-5869
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'}
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/