I'm beginner in Grails and I have a problem when I try to save a POGO
I have created 1 domain class
class Book {
String title
}
Then, I have generated the controller and view automatically.
Now, I want to be able to create a book with the code by clicking "create" (I know it is possible directly with the code generated but for my example I want to do it by the code). To do this, I have modified the method 'save(Book bookInstance)' in the controller like this
#Transactional
def save(Book bookInstance) {
def book = new Book(title:"New Grails Book").save()
But, when I go to the URL localhost:8080/myApp/book/create and then I click "Create", I have the error
message -> /myApp/WEB-INF/grails-app/views/book/save.jsp
description -> The requested resource is not available.
When I put this code in bootStrap, it is OK, so I don't understand why it is not in the controller
When you have a hasMany property in a domain class, Grails adds a Set property to the domain class with an AST transformation (so it's actually there in the bytecode, and it's visiable to Java) to represent the collection, and when you add a belongsTo a field of that type is added. So it's as if you had this code:
class Author {
Set<Book> books
static hasMany = [books: Book]
String name
}
and
class Book {
Author author
static belongsTo = [author: Author]
String title
}
The AST xform uses the map key as the field name, so you can use any valid field name, but the convention is to do what you did.
Properties are nullable:false by default, so your code doesn't save the Book instance because you didn't set the author property. When doing this explicitly you typically don't create the Book directly, but instead add it to the Author's collection using the dynamic addToBooks method. This sets the author field back-reference and when you save the author, the book is transitively validated and saved. This is all handled for you when you have code like new Book(params).save(), and you can do it directly, e.g.
Author author = ...
def book = new Book(title:"New Grails Book", author: author).save()
If you're using a generated controller and GSPs, there should be an author id in the params map, it'll likely be author.id, so that first line would be
Author author = Author.get(params['author.id'])
but you can add
println params
at the top of the action method to see all of the submitted params.
In general you don't want to look at the return value of the save call, since it will be null if there's a validation error and there's no way to retrieve the errors. So change
def book = new Book(...).save()
to
def book = new Book(...)
book.save()
and now you can call book.hasErrors(), book.getErrors(), book.errors, etc. to see if it was successful and if not, what went wrong.
But that's not the exact problem you're seeing, just one you will when you fix your problem. There's no save.gsp, and Grails also looks for save.jsp and confusingly includes that name in the not-found message. The save method is accessed via a POST request, typically from the form generated by the create action, and it either re-displays create.gsp with the submitted data and error messages when validation fails, or redirects to the view action when the save succeeds. There's no need for a save.gsp when using the generated code.
Related
Grails will create an id and a version columns from a domain class automatically. I want to use my own column for the primary key. So, I follow the doc to change the mapping.
class book {
String isbn
static mapping = {
id generator: 'assigned', name: 'isbn'
}
}
So far so good. The isbn column is now the primary key.
I use generate-all to create the view and controller. However, the data binding won't work anymore.
Create and Save work no problem. It binds a book to the view. I can add a new book to the database no problem.
def create() {
respond new Book(params)
}
def save(Book book) {
if (book == null) {
notFound()
return
}
...
}
But the Update action does not bind. book is null after I click the Update button from the Edit view.
def update(Book book) {
if (book == null) {
notFound()
return
}
...
}
The codes generated by generate-all in the Save and Update actions are the same. I don't understand why it will bind the book to the Save action but not to Update action.
Would you show me the problem please?
Many Thanks!
I think I figure it out. When I bind an object to a view, Grails is hardcoded to look for the id property. It has to be spelled "id". If there is no "id" property in the domain class, Grails will not bind.
The way I figure this out is to look at the actual HTML generated by the server.
If there is an id property bind to the view, I see the HTML has the ../controller/action/id link.
If the id property is missing, the HTML link is just ../controller/index
I am new to Grails. So, I guess in order for the binding to work, I need to have an id property for Grails to put in the link.
I think this is a REST call. I don't know what REST is though.
So, I will try to add an dummy id property to my Book domain class to see if Grails will take the bait. I will set up the domain so Grails won't generate the id column in the database table. The id property is used locally only. No need to save it to the database.
class book {
String isbn
String id
static mapping = {
id generator: 'assigned', name: 'isbn'
}
}
I will copy the isbn value to the id property. I am not sure if this will work or not. I hope Grails will generate the link in the view with the isbn string in the id property instead of the default integer id value.
../controller/action/978-3-16-148410-0
When you generate grails views, grails looks at your relationships and generates the right html for your form data to be automatically binded to the back end domain. For one to one associations grails creates a drop down list.
However, you might not want to present that property as a drop down list but something more custom (for example a text field with autocomplete). As soon as you do that the value that comes to the controller from that field, comes in as a String and you have to first:
Clear errors
Perform a findBy based on a given param and assign it to the property of the domain
I really want to avoid doing findBys in the controller as much as possible because it seems like I am doing logic/things that should not go there. The controller should delegate to the Service layer. It is not clear to me from the grails documentation how would I do that by using bindData which seems to work really well with String, date, Integer properties etc.. but I do not see how bindData is used for properties that are other domains.
I also really want to avoid passing the params object to the Service layer as it seems less reusable (or maybe not, correct me if I am wrong). I guess that I do not like how it looks semantically. I would prefer the first over the second:
#Transactional
class WithdrawService {
def addWithdraw(Withdraw withdraw) {
//perform business logic here
}
def createWithdraw(Map params){
//perform business logic here
}
}
Let's take the following example:
class Withdraw {
Person person
Date withdrawDate
}
and the parent lookup table
class Person {
String name
String lastName
static constraints = {
}
#Override
public String toString() {
return "$name $lastName"
}
}
In order for the bind to happen automatically without any extra work grails passes in the following request params to automatically bind the one to one:
person.id
a person map with the id.
[person.id:2, person:[id:2], withdrawDate:date.struct, withdrawDate_month:11, create:Create, withdrawDate_year:2015, withdrawDate_day:10, action:save, format:null, controller:withdraw]
What is the best way to go about this?
Pass two hidden fields that look exactly like this: person.id:2, person:[id:2] that get populated as a result of the Ajax call that populates the autocomplete?
In the controller do a Person.findBySomeKnownProperty(params.someKnownValue)
Or any other approach?
I'm still new to Grails and GORM and I got stumped on this and wasn't able to figure out what I am doing wrong. The intent is to automatically relate the record to the logged in user through the Shiro plugin for Grails.
Class User { static hasMany = [stuff: Stuff] }
Class Stuff { static belongsTo = [user:User] }
Class StuffController {
def create = {
params.put('user', User.createCriteria().get{eq('username',SecurityUtils.subject.principal)}.id)
def stuffInstance = new Stuff(params)
stuffInstance.save()
}
}
I saw in the generate-views version of the create scaffold that the relevant field was referred to as name="user.id", but neither it nor variants (such as user_id) seems to work. The query to the Users domain returns the record id necessary, and params.put in this context seems to correctly append the params object with the new value when I render to a test page (so I'm guessing it's not immutable), but this is what I get from the save():
Property [user] of class [class org.stuffing.Stuff] cannot be null
I've even tried flipping it around and going the other way, with the same result:
User.createCriteria().get{eq('username',SecurityUtils.subject.principal)}
.addToStuff(new Stuff(params))`
.save()
Anyone able to enlighten me on what I'm missing here?
Thanks!
EDIT:
Apparently I was being braindead; I was overriding the "create" method, but the default action is "save" in the _form.gsp template, so it wasn't executing that branch.
On the plus side, I did learn about dynamic finders via Burt below, so it wasn't a total wash.
Thanks for your time, guys!
Your code can be a lot cleaner - there's no reason to use createCriteria here. If you're searching by username, use a dynamic finder:
def stuffInstance = new Stuff(params)
def user = User.findByUsername(SecurityUtils.subject.principal)
stuffInstance.user = user
if (!stuffInstance.save()) {
// inspect stuffInstance.errors
}
I am using struts 2.2.3 in web application. I want to use struts2 annotations based validations for the form data submitted from UI. The scenario is:
User fills all the form fields and click on submit button.
I have created a Person class for storing registration form data and RegistrationAction which triggers the logic for registration. RegistrationAction holds reference to person object with respected getters/setters.
Please suggest how to validate individual fields of Person class after form submission, using struts 2 annotations.
Thanks.
Try to make it using the fieldName property of the Validations annotation. Something like the following:
#Validations(
requiredFields =
{#RequiredFieldValidator(type = ValidatorType.SIMPLE, fieldName = "person.name", message = "You must enter a value name.")}
)
Please refer the below link for further reference:
Struts2 Validation-annotation
On the setter method use
#RequiredFieldValidator(type=ValidatorType.FIELD, message="your message here")
#Validator of #Validations(...) not necessary
Have you tried the Struts2 Validation Annotation.Its quite simple and Straight forward all you need is to define the #Validation() annotation at Class level and Apply standard or custom annotations as per your use-case.
Refer official Annotation based Validation document.
Validation-Annotation
You mention a separate Person class, so I am guessing that this is a model-driven Action or using a function like setPerson in the Action class. If you want to validate the individual fields within Person, you will need to use a VisitorFieldValidator in the relevant Action method, and then put field validations on the Person object itself.
For example in the Action
#VisitorFieldValidator(message = "")
public void setPerson(Person person) {
this.person = person;
}
and in Person
#RequiredFieldValidator(message = "Last name is required.")
public void setLastName(String lastName) {
this.lastName = lastName;
}
Given the following two domain classes:
class Book {
String title
static hasMany = [authors: Author]
static belongsTo = Author
static constraints = {
title(nullable: false)
}
}
class Author {
static hasMany = [books: Books]
}
We create and persist domain objects in services and make use of the data binding feature of Grails. Such a method looks like the following one:
def createAndPersistBook(params) throws ValidationException {
log.debug("Attempt to create and persist book")
Book book = new Book(params)
book.save(flush: true, failOnError: true)
log.debug("Created: ${book}")
book
}
When we pass the params map
params = ["authors": "[2]"]
to the service method (there is no title defined thus validation will fail) the association from the newly created book to the already existing author (and vice-versa) is done by data binding. But since the title is nullable: false and not defined a ValidationException is thrown and the transaction is rolled back.
What we expected now is that the book is not being saved, but Book.list().isEmpty() returns false. We think that this is because of the dirty-check by hibernate, meaning the books collection of the existing author has changed and will be persisted and this save gets cascaded to the book instance.
What is the best way to prevent grails from saving the book in this scenario? Or why is the association done by data binding not properly rolled back when validation fails?
If you've specified that your service is transactional, any uncaught exception will cause a transaction to rollback within a service method. The only thing that might stand in your way is if your RDBMS does not support true transactions/rollback.
Have you specified whether the service is transactional? You should have a statement such as below to declare the service is transactional.
def transactional = true