My controller:
def getMenuItemCount(String name){
def myCount = MenuItem.countByMenuItemCategory(name)
render myCount
}
What do i call in another gsp so that myCount appears. createLink doesnt seem to work
${createLink(action:'getMenuItemCount', params: [name:ci.name])}
A model has to be returned instead of rendering it in order to access it in a gsp.
def getMenuItemCount(String name){
[ myCount: MenuItem.countByMenuItemCategory(name) ]
}
Also, (not related to question really), try to avoid using action names as get* and set*. I have seen discrepancies with that nomenclature.
Related
I am struggling to find the best way to render records. So far, I did it the following way but, despite having includes when I fetch the main object, I get tons of DB queries when calling as_json for the child record that was included. What am I missing? Is there even a better way too do what I want to do?
I don't see how to have a better render since I want to decide what attributes and methods to serialise and use custom scopes on arrays of associate records.
My controller
def show
# The include below seems to be useless, the DB is queried again on render.
#grandParent = GrandParent.includes(parents: { children: %i[grand_children friends] })
.find_by_name(params[:name])
return head :not_found unless #grandParent
render json: grand_parent_as_json, status: :ok
end
private
def grand_parent_as_json
json = #grandParent.as_json(
only: %i[attr1 attr2],
methods: %i[meth1 meth2]
)
# I don't see a better way to render it since I want to use a custom scope on parents
json[:parents] = #grandParent.parents.ordered_by_birthdate(:desc).map do |parent|
parent_as_json parent
end
json
end
# The include below seem to be the one responsible for querying the DB again.
def parent_as_json(parent)
parent.as_json(
only: %i[attr1 attr2],
methods: %i[meth1 meth2],
include: [
children: {
only: %i[attr1 attr2],
include: [
grand_children: { %i[attr1 attr2] }
]
}
]
)
end
Pretty sure there is a more elegant way to fix this but the issue is indeed the scope being used here:
#grandParent.parents.ordered_by_birthdate(:desc)
The reason being is the scope is guaranteed to return a new ActiveRecord::Relation which when accessed hits the DB.
It may not be the best answer but it will work by changing your initial query, to include an .order for the birthdate field.
#grandParent = GrandParent
.includes(parents: { children: %I[grand_children friends] })
.order("parents.birthdate DESC")
.find_by_name(params[:name])
Then remove the .ordered_by_birthdate as you map the parent objects as they are already in the order you wanted. This has the disadvantage not using the scope ordered_by_birthdate defined on Parent. This might be ok depending on how you view responsibilities of controller vs model.
Alternatively the above code snippet could also be part part of a scope on GrandParent e.g.
class GrandParent
scope :family_tree, -> { includes(parents: { children: %I[grand_children friends] }).order("parents.birthdate DESC") }
end
Then you could do:
GrandParent.family_tree.find_by_name(params[:name])
I'm trying to implement a counter filter in my json. When I access the url api/v1/contacts?Results=2 [sic], I would like to get only two results from my json.
For this, I created two methods in my controller: an index that takes the information from the database and turns render into json, and a method that returns the number of times the json is returned.
class Api::V1::ContactsController < ApplicationController
before_action :results, only: [:index]
def index
#contacts = Contact.all
render json: {results: #contacts[0..#number_results]}
end
def results
if params[:results]
results = params[:results]["results"]
#number_results = results.to_i
else
#number_results = 3
end
end
end
Regardless of the value entered for results =, the value of #number_results is set to 0, so whenever I type results = I, get only the first result of json. If I do not type results = in the url, it sets #number_results to 3, and shows four results on the screen.
Can anyone help?
First, in the url you propose, "Results" is capitalized. If that is how you intend to submit it, then you'll need to search for it that way on the back end. But since all your code uses lowercase "results", we'll go with that. You should modify your url in the same way: api/v1/contacts?results=2.
If that's what your url looks like then the number you pass in is accessible in params[:results]. If you pass no parameter, then params[:results] will return nil. Don't call #to_i on the param before you check for its existence, because nil.to_i is 0.
All that said, here's what you probably want:
class Api::V1::ContactsController < ApplicationController
def index
number_results = params[:results] || 3
#contacts = Contact.all.limit(number_results.to_i)
render json: {results: #contacts}
end
end
If the result of params[:results] is nil, then number_results is assigned 3.
Now we use #limit to return only the number of contacts that was requested. This allows us to do away with the #results method entirely. You can also get rid of the before_action callback.
I have the following Search class method that takes a bunch of params and then builds a request.
def self.search(agent, params)
RealPropertySale.where(id: available_ids)
.joins(:address)
.by_state(state)
.by_suburb(suburb)
.by_post_code(post_code)
.by_country(country)
.paginate(page: page)
end
def self.by_state(state)
where(addresses: {state: state})
end
def self.by_suburb(suburb)
where(addresses: {suburb: suburb})
end
def self.by_post_code(post_code)
where(addresses: {post_code: post_code})
end
def self.by_country(country)
where(addresses: {country: country})
end
What is the correct way of handling if one of my custom class methods e.g. self.by_country(country) returns nil so that the query continues with whatever param/s are present. I have tried returning self if one the params is blank but the query is lost and the class is returned resulting in errors.
I agree with #Michael Gaskill that you probably should only call the scopes that actually affect the final query (i.e. that have meaningful params).
However, if you insist on the scopes ignoring nil parameters, you may make them return current_scope instead (which is an undocumented but useful method):
def self.by_state(state)
return current_scope if state.nil?
where(addresses: {state: state})
end
We did something similar by breaking it down like so :
response = RealPropertySale.where(id: available_ids)
.joins(:address)
response = response.by_state(state) if state
response = response.by_suburb(suburb) if suburb
response = response.by_post_code(post_code) if post_code
response = response.by_country(country) if country
response = response.paginate(page: page) if page
I like the readibility. I try to break it down to as many pieces as needed, but this should be adapted to your business logic. Don't know if, for you, it makes sense to check if suburb is provided for example.
I recently created a new controller in a pretty big app, (note: we just upgraded from Grails 2.0.3 to 2.3.7) and the created tests have all been failing inexplicably. Most of my errors consist of the model being [:] after a controller call. And therefore any methods on that model being null. I get stuff like:
groovy.lang.MissingMethodException:
No signature of method:
TransitionController.save() is applicable for argument types: (Transition)
values: [null] Possible solutions: save(), wait(), show(), any(),
wait(long), raw(java.lang.Object) at TransitionControllerSpec.Test the
save action correctly persists an instance(TransitionControllerSpec.groovy:45)
I have tried explicitly assigning the model by doing:
def model = controller.delete()
def model = controller.update()
//....
But I get the same result, an empty model map and null values if I try to access it.
As per this article (https://jira.grails.org/browse/GRAILS-8462) I could also try accessing the model by doing:
def model = controller.modelAndView.model
But this did not work for me either, producing the same results.
Any ideas on what might be happening?
Edit: Here are the first couple of tests
package com.hey.how.are.ya
import grails.test.mixin.*
import spock.lang.*
import org.junit.*
#TestFor(TransitionController)
#Mock(Transition)
class TransitionControllerSpec extends Specification {
def populateValidParams(params) {
assert params != null
params['reason'] = 'just cuz'
}
void "Test the index action returns the correct model"() {
when:"The index action is executed"
controller.index()
then:"The model is correct"
!model.transitionInstanceList
model.transitionInstanceCount == 0
}
void "Test the create action returns the correct model"() {
when:"The create action is executed"
controller.create()
then:"The model is correctly created"
model.transitionInstance!= null
}
//...grails generated tests here
}
Edit: Here's an interesting case! If I do:
void "Test the create action returns the correct model"() {
when:"The create action is executed"
def model = controller.create()
def inst = new Transition()
println "${model}"
println "${model.transitionInstance}"
then:"The model is correctly created"
model.transitionInstance != null
}
Then I get this as output:
[transitionInstance:null]
null
But the test passes. This only happens with create. What is going on??
Edit: Adding code for create and save
def create() {
[transitionInstance: new Transition(params)]
}
def save() {
def transitionInstance = new Transition(params)
if (!transitionInstance.save(flush: true)) {
render(view: "create", model: transitionInstance: transitionInstance])
return
}
flash.message = message(code: 'default.created.message', args: [message(code: 'transition.label', default: 'Transition'), transitionInstance.id])
redirect(action: "show", id: transitionInstance.id)
}
Edit: I can't spend too much time on this, but I'm pretty sure the problem is discrepancies between the controller generated and the test created. For what it's worth I was running version 2.0.2 of the scaffolding plugin and grails 2.3.7. I'm gonna throw out the tests created by the command and start from scratch, thanks for the help!
I had the same issue but solved it like following:
Inside the controller method:
render view: "_myTemplate", model: [instance: myInstance]
So the controller, still renders the template and inside the test spec I can test
assert(model.instance = myExpectedInstance)
I've seen various answers to similar questions, but not for this specific case:
I have a Grails gsp with several fields and a submit button. I want to enter a value in the first field, click submit, and have the controller return values into the remaining fields (including the original entry field). I want to do this with a simple page refresh (no AJAX) and it does not involve a database. I have it working fine with two gsp pages that are identical but have different names. I just call the first one, enter the value, and have the submit action point to the second. But I would like to be able to do it with a single gsp. A simplified version of my controller looks like this:
class CalculatorController {
def index = {
redirect(action: "input")
}
def input= {}
def calculateValues = {
def inputString = params.enterValue
def value2 = 'You entered something on the page'
[field1: inputString, field2: value2]
}
}
you need to have all of logic inside of the same closure if you want to use only one gsp. Test if the params are passed in and return the correct vals
Actually you don't need to have all of your logic in the same closure. Simply specify the view template to use in calculateValues...
def calculateValues = {
def inputString = params.enterValue
def value2 = 'You entered something on the page'
render(view: 'input', model: [field1: inputString, field2: value2])
}
By using the render method you can have multiple controller action reuse the same gsp view.