Grails 2.2.0
I'm exploring grails and ajax, and maybe I'm an overzealous ajax adaptor, but I really think its a gamechanger, so I'm going in headfirst.
Datamodel is a master detail (1:n). A table in the client shows a piece of the list of master's properties. And upon clicking, the line folds open showing all the details (An attribute domain) and an option to add a new detail to the unfolded master. All well, opening this line calls
def show (Long id) {
def product = Product.get(id)
log.info('show ' + (request.xhr ? 'xerhr' : 'normal') + "product is ${product.title}")
def c = Attribute.createCriteria();
def attributeTypes = LookupValue.findByType('m_attr') //list of values for dropdown menu.
def result = c {
eq("movie", product)
}
for (attr in result) {
log.info('value: ${attr.value}')
}
render ([template: "attributes", model:[focus:params.id
, attributeList: result, attributeTypes: attributeTypes]])
}
Where the attributes templates displays all the related attributes (details) and an add option at the end where you can enter a new value and select a value from a dropdown menu. The client does do an ajax post back to the controller's saveAttribute method to save this new Attribute:
def saveAttribute(Long id, Long luv_id, String value) {
def attribute = new Attribute(movie : Product.get(id)
, label : LookupValue.get(luv_id)
, value : value);
attribute.save();
show();
}
and again calls show() to render the newly created list of attributes back to the client. Now show() does render all the existing attributes but it fails to retrieve the just created attribute whereas it definitely did end up in the database. It also shows up when I reload the page. Where do I go wrong? With hibernate I think I would do a session.flush() to get over this.
And maybe my architecture is wrong and I need to define a service and introducing some transaction boundaries?
def saveAttribute(Long id, Long luv_id, String value) {
Attribute attribute = new Attribute(movie: Product.get(id)
, label: LookupValue.get(luv_id)
, value: value).save(flush: true)
forward(action: "show", id: id)
}
Related
My domain object booking has multiple attributes that are allowed to be null, because they will be set later after the object has been saved to the db.
Part of myService.action():
booking.properties = params
if (booking.contactFirstname?.length() <= 1) { booking.errors.rejectValue("contactFirstname", "empty") }
if (booking.contactLastname?.length() <= 1) { booking.errors.rejectValue("contactLastname", "empty") }
if (booking.contactPhone?.length() <= 1) { booking.errors.rejectValue("contactPhone", "empty") }
if (booking.contactMobile?.length() <= 1) { booking.errors.rejectValue("contactMobile", "empty") }
if (booking.contactEmail?.length() <= 1) { booking.errors.rejectValue("contactEmail", "empty") }
if (booking.hasErrors() || ! booking.validate()) {
return [success: false, model: booking]
} else {
booking.save(failOnError: true)
return [success: true, model: booking]
}
My controller does:
def result = myService.action(params)
if (result.success) {
flash.success = message(code: "msg.successfullySaved")
redirect(action: "registerEventConfirmation", id: result.model.uid, params: [lang: params.lang], mapping: "paginated")
} else {
flash.error = message(code: "msg.errorSavingCheckFields")
render(view: "registerEventStep3", params: [lang: params.lang], model: [booking: result.model])
I'm using
hasErrors(bean: booking,field:'contactFirstname', 'has-error')}
to mark error fields.
If I now submit the form without any values in textfields, all fields are red, booking.errors has >0 errors.
If I submit the form after with a firstname, booking.errors is NULL and no other field is marked.
Is this a Bug? I'm with Grails 2.3.6
additional information
I visit the form, submit it empty completely
I see all form fields in red, object.errors has >0 errors (VALID)
I enter a value in the first field, firstname and submit
I see none of the form fields in red, object.errors =0 errors (INVALID)
I re-submit the form with none changes
I see all empty form fields in red, object.errors has >0 errors (VALID)
Now that I fully understand the situation and since I was having trouble sleeping I thought I give you a very concise answer so that you can hopefully make full sense and use things properly.
Firstly I know creating a validation bean sounds like it will be a lot of work so let me teach you how to do it all relatively simply and why it is my preferred method.
It is my preferred method simply because when you do
class MyController {
def myAction(Mybean bean) {
// 1. the object allowed into this save action
// are all that is available objects withing MyBean.
// If it has user define but not telephone. Then
// if telephone is passed to myAction it will fail and not recognise
// field
// When declaring Date someField or User user then the params now
// received as bean this way is now actually properly bound
// to the data / domainType declared.
// Meaning user will now be actual user or someField actually Date
}
So now to explain how to best solve this issue. When creating beans simply copy over the actual domain class from your domain folder into src/groovy/same/package in grails 2 or src/main/groovy/same/package in grails 3
Change name / class or copy as from Booking to BookingBean so it has a different name.
Add #Validateable above actual BookingBean in grails 2 or add implements to main class like Class BookingBean implements Validateable { in grails 3
Now since it is copied all the objects are identical and at this point a save from the controller would be
class MyController {
def myAction(BookingBean bean) {
Booking booking = new Booking()
// this will save all properties
booking.properties = bean
booking.save()
}
}
But you have a special circumstance and you wanted to declare a transient field in the main domain class what I would do instead is
class BookingBean {
def id
String contactFirstname
String contactLastname
boolean secondSave=false
static constraints = {
id(nullable: true, bindable: true)
contactFirstname(nullable:true) //,validator:checkHasValue)
contactLastname(nullable:true) //,validator:checkHasValue)
secondSave(nullable:true,validator:checkHasValue))
}
//use the same validator since it is doing identical check
static checkHasValue={value,obj,errors->
// So if secondSave has a value but contactFirstName
// is null then complain about contactFirstName
// you can see how secondSave gets initialise below
//typical set this to true when you are about to save on 2nd attempt
//then when set run validate() which will hit this block below
// Check all the things you think should have a
// value and reject each field that don't
if (val) {
if ( !obj.contactFirstname) {
errors.rejectValue('contactFirstname',"invalid.contactFirstname")
}
if ( !obj.contactSecondname) {
errors.rejectValue('contactSecondname',"invalid.contactSecondname")
}
//and so on
}
}
So now in your controller:
class MyController {
def save1(BookingBean bean) {
Booking booking = new Booking()
// this will save all properties
booking.whatEver = bean.whatEver
booking.save()
// you can choose to validate or not here
// since at this point the secondSave has
// not been set therefore validation not called as yet in the bean
}
//you probably have id and it should bind with actual domain class
def save2(BookingBean bean) {
booking.secondSave=true
if (!bean.validate()) {
//this is your errors
//bean.errors.allErrors
return
}
//otherwise out of that loop since it hasn't returned
//manually set each object
booking.contactFirstname=bean.contactFirstName
booking.contactSecondname=bean.contactSecondname
booking.save()
}
}
e2a side note - above should answer
well don't validate it until you have created it. Only validate it after you created the object then added a value. Alternative create a function possibly in a validation bean that you run as part of your 2nd check. This Example bean is not validated until formatRequest is called as seen here
I don't grasp the specifics of your question, so I will give some general guidance since I have just dug into this.
Don't call hasErrors() before validate(). If you do, Grails won't hand you errors from domain constraints and you will only end up with the errors you set yourself using rejectValue().
Be careful with using rejectValue(). Try to set all your errors using domain constraints. If you have sophisticated constraints use the validator syntax and obj.getPersistentValue() might be your friend once in a while.
If you still have to use rejectValue(), understand that any later calls to validate() will start from scratch and erase your prior errors. I have written a workaround for this (to be placed in your domain object) although I can't assure you it is 100% ok:
def validateWithErrors(def fields = null) {
def existingErrors = this.errors
def ret = (fields ? this.validate(fields) : this.validate())
existingErrors?.allErrors?.each { error ->
this.errors.rejectValue(error.field, error.code)
}
return (existingErrors?.allErrors ? false : ret)
}
After saving a new instance of a domain class in grails, I have a "create another like this" button that brings up another create screen where the fields are populated with the values of the instance I just created.
In the first try, I passed all the existing field values as params in an alternate create button:
<g:link class="create" action="create"
params="[app:volInstance.app.id,
ass:volInstance.assessment.id,
name:volInstance.volName,
type:volInstance.volType.id,
note:volInstance.volNote,
recommendation:volInstance.recommendation,
discovered:volInstance.dateFound,
url:volInstance.urlParam]">
Create Another like this
</g:link>
and then doing a lot of <g:if> on the next create.gsp to see if the parameters are present. I then advanced to just sending the instance id as a param
<g:link class="create" action="create"
params="[vid:volInstance.id]">
and changed the create method in the controller. This simplified things (no longer have a huge params list):
def create() {
if (params.vid) {
def id = params.vid
def v = Vol.findById(id)
params.volNote = v?.volNote
params.volType = v?.volType
etc......
}
respond new Vol(params)
}
This works nicely and eliminates all the <g:if>s but still have a lot of lines of params.x = v.x
would there be a way to get rid of those lines and just pass the object as a param?
Looks like a good place for a Command Object. You can declare it in your controller, and then pass it as an argument to your action. You can even add validation if you want.
class MyCommand {
Long id
String volNote
String volType
static constraints = {
volNote (blank: false)
//...
}
}
Then in your action:
def create(MyCommand cmd) {
Long id = cmd.id
//...
I have a service which updates a db column. The update is done using executeUpdate. In my test after the service method I'm trying to load the record. The record loads and every field is populated except for the one I just updated in the service.
To make it stranger, when I run the code normally through a browser it works perfectly. I can look in the database and see that the field is being persisted. It's only the integration test that doesn't work. It's got to be some type of hibernate session issue with the dirty field.
Here is my stripped down code. I left out the controller info. My test calls the controller, the controller calls the service.
class BasicProfile {
static hasMany = [ photos:Photo ]
}
class Photo {
BasicProfile basicProfile
String caption
String publicId
static belongsTo = [ basicPofile:profile ]
}
class PhotoService {
def updateCaption() {
...
Photo.executeUpdate("update Photo p set p.caption = ? where p.basicProfile.id = ? and p.publicId = ? ",
[newCaption, profile.id, publicId])
...
}
}
void testUpdateCaption() {
...
controller.updateCaption() //controller just calls the photoService method
//get json result from controller to load publicId
...
Photo photo = Photo.findByPublicId(publicId)
assertEquals "my new caption", photo.caption //photo.caption is null, but the rest of the photo object is populated properly from the database load
}
I've added a breakpoint on the assert so I can view the photo instance. It's a valid instance and every field is populated with the data from when it was created (prior to calling controller.updateCaption(). But after calling controller.updateCaption(), the caption field should have valid data, but it's still null (the default when the instance is created).
That's probably a cache of your domain instance, try this:
void testUpdateCaption() {
controller.updateCaption()
//force the query in a clean hibernate session
Photo.withNewSession {
def photo = Photo.findByPublicId(publicId)
assertEquals "my new caption", photo.caption
}
}
I am trying to reuse the same form for adding and editing employee information. I am using knockout js and on my view I make the knockout model:
var koModel = new EmployeeModel(div);
and if I want to populate the fields from the server I want to do something like this:
var koModel = new EmployeeModel(unserializedModelFromController, div);
I was wondering what is the best way to distinguish if the request is for a new employee or if it is to edit an existing employee.
If you turn your parameters around you can write a single constructor function.
var EmployeeModel = function(div, model) {
if (model) {
// Existing model has been passed, it's an edit request
} else {
// No model has been passed, it's a new request
}
}
This can be called like:
new EmployeeModel(div);
or
new EmployeeModel(div, model);
You can send a parameter with a default value to the view.
If you are editing an employee, you can send the value of id, you're creating not send.
The function that receives a request to store or edit could have a default value.
public void SaveOrEditEmployee(int id=0, ...) //id=0 is a default value
{
if(id==0)
{
//SaveEmployee
}else
{
//EditEmployee
Employee empl = (x => employee.id == id);
...
}
}
Or you can do likewise, receive full model and assess whether the property 'id' already exists in your database
I'm very new to this, so any help is appreciated.
I'll use the Dinners/RSVPs relationship for detailing my problem. One Dinner has many RSVPs.
On my Dinner edit page/view I want to be able to edit the Dinner information AND the RSVPs information.
I have that part working, based on the answer given here by James S:
int i = 0;
foreach (var r in Dinner.RSVPs) {
UpdateModel(r, "Dinner.RSVPs[" + i++ + "]");
}
But what if I want to Delete an RSVP based on the user clicking a checkbox next to the RSVP on the edit view? Something like this on the edit view:
<% int i = 0;
foreach (var rsvp in Model.RSVPs){%>
<%=Html.CheckBox("RemoveRSVP[" + i + "]") %>
<%= Html.TextBox("Dinner.RSVPs[" + i + "].Name", rsvp.Name) %>
<% i++;
}%>
I tried this, but it's not working, obviously:
Dinner dinner = dinnerRepository.GetDinner(id);
int i = 0;
foreach (var r in dinner.RSVPs) {
if (Boolean.Equals(RemoveRSVP[i], true){
dinner.RSVPs.Remove(r);
else
UpdateModel(r, "Dinner.RSVPs[" + i+ + "]");
i++;
}
I can't delete/remove an RSVP using UpdateModel can I?
Let me know if anything isn't clear.
Thanks.
I tried this, but it's not working, obviously:
Is your difficulty in actually making the delete go through? Or is it in processing the form to detect which ones should be deleted? e.g. which line doesn't work:
dinner.RSVPs.Remove(r);
or
if (Boolean.Equals(RemoveRSVP[i], true)
?
For #1
If your repository is backed by Linq 2 Sql and RSVP is an entity, you will usually have to cause DeleteOnSubmit() to be called in order for the record to be deleted from the database--just calling Remove() on the association will not be enough. You probably will add one of the following to your DinnerRepository to do this:
DinnerRepository.DeleteRsvp(RSVP item)
DinnerRepository.DeleteRsvp(Dinner dinner, RSVP rsvp)
Alternately, if you want LINQ to perform the delete automatically, you can edit the DBML as XML (right click, open with, XML Editor) and add the following attribute to the entity association:
<Association Name="..." ... DeleteOnNull="true" />
For #2
I usually construct this type of "repeating entity-delete checkbox" form so that the posted values are a list of the entity IDs I want to delete. To facilitate this I use an alternate CheckBox helper:
public static class HtmlExtensions
{
/// <summary>
/// Alternate CheckBox helper allowing direct specification of "name", "value" and "checked" attributes.
/// </summary>
public static string CheckBox(this HtmlHelper html, string name, string value, bool isChecked)
{
string tag = String.Format("<input type=\"checkbox\" name=\"{0}\" value=\"{1}\" {2}/>",
html.AttributeEncode(name),
html.AttributeEncode(value),
isChecked ? "checked=\"checked\" " : ""
);
return tag;
}
}
and create the checkboxes like so:
<%= Html.CheckBox("RemoveRsvpIds", rsvp.RsvpId.ToString(), false) %>
and consume the list like so:
public ActionResult TheFormInQuestion(int dinnerId, int[] removeRsvpIds)
{
var dinner = DinnerRepository.GetDinner(dinnerId);
foreach (var rsvp in dinner.RSVPs)
{
if (removeRsvpIds.Contains(rsvp.RsvpId))
{
// Perform Delete
}
else
{
// Perform Update
}
}
// The rest
}
I can't delete/remove an RSVP using UpdateModel can I?
The purpose of UpdateModel() is to automagically copy property values from the posted form onto an already-existing object--not to create new or destroy existing entities. So no, not really. It wouldn't be the expected behavior.
I am not totally familiar with the NerdDinner code but don't they use Linq to SQL for their backend? If that is the case I would think you could tackle this in a traditional web approach and append the record ID to the value of each check box in a list of items to be deleted. Then you could catch the collection of IDs on the server side and do a DeleteAllOnSubmit by passing a selection query of entities to the delete call? Let me know if you need more detail.