I have to implement map values in my Grails app.
I have a class that can contain 0..N OsmTags, and the key is unique.
In Java I would model this with a Map in each object, but I don't know how to map classes in Grails.
So I defined this class:
class OsmTag {
/** OSM tag name, e.g. natural */
String key
/** OSM tag value, e.g. park */
String value
static constraints = {
key blank:false, size:2..80,matches:/[\S]+/, unique:false
value blank:false, size:1..250,matches:/[\S]+/, unique:false
}
}
That works ok, but it's actually quite ugly because the tag key is not unique.
Is there a better way to model this issue?
Cheers
If your tags are simple strings, then you can use a map directly.
class Taggable {
Map tags // key : String, value : String
}
If I understand your question correctly, then you want to ensure that each tag is unique within a particular instance of the tagged entity?
Assume that the entity to which the tags are attached is named Taggable, then
you can enforce this requirement using a custom constraint:
class Taggable {
static hasMany = [ tags: OsmTag ]
}
class OsmTag {
static belongsTo = [ taggable: Taggable ]
/** OSM tag name, e.g. natural */
String key
/** OSM tag value, e.g. park */
String value
static constraints = {
key(blank:false, size:2..80,matches:/[\S]+/, unique:false,
validator: { val, obj ->
if (obj.taggable.tags.key.count(val > 1)) {
return false
}
}
)
value(blank:false, size:1..250,matches:/[\S]+/, unique:false)
}
}
If you're looking for a NoSQL solution, you could try using MongoDB with Grails. The most recent version (1.4) supports Geospatial indexing and querying.
Related
I needed to create a custom join table to re-model a many-to-many mapping and following some great posts on here came up with the model below.
Now my question is, if I have either a Course or Journey object in a GSP, how do I access the extra column data belonging to the join table.
In this example, I want to access the field named extraColumn1 in the CourseJourneyDetail within my GSP if I have either a journey or course instance
I've tried the following :
${course.courseJourneyDetail.extraColumn1}
but it didn't work.
Here are (relevant parts of) my domain classes :
class Course {
static hasMany = [journies: CourseJourneyDetail]
String courseName
String organisersDescription
Set<Journey> getJournies() {
return CourseJourneyDetail.findAllByCourse(this)*.journey
}
}
class Journey {
static hasMany = [courses: CourseJourneyDetail]
java.util.Date dateCreated
java.util.Date lastUpdated
boolean enabled = true
User user
Set<Course> getCourses() {
return CourseJourneyDetail.findAllByJourney(this)*.course
}
}
class CourseJourneyDetail implements Serializable {
String extraColumn1
static belongsTo = [course: Course, journey: Journey]
boolean equals(other) {
if (!(other instanceof CourseJourneyDetail)) {
return false
}
other.journey?.id == journey?.id &&
other.course?.id == course?.id
}
int hashCode() {
def builder = new HashCodeBuilder()
if (course) builder.append(course.id)
if (journey) builder.append(journey.id)
builder.toHashCode()
}
static constraints = {
}
static mapping = {
version false
id composite: ['course', 'journey']
}
}
Since you've established that each Course/Journey has a collection of CourseJourneyDetail's rather than a single instance, ${course.courseJourneyDetail.extraColumn1} won't work (as you've discovered).
If you break down your groovy expression into this: course.courseJourneyDetail, it doesn't really make sense based on the relationships you have created. The reason being, Course doesn't have a single CourseJourneyDetail but rather a collection.
If your desire is to have Course and Journey in a one-to-one relationship, but with a join table with additional columns, then your domain structure needs to change to reflect this: rather than using static hasHany in each class, you would switch to a single instance.
If your desire is to keep the many-to-many relationship, then you need to think about how to fetch the appropriate join object that represents the association. One example:
CourseJourneyDetail.findAllByCourseAndJourney(<courseInstance>, <journeyInstance>)
If you want the additional columns collected for all of the many-to-many associations, you can use a syntax that you are already using in your convience methods (getJournies and getCourses):
course.journies*.extraColumn1
This would output an array of Strings, so its usage makes less sense within a ${} expression; and more sense within a g:each. It entirely depends on how you plan on using this data.
I need to map Domain classes and subclasses of a legacy database.
The model thath I need to recreate with Grails is to this tables:
Tables structure
CARD_PAYMET and CHEQUE_PAYMENT is subclasses of PAYMENT and share a composite key of two field: OrderId and PaymentId.
I try some ex scenarios, but I can´t arrive to solution. None of then recreate the same model data, and I can´t change this model.
Can any one help me?
Thanks.
Your database looks like a good fit for table-per-subclass inheritance. First, since you're using a composite primary key, your domain classes need to implement the Serializable interface. Then it's a matter of mapping each table column to a property.
import groovy.transform.EqualsAndHashCode
#EqualsAndHashCode(includes=['orderId', 'paymentId'])
class Payment implements Serializable {
int orderId
int paymentId
float amount
static mapping = {
version false
tablePerHierarchy false
id composite: ['orderId', 'paymentId']
orderId column: 'OrderId'
paymentId column: 'PaymentId'
/* Assuming case-insensitive db, so I left out 'Amount'. */
}
}
class CardPayment extends Payment {
String cardType
static mapping = {
version false
cardType column: 'CardType'
}
}
class ChequePayment extends Payment {
int checkNumber
static mapping = {
version false
checkNumber column: 'CheckNumber'
}
}
Note: In this example I used Groovy's EqualsAndHashCode AST transformation to implement Serializable.
With the domain classes in place you'll be able to do GORM polymorphic queries:
def payments = Payment.list() // All Payments (Payment, CardPayment, and ChequePayment).
def cardPayments = CardPayment.list() // Only CardPayments.
...
def nicePayments = Payment.where { amount > 1000 }.list()
In many classes in my project, I have a String description and static mapping = { description sqlType: 'MEDIUMTEXT' }.
Is there anyway to declare a StringMediumText description which automatically links to a column in the database with sqlType: 'MEDIUMTEXT'?
So in the future I just need to declare:
Person {
StringMediumText description
}
You can do it by defining your own Hibernate user type
class Person {
String description
static mapping = {
description type: StringMediumTextType
}
}
You find everything regarding your question in the Reference Guide. Hope this helps
I am trying to find a way to add dynamic fields to a grails domain class. I did find the dynamic domain class plugin based on Burt's article, but this is way too much for our needs.
Supposed we have a domain class of person:
class Person extends DynamicExtendableDomainObject {
String firstName
String lastName
static constraints = {
firstName(nullable: false, blank: false, maxSize: 50)
lastName(nullable: false, blank: false)
}
}
Now customer a wants to also have a birthdate field in this. By using some sort of management tool, he adds this extra field in the database.
Customer b wants to also have a field middle name, so he is adding the field middle name to the person.
Now we implemented a DynamicExtendableDomainObject class, which the Person class inherits from. This adds a custom field to each Domain class inheriting from this to store the dynamic properties as JSON in it (kind of like KiokuDB in Perl stores them).
Now when Person is instantiated, we would like to add those dynamic properties to the Person class, to be able to use the standard Grails getter and setter as well as Templating functions for those.
So on customer a we could use the scaffolding and person would output firstName, lastName, birthDate, on customer b the scaffolding would output firstName, lastName, middleName.
The storing of the properties will be implemented by using the saveinterceptor, to serialize those properties to JSON and store them in the special field.
But we have not yet found a way to add these JSON properties dynamically to the domain class during runtime. Is there a good way to handle this? And if so, how to best implement this?
You can try to add the properties at runtime to the DomainClass of type DynamicExtendableDomainObject by expanding getProperty(), setProperty(), setProperties() in the metaClass and then use beforeUpdate(), beforeInsert() and afterLoad() to hook into Persistence.
For example in Bootstrap (or service):
def yourDynamicFieldDefinitionService
for(GrailsClass c in grailsApplication.getDomainClasses()){
if(DynamicExtendableDomainObject.isAssignableFrom(c.clazz)){
Set extendedFields = yourDynamicFieldDefinitionService.getFieldsFor(c.clazz)
//getProperty()
c.clazz.metaClass.getProperty = { String propertyName ->
def result
if(extendedFields.contains(propertyName)){
result = delegate.getExtendedField(propertyName)
} else {
def metaProperty = c.clazz.metaClass.getMetaProperty(propertyName)
if(metaProperty) result = metaProperty.getProperty(delegate)
}
result
}
//setProperty()
c.clazz.metaClass.setProperty = { propertyName , propertyValue ->
if(extendedFields.contains(propertyName)){
delegate.setExtendedField(propertyName, propertyValue)
delegate.blobVersionNumber += 1
} else {
def metaProperty = c.clazz.metaClass.getMetaProperty(propertyName)
if(metaProperty) metaProperty.setProperty(delegate, propertyValue)
}
}
//setProperties()
def origSetProperties = c.clazz.metaClass.getMetaMethod('setProperties',List)
c.clazz.metaClass.setProperties = { def properties ->
for(String fieldName in extendedFields){
if(properties."${fieldName}"){
delegate."${fieldName}" = properties."${fieldName}"
}
}
origSetProperties.invoke(delegate,properties)
}
}
}
with
abstract DynamicExtendableDomainObject {
String yourBlobField
Long blobVersionNumber //field to signal hibernate that the instance is 'dirty'
Object getExtendedField(String fieldName){
...
}
void setExtendedField(String fieldName, Object value){
...
}
def afterLoad(){
//fill your transient storage to support getExtendedField + setExtendedField
}
def beforeUpdate(){
//serialize your transient storage to yourBlobField
}
def beforeInsert(){
//serialize your transient storage to yourBlobField
}
}
I've got a grails domain class I have to persist in Redis, something like this:
class A {
String one
Integer two
B three
E four
mapWith = "redis"
}
class B {
String name
}
enum E {
VALUE1, VALUE2
}
When I persist an instance of class A with the GORM .save() method, Redis saves it correctly except for the enum field "four".
As you can see the fact is known and reported here: http://jira.grails.org/browse/GPREDIS-3
Is there a good workaround to save Enum or something similar?
We're thinking about an array of String objects, what do you think?
I've got this mostly implemented but it doesn't work for Gemfire and I'm waiting until it's fixed for all the supported nosql providers before pushing the fix. As a workaround you can use the inList constraint with a combination of a persistent String property and a non-persistent get/set pair with the name of your current property, e.g.
class A {
String one
Integer two
B three
String fourString
void setFour(E e) {
fourString = e?.name()
}
E getFour() {
fourString ? E.valueOf(fourString) : null
}
static constraints = {
fourString inList: E.values()*.name()
}
static transients = ['fourString']
static mapWith = "redis"
}