I am purposely causing a cat instance to fail. The following test passes.
void testSomething() {
Cat.metaClass.save = {
throw new Exception("Asdasd")
}
shouldFail(Exception){
Cat cat = new Cat(name: "asd")
cat.save()
}
GroovySystem.metaClassRegistry.removeMetaClass(Cat.class)
}
But, when i set the failOnError property for the save method then this fails. How can i alter the save using metaClass in order to make the save(failOnError:true) throw an exception? I appreciate any help! Thanks!
void testSomething() {
Cat.metaClass.save = {
throw new Exception("Asdasd")
}
shouldFail(Exception){
Cat cat = new Cat(name: "asd")
cat.save(failOnError: true)
}
GroovySystem.metaClassRegistry.removeMetaClass(Cat.class)
}
One alternative to doing the same test is to pass in invalid parameters to the domain instance so that the validation fails and exception is thrown but this will not work in all cases because in some cases the domain instance doesn't require any parameters given by the user. So, in order to simulate the failure of domain save() in this case we will need a way to mock the save failure. So, i appreciate if anyone has answer to how to mock save with or without save params like save(flush:true), save(failOnError:true). Thanks!
Your first instance of metaClassing the save() is fine.
When trying to metaClass the save(failOnError: true) version, you have to alter your metaClassing statement to match the signature of the actual employed method. A "save()" invocation is not the same as a "save(failOnError:true)" invocation. Try this (I suspect the parameter is strictly typed, so I'm using Map. :
Cat.metaClass.save = { Map map ->
throw new Exception("failOnError is true")
}
Related
Using Grails 2.5.6 here. I'm trying to access a Set of Strings off of my domain class in the beforeDelete GORM event. I'm seeing the deletes for this set getting issued in the database log before even getting to my breakpoint in the beforeDelete.
I'm getting a NullPointerException on my println(strings) below in my domain class.
My test domain class looks like
class DeleteTest {
Integer id
Set<String> stringSet
String prop1
String prop2
static hasMany = [stringSet: String]
static constraints = {
prop1(maxSize: 20)
prop2(maxSize: 20)
}
static mapping = {
stringSet(joinTable: [column: 'delete_test_string_set', length: 15])
}
def beforeDelete() {
withNewSession {
Set<String> strings = this."stringSet"
println(strings)
}
}
}
And I've made a test controller like this.
class DeleteTestController {
def create() {
DeleteTest test = null
DeleteTest.withTransaction {
test = new DeleteTest(
prop1: 'Test',
prop2: 'another test',
stringSet: ['str1', 'str2', 'str3']
).save()
}
render (test as JSON)
}
def delete() {
DeleteTest test = DeleteTest.findByProp1('Test')
DeleteTest.withTransaction {
test.delete()
}
render(test as JSON)
}
}
How can I get my stringSet in the beforeDelete event?
One easy way is to make sure to load stringSet before calling the delete. However, there are clearly some odd behaviors going on here and I'll describe what I have found so far.
Simple Answer
def delete() {
DeleteTest test = DeleteTest.findByProp1('Test')
test.stringSet?.size() // <-- force load here
DeleteTest.withTransaction {
test.delete()
}
render(test as JSON)
}
Other Considerations
I tried making stringSet eager loaded. This did not work as expected and in the beforeDelete code it would often be a single value or blank.
I also tried making StringSet a Set where I defined a single GORM object MyString containing the value. This did work (though I had to make it eagerly fetched), but I did not consider this to be a valid solution for your case since I assume you have data already and can't just replace it.
Based on some debug digging, I'm guessing (but it really is just a guess) that the collection is deleted before the beforeDelete event fires, and so it can't be lazily loaded at that point even in a new transaction. I would expect that someone else could weigh in on whether that's right or not, but grails 2 expertise is getting harder to find these days.
I'm using grails 2.1.1 and I getting the message "No signature of method: ClassA.save() is applicable for argument types: () values: []" when trying to save an object in production environment.
This is the code:
def method(campId, userId){
ClassA cbl = new ClassA()
cbl.userId = userId
cbl.campId = campId
cbl.save(flush:true)
}
This code works fine in development but when I execute the code in production I have this problem.
A couple of things to look out for
You say it works in dev but not in production so the first starting point of any investigation is what is the difference between both. Meaning do a show create table class_a on both product/development environments.
It maybe something has changed and it has some required value that is no longer provided
Step 2:
It is obviously not saving so you need to see if any errors are thrown
def method(campId, userId){
if (campId && userId) {
try {
ClassA cbl = new ClassA()
cbl.userId = userId
cbl.campId = campId
if (!cbl.save(flush:true)) {
//println cbl.errors
//log.info "${cbl.errors}"
}
}catch (Exception e) {
//println "E really happended $e"
}
}else {
println "oh my ... a certain value has not been provided"
}
}
You see in above code an if statement to ensure both values provided since you are setting without checking, the try catch was last alternative to try but the if it saves println error is the next thing to try
Finally there are many ways to save a class just in case as Anton suggest you have some other inbuild functions in ClassA when it comes to saving and new ClassA() may cause an issue maybe since you are missing
ClassA() {
super()
}
or something then I would lastly try this as a test
ClassA cbl = new ClassA(userId:userId,campId:campId)?.save(flush:true)
Same logic applied differently
I stumbled across this when doing a refactoring with Grails 2.0.1 but I pulled the basics of this problem out into a straight groovy 1.8.6 test and it still failed. I came across it because my method used to take no parameters and I changed it to take 1 parameter. When I changed the implementing production code none of my tests failed. Which is weird because the metaClassing that I had in the test was setup to accept no parameters but it was still responding to my production code when I passed in a parameter. So in the example below I'm wondering why the second metaClassing is being invoked and not the first. It doesn't accept any parameters and as you can see I'm passing one in. If you switch the order of the metaClassing then it works appropriately but order shouldn't matter in this case since the method signature is different. Any insight into why this is happening would be greatly appreciated.
import groovy.util.GroovyTestCase
class FirstTest extends GroovyTestCase {
void testStuff() {
def object = new Object()
object.metaClass.someMethodName = {Object obj ->
"ONE"
}
object.metaClass.someMethodName = {
"TWO"
}
def result = object.someMethodName(new Object())
assert "ONE" == result //result is equal to "TWO" in this case
}
}
EDIT
Seems as if my above code may be more confusing than helpful so here is the actual code.
Original Production Code:
def create() {
render(view: "create", model: [domains: Domain.myCustomListMethod().sort{it.cn}])
}
Original Test Code:
#Test
void createShouldIncludeAListOfAllDomainsInModel() {
def directory = GldapoDirectory.newInstance(
"", [
url: "http://url.com",
userDn: "someUserName",
password: "superSecretPassword"
])
controller.session.userDirectory = directory
Domain.metaClass.'static'.myCustomListMethod = {
[[cn:"1"], [cn:"2"]]
}
controller.create()
assert [[cn:"1"], [cn:"2"]] == controller.modelAndView.model.domains
}
I then updated the production code to pass in the session.userDirectory and my test still passed unmodified even though it's not setup to receive any parameters:
def create() {
render(view: "create", model: [domains: Domain.list(session.userDirectory).sort{it.cn}])
}
Closures by default take one parameter (of class Object), even if none is declared (accessible via the default variable it)
So your second closure is overriding the first
I have a need to have a method to return Id in case of success and list of errors in case of fail.
ex code snippet:
def save = {
def errors = []
if(Employee.save(flush:true)){
return Employee.id
}else{
errors.add("Can't be saved")
return errors.
}
}
In Service class
ICalling
Employee.save() - .. so how to check if it is error or id that save method returns
Any suggestions around would be appreciated.
I agree with Burk not to return different types, it can lead to unexpected errors.
Another solution to the problem is using Java's exception handling mechanism. You can add a context field to the Exception which will hold the list of validation errors.After catching the exception you can extract the errors.
void save(Employee employee) {
// do save
// ...
// on error:
def errors = [ "terrible error nr. 5" ]
throw new ValidationException(errors)
}
try {
fooService.save(employee)
} catch(ValidationException e) {
def errors = e.erorrs
// do stuff with the errors
}
An additional advantage: When no validation error is expected, the try-catch block can be ommited in Groovy, which makes the code cleaner because you don't have to care about any validation error fields.
Don't do this - even if you can make it somewhat more usable with Groovy, it's a bad idea. In this case though, there are a few simple solutions. If you're just passing the Employee instance and saving it in the service method, you don't need to return anything:
void save(Employee employee) {
employee.save(flush:true)
}
This is because if it's successful, the id will be set on the instance you passed in, and if not there will be one or more validation errors in the errors property (there's no need for you to return a generic error message when there are actually useful error messages available).
For example this would be the code you'd have in a controller calling the service:
def employee = new Employee(...)
fooService.save(employee)
if (employee.hasErrors()) {
// do something with employee.errors
}
else {
// success - use the id if you need via employee.id
}
If you want to pass in the data to create and save the new instance and return an Employee (this is the approach I usually take), it's similar:
Employee save(String name, int foo, boolean bar, ...) {
Employee employee = new Employee(name: name, foo: foo, bar: bar, ...)
employee.save(flush:true)
return employee
}
In this second case it's important to separate the save call and the return, since if there is a validation error save returns null and you want to always return a non-null instance. So do not do this:
return employee.save(flush:true)
If you separate them you can check the errors and/or the id.
Also, make sure that you do not use closures in services like you have in your code (def save = { ...). Only methods will be transactional since the Spring transaction handling doesn't know about Groovy closures - they're just fields that Groovy calls as if they were methods, but they're not.
In my Grails app, I have defined the following (simplified) web flow
def registerFlow = {
start {
action {RegistrationCommand cmd ->
try {
memberService.validateRegistrationCommandDTO(cmd)
} catch (MemberException ex) {
flow.regErrorCode = ex.errorCode
throw ex
}
}
on("success").to "survey" // The 'survey' state has been omitted
on(MemberException).to "handleRegMemberException"
on(Exception).to "handleUnexpectedException"
}
handleRegMemberException {
action {
// Implementation omitted
}
}
handleUnexpectedException {
redirect(controller:'error', action:'serverError')
}
}
If a MemberException is thrown by the "start" state, execution should proceed to the "handleRegMemberException" state, but it doesn't. Is there something wrong with my flow definition, or my understanding of how this should work?
Thanks,
Don
I am still pretty new to Groovy and Grails, but I have a suggestion. Perhaps the issue has something to do with the difference in the way the Grails framework (and Groovy for that matter) handles checked vs. unchecked exceptions.
If MemberException is a checked exception (extends Exception), perhaps the 'throw' that is inside the closure behaves to bring execution the whole way out of the webflow. You and I both could do some RTFM'ing on this one... I see the Groovy book on my bookshelf from here. As a quick answer, I would say change MemberException to an unchecked exception (extends RuntimeException) and see if you get the same results. Or, you could wrap MemberException in a RuntimeException...
throw new RuntimeException(ex)
The flow should behave as you expect.
Something might be wrong with your flow, for example some other error in service, but it is not clear from your question what is actually going on. You say how you expect flow to behave and then you say it doesn't behave the way you expected, but you don't say how it actually behaves.
I suggest adding some traces to your flow to see what is actually going on.
By the way, there are some known bugs with different versions of grails and webflows are broken in grails 1.2-M3:
http://jira.codehaus.org/browse/GRAILS-5185
Here is my flow similar to what you have programmed:
class SomeController {
def index = {
redirect(action:'someProcess')
}
def someProcessFlow = {
start{
action{
dosome ->
println "-> inside start action closure"
try{
println "-> throwing IllegalArgumentException"
throw new IllegalArgumentException()
}catch(IllegalArgumentException ex){
println "-> inside catch"
throw ex
}
throw new Exception()
"success"
}
on("success").to "helloPage"
on(IllegalArgumentException).to "illegal"
on(Exception).to "handleException"
}
illegal{
action{
println "-> illegal handled"
"success"
}
on("success").to "helloPage"
}
handleException{
action{
println "-> generic exception handled"
"success"
}
on("success").to "helloPage"
}
helloPage()
}
}
It behave as you expect and the output is:
-> inside start action closure
-> throwing IllegalArgumentException
-> inside catch
2009-11-03 11:55:00,364 [http-8080-1] ERROR builder.ClosureInvokingAction
- Exception occured invoking flow action: null
java.lang.IllegalArgumentException
at SomeController$_closure2_closure3_closure6.doCall(SomeController:18)
at java.lang.Thread.run(Thread.java:619)
-> illegal handled