I'm implementing Audit Logging 1.1 Grails Plugin to track the changes to my domain classes midway of our project implementation. These are an example domain object for our scenario:
Students need to answer questions. A question can ask for a single or a multiple answers.
class Question {
static auditable = true
Integer id
String content
static hasMany = [
answers: Answer
]
}
class Student {
static auditable = true
Integer id
String name
static hasMany = [
answers: Answer
]
}
class Answer implements Serializable {
static auditable = true
Integer sequence
String value
static belongsTo = [
student: Student,
question: Question
]
static mapping = {
id composite: ["student", "question", "sequence"]
}
}
Every time I perform insert/updates to any of these tables, the plugin fires an event and logs it to my AuditLog table. All DML are successfully logged as expected except for the Answer table. The problem is that the PERSISTED_OBJECT_ID is always null:
+----+---+------------+------------+---------------------+---------------+-----------+-----------+
| ID | … | CLASS_NAME | EVENT_NAME | PERSISTED_OBJECT_ID | PROPERTY_NAME | OLD_VALUE | NEW_VALUE |
+----+---+------------+------------+---------------------+---------------+-----------+-----------+
| … | … | Answer | UPDATE | | value | A | B |
| … | … | Answer | UPDATE | | value | B | A |
+----+---+------------+------------+---------------------+---------------+-----------+-----------+
I tried to include the logIds = true config but it still not persisting. Without that column, I cannot identify which Answer is updated by whom. I'm expecting that this would be the case of all the composite primary keys domain classes that I have.
What can I do to fix this?
I had the same issue with 2.x plugin version when my new/oldValue was domain class object. I decided it by overriding toString() method for domain which was saved as value. Try to override toString() for your "student" and "question" domain.
Update:
I didn't use composite key as Id in my project (it seems it's why your persistedObjectId is empty) and also I used 2.x version of plugin. My decision for problems which looks like your, may be it will help you :
I have domains:
class Manager {
}
class Customer {
}
class Item {
Manager manager
Customer customer
Item parentItem
BigDecimal qtyOnHand
}
I log changes of qtyOnHand and parentItem. With first everything is easy. For second (as I remember default value will be smth like this: "[id:15]Object123402"):
1. overrided toString()
public String toString() {
id
}
2. in configurations: logIds = false => plugin writes only id of object
Also I log managerId and customerId using "uri" for it (for simple it will be):
def getAuditLogUri = {
managerId as String
}
We can write all in uri and than past in beforeInsert:
class Item {
......
def getAuditLogUri = {
getAuditFields()
}
private Map getAuditFields() {
return [managerId: manager.id, customerId: customer.id]
}
....
}
class AuditLog{
String actor
String uri
String className
String persistedObjectId
String propertyName
String oldValue
String newValue
String managerId
String customerId
.....
def beforeInsert() {
getAdditionalFields(uri)
}
private void getAdditionalFields(String fields) {
Map map = Eval.me(fields)
managerId = map.managerId
customerId = map.customerId
}
Everything works in my project, I think the it will work the same for persistedObjectId-field
Related
I have 3 domain class and I need to define their relationships but I find no luck in defining them correctly. Can somebody rectify my code? I have always get the error unknown column pointing to an id. Please help. Below are my codes. Thank you.
Todo.groovy class
package todoScaff
import userScaff.User
import categoryScaff.Category
class Todo {
String name
String note
Date createDate
Date dueDate
Date completedDate
String priority
String status
User owner
Category category
static belongsTo = [User, Category]
static constraints = {
name(blank:false)
createDate()
priority()
status()
note(maxsize:1000, nullable:true)
completedDate(nullable:true)
dueDate(nullable:true)
}
String toString() {
name
}
}
Category.groovy class
package categoryScaff
import todoScaff.Todo
import userScaff.User
class Category {
String name
String description
User user
static belongsTo = User
static hasMany = [todos: Todo]
static constraints = {
name(blank:false)
}
String toString(){
name
}
}
User.groovy class
package userScaff
import todoScaff.Todo
import categoryScaff.Category
class User {
String userName
String fname
String lname
static hasMany = [todos: Todo, categories: Category]
static constraints = {
userName(blank:false, unique:true)
fname(blank:false)
lname(blank:false)
}
String toString(){
"$lname, $fname"
}
}
Errors
I'm fairly certain your SQL tables are incorrect.
I created a small project with your 3 domain classes and used the grails command schema-export; after removing everything that doesn't relate to the domain relationship, the structure should look like this:
create table category (
id bigint,
user_id bigint,
);
create table todo (
id bigint,
category_id bigint,
owner_id bigint,
);
create table user (
id bigint
);
If your table structure doesn't resemble this, you should either change it to match or use the static mapping = {} closure in the domain class to indicate the proper column names.
Static Mapping
In Todo:
static mapping = {
owner column: 'owner_id' // change owner_id to match your owner column within todo
category column: 'category_id' // change category_id to match your category column within todo
}
In Category:
static mapping = {
owner user: 'user_id' // change user_id to match your user column within category
}
I have two classes: sample, and parameter. I also have a sample_sample_parameter lookup table which exists to hold the sample id and the parameter id. This is mapped in my grails app.
I was able to write an sql query that works in squirrel:
select s.* from sample s, sample_sample_parameters sp where s.id = sp.sample_id and sp.sample_parameter_id = 41
where 41 would be replaced with the parameter.id variable passed from the gsp page to the action. I have also tried to make it work with executeQuery but it tells me that sample is not mapped.
How do I turn this query into a gorm recognizable form?
class Sample {
Date collectionDate // date the sample was collected
Date sampleReceivedDate // date the sample arrived on site
Date dateCreated
String sampleComments // details about the sample
String labAccessionID // internal reference
String sampleGender // is it from a male or female?
String sampleAge // the age of the animal the sample was taken from
String sampleBreed // the breed of animal
String sampleNameID // patient name
String filepath
String enteredby
String sg
String mosm
static searchable = true
static hasMany =[sampleParameters:SampleParameter, ficsruns:Ficsrun]//[tags:Tag]// a Sample can have many parameters
/* mappedBy allows two tables share a common table. It creates two join tables, one for each.
* SampleParameter is the table being shared by Sample and SampleType tables */
static mappedBy=[sampleParameters:"samples"]//[tags:"domainClass1s"]/*http://www.van-porten.de/2010/09/multiple-many-to-many-in-grails/*/
static belongsTo = [sampleType:SampleType, labFinding:LabFinding, sampleSource:SampleSource, species:SpeciesList] // creates dependencies
static constraints = {
sampleType(blank:false)
sampleNameID(blank:false)
collectionDate(blank:false)
sampleReceivedDate(blank:false)
sampleComments(nullable:true, maxSize:1000)
labAccessionID(nullable:true)
sampleGender(blank:false, inList:["M","F","NM","SF", "UNK"])
sampleAge(nullable: true)
sampleBreed(nullable:true)
sampleSource(blank:false)
species(blank:false)
labFinding(nullable:true)
filepath(nullable:true)
enteredby(nullable:true)
sg(nullable:true)
mosm(nullable:true)
dateCreated()
}
/* This section is for static mapping to the hematology database*/
static mapping = {
version false
id generator:'sequence', params:[sequence:'SHARED_SEQ']
}
String toString(){
"${sampleNameID}"
}
}
class SampleParameter implements Comparable{
String name
String value
static hasMany = [
samples:Sample, //domainClass1s: DomainClass1,
sampleTypes:SampleType //domainClass2s: DomainClass2
]
static mapping = {
version false
id generator:'sequence', params:[sequence:'SHARED_SEQ']
}
static mappedBy = [samples:"sampleParameters",sampleTypes:"sampleParameters"]//[domainClass1s: "tags", domainClass2s: "tags"]
static belongsTo =[Sample,SampleType] //[DomainClass1, DomainClass2]
static constraints = {
name(blank:false)
//value()
//value(unique:true)
value (unique: 'name')
}
#Override public String toString() {
return name + " " + value
}
#Override
public int compareTo(Object o) {
if (o == null || this == null) {
return 0;
} else {
return value.compareTo(o.value)
}
}
}
As a first suggestion, when you have the paramter's id, do the following.
Parameter p = Parameter.get(params.id) // or wherever your id is stored
List<Sample> samples = Sample.findAllByParameter(p) // this assumes, the parameter property is actually named 'parameter'
Of course there is no error handling in place right now, but you'll get the idea.
Welcome to GORM, welcome to Grails.
The problem is that you're not using a HQL query in your executeQuery method. Instead you're using native sql. From the manual:
The executeQuery method allows the execution of arbitrary HQL queries.
Take a look at the specification to see ways of doing that. Which, by the way, are way easier than native sql.
List<Sample> samples = Sample.findAllBySampleParameter(SampleParameter.get(variable))
Give it a try?
How can I retrieve a list of Grails composite id properties?
I have tried the following but the id property is returning a Long:
Domain
class Domain implements Serializable {
String field1
String field2
static mapping = {
id composite: ['field1', 'field2']
}
}
Test
def d = DefaultGrailsDomainClass(Domain.class)
assert(d.identifier.type == java.lang.Long)
After deep diving GORM I found the solution:
GrailsDomainBinder.getMapping(Domain).identity.propertyNames
This is my first project with many-to-many relationship and after reading some grails stuff of ORM, I decided to let grails do the work for me, like creating the tables. Actually, my deal was:
1 User has many Groups
1 Group belongs to an User and has many Users(except the owner)
Here are my classes:
class User {
String name
List groups
static hasMany = [groups : Group]
static mapping = {
table 'users'
version false
}
}
class Group {
String name
List members
static belongsTo = User
static hasMany = [members : User]
static mapping = {
table 'groups'
version false
}
}
After that, what I got from grails was 3 tables. The users table was ok, but the next 2 wasn't what I expected.
table **groups** | id | name
table **users_groups** | group_id | user_id | members_idx | groups_idx
Well, what I wanted was something like this:
table **groups** | id | name | user_id
table **users_groups** | group_id | members_idx | groups_idx
I don't know if I did something wrong or how can I fix it, but anyway... I'm still getting another problem when I try to do user.addToGroups(new Group()) . My users_groups table duplicates this register, one with the members_idx null, and the other register with the groups_idx null.
How about removing belongsTo from group and leave hasMany:
class User {
String name
List groups
static hasMany = [groups : Group]
static mapping = {
table 'users'
version false
}
}
class Group {
String name
List members
static hasMany = [members : User]
static mapping = {
table 'groups'
version false
}
}
EDIT
I think You have to make a domain class which will actually join groups and users.
I think You should try this:
class User {
String name
static hasMany = [groups : UsersGroups]
static mapping = {
table 'users'
version false
}
}
class Group {
String name
static hasMany = [members : UsersGroups]
static mapping = {
table 'groups'
version false
}
}
class UsersGroups {
static belongsTo = [group: Group, user: User]
static mapping = {
version false
}
}
It will create another table which actually joins groups and users.
If you change the belongsTo to use Map syntax it will use a foreign key and not a join table:
static belongsTo = [user: User]
and you'll have a user field as a back-reference.
I have done a similar issue before, so I think this may work:
class User {
String name
hasMany = [groups : Group]
static mapping = {
table 'users'
version false
}
}
class Group {
String name
static belongsTo = [owner : User]
static hasMany = [members : User]
static mapping = {
table 'groups'
version false
}
}
About the problem with user.addToGroups, I think that you should set the owner when creating the group first. Then add other users to group.
I am having problems persisting domain objects where I have a many-to-many relationship with a join table
class A{
String name
static hasMany = [bs:B]
}
class B{
String surname
static belongsTo=A
static hasMany=[as:A]
}
A a = new A(name:'Test')
B b = new B(surname:'user')
a.addToBs(b)
a.save(flush:true)
Then what I would expect to see is the following
Table A Table AB Table B
id name a_id b_id id surname
1 Test 1 1 1 User
However, the data only persists into table A.
Does anybody know what I am doing wrong ?
thanks
I have found this link which is shows a clear way of mapping many to many relationships
http://chrisbroadfoot.id.au/2008/07/19/many-to-many-relationship-mapping-with-gorm-grails
I still havent been able to get it working the way that I want to at the moment
I tried imitating your code and cascading works for me.
Class A:
package searcher
class A {
String name
static hasMany = [bs:B]
static constraints = {
}
public String toString() {
def s = "Name: $name\n Bs: "
bs.each {
s = "$s $it "
}
return s
}
}
Class B:
package searcher
class B {
String surname
static belongsTo = A
static hasMany = [as:A]
static constraints = {
}
}
Controller Source:
package searcher
class ManyController {
def ab = {
A a = new A(name:'Test')
B b = new B(surname:'user')
a.addToBs(b)
a.save(flush:true)
render A.list()
}
}
Produces output:
[Name: Test Bs: searcher.B : 1 ]
I didn't run into the problem you did, but I did have some initial trouble that was fixed by doing a grails clean. Have you tried a variety of database configurations? I was just using an in memory hsqldb set to create-drop. If you're using a DBMS that I happen to have installed I'll try pointing it to another database and giving it a whirl.