Repeating code using Grails domain find method - grails

Initial Problem
If you have different methods that basically have only one line different, would there be a way to make it DRY by creating one method.
Example:
def showA( ) {
def instance
try {
instance = A.findById( params.id )
} catch ( Exception e ) {
def message = "Error while retrieving details for the given id ${ params.id }, $e"
log.error message
responseAsJson( 400, "Invalid id", message )
return false
}
return checkAndRender(instance, params.id);
}
def showB( ) {
def instance
try {
instance = B.findByBId( params.BId )
} catch ( Exception e ) {
def message = "Error while retrieving details for the given id ${ params.id }, $e"
log.error message
responseAsJson( 400, "Invalid id", message )
return false
}
return checkAndRender(instance, params.id);
}
So, would there be a way to make one method and simply pass as parameter:
The domain class
the ID to search for
Or would it be better to pass an SQL statement instead?
Update
Based on #dmahapatro comment, I came up with the following:
def showA( ) {
def clos = {id -> A.findByAId( id ) }
return findAndShow(clos, params.AId, params )
}
def showB( ) {
def clos = {id -> B.findByBId( id ) }
return findAndShow(clos, params.BId, params )
}
def findAndShow(Closure closure, def id, def p)
{
def instance
try {
instance = closure(id)
}
catch ( Exception e ) {
def message = "Error while retrieving instance details for the given id ${ id }, $e"
log.error message
responseAsJson( 400, "Invalid Id", message )
return false
}
return checkAndRender(instance, id);
}
Only remaining issues are:
How to cleanup even further / make it cleaner.
How to bypass warning:
The [findAndShow] action in [ApiController] accepts a parameter of
type [groovy.lang.Closure]. Interface types and abstract class types
are not supported as command objects. This parameter will be ignored.
def findAndShow(Closure closure, def id, def p)

First thing you should worry if you want a DRY code, is define a better exception handling. Try-catching your code everywhere to handle response to the client is not very DRY, if you put your data-access code in services, you can throw exceptions from them and use a global controller for catch the errors and handle the responses. E.g:
class ErrorController {
def serverError() {
if (request.format == 'json') {
//Code for handling errors in json request, request.exception stores the data about the exception.
} else {
//Code for handling errors in non-json request, e.g:
render(view: 'error', model: [msg: 'Something went wrong']) //add an error view for this
}
}
}
If you like, you can also add handlers for other types of errors (403, 404, etc)
Add to UrlMappings.groovy
"500"(controller: "error", action: "serverError")
Now you can refactor your code using your new error handling, and reflection:
Controller:
class MyController {
def myService
def show() {
def result = myService.myFind(params.className,params.id)
render result as JSON //Render stuff
}
}
Service:
import grails.util.Holders
class MyService {
def myFind(String className, Long id) {
def result = Holders.getGrailsApplication().getDomainClass('com.mypack.'+ className).findById(id)
if(!result) {
throw new ServiceException('really descriptive and usefull error msg')
}
}
}
I defined a ServiceException class so i can add custom logic for it in my ErrorController using the instanceOf operator.

Related

Service validate domain model

