addTo failing to add - grails

I have 2 domain classes: Category and CatAttribute, they have a many-to-one relationship, Category has 2 List of CatAttribute.
class Category {
static constraints = {
description nullable: true
name unique: true
}
static hasMany = [params:CatAttribute, specs:CatAttribute]
static mappedBy = [params: "none", specs: "none"]
static mapping = {
}
List<CatAttribute> params //required attributes
List<CatAttribute> specs //optional attributes
String name
String description
}
and my CatAttribute class:
class CatAttribute {
static constraints = {
}
static belongsTo = [category: Category]
String name
}
When I tried to create new objects, it fails to save:
def someCategory = new Category(name: "A CATEGORY")
.addToSpecs(new CatAttribute(name: "SOMETHING"))
.addToParams(new CatAttribute(name: "onemore attribute"))
.save(flush: true, failOnError: true)
The domain classes here are simplified/data are mocked for illustration purposes only, the real production code is a lot more complex, but the relationship between the two domains is the same.
Validation errors occur on .addToSpec line:
Field error in object 'Category' on field 'specs[0].category': rejected value [null];
This error has to do with me putting 2 lists of CatAttribute objects in the same domain Category, if I remove either of those and proceed with my object creation,everything is perfectly fine, the way I mapped the domain class Category is all based on grails ref
, so i don't think there is anything wrong with the mapping, but if there is, please let me know.

Do you really need the associations as List (do you index), by default they are Set.
Modify Category as below and you should be good:
class Category {
String name
String description
static hasMany = [params: CatAttribute, specs: CatAttribute]
static mappedBy = [params: "category", specs: "category"]
static constraints = {
description nullable: true
name unique: true
params nullable: true //optional
}
}
if you need a 1:m relation.

Related

Grails find results in null List for hasMany relationship

I have created an object (which is nested within a nested object) with a hasMany relationship and when I save the object, the items are saved, the list is fully populated. But when I use find to get the object back from the database, the list is null
class Parent {
int age
String name
List<Child> children
static hasMany = [
children: Child
]
}
So when I use Parent.findAll(), I get
{
age: 34
name: 'John Smith'
children: null
}
What's weird is other classes I have don't display this behavior. I set lazy to false for the list, but that doesn't seem to do anything. What gives?
EDIT: Here is the child class
class Child {
static constraints = {
firstName nullable: true
middleName nullable: true
displayName nullable: true
story nullable: true
}
static belongsTo = [
parent: Parent,
teacher: Teacher
]
String firstName
String middleName
String displayName
String story
}
OK, I figured it out. Lazy loading is to blame. All I had to do was add
children lazy: false
to the constraints on the parent class and the problem was solved!

Grails domain hasMany validation

I have a case that validation is done on domain properties but not on the an associated (hasMany) properties.
Is there any configuration I can add to enable the validation on both properties (domain and hasMany).
grails version : 3.1.14
Example:
class Person {
String name;
static hasMany = [location: Location]
static constraints = {
name nullable: true
}
}
class Location {
String address
String city
State state
String zip
static constraints = {
address nullable: true
}
}
According to the documentation the validation should work for has-many associations as you wish: http://docs.grails.org/3.1.14/ref/Domain%20Classes/validate.html
But in my test's it does not work eather.
An other solution is to work with the constraints:
static constraints = {
name nullable: true
location validator: {val, obj ->
val.every { it.validate() } ?: 'invalid'
}
}

Grails inheritance and conflicting one-to-many and many-to-many relationship

