Controller for a Bug:
this is the create method for a bug, I printed out bugInstance.activities and it had my activity object in it
def create = {
def bugInstance = new Bug()
def activity = new Activity(description:"created")
bugInstance.properties = params
bugInstance.addToActivities(activity)
return [bugInstance: bugInstance]
}
Then I looked at the save method, and printed the exact same thing, and the result is null, so somehow it's lost the activity I created, and I have no idea why. Is this really the default behavior? Am I doing something really basic wrong, because there doesn't seem to be any reason such a simple piece of code wouldn't work.
def save = {
def bugInstance = new Bug(params)
println bugInstance.activities
if (bugInstance.save(flush: true)) {
flash.message = "${message(code: 'default.created.message', args: [message(code: 'bug.label', default: 'Bug'), bugInstance.id])}"
redirect(action: "show", id: bugInstance.id)
}
else {
render(view: "create", model: [bugInstance: bugInstance])
}
}
I know I can work around this by adding the activity in the save method, but why do I lose the activity from create() -> save()
You never call save() on the new instance:
def create = {
def bugInstance = new Bug()
def activity = new Activity(description:"created")
bugInstance.properties = params
bugInstance.addToActivities(activity)
bugInstance.save()
return [bugInstance: bugInstance]
}
You don't need to save the Activity because it'll be transitively saved since it's in the activities collection.
might sound like a stupid question but are you setting a hidden parameter or anything in the create.gsp with the Bug Instance instantiated in the create?? I mean, I don't see anything wrong with what your doing here. What does your create.gsp look like?
If you are using the auto-generated create.gsp the activities set will not be included in the form. It is of course there in the model, but there will be no activities field rendered on the client side. When it comes back to save, it is clear that the activity is lost. Depending on what you want to achieve you could add some activity selector to the create.gsp or (to start with) a hidden field with your activities description, but then in the save action I guess you have to handle the the activities param in any case, as the magic of grails doesn't go as far as instanciating the Activity for you. The same way as you instantiate the Bug itself in the save action, you have to instantiate the Activity and even save it, if you want it to be persisted.
Edit: If you really want to pass around the whole activities list, you could make use of indexed properties.
In create.gsp add this:
<g:each status="i" var="activity" in="${bugInstance.activities}">
<!-- one hidden field for each property of each attached activity -->
<g:hiddenField
name="activities[${i}].description"
value="${activity.description}" />
</g:each>
And in the save method this:
params.activities.each{ activity ->
bugInstance.addToActivities(new Activity(activity))
}
But in your case it might be sufficient to instantiate the one activity from a single field.
Related
I'm using Grails 2.3.11.
When I'm going to localhost:8080/myapp/questionnaire/show/3, application shows a blank page. And debug won't even step into any of the methods below.
class QuestionnaireController {
def beforeInterceptor = [action: this.&loadQuestionnaire, only: ['show']]
Questionnaire questionnaireInstance
def show = {
render(view: 'show', model: [questionnaire: questionnaireInstance])
}
private def loadQuestionnaire() {
questionnaireInstance = Questionnaire.findById(params.id)
if(!questionnaireInstance) {
redirectWhenNotFound(params.id)
}
}
private def redirectWhenNotFound(def id) {
flash.message = message(code: 'default.not.found.message', args: [
message(code: 'questionnaire.label'),
id
])
redirect(uri: "/")
}
However, When I try localhost:8080/myapp/questionnaire/show, it goes to loadQuestionnaire(), obviously can't find domain instance, because of params.id not existing, so it goes to redirectWhenNotFound() and redirects it properly to the main page.
Question is - why is it not working normally?
Edit:
It shows a blank page only when there is an instance with the given id, when there isn't, it goes into the method.
Eventually I found out the solution, which is in the answer below.
After checking few more things, that it only fails when there is an instance with the given id, and it goes through the method when there is no instance with the given id, it turned out there has been a misspelled column in a questionnaire table, after editing it, Grails updated the schema and not deleted the old one.
Probably this was the problem, because I dropped that column, removed all rows, restarted application, created a new instance and it works now.
The problem was that it was failing silently, so I am leaving this answer for anyone, who might be having the same problem in the future.
My Grails app is using version 2.3.6
In this app there's a Controller class:
class UserOrderController {
def index(Integer max) {
params.max = Math.min(max ?: 20, 100)
respond UserOrder.list(params), model:[userOrderInstanceCount: WorkOrder.count()]
}
}
Now in another Controller i am accessing the UserOrder objects and filtering based on product ID. Product ID is a string property in UserOrder domain class.
The other controller is:
class UserOrderFilterController {
def getUOBasedOnID () {
// Here i get a new list for **UserOrder**
// Now i want to draw the UserOrderController page with the new list
}
}
I am pretty new to Grails and not sure how to do this.
Should i be creating a new INDEX function in UserOrderController class and pass the new list?
Like shown below:
class UserOrderController {
def index(Integer max) {
params.max = Math.min(max ?: 20, 100)
respond UserOrder.list(params), model:[userOrderInstanceCount: userOrder.count()]
}
def index(List UserOrder) {
// create page???
}
}
UPDATE:
The UserOrderFilterController has it's own index.gsp file.
What am doing is: Accessing all the objects for UserOrder domain class and filter them based on a property.
Now in the index.gsp of UserOrderFilterController i will show the total number of objects/orders found. This number will be shown with a hyperlink using href and when the user clicks on it, it will go to index.gsp page of UserOrderController with only the filtered UserOder's displayed.
So what am expecting is:
<a href='${createLink(controller:'UserOrder', action:'index')}'>%s</a>
A href like shown above with a params field that will have the filtered list of UserOrder's.
I have no idea how to add the params/list to href. Is this possible?
Each action in Grails has a matching view. Duplicating actions is asking for trouble. If you have some new functionality that deserves a page of its own, then you should create a new action and a new view.
If it belongs on the home page, then put it through the same index method.
Note that in grails, you simply pass the values to the .gsp page and the .gsp page is what handles the formatting of the data. Your action should have absolutely 0 knowledge of the structure of the view. This is a core concept in MVC.
In this case, you can redirect to a controller, as per the docs. The one that should interest you the most is this:
redirect(controller: "yourController", action:"index", params=[filteredList: filteredList]);
This will redirect to the existing index action in your UserOrderController, and pass in the filtered list. From there, you can have something like..
if(params.filteredList) {
// You know this came from the filtered controller, so display this and don't
// make a DB call.
}
Point to Note
The structure of your grails application worries me. Why have you got an entirely separate controller for simply filtering data?
Edit
Taking the createLink approach can be improved a bit. Instead of..
<a href='${createLink(controller:'UserOrder', action:'index')}'>%s</a>
You can use the g:link functionality:
<g:link controller="UserOrder" action="index" params=['filteredList':filteredList]>%s</g:link>
If you are trying to create new controller for filter UserOrderFilterController, you can do
class UserOrderFilterController {
def getUOBasedOnID () {
//get filtered list
respond filteredList, [view:'userOrder/index',model:[userOrderInstanceCount: filteredListCount]]
}
}
You can see more about respond here.
Grails noob here. I am build a simple grails app where I have the default create/ edit / list / show pages for a Product domain object. I also have the ProductController. The create method by default does this:
def create() {
[productInstance: new Product(params)]
}
My understanding is this will take me to the create page. And there will be nothing in the params object so everything will be blank.
I want to change the behaviour so that the when the create() method is invoked, a pop up is returned to the User: Sorry you are not allowed to create new data.
The user will stay on the same page and the only thing that will happen is the pop up.
How do I do this without using JavaScript?
Thanks
If the create method is not allowed, why not just remove the create() method from the controller, delete the create.gsp page and remove the 'New' button from the list page?
If you do want to offer a 'New' button and display a popup message that says you can't use the button, that is probably easiest done in javascript.
If you can just display the message in the default grails message panel rather than in a popup, then change the controller method to
def create() {
flash.message = "Sorry you are not allowed to create new data."
redirect(action: "list")
}
Prior to sending the user to the GSP view, you should make a decision in the controller, if the user is allowed to execute the create action. In your controller this could be something simple like:
params.allowed = false
The result of this decision is then passed to you GSP where you can evaluate it inside the GSP using something like:
<g:if test="${params.allowed == false}">
Alert: you are not allowed...
</g:if>
For the alerts, there are good looking alternatives to javascript like: http://getbootstrap.com/components/#alerts
My question is similar to this following post
Render a view of another controller
I have a TestConfigController my question is what can I do in case validation fails and I want to render controller:test and view:edit rather then controller:testCOnfig and view:edit
def save() {
def testConfigInstance = new TestConfig(params)
if (!testConfigInstance.save(flush: true)) {
/*Rather then view:"edit" i want view:"/test/edit" which does not work */
render(view:"edit", model: [testConfigInstance: testConfigInstance],id:params.test.id)
return
}
println "+++++++++++++++++++++++++"
flash.message = message(code: 'Data successfully saved', args: [message(code: 'testConfig.label', default: 'Successfully saved')])
redirect(action: "edit", controller:"test", id:params.test.id)
}
Any pointers? I have already looked into grails redirect which does not have "model" param and thus can not pass the validation errors to the view
Also I have looked in to grails render which does not have controller param so that I can go back to different controller!
Please let me know if more detail/code is needed
EDIT
Following happens while using one of the two things
render(view:"/test/edit", model: [testConfigInstance: testConfigInstance],id:params['test.id'])
The code above renders the page /test/edit with no reference to testid eventually erroring out saying "test.id" can not be null.. (means its rendering /test/edit and not /test/edit/1)
render(view:"/test/edit/"+params['test.id'], model: [testConfigInstance: testConfigInstance],id:params['test.id'])
The code above leads to following error
The requested resource (/EasyTha/WEB-INF/grails-app/views/test/edit/1.jsp) is not available.
Either one of the above code renders just "/test/edit" no id at the end, thus eventually erroring out saying test.id can not be null.
The value of id that you are trying to append in the view path should be a part of the model map. The values that you provide in the model map are available in the view that is rendered.
In the first option that you tried, the id parameter doesn't make any difference as the render method doesn't use any 'id' parameter (the redirect method uses the id parameter to generate the redirect url).
Your code snippet should be something like this:
render(view:"/test/edit", model: [testConfigInstance: testConfigInstance, id:params['test.id']])
The render method that you are using here doesn't redirect you to some other action. render just prints the parsed viewName to the output stream. Eg. render(view:"/test/edit") just renders the edit.gsp view. It isn't actually redirecting you to the edit action of test controller. Therefore, just passing the id in the model map won't give you access to the testInstance on the view. You will have to get the testInstance By id and pass it to the view in the model map
render(view:"/test/edit", model: [testConfigInstance: testConfigInstance, testInstance: Test.get(params['test.id'] as Long)])
Anuj Arora is right:
If you just want to render an arbitrary view you can use the full path to the view related to the grails-app/view folder:
In your case:
render(view:"/test/edit", model: [testConfigInstance: testConfigInstance],id:params.test.id)
should work.
If you are only wanting to render the view /test/edit, then the call render(view:'/test/edit',...) should be all you need.
If instead, you also want to include some of the processing from the TestController and edit action, then look at the chain() call. It has a model parameter where you can pass the validation errors and controller/action parameters to redirect to the other controller.
I'm having the strangest problem with a controller in a Grails project. I am trying to do a simple update of a domain object. Here is a simplified version of the controller
def updateRecord = {
def foundHVT = Process.get(params.hvt)
foundHVT.summaryBy = params.summaryBy
foundHVT.catalogBy = params.catalogBy
foundHVT.editBy = params.editBy
foundHVT.produceBy = params.produceBy
foundHVT.correctedBy = params.correctedBy
// a bunch more of these
foundHVT.save(flush: true);
redirect (action:resource, id: params.hvt)
}
If I run the a new instance of the application of and use this controller to update an object, it doesn't work, the object doesn't save. It will look fine within the controller. I can, for example, re-query the object and the changes are there, post save.
Now here's where it gets weird. If i use the preset scaffold edit controller and update/save an domain object -- and then switch back to this "updateRecord" controller it works FINE until i shut down the server it is working on?!?
I realize I am missing something very basic, but I can't find what it is. Any guidance would be most graciously appreciated.
DM
As HVGOTCODES noted Grails Clean seems to have fixed whatever weirdness was going on with this controller.
try putting a "def scaffold=true" in your controller if it does not already have the normal entry points.
Probably scaffolding save fills some field that you don't.
Possible problems:
Do check save() result and render foundHVT.errors the way Grails does. Add failOnError: true parameter to save() or just check foundHVT.hasErrors(). Look at foundHVT.errors.allErrors for validation problems.
Why not foundHVT.properties = params?
What is there is no foundHVT?