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(...)
Related
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.
WhenI try to access params in a Grail filter, in a utility method called in the before {} closure I get:
groovy.lang.MissingPropertyException: No such property: params for class: myproject.MyFilters
How do I access the equivalent of the params object in a filter?
Thanks
You can do something like this...
// grails-app/conf/paramsinfilter/DemoFilters.groovy
package paramsinfilter
class DemoFilters {
def filters = {
all(controller:'*', action:'*') {
before = {
// params is available here
println "Params in before filter: $params"
}
after = { Map model ->
// params is available here
println "Params in after filter: $params"
}
afterView = { Exception e ->
// params is available here
println "params in afterView filter: $params"
}
}
}
}
EDIT
I see now that the question has been edited. If you want to refer to the params in a utility method that you invoke from the filter you have a number of options depending on what you are really trying to do, but most likely what you will want to do is pass the params as an argument to the utility method.
// grails-app/conf/paramsinfilter/DemoFilters.groovy
package paramsinfilter
class DemoFilters {
def filters = {
all(controller:'*', action:'*') {
before = {
// params is available here
helper(params)
}
after = { Map model ->
// params is available here
helper(params)
}
afterView = { Exception e ->
// params is available here
helper(params)
}
}
}
private helper(params) {
println "Params in helper: $params"
}
}
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.
I'm doing Grails To Action sample for chapter one. Every was just fine until I started to work with Services. When I run the app I have the following error:
groovy.lang.MissingPropertyException: No such property: quoteService for class: qotd.QuoteController
at qotd.QuoteController$_closure3.doCall(QuoteController.groovy:14)
at qotd.QuoteController$_closure3.doCall(QuoteController.groovy)
at java.lang.Thread.run(Thread.java:619)
Here is my groovie QuoteService class, which has an error within the definition of GetStaticQuote (ERROR: Groovy:unable to resolve class Quote)
package qotd
class QuoteService {
boolean transactional = false
def getRandomQuote() {
def allQuotes = Quote.list()
def randomQuote = null
if (allQuotes.size() > 0) {
def randomIdx = new Random().nextInt(allQuotes.size())
randomQuote = allQuotes[randomIdx]
} else {
randomQuote = getStaticQuote()
}
return randomQuote
}
def getStaticQuote() {
return new Quote(author: "Anonymous",content: "Real Programmers Don't eat quiche")
}
}
Eclipse show me an error flag on the definition of getStaticQuote:
ERROR: Groovy:unable to resolve class Quote
Any Clues?
Controller groovie class
package qotd
class QuoteController {
def index = {
redirect(action: random)
}
def home = {
render "<h1>Real Programmers do not each quiche!</h1>"
}
def random = {
def randomQuote = quoteService.getRandomQuote()
[ quote : randomQuote ]
}
def ajaxRandom = {
def randomQuote = quoteService.getRandomQuote()
render "<q>${randomQuote.content}</q>" +
"<p>${randomQuote.author}</p>"
}
}
Quote Class:
package qotd
class Quote {
String content
String author
Date created = new Date()
static constraints = {
author(blank:false)
content(maxSize:1000, blank:false)
}
}
I'm doing the samples using STS. Any advice?
Regards,
Francisco
do
def quoteService
at the top of your controller and it will be injected into the controller automatically
groovy.lang.MissingPropertyException: No such property: quoteService for class: qotd.QuoteController
I dont code in grails but it appears as though you need to declare quoteService somewhere in the controller.
I did
def quoteService = new QuoteService()
and it solved my problem
I'm trying to create a a custom constraint. I've put the logic in a service:
class RegExpManagerService {
boolean transactional = false
def messageSource
def lookupRegexp(regExpression,Locale locale) {
def pattern = messageSource.getMessage( regExpression,null,locale )
return pattern
}
def testRegexp(regExpression,text,Locale locale) {
return text ==~ lookupRegexp(regExpression,locale)
}
}
and tried to inject it in my domain controller:
class Tag extends IbidemBaseDomain {
def regExpManagerService
static hasMany=[itemTags:ItemTag]
static mapping = {
itemTags fetch:"join"
}
//Long id
Date dateCreated
Date lastUpdated
String tag
// Relation
Tagtype tagtype
// Relation
Customer customer
// Relation
Person updatedByPerson
// Relation
Person createdByPerson
static constraints = {
dateCreated(nullable: true)
lastUpdated(nullable: true)
tag(blank: false,validator: {val,obj ->
regExpManagerService.testRegexp(obj.tagtype.regexpression,val,local)
})
tagtype(nullable: true)
customer(nullable: true)
updatedByPerson(nullable: true)
createdByPerson(nullable: true)
}
String toString() {
return "${tag}"
}
}
When the constraint gets executed I get this error:
2009-08-24 18:50:53,562 [http-8080-1] ERROR errors.GrailsExceptionResolver - groovy.lang.MissingPropertyException: No such property: regExpManagerService for class: org.maflt.ibidem.Tag
org.codehaus.groovy.runtime.InvokerInvocationException: groovy.lang.MissingPropertyException: No such property: regExpManagerService for class: org.maflt.ibidem.Tag
The constraints closure is static, so it can't see the instance field 'regExpManagerService'. But you have the object being validated so you can access it from that:
tag(blank: false,validator: {val,obj ->
obj.regExpManagerService.testRegexp(obj.tagtype.regexpression,val,local)
})