The docs say that depending on version, accessing Domain.constraints or Domain.constrainedProperties should give a Map of key values.
https://grails.github.io/grails2-doc/2.5.4/ref/Domain%20Classes/constraints.html
At runtime the static constraints property is a Map such that the keys in the Map are property names and the values associated with the keys are instances of ConstrainedProperty:
However, using 2.5+, accessing the constraints property at runtime doesn't give a map, but a closure, and I can't access the ConstrainedProperty instances.
I tried using grails class utils to access the static property also
GrailsClassUtils.getStaticFieldValue(Domain,"constraints")//this is still a closure
GrailsClassUtils.getStaticFieldValue(Domain,"constrainedProperties")//null, this property doesn't exist below version 3.0
Property access doesn't work for me like the example in the docs
Domain.constraints //returns closure
but using the method getter does
Domain.getConstraints() //returns the map
See the project at https://github.com/jeffbrown/constraintsmapdemo.
https://github.com/jeffbrown/constraintsmapdemo/blob/master/grails-app/domain/demo/Widget.groovy:
package demo
class Widget {
int width
int height
static constraints = {
width range: 1..100
height range: 1..50
}
}
The test at https://github.com/jeffbrown/constraintsmapdemo/blob/master/test/unit/demo/WidgetSpec.groovy passes:
package demo
import grails.test.mixin.TestFor
import spock.lang.Specification
#TestFor(Widget)
class WidgetSpec extends Specification {
void "test accessing the constraints property"() {
when:
def propValue = Widget.constraints
then:
propValue instanceof Map
propValue.containsKey 'width'
propValue.containsKey 'height'
}
}
If you are not using static compilation, Widget.constraints will evaluate to the Map. If you are using static compilation, Widget.getConstraints() will return the Map but Widget.constraints will evaluate to the closure.
Related
So, basically I need to create restrictions of which types can be used in a Type variable, something like this:
class ElementFilter<T extends Element> {
final Type<T> elementType; // What I want is something like Type<T>, but Type does not have a generic parameter
ElementFilter(this.elementType);
}
List<T> filterElements<T extends Element>(ElementFilter<T> element) {
return elements.where((el) => _isOfType(el, element.type)).toList();
}
filterElements(ElementFilter(ClassThatExtendsElement)); // Would work fine
filterELements(ElementFilter(String)); // Error, String does not extends Element
So it would only be possible to create ElementFilters with types that extend Element. Is this possible in some way?
I think you probably want:
/// Example usage: ElementFilter<ClassThatExtendsElement>();
class ElementFilter<T extends Element> {
final Type elementType;
ElementFilter() : elementType = T;
}
Unfortunately, there's no way to make the generic type argument non-optional. You will have to choose between having a required argument and having a compile-time constraint on the Type argument.
Dart doesn't support algebraic types, so if you additionally want to support a finite set of types that don't derive from Element, you could make specialized derived classes and require that clients use those instead of ElementFilter. For example:
class StringElementFilter extends ElementFilter<Element> {
#override
final Type elementType = String;
}
(You also could create a StringElement class that extends Element if you want, but at least for this example, it would serve no purpose.)
I highly recommend not using Type objects at all. Ever. They're pretty useless, and if you have the type available as a type parameter, you're always better off. (The type variable can always be converted to a Type object, but it can also be actually useful in many other ways).
Example:
class ElementFilter<T extends Element> {
bool test(Object? element) => element is T;
Iterable<T> filterElements(Iterable<Object?> elements) =>
elements.whereType<T>();
}
List<T> filterElements<T extends Element>(ElementFilter<T> filter) =>
filter.filterElements(elements).toList();
filterElements(ElementFilter<ClassThatExtendsElement>()); // Would work fine
filterElements(ElementFilter<String>()); // Error, String does not extends Element
I am trying to come up with an example of how we could search for the #override metadata annotation using reflection in Dart.
In the examples I used to learn the dart:mirrors library and reflection, they were always searching for custom made annotations.
Here is an example where they search for a custom "Todo" annotation
When searching for custom made annotations, they would simply compare the metadata's reflectee to the class data type to check for a match.
In Dart's documentation linked below, you can see an example implementation of an _Override instance.
Here is Dart documentation on the override constant
This lead to me to try:
if(meta.reflectee is _Override) {
print('Found!);
}
But the "_Override" cannot be resolved and suggest no imports to access such a data instance.
I am able to toString the reflectee for comparison but I feel like it is a dirty solution:
if (meta.reflectee.toString() == "Instance of '_Override'") {
print('Found!');
}
When using the #override annotation, I am struggling to find a way to compare the metadata's reflectee to the instance type of _Override.
Here is the Dog class:
class Dog extends Animal {
Dog(String name) : super(name);
#override
void makeNoise() {
print('Bark, bark!');
}
}
Here is my reflection search code:
Dog dog = Dog('Harper');
InstanceMirror instanceMirror = reflect(dog);
ClassMirror classMirror = instanceMirror.type;
classMirror.instanceMembers.forEach((_, member) {
print(member.owner.simpleName);
print(member.simpleName);
print(member.isRegularMethod);
member.metadata.forEach((meta) {
print(meta.reflectee);
if (meta.reflectee is _Override) {
print('Found!');
}
});
});
Finally, here is my output when the instanceMembers.forEach loop gets to the method I am interested in:
Symbol("Dog")
Symbol("makeNoise")
true
Instance of '_Override'
Use:
if (meta.reflectee == override) {
print('Found!);
}
or
if (identical(meta.reflectee, override)) {
print('Found!);
}
Dart constants are canonicalized, and the override object (an instance of the private _Override class) does not override operator== from Object, so the two expressions do the same thing.
For annotation classes which have data, you don't know the exact instance, so you have to do type checks. For marker annotations like override, which only have one instance, you can compare to the exact constant instance used for annotating.
Say I have the following Annotation and 2 classes:
class AppModel extends Reflectable {
final String name;
const AppModel([this.name])
: super(newInstanceCapability, metadataCapability);
}
const appModel = const AppModel();
#appModel
class ImGonnaBePickedUp {
}
#AppModel(' :( ')
class AndImNotPickedUpOnServer_IDoOnWebClient {
}
main() {
appModel.annotatedClasses // that's what I mean by "Picked Up".
}
On CmdApp side (Server): only AndImNotPickedUpOnServer_IDoOnWebClient is given in appModel.annotatedClasses.
On the web side, both classes are given.
Long story short, how do I retrieve classes annotated with direct const constructor calls like in the example above #AppModel(' :( ') (for both CmdApp and Web)?
since version 0.5.4 reflectable classes doesn't support constructors with arguments
This appears in reflectable documentation:
Footnotes: 1. Currently, the only setup which is supported is when the metadata object is an instance of a direct subclass of the class [Reflectable], say MyReflectable, and that subclass defines a const constructor taking zero arguments. This ensures that every subclass of Reflectable used as metadata is a singleton class, which means that the behavior of the instance can be expressed by generating code in the class. Generalizations of this setup may be supported in the future if compelling use cases come up.
one possible solution could be to use a second annotation to handle the name, for example:
import 'package:reflectable/reflectable.dart';
import 'package:drails_commons/drails_commons.dart';
class AppModel extends Reflectable {
const AppModel()
: super(newInstanceCapability, metadataCapability);
}
const appModel = const AppModel();
class TableName {
final String name;
const TableName(this.name);
}
#appModel
class ImGonnaBePickedUp {
}
#appModel
#TableName(' :( ')
class AndImNotPickedUpOnServer_WorksOnWebClient {
}
main() {
print(appModel.annotatedClasses); // that's what I mean by "Picked Up".
print(new GetValueOfAnnotation<TableName>()
.fromDeclaration(appModel.reflectType(AndImNotPickedUpOnServer_WorksOnWebClient)).name);
}
Note: I'm also using drails_common package
Similar to my last question (Grails databinding: creating instances of an abstract class), I want to use data binding with a class that contains a collection of abstract classes with a hasMany relationship, but in this case, instead of using a List, I'm using a Map.
I created a smalll project with a failing integration test to show the problem that can be found in Github, run it with:
grails test-app -integration -echoOut DataBinding
Anyway, I'll explain the problem by describing the classes and the test here:
class LocalizableContent {
Map contentByLocale = [:].withDefault { locale -> new Text() }
static hasMany = [ contentByLocale : Content ]
}
abstract class Content {
static belongsTo = [ localizableContent : LocalizableContent ]
static constraints = {
localizableContent nullable:true
}
}
class Text extends Content {
String text
}
As you can see, I'm already using the withDefault trick, but apparently it's not being called by Grails / Spring (I even tried to throw an exception in the default closure to verify that the code is not executed).
For the sake of the test, I also created a LocalizableContentController which is empty. With all that, the following integration test then fails:
void testMapDatabinding() {
def rawParams = [ 'contentByLocale[en].text': 'Content' ]
def controller = new LocalizableContentController()
controller.request.addParameters(rawParams)
controller.request.setAttribute(GrailsApplicationAttributes.CONTROLLER, controller)
def localizableContent = new LocalizableContent(controller.params)
assert localizableContent?.contentByLocale['en']?.text == 'Content'
}
It says that localizableContent.contentByLocale is a map which looks like ['en': null], so apparently the data binding is understanding the map syntax and trying to create an entry for the 'en' key. But is not trying first to get the entry for that key, since the withDefault is not being called.
The following one tests that the withDefault works fine, and it passes:
void testMapByDefaultWithNoDatabinding() {
assert new LocalizableContent().contentByLocale['en']?.getClass() == Text
}
What am I missing here?
withDefault is nothing but a pattern to provide a valid value if you face an unknown key. For example, consider the below use case:
def map = [:].withDefault{k->
println k //Should print 'a'
10
}
map.test = 32
assert map.test == 32
assert map.a == 10
It takes the unknown key as the parameter, you cannot pass in any value to it, which is kind of logical, because it provides a default value instead of a value being provided.
In your case, the data binding would work if set the value to Text like:
Map contentByLocale = [:].withDefault { locale ->
//locale is the key. 'en' in this case
new Text(locale: locale, text: 'Content')
}
provided you have your Text class defined as
class Text extends Content{
String locale
String text
}
I defined an annotation as follows
import java.lang.annotation.ElementType
import java.lang.annotation.Inherited
import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy
import java.lang.annotation.Target
/**
* Annotation for any object that exposed a remote interface
*/
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
public #interface Remote {
String label()
}
and i'm trying to use it this way
import com.yascie.annotation.Remote
#Remote("Bar")
class Foo {
String name
String value
static String code
}
I keep getting though an error saying that the annotation is missing element label
java.lang.annotation.IncompleteAnnotationException: Remote missing element label
Now when i tried to inspect the annotation object i can see that a label method is available trough a proxy but i can't access it. Any ideas ?
Remote annotation = objectClass.clazz.getAnnotation(Remote.class);
annotation.metaClass.methods.each {println it}
public final java.lang.String $Proxy14.label()
ken
You have two options. If you want to use the #Remote("Bar") syntax then you need to change the label() method to value() since that's the method name for the default property for annotations when the name isn't specified.
If you want it to be called label() though, specify it as #Remote(label="Bar")