I'm learning grails by trying to create a simple twitter copy. I'm currently trying to incorporate followers and groups. I originally came up with a very basic database structure, and I've had no luck in implementing it. The design for relationships is as follows:
Person:
has many: Groups, Tweets, (Person as followers through User2Person)
Group:
has many: (Person as followers through User2Person)
belongs to: Person as owner
User2Person:
belongs to: (Person or Group)
belongs to: Person
Basically, I want Person and Group to be an instance of User, and then create a table that maps User to Person. This way, only one table is created/used for the relationship between Group2Person and Person2Person.
More information: A Group is created by a Person and so it should have an "owner" (person_id). It also has many followers (i.e. members). Group cannot follow other groups, but a Person can follow either another Person or a Group.
Below is how I implemented this in grails:
User
abstract class User {
static hasMany = [followers: Person]
static mappedBy = [followers: "followed"]
String name
Date dateCreated
Date lastUpdated
static constraints = {
name shared: "mustFill", size: 3..20
}
}
Person
class Person extends User {
static belongsTo = [followed: User]
static hasMany = [tweets: Tweet, groups: Group]
static mappedBy = [groups: "owner"]
String username
String email
static constraints = {
username shared: "mustFill", unique: true, size: 4..15
email shared: "mustFill", email: true
}
static mapping = {
tweets sort: 'dateCreated', order: 'desc'
}
}
Group
class Group extends User {
Person owner
String description
def getTweets() {
return followers.tweets.flatten()
}
static transients = {
tweets
}
}
Tweet (Just in case?)
class Tweet {
static belongsTo = [author: Person]
String text
Date dateCreated
static constraints = {
text shared: "mustFill", maxSize: 140
}
}
When I run the cmd grails schema-export, I get the following error: "| Error Error loading plugin manager: Domain classes [class tweeter.Group] and [class tweeter.Person] cannot own each other in a many-to-many relationship. Both contain belongsTo definitions that reference each other. (Use --stacktrace to see the full trace)"
I was able to get the database to create almost the correct schema. Unfortunately, the join table's primary key for User2Person (a.k.a. followers) used (user_id, person_id). That meant that I could not have two records such as: (1, 2) and (2, 1) (e.g. two users are following each other). Below is the updated classes (commit):
User
class User {
static belongsTo = Person
static hasMany = [followers: Person]
String name
Date dateCreated
Date lastUpdated
static constraints = {
name shared: "mustFill", size: 3..20
}
}
Person
class Person extends User {
static hasMany = [tweets: Tweet, groups: Group, follows: User]
static mappedBy = [tweets: "author", groups: "owner"]
String username
String email
static constraints = {
username shared: "mustFill", unique: true, size: 4..15
email shared: "mustFill", email: true
}
static mapping = {
tweets sort: 'dateCreated', order: 'desc'
}
}
The follower table in the schema looked like:
create table user_follows (
user_id bigint,
follows__id bigint,
primary_key(user_id, follows__id)
)
I scoured the web for information about changing the primary key for a join table. The best I could find was about using code like:
static mappedBy = { followers joinTable: [name:"someName", ...] }
Unfortunately, I had a hard time finding good documentation on the joinTable mapping, and most sources seemed to indicate that it was not possible to change the primary key of join tables easily. I then decided to use a separate domain class to define the join table following this guide: Many-to-Many Mapping without Hibernate XML. Below is the final updated code (commit):
User
class User {
static belongsTo = Person
static hasMany = [people: UserFollower]
static mappedBy = [people: "followed"]
String name
Date dateCreated
Date lastUpdated
static constraints = {
name shared: "mustFill", size: 3..20
}
static transients = {
followers
}
def getFollowers() {
return people.collect { it.follower }
}
void addToFollowers(Person person) {
UserFollower.link(this, person)
}
void removeFromFollowers(Person person) {
UserFollower.unlink(this, person)
}
}
Person
class Person extends User {
static hasMany = [tweets: Tweet, groups: Group, users: UserFollower]
static mappedBy = [tweets: "author", groups: "owner", users:"follower"]
String username
String email
static constraints = {
username shared: "mustFill", unique: true, size: 4..15
email shared: "mustFill", email: true
}
static mapping = {
tweets sort: 'dateCreated', order: 'desc'
}
static transients = {
follows
}
def getFollows() {
return users.collect { it.followed }
}
void addToFollows(User user) {
UserFollower.link(user, this)
}
void removeFromFollows(User user) {
UserFollower.unlink(user, this)
}
}
UserFollower
class UserFollower {
User followed
Person follower
static constraints = {
followed nullable: false
follower nullable: false
}
static void link(User user, Person person) {
UserFollower f = UserFollower.findByFollowedAndFollower(user, person)
if(!f) {
f = new UserFollower()
user.addToPeople(f)
person.addToUsers(f)
f.save()
}
}
static void unlink(User user, Person person) {
UserFollower f = UserFollower.findByFollowedAndFollower(user, person)
if(f) {
f = new UserFollower()
user.removeFromPeople(f)
person.removeFromUsers(f)
f.delete()
}
}
}

Inconsistency in GORM session if addTo* vs pass as argument at initialization

I have Many-To-Many relationship between RentalUnit and Review(there may be review for guests staying in multiple rental units). There is cascading on Delete from RentalUnit to Review but none cascading from Review to RentalUnit
While working with tests, i found following inconsistency in GORM session
def review2 = new Review(rentalUnits: [rentalUnit], ...., isApproved: false).save(flush: true)
review2.addToRentalUnits(rentalUnit2)
The 'rentalUnit2' object will have association to the 'review2' whereas the 'rentalUnit' does not.
How do i ensure consistent session while pass RentalUnit object at initialization or via addTo*?
p.s. Here is complete code
class Review {
String submittedBy
String content
String dateReceived
boolean isApproved
final static DateFormat DATEFORMAT = DateFormat.getDateInstance(DateFormat.MEDIUM)
static belongsTo = RentalUnit
static hasMany = [rentalUnits: RentalUnit]
static mapping = {
rentalUnits cascade: "none"
}
static constraints = {
submittedBy blank: false, size: 3..50
content blank: false, size: 5..255
dateReceived blank: false, size: 11..12, validator: {
try{
Date date = DATEFORMAT.parse(it)
return DATEFORMAT.format(date) == it
}catch(ParseException exception){
return false
}
}
rentalUnits nullable: false
}
}
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
nickname blank: false
}
}
Your answer is in your question - use addToRentalUnits. It does three things; it initializes the collection to a new empty one if it's null (this will be the case for new non-persistent instances, but not for persistent instances from the database which will always have a non-null (but possibly empty) collection), adds the instance to the collection, and sets the back-reference to the containing instance. Simply setting the collection data just does the first two things.

belongsTo multiple Domain

I have 4 classes, incidents,problems, requests and another is Attachment.
Every domain look like.........
Class Incidents
{
// other fields
static hasOne = [attachment: Attachment]
static constraints = [attachment nullable:true]
}
Class Problems
{
// other fields
static hasOne = [attachment: Attachment]
static constraints = [attachment nullable:true]
}
Class Requests
{
// other fields
static hasOne = [attachment: Attachment]
static constraints = [attachment nullable:true]
}
Class Attachment
{
// other fields
static belongsTo= [
incident: Incidents,
problem: Problems,
requests: Requests
]
static constraints = {
incident nullable: true
problem nullable: true
requests nullable: true
}
when I am saving object of incident, it throws exception like Column 'problem_id' cannot be null.
what to do?
Try to remove the hasOne on Class Incidents, Problems, Requests and replace it with
Attachment attachment
static constraints = {attachment: unique: true, nullable:true}
static mapping = {
attachment cascade: "delete"
}

Resources