I have the following domain class:
package com.example
class Location {
String state
def getStatesList(){
def states = ['AL','AK','AZ','AR','CA','CO','CT',
'DC','DE','FL','GA','HI','ID','IL','IN','IA',
'KS','KY','LA','ME','MD','MA','MI','MN','MS',
'MO','MT','NE','NV','NH','NJ','NM','NY','NC',
'ND','OH','OK','OR','PA','RI','SC','SD','TN',
'TX','UT','VT','VA','WA','WV','WI','WY']
return states
}
}
In my gsp, I am trying to display the state list in a select dropdown as such
<g:select name="location.state" class="form-control" from="${com.example.Location?.getStatesList()}" value="${itemInstance?.location?.state}" noSelection="['': '']" />
In doing so, I am receiving "missing method exception"
If I change the method with list, I no longer receive the error, but I don't want that.
from="${com.example.Location?.list()}" // works
from="${com.example.Location?.getStatesList()}" // does not work
Any help is greatly appreciated.
As dmahaptro said, you can correct this issue by making getStatesList() a static method.
class Location {
String state
static List<String> getStatesList() {
['AL','AK','AZ','AR','CA','CO','CT',
'DC','DE','FL','GA','HI','ID','IL','IN','IA',
'KS','KY','LA','ME','MD','MA','MI','MN','MS',
'MO','MT','NE','NV','NH','NJ','NM','NY','NC',
'ND','OH','OK','OR','PA','RI','SC','SD','TN',
'TX','UT','VT','VA','WA','WV','WI','WY']
}
}
Then you'll be able to execute Location.statesList or Location.getStatesList().
Alternative
I think a cleaner alternative is using a final constant:
class Location {
String state
static final List<String> STATES =
['AL','AK','AZ','AR','CA','CO','CT',
'DC','DE','FL','GA','HI','ID','IL','IN','IA',
'KS','KY','LA','ME','MD','MA','MI','MN','MS',
'MO','MT','NE','NV','NH','NJ','NM','NY','NC',
'ND','OH','OK','OR','PA','RI','SC','SD','TN',
'TX','UT','VT','VA','WA','WV','WI','WY']
}
Then you can access the list the same way: Location.STATES. The difference is that the all-caps name implies a value that does not change (and does not require accessing the database).
list() is a method on the domain object's metaclass. In order to do what you're trying to do you'd have to instantiate an instance of Location (or add to the metaclass). I'd personally use an Enum instead if I were you.
You have to make getStatesList() static because you are not accessing an instance of the Location class.
Related
I am trying to find the replacement for org.apache.struts.actions.DispatchAction.types in Struts2.
Below code snippet:
if(types != null)
{
if(types.length == forward.length)
{
select.add(type[0]);
}
}
The forward is a string type object and select is a list type array.
The documentation for DispatchAction.types:
types
protected java.lang.Class[] types
The set of argument type classes for the reflected method call. These are the same for all calls, so calculate them only once.
There's not the same thing in Struts2. You should refresh your mind to understand that struts2 works differently than struts-1.
For example you can map your actions directly to the method. You can use SMI for method call like in this answer
You should place the annotation directly on the method. Because if you put it on the class the default method execute() is used for the mapping.
#ParentPackage("basePackage")
#Namespace("/")
#AllowedMethods("test")
public class UserAction {
#Action(value = "userAction")
public String test() {
// your code
rerurn Action.SUCCESS;
}
}
or use wildcard mapping like this answer.
The action name is overridden if used in the same package. The action name maps to a specific method or execute.
You can use wildcard mapping to put a method name in the action.
<action name="*product" class="com.DispatchAction" method="{1}">
I have the following Groovy script as part of a Jenkins pipeline
permissions.groovy
enum PermissionType {
ANONYMOUS,
AUTHENTICATED
}
def get_job_permissions(PermissionType permission) {
...
}
return this
I load this file into another Groovy file as part of my Jenkins pipeline and call get_job_permissions passing through one of the enums as a parameter.
pipeline.groovy
def job_permissions = load 'permissions.groovy'
job_permissions.get_job_permissions(job_permissions.PermissionType.AUTHENTICATED)
Jenkins fails on this with the following error (I've verified that in this case 'Script3' is the call to get_job_permissions with the enum parameter).
groovy.lang.MissingPropertyException: No such property: PermissionType for class: Script3
I know the script load and call is correct, as I can change the signature of get_job_permissions to the following, pass through a random string in pipeline.groovy and the call goes through correctly.
def get_job_permissions(def permission) {
...
}
If I don't change the signature, and still pass through a random string, Jenkins fails the build as it can't find the method it thinks I'm calling (which is true, it's not there, it's expecting a PermissionType type).
I've tried a number of different things to expose PermissionType to the calling script
Adding #Field (not legal Groovy)
Changing the enum definition to public def PermissionType (not legal Groovy)
Removing and adding public to the enum definition
Changing the case (though I believe enums need to start with an upper case character?)
None of these solutions allow me to reference the enum type from the calling script, and I understand this is because I'm trying to access a type by referencing it through an instance of the script.
But if I can't do it this way, what is the best way to do it?
Thanks
I managed to get something to work - I certainly know it's probably not the right, or even good, way to do it, but it unblocked me and gave me what I needed.
Rather than define the enum in a script as you normally would
enum PermissionType {
ANONYMOUS,
AUTHENTICATED
}
I created a class containing the enum with member variables initialised to the values within the enum.
permissions.groovy
public class PermissionTypes {
public enum Values {
ANONYMOUS,
AUTHENTICATED
}
public final PermissionTypes.Values ANONYMOUS = PermissionTypes.Values.ANONYMOUS
public final PermissionTypes.Values AUTHENTICATED = PermissionTypes.Values.AUTHENTICATED
}
#Field final PermissionTypes Permissions = new PermissionTypes()
I can then expose an instance of that class in the script, load it as normal and I finally get access to the enum values.
pipeline.groovy
def job_permissions = load 'permissions.groovy'
job_permissions.get_job_permissions(job_permissions.Permissions.AUTHENTICATED)
I think we can all agree this is slightly bonkers, but it gave me what I needed.
Only issues I have with this (which I can live with for now)
You can only load the file ones in a script otherwise you get a duplicate class exception
You can't use the type in an external method, only the values - OK for me since any methods taking in the type are local to the class definition
Would still love to know the right way to do this :)
I ran into this problem recently and found a different solution that looks less hacky.
enum PermissionType {
ANONYMOUS,
AUTHENTICATED
}
def get_job_permissions(PermissionType permission) {
...
}
// Do this before you return out to make the enum available as well
this.PermissionType = PermissionType
return this
I prefer to use:
MyEnumClass.groovy:
package cl.mypackage.utils
class MyEnumClass {
static enum MyEnum {
FOO, BAR, QWERTY
}
}
How to use:
import cl.mypackage.utils.MyEnumClass
def in_some_place() {
def fooEnum = MyEnumClass.MyEnum.FOO
}
Regards
Given the following domain class:
class Dog {
Object name // changing the type to String fixes it
}
And this unit test:
import grails.test.mixin.Mock
import grails.test.mixin.TestMixin
import grails.test.mixin.support.GrailsUnitTestMixin
import spock.lang.*
/**
* See the API for {#link grails.test.mixin.support.GrailsUnitTestMixin} for usage instructions
*/
#TestMixin(GrailsUnitTestMixin)
#Mock([Dog])
class DogSpec extends Specification {
def setup() {
}
def cleanup() {
}
void "test something"() {
Dog dog = new Dog(name:"sparky")
// dog.name = "sparky" // adding this line also fixes it
expect:"fix me"
dog.name == "sparky"
}
}
Running grails test-app fails, but if you change the type of Dog.name to String, it works fine. Debugging brings me to realize that Dog.name never gets assigned and is null. If I were to set dog.name via regular assignment after constructing it as above, the test passes.
This issue does not occur in Groovy Script using the same map constructor assignment.
I want my type to be Object as it varies depending on the use case.
Any idea why this is happening? Is it a bug in Grails?
I did some debugging and I found out that when a field accepts an Object, grails framework doesn't do databinding on this property.
More specifically, the Dog#name field doesn't belongs to the whitelist of properties when It is declared as Object.
That's why this doesn't work.
If you want to debug, see the
grails.web.databinding.DataBindingUtils#getBindingIncludeList(final Object object)
If you put a breakpoint there, you will see how grails generates the whitelist of properties.
I think this makes a lot of sense since It's related to security!
See more about it here
If you wanna bind on an Object field nevertheless, this code may help you:
class Dog {
Object name
static constraints = {
name(bindable: true)
}
}
I have the following classes, they are bit simplified for example.
class LineInfo
{
Id,
LineNumber,
SubAccountNumer,
MobileNumber
}
class SuspendLinesVM{
public List<LineInfo> Lines{ get;set; }
}
I receive SuspendLinesVM in my action and all Lines are created dynamicaly from client. Each element that belongs to concrete LineInfo in form has name with template 'lineid{Id}_ElementName'. So they comes to me in form like:
lineid0001_LineNumber
lineid0001_SubAccountNumer
lineid0001_MobileNumber
lineid0021_LineNumber
lineid0021_SubAccountNumer
lineid0021_MobileNumber
When some error occures while validation, I need a way to set the failed property as it came in request to highlight invalid fields in the view.
I left questions where I got confused.
public class LineInfoValidator: AbstractValidator<LineInfo>
{
public LineInfoValidator()
{
RuleFor(m => m.LineNumber)
.NotEmpty().WithMessage("Line # is required").OverridePropertyName( ??? )
.InclusiveBetween(1, 9999).WithMessage("Line # must be in range [1, 9999]").OverridePropertyName( ??? )
...
I need a way to do something like *(instance, propertyName) => return string.format('lineid_{0}_{1}', instance.Id, propertyName)*.
Any ideas ?
Resolved with 'WithState' method. Thanks to Jeremy! His solution is here http://fluentvalidation.codeplex.com/discussions/278892
JeremyS
Nov 10, 2011 at 5:16 PM
Unfortunately this isn't something that is supported.
Property names are resolved only once when the validator is instantiated, as opposed to error messages which are generated when the validator is executed. In this case, you need to inspect the instance being validated to generate the property name, which isn't actually possible - the closest you'd be able to get is to use the WithState method to associate some custom state with the failure:
RuleFor(x => x.LineNumber)
.NotEmpty()
.WithState(instance => string.Format("lineid_{0}_LineNumber", instance.Id));
Once you invoke the validator and get back the ValidationResult, you can retrieve this from the CustomState property on the ValidationFailure.
Jeremy
Given that you're using FluentValidator, you should be able to set the collection validator on the SuspendedLinesVM object like so:
public class SelectedLinesVMValidator : AbstractValidator<SelectedLinesVM>
{
public SelectedLinesVMValidator()
{
RuleFor(x=>x.Lines).SetCollectionValidator(new LineInfoValidator());
}
}
If you do this, then as per the documentation you will get back a collection of errors which relate to the index of the failed property.
I tried to change the standard 'id' in grails:
calls Book {
String id
String title
static mapping {
id generator:'assigned'
}
}
unfortunately, I soon noticed that this breaks my bootstrap. Instead of
new Book (id:'some ISBN', title:'great book').save(flush:true, failOnError:true)
I had to use
def b = new Book(title:'great book')
b.id = 'some ISBN'
b.save(flush:true, failOnError:true)
otherwise I get an 'ids for this class must be manually assigned before calling save()' error.
but that's ok so far.
I then encountered the same problem in the save action of my bookController. But this time, the workaround didn't do the trick.
Any suggestions?
I known, I can rename the id, but then I will have to change all scaffolded views...
That's a feature of databinding. You don't want submitted data to be able to change managed fields like id and version, so the Map constructor that you're using binds all available properties except those two (it also ignores any value for class, metaClass, and a few others).
So there's a bit of a mismatch here since the value isn't managed by Hibernate/GORM but by you. As you saw the workaround is that you need to create the object in two steps instead of just one.
I can't replicate this problem (used Grails 2.0.RC1). I think it might be as simple as a missing equal sign on your static mapping = { (you just have static mapping {)
Here's the code for a domain object:
class Book {
String id
String name
static mapping = {
id generator:'assigned'
}
}
And inside BootStrap.groovy:
def init = { servletContext ->
new Book(name:"test",id:"123abc").save(failOnError:true)
}
And it works fine for me. I see the id as 123abc.
You need to set the bindable constraint to true for your id prop, e.g.
class Employee {
Long id
String name
static constraints = {
id bindable: true
}
}