I face a problem with the message method from the domain validation from the service. Does anyone knows how to fix this? Below is the service method. When my controller is calling: glossaryService.post(params, 'save').
#Transactional
class GlossaryService {
// Saving glossary
def post( def params,def currentURI ) {
params.status = params.status ? Status.PUBLISHED.value() : Status.DRAFT.value()
def result = [success: false, message: ""]
def glossary = null
if (params.action == 'save') {
glossary = new Glossary(params)
} else if (params.action == 'update') {
glossary = Glossary.get(params.id)
glossary.name = params?.name
glossary.description = params?.description
glossary.glossaryTerm = params?.glossaryTerm
glossary.status = params?.status
}
if (glossary.validate()) {
result = glossary.save(flush: true, failOnError: true)
}
if (glossary.hasErrors()) {
transactionStatus.setRollbackOnly()
result.errors = glossary.errors
def errors = glossary.errors.allErrors.collect {
message(error: it)
}
respond glossary.errors, view: '/admin/glossary/'+currentURI, model: [
glossary: params
]
return
}
return result
}
}
The error it prompts is:
Class: groovy.lang.MissingMethodException
Message: No signature of method: com.content.GlossaryService.message() is applicable for argument types: (java.util.LinkedHashMap) values: [[error:Field error in object 'com.content.Glossary' on field 'name': rejected value [null]; codes [com.content.Glossary.name.nullable.error.com.content.Glossary.name,com.content.Glossary.name.nullable.error.name,com.content.Glossary.name.nullable.error.java.lang.String,com.content.Glossary.name.nullable.error,glossary.name.nullable.error.com.content.Glossary.name,glossary.name.nullable.error.name,glossary.name.nullable.error.java.lang.String,glossary.name.nullable.error,com.content.Glossary.name.nullable.com.content.Glossary.name,com.content.Glossary.name.nullable.name,com.content.Glossary.name.nullable.java.lang.String,com.content.Glossary.name.nullable,glossary.name.nullable.com.content.Glossary.name,glossary.name.nullable.name,glossary.name.nullable.java.lang.String,glossary.name.nullable,nullable.com.content.Glossary.name,nullable.name,nullable.java.lang.String,nullable]; arguments [name,class com.content.Glossary]; default message [Property [{0}] of class [{1}] cannot be null]]] Possible solutions: isCase(java.lang.Object)
Inject the messageSource
MessageSource messageSource
Then use it
messageSource.getMessage(...)

Grails Reusable Service for saving Domain Objects

