in a Grails 5 app I've noticed inconsistent behaviour when validating a domain from a unit test spec. In this example, I've created a 'Customer' domain that contains a name and a firmId field.
When Customer does not implement MultiTenant, and we attempt to save a Customer with no firmId specified, a ValidationException is thrown as expected.
However, when Customer implements MultiTenant, then the ValidationException is NOT thrown, despite us deactivating gorm multitenancy in the Spec.
//class Customer {
class Customer implements MultiTenant<Customer> {
String name
Long firmId
static constraints = {
name nullable: false
firmId nullable: false
}
static mapping = {
tenantId name: 'firmId'
}
}
And the Spec
class CustomerSpec extends Specification implements DataTest {
#Override
Closure doWithConfig() {
{ config ->
config.grails.gorm.multiTenancy.mode = null
}
}
def setupSpec() {
mockDomains Customer
}
void "requires firmId"() {
when:
Customer customer = new Customer()
customer.name = "Dave"
customer.save(failOnError:true, flush:true )
then:
thrown ValidationException
}
}
I've created an application to demonstrate the issue:
https://github.com/davebrown1975/grails_multitenant_validation
Has anyone seen this behaviour before and know of a workaround?
Thanks
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'm having problems trying to update a record on a Grails 2.3.7 Project
I don't want to modify the fields that are part of the composite Id, I just want to update the other fields.
Just one class, few properties, but every time I try to update, it throws me the "not unique error" when this lines runs:
personInstance.validate()
if (personInstance.hasErrors()) {
respond personInstance.errors, view:'create'
return
}
My class looks like this:
class Person implements Serializable {
static constraints = {
name(unique: lastName)
}
static mapping = {
id generator: 'assigned'
id composite: ["name", "lastName"]
}
//Override equals and hashcode methods
boolean equals(other) {
if (!(other instanceof Person)) {
return false
}
other.name == name && other.lastName == lastName
}
int hashCode() {
def builder = new HashCodeBuilder()
builder.append name
builder.append lastName
builder.toHashCode()
}
String name
String lastName
String description
}
And the controller action:
def update() {
def personInstance = Person.get(new Person(name:params.name, lastName:params.lastName))
personInstance.properties = params
personInstance.validate()
if (personInstance.hasErrors()) {
respond personInstance.errors, view:'create'
return
}
personInstance.save flush:true
request.withFormat {/*etc...*/}
}
When I use validate(), it throws me a Grails unique key error, when I avoid validation its a BD not unique PK error.
Is like Grails didn't know if I want to do an insert or an update when I personInstance.validate().
Is there a way to manage this in a correct way that I'm not seeing?
Or am I forced to avoid validation?
am I doing something wrong?
Thanks in advance.
I believe the GORM mapping DSL expects just one id definition. Try combining your two id lines into just this one:
id generator: 'assigned', composite: ["name", "lastName"]
Also, in addition to implementing Serializable, your domain class should override equals and hashCode, as described under "Composite Primary Keys" here: http://grails.org/doc/latest/guide/single.html#identity
My Grails app's domain model has the following requirements:
a user belong to zero or one organisations
an organisation is either a charity or a company
charities and companies have some some common fields and also some (non-nullable) fields that are unique to each organisation type
I put the common organisation fields into an abstract Organisation class which Charity and Company both extend. I can't store this hierarchy in a single table because there are non-nullable fields that are specific to each organisation type. The relevant parts of the domain model are shown below:
class User {
String name
static belongsTo = [organization: Organization]
static constraints = {
organization nullable: true
}
}
abstract class Organization {
String name
static hasMany = [users: User]
static mapping = {
tablePerHierarchy false
}
}
class Charity extends Organization {
// charity-specific fields go here
}
class Company extends Organization {
// company-specific fields go here
}
When I look at the MySQL schema generated from this model, the inheritance relationship between organisation-company and organisation-charity seems to have been completely ignored. Although there is an organisation table with a name column, it has no primary-foreign key relationship with either company or charity
I see the same result as IanRoberts for both MySQL and H2. In other words: no join table generated, but the expected organization_id FK in the users table.
With "Table per subclass" mapping (tablePerHierarchy false), you end up with an implied one-to-one relationship in the database. Primary Keys for Charity and Company will have the same value as the PK for the parent Organization. The schema generated by GORM/Hibernate3 doesn't appear to enforce this with referential integrity constraints. It's pure Hibernate magic. A bit more detail here
Solved!
Add the class below to src/java (this class cannot be written in Groovy)
package org.example;
import org.codehaus.groovy.grails.orm.hibernate.cfg.GrailsAnnotationConfiguration;
import org.hibernate.MappingException;
import org.hibernate.mapping.JoinedSubclass;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.RootClass;
import java.util.Iterator;
public class TablePerSubclassConfiguration extends GrailsAnnotationConfiguration {
private static final long serialVersionUID = 1;
private boolean alreadyProcessed = false;
#Override
protected void secondPassCompile() throws MappingException {
super.secondPassCompile();
if (alreadyProcessed) {
return;
}
for (PersistentClass persistentClass : classes.values()) {
if (persistentClass instanceof RootClass) {
RootClass rootClass = (RootClass) persistentClass;
if (rootClass.hasSubclasses()) {
Iterator subclasses = rootClass.getSubclassIterator();
while (subclasses.hasNext()) {
Object subclass = subclasses.next();
// This test ensures that foreign keys will only be created for subclasses that are
// mapped using "table per subclass"
if (subclass instanceof JoinedSubclass) {
JoinedSubclass joinedSubclass = (JoinedSubclass) subclass;
joinedSubclass.createForeignKey();
}
}
}
}
}
alreadyProcessed = true;
}
}
Then in DataSource.groovy set this as the configuration class
dataSource {
configClass = 'org.example.TablePerSubclassConfiguration'
pooled = true
driverClassName = "org.h2.Driver"
username = "sa"
password = ""
dbCreate = "update"
url = "jdbc:h2:mem:testDb;MVCC=TRUE;LOCK_TIMEOUT=10000"
}
I've submitted a pull request to Grails that fixes this. The fix was be included in Grails 2.3.9.
ORM is not RDBS.
tablePerHierarchy false
so You Have three tables: Organization, Charity, Company. User belongs to only Organization (not Charity or Company). How are you going to get value of specific fields?
There is USER. We know ORGANIZATION, but we don't know Charity or Company. I think you underst...
I can suggest you three solutions:
1. tablePerHierarchy true (But you need to have nullable charity\Company -specific fields )
2.
class User {
static belongsTo = [charity: Charity, company: Company]
}
class Charity {
String name
static hasMany = [users: User]
// charity-specific fields go here
}
class Company {
String name
static hasMany = [users: User]
// company-specific fields go here
}
3.
class User {
static belongsTo = [organization: Organization]
}
class Organization {
String name
Charity charity //nullable
Company company //nullable
static hasMany = [users: User]
}
class Charity {
static belongsTo = [organization: Organization]
// charity-specific fields go here
}
class Company {
static belongsTo = [organization: Organization]
// company-specific fields go here
}
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.
I have two domain-classes. One is a "Partner" the other is a "Customer". A customer can be a part of a Partner and a Partner can have 1 or more Customers:
class Customer {
Integer id
String name
static hasOne = [partner:Partner]
static mapping = {
partner joinTable:[name:'PartnerMap',column:'partner_id',key:'customer_id']
}
}
class Partner {
Integer id
static hasMany = [customers:Customer]
static mapping = {
customers joinTable:[name:'PartnerMap',column:'customer_id',key:'partner_id']
}
}
However, whenever I try to see if a customer is a part of a partner, like this:
def customers = Customer.list()
customers.each {
if (it.partner) {
println "Partner!"
}
}
I get the following error:
org.springframework.dao.InvalidDataAccessResourceUsageException: could not execute query; SQL [select this_.customer_id as customer1_162_0_, this_.company as company162_0_, this_.display_name as display3_162_0_, this_.parent_customer_id as parent4_162_0_, this_.partner_id as partner5_162_0_, this_.server_id as server6_162_0_, this_.status as status162_0_, this_.vertical_market as vertical8_162_0_ from Customer this_]; nested exception is org.hibernate.exception.SQLGrammarException: could not execute query
It looks as if Grails is thinking partner_id is a part of the Customer query, and it's not... It is in the PartnerMap table, which is supposed to find the customer_id, then get the Partner from the corresponding partner_id.
Anyone have any clue what I'm doing wrong?
Edit: I forgot to mention I'm doing this with legacy database tables. So I have a Partner, Customer and PartnerMap table. PartnerMap has simply a customer_id and partner_id field.
Given the way 1-many works when you want a join table, I think it's not possible with standard GORM to make it bidirectional and access a Customer's Partner. But you can map the join table with a domain class and access things that way:
Customer:
class Customer {
String name
def getPartner() {
PartnerMap.findByCustomer(this)?.partner
}
}
Partner:
class Partner {
String name
def getCustomers() {
PartnerMap.findAllByPartner(this)*.customer
}
}
PartnerMap:
import org.apache.commons.lang.builder.HashCodeBuilder
class PartnerMap implements Serializable {
Partner partner
Customer customer
boolean equals(other) {
if (!(other instanceof PartnerMap)) {
return false
}
other.partner?.id == partner?.id &&
other.customer?.id == customer?.id
}
int hashCode() {
def builder = new HashCodeBuilder()
if (partner) builder.append(partner.id)
if (customer) builder.append(customer.id)
builder.toHashCode()
}
static PartnerMap get(long partnerId, long customerId) {
find 'from PartnerMap where partner.id=:partnerId and customer.id=:customerId',
[partnerId: partnerId, customerId: customerId]
}
static PartnerMap create(Partner partner, Customer customer, boolean flush = false) {
new PartnerMap(partner: partner, customer: customer).save(flush: flush, insert: true)
}
static boolean remove(Partner partner, Customer customer, boolean flush = false) {
PartnerMap instance = PartnerMap.findByPartnerAndCustomer(partner, customer)
instance ? instance.delete(flush: flush) : false
}
static void removeAll(Partner partner) {
executeUpdate 'DELETE FROM PartnerMap WHERE partner=:partner', [partner: partner]
}
static void removeAll(Customer customer) {
executeUpdate 'DELETE FROM PartnerMap WHERE customer=:customer', [customer: customer]
}
static mapping = {
id composite: ['customer', 'partner']
version false
table 'PartnerMap'
}
}
Since you're not using hasMany, you lose the addToXXX dynamic method, but you can call PartnerMap.create() to relate two instances. You also lose the collection and back-ref in the domain classes, but I added utility methods for those.