All of this is on Grails 2.2.3.
I have two classes in a One-to-many relationship, and a service which removes a list of ids
class Box {
String name
static hasMany = [items:ItemDomain]
static constraints = {
items(nullable:true)
}
}
and
class ItemDomain { String name Box box
static belongsTo = Box
static constraints = {
name(blank:false,unique:['box'], maxSize:127)
box(nullable:false) } }
In the service, here's the problem section:
def itemsToDelete = params.itemsToDelete //List of DB ids
List<ItemDomain> items= []
items.addAll(box.items) //Copy the list to avoid concurrent mod exception
for(ItemDomain item : items)
{
if(itemsToDelete.contains(item.id))
{
box.removeFromItems(item)
item.delete()
}
box.save(flush: true)
}
This works fine when running the application, but from integration testing it fails with
InvalidDataAccessApiUsageException: deleted object would be re-saved by cascade (remove deleted object from associations)
If I take out the flush, and eventually it will fail with:
Field error in object 'mypackage.ItemDomain' on field 'box': rejected value [null];
Adding logging, I see the size of box.items before entering the loop is the same as it is after exiting the loop, printing the items in the loop before and after shows that the item.box field for the deleted items changes to null. I've tried messing with the cascade mapping in the parent class... I'm at a loss as to whether I'm doing something wrong or if this is an issue with integration testing. The only similar issues I found were against grails 1.1 and had no resolution in the threads that I found.
I appreciate any feedback.
So, not surprisingly, I was doing something wrong. It turns out that my equals() and hashCode() implementations on the ItemDomain class were including a field that was never supposed to change, but due to requirements creep, was now changing and the methods never got updated properly.
Related
I'm not a programming savvy person, so please bear with me.
I've read blog entries and docs about command object. I've never used it and was wondering if I should. (I probably should...)
My project requires parsing, sorting, calculating, and saving results into database when users upload files.
So according to one of the blog entries I read and its corresponding github code,
1) SERVICE should receive file uploads, parse uploaded files (mainly docs and pdfs), sort parsed data using RegEx, and calculate data,
2) COMMAND OBJECT should call SERVICE, collect results and send results back to controller, and save results into the database,
3) CONTROLLER should receive request from VIEW, get results from COMMAND OBJECT, and send results back to VIEW.
Did I understand correctly?
Thanks.
I found this to be the best setup. Here is an example that I use on production:
Command Object (to carry data and ensure their validity):
#grails.validation.Validateable
class SearchCommand implements Serializable {
// search query
String s
// page
Integer page
static constraints = {
s nullable: true
page nullable: true
}
}
Controller (directs a request to a Service and then gets a response back from the Service and directs this response to a view):
class SomeController {
//inject service
def someService
def search(SearchCommand cmd) {
def result = someService.search(cmd)
// can access result in .gsp as ${result} or in other forms
render(view: "someView", model: [result: result])
}
}
Service (handles business logic and grabs data from Domain(s)):
class SomeService {
def search(SearchCommand cmd) {
if(cmd.hasErrors()) {
// errors found in cmd.errors
return
}
// do some logic for example calc offset from cmd.page
def result = Stuff.searchAll(cmd.s, offset, max)
return result
}
}
Domain (all database queries are handled here):
class Stuff {
String name
static constraints = {
name nullable: false, blank: false, size: 1..30
}
static searchAll(String searchQuery, int offset, int max) {
return Stuff.executeQuery("select s.name from Stuff s where s.name = :searchQuery ", [searchQuery: searchQuery, offset: offset, max:max])
}
}
Yes, you understood it correctly except the one thing: command object shouldn't save the data to DB - let service to do that. The other advantage of command object is data binding and validation of data from the client. Read more about command objects here grails command object docs
You can also find helpful information regarding your question in this article
grails best practices
I guess not. Its not really related to whether the save is done in a service it should always attempt to carry out complex stuff and specifically db stuff in a service. so that is regardless. I tend to not use command object but have got hooked on helper classes aka beans that sit in src/main/groovy and do all of the validation and formatting. I just did a form and in it has feedback and reason.
Initially I thought I would get away with
def someAction(String feedback, String reason) {
someService.doSomething(feedback,reason)
}
But then I looked closed and my form was firstly a textarea then the selection objects were bytes so above was not working and to simply fix it without adding the complexity to my controller/service I did this:
packe some.package
import grails.validation.Validateable
class SomeBean implements Validateable {
User user
byte reason
String feedback
static constraints = {
user(nullable: true)
reason(nullable:true, inList:UsersRemoved.REASONS)
feedback(nullable:true)
}
void setReason(String t) {
reason=t as byte
}
void setFeedback(String t) {
feedback=t?.trim()
}
}
Now my controller
class SomeController {
def userService
def someService
def doSomething(SomeBean bean){
bean.user = userService.currentUser
if (!bean.validate()) {
flash.message=bean.errors.allErrors.collect{g.message([error : it])}
render view: '/someTemplate', model: [instance: bean,template:'/some/template']
return
}
someService.doSomeThing(bean)
}
}
Now my service
Class SomeService {
def doSomeThing(SomeBean bean) {
if (bean.user=='A') {
.....
}
}
All of that validation would have still had to have been done somewhere, you say no validation but in a good model you should do validation and set things to be stored in proper structures to reduce overloading your db over time. difficult to explain but in short i am talking about your domain class objects and ensuring you are not setting up String something string somethingelse and then not even defining their lenghts etc. be strict and validate
if you have a text area this will be stored in the back end - so you will need to trim it like above - you will need to ensure the input does not exceed the max character of the actual db structure which if not defined will probably be 255
and by doing
static constraints = {
user(nullable: true)
reason(min:1, max:255, nullable:true, inList:UsersRemoved.REASONS)
Has already invalidated it through the bean.validate() in the controller if the user exceeded somehow my front end checks and put in more than 255.
This stuff takes time be patient
Edited to finally add in that example byte - is one to be careful of -
When adding any String or what ever I have started to define the specific like this and in the case of byte if it is a boolean true false - fine if not then define it as a tinyint
static mapping = {
//since there is more than 1 type in this case
reason(sqlType:'tinyint(1)')
feedback(sqlType:'varchar(1000)')
// name(sqlType:'varchar(70)')
}
If you then look at your tables created in the db you should find they have been created as per definition rather than standard 255 varchar which I think is the default for a declared String.
So I decided to use AjaxDependencySelection Plugin for Grails, and it has proven to be very useful. However, I am trying to implement autoComplete boxes, and it does not seem to be saving the object id when using an Autocompleted selection. Here is my implementation in my gsp
<g:selectPrimary id="template" name="template"
domain='dms.nexusglobal.Template'
searchField='templateName'
collectField='id'
domain2='dms.nexusglobal.Tag'
bindid="template.id"
searchField2='tagName'
collectField2='id'
hidden="hiddenNew"
noSelection="['': 'Please choose Template']"
setId="tag"
value="${documentPartInstance?.template}"/>
<g:selectSecondary id="tag" name="tag"
domain2='dms.nexusglobal.Subtag'
bindid="tag.id"
searchField2='subtagName'
collectField2='id'
autocomp="1"
noSelection="['': 'Please choose Tag']"
setId="subtag"
value="${documentPartInstance?.tag}"/>
<g:autoCompleteSecondary id="subtag" name="subtagId"
domain='dms.nexusglobal.Subtag'
primarybind='tag.id'
hidden='tag'
hidden2='hidden5'
searchField='subtagName'
collectField='id'
value='${documentPartInstance?.subtag}'/>
<input type=hidden id="hidden5" name="subtagId" value="${documentPartInstance?.subtag}"/>
However, everytime I save it, I am presented with this error Column 'subtag_id' cannot be null . Here is my domain class definition for Subtag
class Subtag {
static scaffold = true
String subtagName
static belongsTo = [tag : Tag]
public Subtag()
{
}
public Subtag(String s)
{
subtagName = s
}
static constraints = {
}
String toString(){
subtagName
}
}
Tag hasMany subtags as well
It seems to be creating new Subtag instances when using the autoselect box (as an error shows up saying Could not find matching constructor for:packagename.Subtag(java.lang.String) Although this is a feature I am looking to implement in my application at later stages (being able to create new Subtags on the fly when creating a document Part), right now, all I would like to be able to do is just choose from my already existing subtags.
When I add in a string constructor, it comes back with the error that Column subtag_id cannot be null
I have developed it so will try help you through your issue.
The problem is that you are trying to push a value from selectSecondary and update the elementId of g:autocomplete which is actually a seperate entity.
I will update the plugin with a new method, need to test it out first.. Also take a look at g:selectAutoComplete. Although this method would only work if your secondary was the primary task... so no good in that case either..
hang on and look out for 0.37 release
Released 0.37 documentation on how to do such a thing here: https://github.com/vahidhedayati/ajaxdependancyselection/wiki/from-selection-to-autocomplete---how-to
Setting up some integration tests, I'm having issues with domain class equality. The equality works as expected during normal execution, but when testing the Service methods through an integration test, the test for equality is coming back false.
One service (called in the setUp() of the Test Case) puts a Domain object into the session
SomeService {
setSessionVehicle(String name) {
Vehicle vehicle = Vehicle.findByName(name)
session.setAttribute("SessionVehicle", vehicle)
}
getSessionVehicle() {
return session.getAttribute("SessionVehicle")
}
}
Elsewhere in another service, I load an object and make sure the associated attribute object matches the session value:
OtherService {
getEngine(long id) {
Vehicle current = session.getAttribute("SessionVehicle")
Engine engine = Engine.get(id)
if(!engine.vehicle.equals(current)) throw Exception("Blah blah")
}
}
This works as expected during normal operation, preventing from loading the wrong engine (Ok, I sanitized the class names, pretend it makes sense). But in the integration test, that .equals() fails when it should succeed:
Vehicle testVehicle
setUp() {
Vehicle v = new Vehicle("Default")
v.save()
someService.setSessionVehicle("Default")
testVehicle = someService.getSessionVehicle()
}
testGetEngine() {
List<Engine> engines = Engine.findAllByVehicle(testVehicle)
//some assertions and checks
Engine e = otherService.getEngine(engines.get(0).id)
}
The findAll() call is correctly returning the list of all Engines associated with the vehicle in the session, but when I try to look up an individual Engine by ID, the equality check for session Vehicle vs Vehicle on the found Engine fails. Only a single vehicle has been created at this point and the Exception message displays that the session Vehicle and the Engine.Vehicle exist and are the same value.
If I attempt this equality check in the testCase itself, it fails, but I'm able to change the testCase to check if(vehicle.id == sessionVehicle.id) which succeeds, but I'm not keen on changing my production code in order to satisfy an integration test.
Am I doing something wrong when setting up these domain objects in my test case that I should be doing differently?
First of all, the equality check you are doing is just checking the reference. You should not use the default equals method for your check, better override the equals method in domain class.
There are two ways you can override the equals method:
1) you can use your IDE to auto-generate code for equals method(a lot of null checking etc..).
2) Preferred way: You can use EqualsBuilder and HashCodeBuilder classes from the Apache Commons project. The library should be already available to your application, or download the JAR file and place in lib. Here is sample code for using EqualsBuilder:
boolean equals(o) {
if ( !(o instanceof Vehicle) ) {
return false
}
def eb = new EqualsBuilder()
eb.append(id, o.id)
eb.append(name, o.name)
eb.append(otherProperties, o.otherProperties)
....
return eb.isEquals()
}
Another point is: how you are getting session in service? from RequestContextHolder? Its a good practice to not access session directly from service, rather send the value as method parameter in the service.
I have two entities in my EDM, Application and Address pretty much like the following:
class Application
{
ICollection<Address> Addresses { get; set; }
}
class Address { }
On the client, I create an instance of each and try to add the address instance to the Application.addresses collection:
var address = addressType.createEntity(...);
var application = applicationType.createEntity(...);
application.addresses.push(address);
Unfortunately, I get a runtime exception saying: "Unable to get value of the property 'name': object is null or undefined".
I tracked the exception back to the checkForDups function in breeze.debug.js#9393-9404 (v1.2.8):
function checkForDups(relationArray, adds) {
// don't allow dups in this array. - also prevents recursion
var inverseProp = relationArray.navigationProperty.inverse;
var goodAdds = adds.filter(function(a) {
if (relationArray._addsInProcess.indexOf(a) >= 0) {
return false;
}
var inverseValue = a.getProperty(inverseProp.name);
return inverseValue != relationArray.parentEntity;
});
return goodAdds;
}
As it happens, my entities are in a one-to-many unidirectional relationship (without an inverse navigation property); as a result at runtime relationArray.navigationProperty.inverse is undefined and so the error when trying to access the name property.
Adding a simple check fixes the problem, and allows adding to the collection:
if (!inverseProp) {
return true;
}
So after all this the question is: is this a bug or is it simply that Breeze does not support one-to-many unidirectional?
Edit As of v Breeze 1.3.5, available now (June 4 2013), this has been fixed.
Edit: Ok, this IS a bug, but I couldn't get the fix in for this current release. I will try to get it in the following release.
The fix that you suggested, which was a good idea, actually only hides the issue.
The real problem is that breeze does not have sufficient metadata for the case where we have a unidirectional navigation in the 1->n direction (i.e. not in the n->1 direction). Because of this, duplicate entity checking in the navigation collection will not work and automatic hookup of children to parents will be missing as well.
The simplest work around until we get the fix in is to simply make it a bidirectional navigation. Note that unidirectional navigation in the other direction works just fine.
This is likely a bug
We do have an example in our DocCode sample project of a unidirectional navigation between OrderDetails and Products. But in that case we allow navigation from a OrderDetail -> Product (1-1) but not from Product -> OrderDetails (1-n).
Your case appears to be the opposite, i.e. allowing 1-n but disallowing the corresponding 1-1. I will create some tests and if I can repro this, it will be fixed in the next release.
I will post back here when that occurs. ( and thx for finding it :)
I tried to change the standard 'id' in grails:
calls Book {
String id
String title
static mapping {
id generator:'assigned'
}
}
unfortunately, I soon noticed that this breaks my bootstrap. Instead of
new Book (id:'some ISBN', title:'great book').save(flush:true, failOnError:true)
I had to use
def b = new Book(title:'great book')
b.id = 'some ISBN'
b.save(flush:true, failOnError:true)
otherwise I get an 'ids for this class must be manually assigned before calling save()' error.
but that's ok so far.
I then encountered the same problem in the save action of my bookController. But this time, the workaround didn't do the trick.
Any suggestions?
I known, I can rename the id, but then I will have to change all scaffolded views...
That's a feature of databinding. You don't want submitted data to be able to change managed fields like id and version, so the Map constructor that you're using binds all available properties except those two (it also ignores any value for class, metaClass, and a few others).
So there's a bit of a mismatch here since the value isn't managed by Hibernate/GORM but by you. As you saw the workaround is that you need to create the object in two steps instead of just one.
I can't replicate this problem (used Grails 2.0.RC1). I think it might be as simple as a missing equal sign on your static mapping = { (you just have static mapping {)
Here's the code for a domain object:
class Book {
String id
String name
static mapping = {
id generator:'assigned'
}
}
And inside BootStrap.groovy:
def init = { servletContext ->
new Book(name:"test",id:"123abc").save(failOnError:true)
}
And it works fine for me. I see the id as 123abc.
You need to set the bindable constraint to true for your id prop, e.g.
class Employee {
Long id
String name
static constraints = {
id bindable: true
}
}