I have a Grails project with multiple Domain Classes, and I want to make a persistence service as reusable as possible by only having one save() inside of it. To try and achieve this I have done the following in my project.
//PersistenceService.groovy
#Transactional
class PersistenceService {
def create(Object object) {
object.save flush: true
object
}
//BaseRestfulController
class BaseRestfulController extends RestfulController {
def persistenceService
def save(Object object) {
persistenceService.create(object)
}
//BookController
class BookController extends BaseRestfulController {
private static final log = LogFactory.getLog(this)
static responseFormats = ['json', 'xml']
BookController() {
super(Book)
}
#Transactional
def save(Book book) {
log.debug("creating book")
super.save(book)
}
So basically I have a bunch of domains for example Author etc, each with their own controller similar to the bookController. So is there a way to reuse the service for persistence like I am trying above?
Thank you
I'm doing something similar, but mainly because all my entities are not actually removed from the database but rather "marked" as removed. For several apps you need such an approach since it's critical to prevent any kind of data loss.
Since most databases do not provide support for this scenario, you can't rely on foreign keys to remove dependent domain instances when removing a parent one.
So I have a base service class called GenericDomainService which has methods to save, delete (mark), undelete (unmark).
This service provides a basic implementation which can be applied to any domain.
class GenericDomainService {
def save( instance ) {
if( !instance || instance.hasErrors() || !instance.save( flush: true ) ) {
instance.errors.allErrors.each {
if( it instanceof org.springframework.validation.FieldError ) {
log.error "${it.objectName}.${it.field}: ${it.code} (${it.rejectedValue})"
}
else {
log.error it
}
}
return null
}
else {
return instance
}
}
def delete( instance, date = new Date() ) {
instance.dateDisabled = date
instance.save( validate: false, flush: true )
return null
}
def undelete( instance ) {
instance.dateDisabled = null
instance.save( validate: false, flush: true )
return null
}
}
Then, in my controller template I always declare two services: the generic plus the concrete (which may not exist):
def ${domainClass.propertyName}Service
def genericDomainService
Which would translate for a domain called Book into:
def bookService
def genericDomainService
Within the controller methods I use the service like:
def service = bookService ?: genericDomainService
service.save( instance )
Finally, the service for a given domain will inherit from this one providing (if needed) the custom logic for these actions:
class BookService extends GenericDomainService {
def delete( instance, date = new Date() ) {
BookReview.executeUpdate( "update BookReview b set b.dateDisabled = :date where b.book.id = :bookId and b.dateDisabled is null", [ date: date, bookId: instance.id ] )
super.delete( instance, date )
}
def undelete( instance ) {
BookReview.executeUpdate( "update BookReview b set b.dateDisabled = null where b.dateDisabled = :date and b.book.id = :bookId", [ date: instance.dateDisabled, bookId: instance.id ] )
super.undelete( instance )
}
}
Hope that helps.

Grails actions getting called twice

The getStarted action redirects to companyInfo action which renders companyInfo.gsp and immediately after the page rendering, companyInfo action getting called one more time. I don't understand what the problem is.
class MyController {
#Secured('ROLE_USER')
def getStarted(){
def renderParams = [view: 'getStarted', model: [:]]
if(request.method != 'POST') {
render(view: 'getStarted')
} else {
def company = new Company()
.......
redirect(action: 'companyInfo', params: [id: company.id])
}
}
#Secured('ROLE_USER')
def companyInfo() {
def renderParams = [view: 'companyInfo', model: [:]]
if (request.method != 'POST') {
renderParams.model.cmpId = params?.id
render(renderParams)
}
}
}
See this answer. Grails trys to map get* to properties. And when the controller is called grails tries to map getStarted to a property called started, calling the method. So, Never Use get**** as your action name

Cannot invoke method pullLogs() on null object when calling service in controller

I have search around stack overflow and have found some similar instances of my problem but there fixes done seem to work for mine. (example of simular one:Grails - Can't call service from Controller --> always get "Cannot invoke method on null object error")
My service can be summed up like this
class AuditService {
AuditService auditService
def sql
def dataSource
static transactional = true
def pullLogs(String username, String id) {
if(username != null && id != null) {
sql = new Sql(dataSource)
println "Data source is: " + dataSource.toString()
def schema = dataSource.properties.defaultSchema
sql.query('select USERID, AUDIT_DETAILS from DEV.AUDIT_LOG T WHERE XMLEXISTS(\'\$s/*/user[id=\"' + id + '\" or username=\"'+username+'\"]\' passing T.AUDIT_DETAILS as \"s\") ORDER BY AUDIT_EVENT', []) { ResultSet rs ->
while (rs.next()) {
def auditDetails = new XmlSlurper().parseText(rs.getString('AUDIT_EVENT_DETAILS'))
println auditDetails
}
}
sql.close()
}
}
}
The way im trying to call it is likes this
UserController {
def auditService
show(Long id){
def UserInstance = User.get(id)
//Also tried def auditResults = auditServices.pullLogs(UserInstance.username, UserInstance.id)
def auditResults = auditServices(UserInstance.username, UserInstance.id)
System.out.println(" "+ auditResults)
[UserInstance: UserInstance,params:params]
}
}
The error I get is
Class:
java.lang.NullPointerException
Message:
Cannot invoke method pullLogs() on null object
Im pretty stumped. (Query was given to me)
Any Ideas/Opnions/Help is greatly appriciated!
Thanks!
In UserController you have
def auditService
But then
def auditResults = auditServices(UserInstance.username, UserInstance.id)
which should be
def auditResults = auditService.pullLogs(UserInstance.username, UserInstance.id)
As for the "FactoryBean not initialized" error, for that you can simply remove the
AuditService auditService
from inside AuditService - it isn't necessary as you can just use this if you need a reference to AuditService from within its own code.
The name of your service is LogService or AuditService? If it's AuditService your attribute name in the controller have an s that shouldn't.
class UserController {
def auditService //Name should be the same of the service, not in plural
...
}

How to pass error messages between Grails controllers?

I'm trying to pass an error message from a grails controller to a grails error controller in order to display an error message in the HTTP response, but I'm not sure what parameter is holding the error message in the error controller.
URLMappings.groovy
All 500 errors are mapped to ErrorsController
"500"(controller: "errors", action: "serverError")
GenericController
def {
try{
//do some work
}catch(Exception e){
response.sendError(500, e.getMessage())
}
}
ErrorsController
def serverError = {
render( how can I access the exception details here?? )
}
I need to access the exception in the ErrorsController so I can output it to the HTTP response.
The usual way to pass short informational messages between controllers is to place it in the flash scope. For example:
def myAction = {
try {
...
} catch (Exception e) {
flash.message = e.message
response.sendError(500)
}
}
In this particular case, though, why are you catching the exception? If you let the exception fall through, grails will automatically generate a server error and call the "500" mapping. In your error controller, the exception will be available as request.exception.
In your ErrorsController:
def serverError() {
render request.getAttribute('javax.servlet.error.message')
}
renders the message from e.getMessage().

Resources