I have two entities; municipality and city. Municipalities are assumed to have unique names whereas cities are assumed to be unique within their municipality.
Is there a way to set up a constraint for cities so that the combination of its name and its municipality's name must be unique?
Entities
class XdCity(entity: Entity) : XdEntity(entity) {
companion object : XdNaturalEntityType<XdCity>()
var name by xdRequiredStringProp()
var municipality: XdMunicipality by xdLink1(
XdMunicipality::cities,
onDelete = OnDeletePolicy.CLEAR,
onTargetDelete = OnDeletePolicy.CASCADE
)
}
class XdMunicipality(entity: Entity) : XdEntity(entity) {
companion object : XdNaturalEntityType<XdMunicipality>()
var name by xdRequiredStringProp(unique = true)
val cities by xdLink1_N(
XdCity::municipality,
onDelete = OnDeletePolicy.CASCADE,
onTargetDelete = OnDeletePolicy.CLEAR
)
}
Test case
#Test
fun testAddSameCityName() {
Database.store.transactional {
val municipality = XdMunicipality.new("Mun 1")
val city = XdCity.new("City")
city.municipality = municipality
}
// Allow insertion of same city name in other municipality
Database.store.transactional {
val municipality = XdMunicipality.new("Mun 2")
val city = XdCity.new("City")
city.municipality = municipality
}
// Do not allow insertion of existing city name in municipality
assertFailsWith<ConstraintsValidationException> {
Database.store.transactional {
val municipality = XdMunicipality.find("Mun 1")
val city = XdCity.new("City")
city.municipality = municipality
}
}
}
The advised approach is to use xdParent and xdChildren relations between XdCity and XdMunicipality.
It may be not so easy to change relation when a database has history. To solve the problem with unique names in scope of municipality you can use composite indexes for entity types like here:
companion object : XdNaturalEntityType<XdCity>() {
override val compositeIndices = listOf(
listOf(XdCity:: municipality, ...may goes anything like XdCity::name)
)
}
Related
I need some help on my API, when I'm on web, the order is saving correct, but when its on API, it goes all wrong:
def test = parseJSON.sort { a, b -> a.ID <=> b.ID } //or dateTime, will print the same
//order when I print each of them
[IDWeb:0, conductivity:0, ReportId:2, dissolvedOxygen:0, levelWater:1, ID:1, ph:0, redoxPotential:0, temperature:0]
[IDWeb:0, conductivity:0, ReportId:2, dissolvedOxygen:0, levelWater:0, ID:2, ph:0, redoxPotential:0, temperature:0]
[IDWeb:0, conductivity:0, ReportId:2, dissolvedOxygen:0, levelWater:0, ID:3, ph:0, redoxPotential:0, temperature:0]
[IDWeb:0, conductivity:0, ReportId:2, dissolvedOxygen:0, levelWater:4, ID:4, ph:0, redoxPotential:0, temperature:0]
test.each{
def sample = new SampleWater()
sample.levelWater = it.levelWater
sample.conductivity = it.conductivity
sample.dissolvedOxygen = it.dissolvedOxygen
sample.redoxPotential = it.redoxPotential
sample.ph = it.ph
sample.temperature = it.temperature
water.addToSamples(sample)
}
return water
My problem is that addTo is not saving in order. How can I solve this?
Make sure you have defined the type of samples as a List in your Water domain class so that we can maintain the insertion order:
class Water {
static hasMany = [samples: Sample]
List<Sample> samples = []
}
class Sample {
def levelWater
}
By default implementation of hasMany is of type Set which does not maintain the insertion order but is responsible for uniqueness.
Since, now you samples will be saved in the same order as they are inserted.
You have to specify with order you want to apply to the list of SampleWater in the "water" domain class.
i.e:
class BlogCategory {
static hasMany = [
entries : BlogEntry
]
static mapping = {
entries: sort:'dateCreated', order:'desc'
}
}
In this example BlogEntry will be ordered respect dateCreated.
Just for background, let us have these three domain classes:
class Group {
Long id
Person person
}
class Person {
Long id
Country country
String name
}
class Country {
Long id
}
So, with these classes in mind, I am given a Group object's id as well as a Country object's id. I would like to get the list of Person objects based on these two.
It seems relatively simple, but I am new to criteria queries and so I am struggling to figure out what I am doing wrong. This is what I have so far:
def c = Group.createCriteria()
def names = c.list (order: "asc") {
createAlias('person', 'p')
createAlias('p.country', 'c')
and {
eq ('c.id', Long.valueOf(countryId))
eq ('id', groupId)
}
projections {
property('p.name')
}
}
Of course, this is wrong as it is throwing errors. Can someone please let me know what I am doing wrong?
Thanks for your help!
static def searchPersons(Map params) {
return Group.createCriteria().list(params, {
if(params.groupId) {
eq('id', params.groupId.toLong())
}
person {
order('name')
if(params.countryId) {
country {
eq('id', params.countryId.toLong())
}
}
}
projections {
property('person')
}
})
}
Still, it might be better to add the necessary associations (hasMany, etc.) on your domains.
The first thing you can do is improve the associations between your domain classes. This will help make criteria queries simpler (and deter monkey-patching later).
In your example, the association between Person an Group is a one-to-many; one person can have many groups. That may be your intention, but it also means that a group can have only one person. Basically, there's no way to group people together.
I'm going to assume that you want a many-to-one relationship so that many Person (people) can be in the same Group. With this in mind, the domain classes (with the explicit IDs left in) would look like this:
class Group {
Long id
}
class Person {
Long id
Country country
String name
Group group
}
class Country {
Long id
}
As for the query, since your expected result is instances of Person, the best place to start is with Person rather than Group.
List of Person instances
Here's how to get a list of Person instances.
Where query
def people = Person.where {
country.id == countryId
group.id == groupId
}.list()
Criteria query
def people = Person.withCriteria {
country {
eq 'id', countryId as Long
}
group {
eq 'id', groupId
}
}
List of Person names
Notice that there's a discrepancy between your question and example. You asked for a list of Person instances, yet your example demonstrates attempting to get a list of Person names .
Here's how to get a list of names of the Person instances.
Where query
def names = Person.where {
country.id == countryId
group.id == groupId
}.projections {
property 'name'
}.list()
Criteria query
def names = Person.withCriteria {
country {
eq 'id', countryId as Long
}
group {
eq 'id', groupId
}
projections {
property 'name'
}
}
I would like to have a util for building queries, so that I can add specificity to a common query rather than hard coding similar queries over and over again.
For instance:
DetachedCriteria query = DeviceConfiguration.where { ... }
while(query.list(max: 2).size() > 1) QueryUtil.addConstraint(query, newConstraint)
But I'm having trouble with queries that involve many-to-many relationships.
If my domain classes are:
class StringDescriptor {
String name
String stringValue
static hasMany = [ deviceConfigurations: DeviceConfiguration ]
static belongsTo = DeviceConfiguration
}
class DeviceConfiguration {
Integer setting1
Integer setting2
static hasMany = [ stringDescriptors: StringDescriptor ]
}
And my device configurations look like this:
DeviceConfiguration hondaAccord = new DeviceConfiguration(setting1: 1, setting2: 1)
DeviceConfiguration hondaCivic = new DeviceConfiguration(setting1: 2, setting2: 2)
DeviceConfiguration accord = new DeviceConfiguration(setting1: 3, setting2: 3)
StringDescriptor hondaDescriptor = new StringDescriptor(name: "make", stringValue: "honda")
StringDescriptor civicDescriptor = new StringDescriptor(name: "model", stringValue: "civic")
StringDescriptor accordDescriptor = new StringDescriptor(name: "model", stringValue: "accord")
hondaAccord.addToStringDescriptors(hondaDescriptor)
hondaAccord.addToStringDescriptors(accordDescriptor)
hondaCivic.addToStringDescriptors(hondaDescriptor)
hondaCivic.addToStringDescriptors(civicDescriptor)
accord.addToStringDescriptors(accordDescriptor)
hondaAccord.save(failOnError: true)
hondaCivic.save(failOnError: true)
accord.save(failOnError: true, flush: true)
I would like to be able to do this:
def query = DeviceCollector.where{ stringDescriptors {name =~ "make" & stringValue =~ "honda"} }
if(query.list(max: 2)?.size() > 1)
def query2 = query.where { stringDescriptors {name =~ "model" & stringValue =~ "civic"} }
if(query2.list(max: 2)?.size() > 1)
//...
But that doesn't work - query2 gives the same results as the first query. And yet when I do THIS, it works perfectly:
def query = DeviceCollector.where{ stringDescriptors {name =~ "make" & stringValue =~ "honda"} }
if(query.list(max: 2)?.size() > 1)
def query2 = query.where { eq('setting1', 1) }
if(query.list(max: 2)?.size() > 1)
def query3 = query.build { eq('setting2', 1) }
Please advise :(
EDIT thanks to injecteer
Now my domain includes this:
class DeviceConfiguration {
//...
static namedQueries = {
byStringDescriptor { String name, String value ->
stringDescriptors {
ilike 'name', name
ilike 'stringValue', value
}
}
}
}
And my attempt to string the queries together looks like this:
//Lists hondaAccord and hondaCivic
DeviceConfiguration.byStringDescriptor("make", "honda").list()
//Lists hondaAccord and accord
DeviceConfiguration.byStringDescriptor("model", "accord").list()
// LISTS NOTHING... BUT WHYYYYY?
DeviceConfiguration.byStringDescriptor("make", "honda").byStringDescriptor("model", "accord").list()
I am confused. Yet again.
EDIT thanks to injecteer's updated answer
Yay, here is the named query that worked for me:
class DeviceConfiguration {
//...
static namedQueries = {
byStringDescriptor { List<StringDescriptor> descriptors ->
sizeEq('stringDescriptors', descriptors.size())
stringDescriptors {
or {
for(descriptor in descriptors) {
and {
ilike 'name', descriptor.name
ilike 'stringValue', descriptor.stringValue
}
}
}
}
}
}
}
The results (YAYYY) :) ...
StringDescriptor hondaDescriptor = new StringDescriptor(name: "make", stringValue: "honda")
StringDescriptor accordDescriptor = new StringDescriptor(name: "model", stringValue: "accord")
//returns nothing - **check**
def hondaQuery = DeviceConfiguration.byStringDescriptor([hondaDescriptor]).list()
//returns accord configuration - **check**
def accordQuery = DeviceConfiguration.byStringDescriptor([accordDescriptor]).list()
//returns just the hondaAccord configuration - **YESSSSSS**
def hondaAccordQuery = DeviceConfiguration.byStringDescriptorUsingOr([hondaDescriptor, accordDescriptor]).listDistinct()
injecteer is my favorite person ever.
Use criteria query or named queries. they both allow for better chaining
class DeviceConfiguration {
static namedQueries = {
byDescriptors { List vals ->
stringDescriptors {
or{
for( def tuple in vals ){
and{
ilike 'name', "%${tuple[ 0 ]}%"
ilike 'stringValue', "%${tuple[ 1 ]}%"
}
}
}
}
}
}
}
so you can call:
DeviceConfiguration.byDescriptors( [ [ 'make', 'honda' ], [ 'model', 'accord' ] ] ).findAllBySetting1( 10 )
you should know, what conjunction is appropriate and or or
UPDATE 2
with so many of ands you won't find anything...
if you fire up a query like blah( honda, accord ).list() it would try find stringDescriptors with name='honda' AND name='accord' which is not possble, so it returns no results!
That's why I tend to think, that your domain model does NOT allow such queries at all - even at SQL-level.
Your attributes shall be clearly distinguishable, so that you can find by honda (type 'make') and accord (type 'model') it shouldn't look for "honda" in "model".
Can a single DeviceConfiguration instance contain several StringDescriptors of the same type?
I have some classes that look like this:
class User {
boolean enabled
String username
}
class ExampleClass {
User firstUser
User secondUser
}
My end goal is to find all instances of User where enabled == true OR the instance of User is associated with ExampleClass.
Where this code is running I don't have access to the variable names firstUser or secondUser.
With that said, I need to be able to find all instance of User associated with ExampleClass, disregarding which variable (firstUser or secondUser) the association was made through. How do I do this?
UPDATE:
The best I can come up with is this method in my User domain class. I the example I gave above I have an ExampleClass which has multiple fields of the User type. In fact I have multiple classes with multiple fields of the User type. This is why I get the domain class from the object being passed in instead of just typing ExampleClass.
static List findAllEnabledOrAssociatedWith( Object obj = null ) {
if( obj?.id ) { // Make sure the object in question has been saved to database.
List list = []
obj.domainClass.getPersistentProperties().each {
if( it.getReferencedPropertyType() == User ) {
def propertyName = it.getName()
list += User.executeQuery( "SELECT DISTINCT ${propertyName} FROM ${obj.class.getSimpleName()} obj INNER JOIN obj.${propertyName} ${propertyName} WHERE obj =:obj", [ obj: obj ] )
}
}
list.unique()
return User.executeQuery( "SELECT DISTINCT users FROM User users WHERE users.enabled=true OR users IN(:list)", [ list: list ] )
} else {
return User.executeQuery( "SELECT DISTINCT users FROM User users WHERE users.enabled=true" )
}
}
def sql = '''
from
User u,
ExampleClass ex
where
u.enabled = 1 or (u = ex.firstUser or u = ex.secondUser)'''
def users = User.findAll(sql)
I have the following code which adapts linq entities to my Domain objects:
return from g in DBContext.Gigs
select new DO.Gig
{
ID = g.ID,
Name = g.Name,
Description = g.Description,
StartDate = g.Date,
EndDate = g.EndDate,
IsDeleted = g.IsDeleted,
Created = g.Created,
TicketPrice = g.TicketPrice
};
This works very nicely.
However I now want to populate a domain object Venue object and add it to the gig in the same statement. Heres my attempt....
return from g in DBContext.Gigs
join venue in DBContext.Venues on g.VenueID equals venue.ID
select new DO.Gig
{
ID = g.ID,
Name = g.Name,
Description = g.Description,
StartDate = g.Date,
EndDate = g.EndDate,
IsDeleted = g.IsDeleted,
Created = g.Created,
TicketPrice = g.TicketPrice,
Venue = from v in DBContext.Venues
where v.ID == g.VenueID
select new DO.Venue
{
ID = v.ID,
Name = v.Name,
Address = v.Address,
Telephone = v.Telephone,
URL = v.Website
}
};
However this doesnt compile!!!
Is it possible to adapt children objects using the "select new" approach?
What am I doing so very very wrong?
Your inner LINQ query returns several objects, not just one. You want to wrap it with a call like:
Venue = (from v in DBContext.Venues
where v.ID == g.VenueID
select new DO.Venue
{
ID = v.ID,
Name = v.Name,
Address = v.Address,
Telephone = v.Telephone,
URL = v.Website
}).SingleOrDefault()
Your choice of Single() vs. SingleOrDefault() vs. First() vs. FirstOrDefault() depends on what kind of query it is, but I'm guessing you want one of the first two. (The "OrDefault" variants return null if the query has no data; the others throw.)
I also agree with Mike that a join might be more in line with what you wanted, if there's a singular relationship involved.
Why are you doing a join and a sub select? You can just use the results of your join in the creation of a new Venue. Be aware that if there is not a one to one relationship between gigs and venues you could run into trouble.
Try this:
return from g in DBContext.Gigs
join venue in DBContext.Venues on g.VenueID equals venue.ID
select new DO.Gig { ID = g.ID, Name = g.Name, Description = g.Description,
StartDate = g.Date, EndDate = g.EndDate, IsDeleted = g.IsDeleted,
Created = g.Created, TicketPrice = g.TicketPrice,
Venue = new DO.Venue { ID = venue.ID, Name = venue.Name,
Address = venue.Address, Telephone = v.Telephone,
URL = v.Website }