What does person.properties = params do?
Well, the short answer is that it matches any key in the params map with the properties of the person object, assigning the value in the params map to the property that matches.
example: Let's say params.id=156 and person has a member property named id. After this call, person.id would equal 156.
Some notes:
If there are keys in params that
don't match properties in person,
that's ok, it just won't do anything
with those.
If there are properties in person that don't have keys in params? Also
ok, it'll skip those too.
This is also very similar to creating a new Person via "new
Person( params )" or calling
"bindData( person, params )".
There is comprehensive documentation on the Grails web site
Behind the scenes, the properties on a Groovy/Grails object is a map of the domain class properties. The params object is also a map of the request parameters - basically the HttpServletRequest object CGI parameters. So the assignment will update the properties map with values from the params map, only where they match.
If you were to do this with straight Servlets & JSP's you would essentially be writing:
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
Person person = new Person();
person.firstName = request.getParameter('firstname');
person.lastName = request.getParameter('lastname');
person.password = request.getParameter('password');
...
}
With Grails, you would essentially just write this in PersonController.groovy:
def save = {
def person = new Person()
person.properties = params
...
}
So with Grails you don't need to worry too much about what the parameter names are since you should be using grails tags to output them and then the params mapping to get them back into the object. This lessens the stupid errors encountered when you mispel a parameter name.
You also can add more properties to the Person domain object and not have to write more getter/setter type statements.
It updates the property values on the person object using the supplied request parameters. This is called data binding and is documented here.
Related
If I have this:
public ActionResult BuscarClientes(SomeClass c)
{ ... code ...}
And I access the url to this action without any parameter (so I don't give any elements to my model), I still get a newly created object. But, I'm wanting to get a NULL object instead if no arguments are given.
This is because I'm using this method as a search action method, and the first time the get is done I don't want to perform any validation and just return the view. After that, the post will be a GET method (its a search I need to make it a get request) with all the values in the query string.
How can I force the model binder to give me a null object if no parameters are given in the query string? Because as it is now, i get a new instance of SomeClass with all its properties set to null. Instead of just a null object.
Try specifying default value to the parameter
public ActionResult BuscarClientes(SomeClass c = null)
{ ... code ...}
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.
I needed a domain class that held a list of Strings. It seems fairly well-known that GORM can't handle this, so I've worked around it. At first I tried using getters and setters in the domain class, but that caused problems. Then I found on Stack Overflow a way to use afterLoad() and beforeValidate() to rewrite properties as shown below. This has worked well to allow me to turn the List into a String for persistence and back to a List for use in the app.
class Entries {
// persisted to database
String _entry
// exposed to app
List entry
static transients = ['entry'] //don't try to persist the List
def afterLoad() {
// split the String from the database into a List
entry = _entry?.split('\\|')
}
def beforeValidate() {
// join the List into a String for persisting
_entry = entry.join('|')
}
static constraints = {
_entry maxSize:4000
}
}
This works fine programmatically. The only problem is that the Grails scaffolding can't deal with this, even if I try to enter a pipe-delimited string. I understand the reason why is that the scaffolding creates a form field for _entry, so entry is null when it tries to save the object. And beforeValidate() relies on a List of Strings to work.
I tried to get around this in the controller, by setting params.entry = params._entry, prior to the call to new Entries(params). [I recognize that this is not a perfect solution, but this was my first pass at getting the form working.] And then I added a test in beforeValidate() to set entry = _entry if entry was null. Basically:
EntriesController.groovy:
params.entry = params._entry // I added this line
def entriesInstance = new Entries(params)
Entries.groovy:
def beforeValidate() {
if( entry == null ) entry = _entry // I added this line
_entry = entry.join('|')
}
I thought that would allow me to enter pipe-delimited strings into the scaffolded Create Entries form and get something into the database.
To my surprise, though, I found that both entry and _entry were null in beforeValidate(), even though I printed and verified that params contained both keys in the controller. I don't understand why this happens. How did my adding a new key to params result in nulls arriving in the domain class?
The follow-up question is, of course, what's the right way to make the scaffolded Create Entries form accept a pipe-delimited String that makes it into the database?
I needed a domain class that held a list of Strings. It seems fairly well-known that GORM can't handle this, so I've worked around it.
I don't agree with you here
class Xyz {
static hasMany = [entries: String]
}
Should create a seperate table to hold your list of strings (It will actually be a Set). Here are the docs
I know Grails has a map based constructor for domain objects, to which you can pass the params of a URL to and it will apply the appropriate field settings to the object using introspection, like this...
myDomainInstance = new MyObject(params)
I was wondering whether there was an equivalent method of taking the params and applying them to an existing object and updating values in the same way that the map constructor must work, something like...
myDomainInstance = params
or
myDomainInstance = fromParams(params)
Am I just wishful thinking or does such a thing exist? I can code it up myself but would rather not if it exists already.
Thanks
Adapted from the grails user guide:
obj = MyObject.get(1)
obj.properties = params
Check out the documentation for 'params' under the controller section for more information.
It really depends on what you are trying to do but an equivalent approach use databinding.
def sc = new SaveCommand()
bindData(sc, params)
This give you the benefit of using custom binding. If let say your date format is not the default one you can redefine it through a bean like this:
public class CustomPropertyEditorRegistrar implements PropertyEditorRegistrar {
public void registerCustomEditors(PropertyEditorRegistry registry) {
registry.registerCustomEditor(Date.class, new CustomDateEditor(new SimpleDateFormat("dd/MM/yyyy"), true));
}
}
Aside from there being less code to write, what are the advantages? Is it more secure?
public JsonResult Update (int id, string name)
{
Person person = new Person{
ID=id,
Name=name
}
SavePerson(person);
return Json(...);
}
OR
public JsonResult Update (Person person)
{
SavePerson(person);
return Json(...);
}
I have to disagree with Nick on the notion of values ending up in the URL string. In fact, there is no difference. Try it! Query string parameters can supply the model values with either method.
Another difference which is possibly significant is that when passing ID and name as arguments, those are the only two fields which can ever be updated. When passing a Person as an argument, potentially other fields could be updated. This may or may not be what you want. But UpdateModel will accept a whitelist of properties you'd like it to update (and similarly for binding a Person instance in an argument), so as long as you remember to consider including a whitelist, there is no real difference here.
To me the biggest difference between the two options you show is who is instantiating the Person instance. When you pass ID and name as arguments, it will always be your controller code which instantiates the Person. When you pass a Person as an argument, it will always be the model binder which instantiates the Person. This could be significant if, rather than instantiating a new Person instance, you would like to materialize an existing instance from a repository.
When using the (int id, string name) approach, it is possible these values would end up in the URL string. The other way, it won't. So, that could possibly be considered more secure.
Other than that, if you change the possible properties in your Person class, you wouldn't have to update the values passed in to the (int id, string name) approach. Although, you can get around this by using UpdateMethod(myPersonInstance).