I have a Blog domain class, which has many messages:
class Blog {
String description
static hasMany = [messages : Message]
static belongsTo = [owner : User]
static constraints = {
description blank: true, nullable: true
}
}
class Message {
String content
String title
User author
Date dateCreated
Date lastUpdated
static hasMany = [comments : Comment]
static constraints = {
content blank: false
author nullable: false
title nullable: false, blank: false
}
static mapping = {
content type: "text"
sort dateCreated: 'desc'
}
}
Message is used also in other places of the application, so association is unidirectional. How can I get 20 latest blog messages, ordered by creation date? By latest blog messages, I mean 20 latest messages which are associated with ANY blog.
class Blog {
...
...
static hasMany [messages: BlogMessages]
...
...
}
class Message {
...
// exactly like you have it
...
}
class BlogMessage extends Message {
Blog blog
}
Then you can fetch like this...
BlogMessage.list([max:20])
def latestMessages = Message.listOrderByDateCreated(max:20, order:"desc")
Related
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'
}
}
I have the following class. In src/groovy,
class Profile {
String firstName
String middleName
String lastName
byte[] photo
String bio
}
The domain classes BasicProfile and AcademicProfile extend Profile.
class BasicProfile extends Profile {
User user
Date dateCreated
Date lastUpdated
static constraints = {
firstName blank: false
middleName nullable: true
lastName blank: false
photo nullable: true, maxSize: 2 * 1024**2
bio nullable: true, maxSize: 500
}
static mapping = {
tablePerSubclass true
}
}
class AcademicProfile extends Profile {
User user
String dblpId
String scholarId
String website
Date dateCreated
Date lastUpdated
static hasMany = [publications: Publication]
static constraints = {
importFrom BasicProfile
dblpId nullable: true
scholarId nullable: true
website nullable: true, url: true
publications nullable: true
}
static mapping = {
tablePerSubclass true
}
}
Then there is a Publication class.
class Publication {
String dblpId
String scholarId
String title
String description
Date publicationDate
int citations
Date dateCreated
Date lastUpdated
static belongsTo = [AcademicProfile]
static hasOne = [publisher: Publisher]
static hasMany = [academicProfiles: AcademicProfile]
static constraints = {
dblpId nullable: true
scholarId nullable: true
title blank: false, maxSize: 100
description nullable: true, maxSize: 500
publicationDate: nullable: true
academicProfiles nullable: false
}
}
Finally, I have a User class.
class User {
String username
String password
String email
Date dateCreated
Date lastUpdated
static hasOne = [basicProfile: BasicProfile, academicProfile: AcademicProfile]
static constraints = {
username size: 3..20, unique: true, nullable: false, validator: { _username ->
_username.toLowerCase() == _username
}
password size: 6..100, nullable: false, validator: { _password, user ->
_password != user.username
}
email email: true, blank: false
basicProfile nullable: true
academicProfile nullable: true
}
}
My questions are as follows.
I want a relationship where each User may optionally have a Profile (either BasicProfile or AcademicProfile). I tried static hasOne = [profile: Profile] but I got errors saying Profile does not agree to the hasOne relationship. So the current setup I have is a workaround. Is there no way a user can have one Profile be it BasicProfile or AcademicProfile?
Secondly, in the current setup, I get the error: Invocation of init method failed; nested exception is org.hibernate.MappingException: An association from the table academic_profile_publications refers to an unmapped class: org.academic.AcademicProfile when I try to run it. A Google search tells me that this is a problem with classes which are inheriting from other classes. So technically, if I don't have a hasMany relationship in Publication with AcademicProfile, it should work without any issues. But I don't want that. Because a publication has many authors (AcademicProfiles in my case) and an author may have many publications. So is there a way to fix this?
You're not using Hibernate inheritance - that requires that all of the classes be mapped. You're just using regular Java/Groovy inheritance where you inherit properties and methods from base classes. But Hibernate isn't aware of that, so it can't do queries on the unmapped base class.
I'm not sure why it's complaining about AcademicProfile, but it could be a secondary bug caused by the core issue.
I find Hibernate inheritance to be way too frustrating to use in most cases, so I use this approach when there is shared code.
It should work if you move Profile to grails-app/domain. Once you do that you should move the tablePerSubclass mapping config to the base class and only specify it once.
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()
}
}
}
I am trying to use the create criteria method in grails but im getting back an empty list im not sure why.
My code is as follow
def results = PostOrder.createCriteria().list() {
posts{
author{
eq('username', lookupPerson().username)
}
}
picture{
user{
eq('username', lookupPerson().username)
}
}
}
PostOrder domain is as follows:
class PostOrder {
String pOrder
Date dateCreated
Picture picture
Post posts
Video video
Boolean favorite = false
static hasMany = [children : Child]
static constraints = {
picture nullable: true
posts nullable: true
video nullable: true
}
}
Post is as follows:
class Post {
String message
User author
Date dateCreated
Child child
boolean postedToAll
String tag
static hasMany = [tags:Tag]
static constraints = {
child nullable: true
tags nullable: true
tag nullable: true
}
}
finally picture is as follows:
class Picture {
String orgName
String urlOrg
String urlWeb
String urlThumb
Date dateCreated
String caption
Child child
User user
Album contained
String tag
boolean postedToAll
static hasMany = [tags:Tag]
static constraints = {
orgName blank: false
caption maxSize: 500
tags nullable: true
caption nullable: true
tag nullable: true
child nullable: true
}
}
To me this would work perfectly fine, can anyone see why is doesn't?
Does it have the same username in both pictures and posts???
If not, you have to surround them with and or{} because by default it is used the and logical
Maybe you should add a logical block (and/or) like this:
def results = PostOrder.createCriteria().list() {
or {
posts{
author{
eq('username', lookupPerson().username)
}
}
picture{
user{
eq('username', lookupPerson().username)
}
}
}
}
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"
}