I'm trying to get my head around GORM and relational mapping. The relationships are working fine but there is one problem. I can't seem too ensure that every MailAddress added to MailingList has a unique address. What would be the must efficient way to do this?
Note: There is no unique constraint on MailAddress.address. Identical addresses can exist in the same table.
class MailAddress {
String name
String email
static belongsTo = MailingList
static constraints = {
name blank:true
email email:true, blank:false
}
}
class MailingList {
String name
static hasMany = [addresses:MailAddress]
static mapping = {
addresses cascade: 'all-delete-orphan'
}
static constraints = {
name blank:false
}
}
As mentioned in the comments by #ibaralf the answer is a custom validator.
The MailingList class needed to validate if all addresses (MailAddress) have a unique e-mailaddress.
I added this constraint to the MailingList class and it worked.
static constraints = {
name blank:false
addresses(validator: {
if (!it) {
// validates to TRUE if the collection is empty
// prevents NULL exception
return true
}
// Grab a collection with all e-mailaddresses in the list
def addressCollection = it*.email
// Compare to a collection with only unique addresses
return addressCollection == addressCollection.unique()
})
}
More info can be found here http://grails.org/doc/2.2.0/ref/Constraints/validator.html
There is a unique constraint you can add:
static constraints = {
name blank:true
email email:true, blank:false, unique: true
}
=> put the unique constraint on the email variable (unique: true). This would prevent identical email addresses to be saved in the table.
Related
I have a MS SQL DB (which I can't change) with 3 tables :
contact
id, name, number
c_group
id,name,email
contact_group
id, contact_id, group_id
In Grails I have 3 domain classes :
class Cgroup {
String name
String email
static constraints = {
}
static mapping = {
table name: "c_group"
version false
}
}
class Contact {
String name
String number
static constraints = {
name nullable:false
}
static mapping = {
version false
}
}
class Contact_group {
Cgroup cgroup
Contact contact
static constraints = {
}
static mapping = {
version false
cgroup_id column: "c_group_id", sqlType: "int"
}
}
I'm trying to create a list using :-
def contactInstance = Contact.get(id) //I have previously got the id
List userGroupList = Contact_group.createCriteria().list(params) {
eq("contact", contactInstance)
}
And it is throwing an error
Invalid column name 'cgroup_id'.. Stacktrace follows:
As you can see, I'm trying to map the correct column name (in the Contact_group domain class) as the table has been renamed from group to c_group in the DB, and just to complicate matters for some reason I decided to call the domain class Cgroup in my Grails app. (I can change this if needs be)
So, I'm now in a bit of a muddle about how best to create the list. Any suggestions or pointers?
In your mapping you should point to the class property (cgroup_id should be cgroup, since that is what you named it in your domain.)
Also, your column name in your table is id not c_group_id so you should set the column as id.
static mapping = {
version false
table name: "contact_group"
cgroup column: "id", sqlType: "int"
}
I have this class:
class Word {
String word
static hasMany = [group: WordGroup]
static belongsTo = [language: Language]
static constraints = {
word(unique: ['language'], maxSize:255)
}
}
Which is working fine, but I would need to associate the unique constraint to both language and group, so different groups can insert the same "word".
The groups are in an intermediate table (WordGroup) as this is a many to many relationship.
If I try to do
word(unique: ['language', 'group'], maxSize:255)
I get the error
Missing IN or OUT parameter in index:: 3
Is there a way to do this?
EDIT: I am adding the other classes referenced in the Word class if it helps
WordGroup
class WordGroup {
static belongsTo = [word: Word, group: Group]
static constraints = {}
}
Group
class Group {
String name
String url
static hasMany = [words: Word]
static constraints = {
name blank: false, unique: true
}
}
Language
class Language {
String name
String code
static constraints = {
name maxSize:255
code maxSize:255
}
}
Thanks.
I have one-to-many relationship between entities - RentalUnit and Review . All tests run fine, except when i am adding the same review to different RentalUnit instances as following:
def review3 = Review.build().save(flush: true)
def rentalUnit2 = RentalUnit.build().
addToReviews(review2).addToReviews(review3).save(flush: true)
assert rentalUnit.reviews.contains(review2)
It seems that GORM and the addTo* method doesn't seem to care if i am adding the same Review instance, so i am guessing there is something missing in my domain classes. What would that be?
Thank you for helping
p.s.
class Review {
String submittedBy
String content
String dateReceived
boolean isApproved
static belongsTo = RentalUnit
static mapping = {
content type: 'text'
}
static constraints = {
submittedBy blank: false, size: 3..50
content blank: false, size: 5..2500
}
}
class RentalUnit {
String name
String nickname
Address address
static hasMany = [reviews:Review]
static mapping = {
reviews cascade: "all-delete-orphan"
}
static constraints = {
name blank: false, unique: true, size: 4..10
nickname blank: false, size: 5..60
}
}
Yes it doesn't care - it doesn't change any other objects' properties. Even about the other end of relationship - review2.rentalUnit (if such field existed) will be null here.
The Review will disappear from rentalUnit.reviews (or review2.rentalUnit would be assigned) the next time the object is loaded from database.
You can assign review2.rentalUnit by hand and validate if it's been added to another RentalUnit, though - the assignment will do no harm.
edit: let's go through the code step-by-step.
rentalUnit1.addToReviews(review2)
Here review2 is added to rentalUnit1.reviews. review2.rentalUnit is NOT assigned, but after save in the database it would point to rentalUnit1. The only persistent representation of RentalUnit.reviews field is a child-to-parent reference field, Review.rentalUnit.
def rentalUnit2 = ...
rentalUnit2.addToReviews(review2).addToReviews(review3).save(flush: true)
Here review2 is added to rentalUnit2.reviews. review2.rentalUnit is NOT assigned again. review2 is NOT removed from rentalUnit1.reviews BUT, after save, in the database, it will point to rentalUnit2.
assert rentalUnit1.reviews.contains(review2)
review2 was NOT removed from rentalUnit1.reviews, so the assertion will pass. BUT in the next session rentalUnit1 and rentalUnit2 will have the correct sets of reviews - only rentalUnit2 will have review2.
Now, if you want to always maintain Java representation consistent, implment a method like this:
class RentalUnit {
RentalUnit addToReviewsAndCheck(Review r) {
if (r.rentalUnit == this) return;
if (r.rentalUnit != null) {
r.rentalUnit.removeFromReviews(r)
}
r.rentalUnit = this
addToReviews(r)
}
}
but it's an overkill, as for me.
i have the following (simplified) domain classes
class Filter {
String name
static hasMany = [answers:Answer]
static belongsTo = [user:User]
}
class User {
String name
static hasMany = [answers:Answer, filters:Filter]
}
class Answer {
String text
}
Then i add answers to the user which is working perfectly. The problem occurs when i delete 1 answer of a user:
def delete = {
def answer = Answer.get(params.id)
def users = User.withCriteria() {
answers{
eq("id", answer.id)
}
}
for (user in users)
user.removeFromAnswers(answer)
answer.delete(flush:true)
redirect(action:"index")
}
What happens here is that ALL user --> answer associations get deleted.
I only want to delete this 1 answer and of cause all associations the answer is used.
I know this has to do with the missing belongsTo, but i can't use it because a ansswer can either belong to a user or to an filter...
You can add the belongsTo to set them to nullable:
class Answer {
String text
static belongsTo = [user:User, filter:Filter]
static constraints = {
user nullable:true
filter nullable:true
}
}
and then just delete the Answer directly in the Controller:
def delete = {
def answer = Answer.get(params.id)
answer.delete(flush:true)
}
GORM will take care of the rest the cascading for you.
I have the following "top-level" (parent) domain entities:
Customer
Company
Contact
And the following child entity:
Address
There is one to many relationship between each of the top level domain entities:
Customer -> Address
Company -> Address
Contact -> Address
i.e. a customer, company or contact can have one or more addresses.
Unfortunately I do not know how to model this in grails / gorm. It seems I can only define one parent or belongsTo declaration in address i.e. I was not able to declare Address using:
Address {
Customer parent //??
Company parent //??
Contact parent //??
}
Can someone tell me if I am missing something or if it's possible to define this type of relationship in a supported way?
Thanks,
cowper
You should be able to use the array version of belongsTo as Tim pointed out:
Address {
static belongsTo = [Customer, Company, Contact]
}
If entities can share a common address may change the way you configure deletes.
Another option is to have those three classes inherit the property from a superclass, but whether or not that makes sense in your case, I don't know (it kind of doesn't look like it).
In our application we have several entities that need addresses. But we've chosen to model them in a many-to-many relationship.
Address looks like this
class Address {
// typical address properties
Set<Company> getCompanies() {
CompanyAddress.findAllByAddress(this).collect { it.company } as Set
}
static constraints = {
// typical constraints
}
}
And for each "parent" we provide a getter. You can see getCompanies() in the code above. If you're only every going to have 1 company per address, then simply have that getter return the 1 company instead of a Set. The inverse is true inside Company, we have a getAddresses().
Company Address, for example, looks like this...
class CompanyAddress implements Serializable{
Address address
Company company
boolean equals(other) {
if (this.is(other)){
return true
}
if (!(other instanceof CompanyAddress)) {
return false
}
other.address?.id == address?.id &&
other.company?.id == company?.id
}
int hashCode() {
def builder = new HashCodeBuilder()
if (address) builder.append(address.id)
if (company) builder.append(company.id)
builder.toHashCode()
}
static CompanyAddress get(long companyId, long addressId) {
find 'from CompanyAddress where address.id=:addressId and company.id=:companyId',
[addressId: addressId, companyId: companyId]
}
static CompanyAddress create(Company company, Address address, boolean flush = false) {
new CompanyAddress(address: address, company: company).save(flush: flush, insert: true)
}
static boolean remove(Company company, Address address, boolean flush = false) {
CompanyAddress instance = CompanyAddress.findByAddressAndCompany(address, company)
instance ? instance.delete(flush: flush) : false
}
static void removeAll(Address address) {
executeUpdate 'DELETE FROM CompanyAddress WHERE address=:address', [address: address]
}
static void removeAll(Company company) {
executeUpdate 'DELETE FROM CompanyAddress WHERE company=:company', [company: company]
}
static mapping = {
id composite: ['address', 'company']
version false
}
}