What is the best / cleanest way to gather common data, such as data for a sidebar. In my application I will only ever have two different sets of data, depending if a User is in a Project or not.
I am doing it like this at the minute :
def dashboard = {
def returnVal = getCommonSidebarContent()
returnVal << getCommonHeaderContent()
returnVal << [
//other data related to the main content of this particular page
]
return returnVal
}
where the likes of the getCommonSidebarContent() will return a map of user's tasks and other data.
I know this is bad, its what I started off with, but as time went on I never got round to sorting it. It starts to look messy with the returnVal statements in almost every controller method.
Maybe a filter can help you:
class MyFilters {
def filters = {
all(controller: '*', action: '*') {
after = { Map model ->
model.myCommonProperty = ...
}
}
}
}
Within a filter you can perform common operation before/after a request is processed (i.e. adding common data to your model). Within all(controller: '*', action: '*') you can define the actions that should be processed by a filter (in this case all actions in all controllers are processed).
An alternative way is using beforeInterceptor in controllers. You can use this if you need common actions in a single controller.
Related
I am using grails-2.1.1. When I load the edit page, I am assigning some value in the edit action in controller. But it is updating my table! although I am not saving. How can I stop it?
Here is my code below. My edit action in controller:
def edit() {
def accTxnMstInstance = AccTxnMst.get(params.id)
if (!accTxnMstInstance) {
flash.message = message(code: 'default.not.found.message', args: [message(code: 'accTxnMst.label', default: 'AccTxnMst'), params.id])
redirect(action: "list")
return
}
accTxnMstInstance?.accTxnDtls?.each {
if (it?.debitCoa != null && it?.debitCoa != "") {
String debitCoaVal = ""
List<String> items = Arrays.asList(it?.debitCoa?.split("\\s*~\\s*"))
items.each {
List itemList = new ArrayList()
List<String> subItems = Arrays.asList(it.split("\\^"))
subItems.each {
itemList.add(it)
}
itemList.add("false")
itemList.add("0")
itemList.each {
debitCoaVal += it.toString() + "^"
}
debitCoaVal += "~"
}
it?.debitCoa = debitCoaVal
debitCoaVal = ""
}
if (it?.creditCoa != null && it?.creditCoa != "") {
String creditCoaVal = ""
List<String> items = Arrays.asList(it?.creditCoa?.split("\\s*~\\s*"))
items.each {
List itemList = new ArrayList()
List<String> subItems = Arrays.asList(it.split("\\^"))
subItems.each {
itemList.add(it)
}
itemList.add("false")
itemList.add("0")
itemList.each {
creditCoaVal += it.toString() + "^"
}
creditCoaVal += "~"
}
it?.creditCoa = creditCoaVal
creditCoaVal = ""
}
}
[accTxnMstInstance: accTxnMstInstance]
}
You can see that I am not saving after assigning the value just passing to view.
Grails uses the Open Session In View (OSIV) pattern, where at the beginning of the web request a Hibernate session is opened (and stored in a thread-local to make it easily accessible) and at the end of the request as long as there wasn't an exception, the Hibernate session is flushed and closed. During any flush, Hibernate looks at all "active" object instances and loops through each persistent property to see if it is "dirty". If so, even though you didn't explicitly call save(), your changes will be pushed to the database for you. This is possible because when Hibernate creates an instance from a database row it caches the original data to compare later to the potentially-changed instance properties.
A lot of the time this is helpful behavior, but in cases like this it gets in the way. There are lots of fixes though. One drastic one is to disable OSIV, but this is generally a bad idea unless you know what you're doing. In this case there are two things you can try that should work.
One is to change AccTxnMst.get(params.id) to AccTxnMst.read(params.id). This will not cause the instance to be strictly "read-only" because you can still explicitly call save() and if something was modified, all of the instance changes will be persisted. But the caching of the original data isn't done for instances retrieved using read(), and there's no dirty checking during flush for these instances (which isn't possible anyway since there's no cached data to compare with).
Using read() is a good idea in general when retrieving instances that are not going to be updated (whether you make property changes or not), and makes the code more self-documenting.
Another option is to call discard() on the instance before the controller action finishes. This "detaches" the instance from the Hibernate session, so when the OSIV filter runs at the end of the request and flushes the Hibernate session, your instance won't be considered dirty since Hibernate won't have access to it.
read() only makes sense for individual instances retrieved by id, whereas discard() is useful for any instance, e.g. if they're in a mapped collection or were retrieved by a non-id query (e.g. a dynamic finder, criteria query, etc.)
I have a method-based route like:
name base: "/" {
controller="api"
action=[GET: "welcome", POST: "post"]
}
And I'd like to apply a filter to handle authorization, e.g.
class RequestFilters {
def filters = {
authorizeRequest(controller: 'api', actionExclude: 'welcome') {
before = {
log.debug("Applying authorization filter.")
}
}
}
}
But when I apply this in practice, the filter runs on all requests (even GET requests, which should use the welcome method and thus should not trigger this filter.)
When I inspect the code running the in the filter, I see that params.action is set to the Map from my routing file, rather than to "welcome". Not sure if this is related to the issue.
My current workaround (which feels very wrong) is to add the following to my filter's body:
if(params.action[request.method] == 'welcome'){
return true
}
The short question is: does Grails support this combination of method-based routing + action-name-based filtering? If so, how? If not, what are some reasonable alternatives for restructuring this logic?
Thanks!
You need to use the filter as below:
class RequestFilters {
def filters = {
authorizeRequest(controller:'api', action:'*', actionExclude:'welcome'){
before = {
log.debug("Applying authorization filter.")
return true
}
}
}
}
Apply the filter to all actions of the controller but "welcome". :)
If there is no other welcome action in the other controllers then, you would not need the controller specified in the filter as well.
authorizeRequest(action:'*', actionExclude:'welcome'){...}
I have already posted this question, but i realised the aswer was not what i was looking for. Imagine this controller:
class exampleController{
def action1 = {
...
[lala: lala, lele: lele]}
...
}
def action15 = {
...
[lala: lala, lele: lele]
}
I want to be able to return in all the action in this controller the same params. Imagining this:
def book = Book.findAllByIsbn(Isbn.get(1))
[book: book]
Is there any way of doing this, besides writing all the same code on all the actions? I have tried this method and it isnt working:
def action5 = {getModel()}
private getModel() {
def book = Book.findAllByIsbn(Isbn.get(1))
[book: book]
}
}
It is not working because, and my thought is, he doest accept multiple [return1: aaa, return2: bbb]. Any suggestion please ? I have also tried filters like in here: Grails controllers repeated code for all actions
but i couldnt managed to make it work. I would apreciated a detailed explanaintion about any of the solutions if possible:p Thanks in advanced,
VA
So it's not the same model, but a model with a repeated part.
You should know that the return value is an ordinary Map.
So, return value can be constructed like return getCommonModel() + [book: currentBook] where getCommonModel() returns another Map.
If you want to return the same model from all your actions, this approach should work:
class ExampleController {
def action5 = {getModel()}
def action1 = {getModel()}
//etc.
private getModel() {
def book = Book.findAllByIsbn(Isbn.get(1))
[book: book]
}
}
If you want to return the same model and render the same view from all your actions, you could return the same ModelAndView from each action, but then I would ask why do you need separate actions if they're all doing exactly the same thing?
I don't really understand your hypothesis
It is not working because, and my thought is, he doest accept multiple [return1: aaa, return2: bbb]
If your suggesting that getModel() can only return a model with a single entry, I find that very hard to believe. Can you elaborate a bit on this, or post some more information (e.g. stacktrace, unit test) that shows how/why it's not working?
Update
After reading your comments below I think I finally understand what you want to achieve, which is to append the model returned by getModel() (above) to the model returned by various other actions. Does this work:
class ExampleController {
def action5 = {
def action5Model = [foo: 'bar']
return addBookModel(action5Model)
}
def action1 = {
def action1Model = [foo2: 'bar2']
return addBookModel(action1Model)
}
//etc.
private Map addBookModel(Map model) {
def book = Book.findAllByIsbn(Isbn.get(1))
model.book = book
return model
}
}
This approach will only work when you want to add the book model within a single controller. If you want to add the book model in several controllers you can do this by:
putting addBookModel in an abstract class that the controllers extend
putting addBookModel in a class that is mixed-in with the controllers (using #Mixin)
putting addBookModel in a filter that is executed after the controller actions
If you are using exact same model in multiple pages. I would recommend you use a taglib for it.
I'm new to Groovy & Grails, and I have a feeling that things don't have to be this ugly... so how can I make this code nicer?
This is a Grails controller class, minus some uninteresting bits. Try not to get too hung up that my Car only has one Wheel - I can deal with that later :-)
changeWheel is an Ajax action.
class MyController {
...
def changeWheel = {
if(params['wheelId']) {
def newWheel = Wheel.findById(params['wheelId'])
if(newWheel) {
def car = Car.findById(params['carId'])
car?.setWheel(newWheel)
if(car?.save()) render 'OK'
}
}
}
}
I'd actually start using Command Objects.
Try this:
class MyController {
def index = {
}
def changeWheel = { CarWheelCommand cmd ->
if(cmd.wheel && cmd.car) {
Car car = cmd.car
car.wheel = cmd.wheel
render car.save() ? 'OK' : 'ERROR'
} else {
render "Please enter a valid Car and wheel id to change"
}
}
}
class CarWheelCommand {
Car car
Wheel wheel
}
and then in your view use 'car.id' and 'wheel.id' instead of 'carId' and 'wheelId'
1) pull
params['wheelId']
and
params['carId']
out into their own defs
2) multiple nested ifs is never optimal. You can get rid of the outermost one by having a validateParams method and rendering some sort of response if wheelId and carId are not set. Or just do
if (carId == null || wheelId == null) {
// params invalid
}
3) Assuming everything is ok you could just do
def newWheel = Wheel.findById...
def car = Car.findById...
if (car != null && newWheel != null) {
car.setWheel(newWheel)
car.save()
render 'OK'
} else {
// either wheel or car is null
}
this gets rid of more nested structures...
4) finally, to make the code self documenting, you can do things like assign the conditional tests to appropriately named variables. So something like
def carAndWheelOk = car != null && newWheel != null
if (carAndWheelOk) {
// do the save
} else {
// car or wheel not ok
}
this might be overkill for two tests, but you only are taking care of one wheel here. If you were dealing with all 4 wheels, this type of things increases readability and maintainability.
Note that this advice works in any language. I don't think you can do too much with groovy's syntactic sugar, but maybe some groovy gurus can offer better advice.
There are a couple of things you could do like move some code to a service or command object. But without altering the structure too much, I (subjectively) think the following would make the code easier to read:
use dot notation instead of array indexing to reference params values (params.wheelId instead of params['wheelId'])
I would invert the if to reduce nesting, I think this makes it more clear what the exceptions are.
For example:
if(!params.wheelId) {
sendError(400, "wheelId is required")
return
}
....
....
if(!newWheel) {
sendError(404, "wheel ${params.wheelId} was not found.")
return
}
Now if you don't mind changing the structure and adding more lines of code...
The act of changing the wheel may be a common occurrence across more than just one controller action. In this case I'd recommend putting the GORM/database logic in a Service class. Then your controller only has to verify it has the correct params inputs and pass those on to the Service to do the actual tire changing. A Service method can be transactional, which you'd want in the case where you might have to dismount the old tire before mounting the new one.
In the Service I would throw exceptions for exceptional cases like when a wheel is not found, a car is not found, or if there's an error changing the tire. Then your controller can catch those and respond with the proper HTTP status codes.
Is there any way to pass model data to a view state? Consider the following example view state:
class BookController {
def shoppingCartFlow = {
showProducts {
on("checkout").to "enterPersonalDetails"
on("continueShopping").to "displayCatalogue"
}
}
}
If I want to pass the data model [products: Product.list()] to showProducts.gsp, is there any way to do this apart from preceding the view state with an action state that stores the model in flow scope?
Thanks,
Don
Hmm, it's been a bit since I did a flow, and your example is simplistic (just for being an example's sake, I hope).
What your missing is the initial action in the flow. Keep in mind that a "view" flow action as your showProducts is just says what to do when your showProducts gsp POSTS. It's the action that SENT you to showProducts that should create the model to be used in showProducts.gsp
def ShoppingCartFlow = {
initialize {
action { // note this is an ACTION flow task
// perform some code
[ model: modelInstance ] // this model will be used in showProducts.gsp
}
on ("success").to "showProducts"
// it's the above line that sends you to showProducts.gsp
}
showProducts {
// note lack of action{} means this is a VIEW flow task
// you'll get here when you click an action button from showProducts.gsp
on("checkout").to "enterPersonalDetails"
on("continueShopping").to "displayCatalogue"
}
// etc. (you'll need an enterPersonalDetails task,
// displayCatalogue task, and they
// should both be ACTION tasks)
}
Make sense?
Maybe I don't understand the question, but can't you do
render (view:"showProducts",
model:[products: Product.list()]
inside your controller?
You can try this (assuming you want go to checkout):
showProducts {
on("checkout"){
// do somethings here too if you like
// then pass your data as below:
[products: Product.list()]
} .to "enterPersonalDetails"
on("continueShopping").to "displayCatalogue"
}