Grails validation with composite key - grails

I'm running into a complex scenario where we are using a legacy database with composite keys and the client wants to be able alter two of the databases pks, "expenseDate" and "adjustmentNumber". In order to be able to change the pks, I've have to use a HQL query. Now of course this has caused another slew of issues with validation. My work around was to populate a domain so that I could validate against it.
So far everything works fine until we have a validation error that I'd like to return to the UI.
I have the following URL which uses the recoveryDetail controller and the edit action to render the page.
http://localhost:8080/pisr/recoveryDetail/edit?division=ALBANY&peid=PI0003&orgkey=14046701&expenseDate=07-22-2015&adjustmentNumber=1
Edit action
def edit() {
//Parse clean url expense date
params.expenseDate = new SimpleDateFormat('MM-dd-yyyy').parse(params.expenseDate)
def recoveryDetailInstance = RecoveryDetail.get(new RecoveryDetail(params))
if(recoveryDetailInstance == null) {
redirect(uri:'/')
return
}
[recoveryDetailInstance: recoveryDetailInstance, disabled: isdisabled(recoveryDetailInstance.batchOverride)]
}
And the following update action.
Update action
#Transactional
def update() {
params.pk_expenseDate = getDateParser(params.pk_expenseDate)
params.expenseDate = getDateParser(params.expenseDate)
params.adjustmentNumber = getAdjustementNumber(params)
RecoveryDetail recoveryDetailInstance = new RecoveryDetail(params);
recoveryDetailInstance.division = params.pk_division
recoveryDetailInstance.peid = params.pk_peid
recoveryDetailInstance.orgkey = params.pk_orgkey
recoveryDetailInstance .validate()
if(recoveryDetailInstance .hasErrors()) {
flash.message = "test"
respond view: "edit", model:[recoveryDetailInstance:recoveryDetailInstance]
return
} else {
def sqlParams = [
pk_division:params.pk_division,
pk_peid:params.pk_peid,
pk_orgkey:params.pk_orgkey,
pk_expenseDate:params.pk_expenseDate,
pk_adjustmentNumber:params.int('pk_adjustmentNumber'),
approved:YesNoTypes.valueOf(params.approved),
batchOverride:YesNoTypes.valueOf(params.batchOverride),
adjustmentFlag:params.adjustmentFlag,
adjustmentNumber:params.adjustmentNumber,
projectHours:new BigDecimal(params.projectHours),
percentEffort:new BigDecimal(params.percentEffort),
totalHours:new BigDecimal(params.totalHours),
expenseDate:params.expenseDate
]
RecoveryDetail.executeUpdate(recoveryDetailQuery, sqlParams)
}
Edit gsp
<g:form class="form-horizontal" url="[resource:recoveryDetailInstance, action:'update']" method="PUT">
<!-- hidden fields contain params from url (composite key)-->
<g:hiddenField name="pk_division" value="${recoveryDetailInstance?.division}"/>
<g:hiddenField name="pk_peid" value="${recoveryDetailInstance?.peid}"/>
<g:hiddenField name="pk_orgkey" value="${recoveryDetailInstance?.orgkey}"/>
<g:hiddenField name="pk_expenseDate" value="${formatDate(format:'MM/dd/yyyy',date:recoveryDetailInstance?.expenseDate)}" />
<g:hiddenField name="pk_adjustmentNumber" value="${recoveryDetailInstance?.adjustmentNumber}"/>
<div class="row">
<div class="col-md-6">
<g:render template="form" model="[recoveryDetailInstance: recoveryDetailInstance, 'mode':'edit']"/>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<g:actionSubmit class="btn btn-primary" action="update" disabled="${disabled}" value="${message(code: 'default.button.update.label', default: 'Update')}"/>
</div>
</div>
</g:form>
The problem
When a user causes a validation error that triggers a server side response, I run into the following problems with the different return types.
redirect - this is returning the flash message, but redirects the gsp to the edit action which in turn fires the initializer query and replaces all the form data with the original data.
Example -
redirect (controller:"recoveryDetail", action:"edit", params:["division":params.pk_division, "peid":params.pk_peid, "orgkey": params.pk_orgkey, "expenseDate":params.expenseDate.format("MM-dd-yyyy"), "adjustmentNumber":params.adjustmentNumber])
respond - So I assumed I needed to just use respond, well it resulted in the following.
URL was changed to http://localhost:8080/pisr/recoveryDetail/update dropping all parameters and a 404 page was returned.
Example
flash.message = "test"
respond view: "edit", model:[recoverDetailInstance:recoverDetailInstance]
return
So my question
How do I throw a server side validation error and return it to the page with the data entered by the user?

You may add the parameters back into the redirect or render call.
redirect (..., params: params)
https://grails.github.io/grails-doc/latest/ref/Controllers/redirect.html
Also, I'd suggest using a service instead of the controller method. Services are already transactional. I would also throw an exception if the hasErrors is true. The exception message or object can be your payload back to the user.

Solution was to use render without the use of params like so.
if(recoveryDetailInstance.hasErrors()) {
render view: "edit", model:[recoveryDetailInstance:recoveryDetailInstance]
return
}

Related

Passing parameter value from react component

In my app, people can comment on pets' images. I am using the react example from here, although I changed quite a few stuff.
Right now, it is successful in displaying the existing comments. Now, when a user is creating a comment, I have to pass the comment body, user id, and pet id. I was trying to do the following:
var CommentForm = React.createClass({
handleSubmit:function()
{
var user=this.refs.user_id.getDOMNode().value.trim();
var comment=this.refs.body.getDOMNode().value.trim();
var pet_id=this.refs.pet_id.getDOMNode().value.trim();
this.props.onCommentSubmit({comment:comment, user:user, pet:pet_id});
if(!user||!comment||!pet_id)
return false;
var formData = $( this.refs.form.getDOMNode() ).serialize();
this.props.onCommentSubmit( formData, this.props.form.action );
// reset form
this.refs.body.getDOMNode().value = "";
},
render: function () {
return (
<form ref="form" className="comment-form" action={ this.props.form.action } accept-charset="UTF-8" method="post" onSubmit={ this.handleSubmit }>
<p><input type="hidden" name={ this.props.form.csrf_param } value={ this.props.form.csrf_token } /></p>
<p><input type="hidden" ref="user" value={ this.props.user_id } /></p>
<p><input type="hidden" ref="pet_id" value={ this.props.pet_id } /></p>
<p><textarea ref="body" name="comment[text]" placeholder="Say something..." /></p>
<p><button type="submit">Post comment</button></p>
</form>
)
}
});
And apparently, it doesn't look like it is passing the pet_id correctly, because I am getting the error message
ActiveRecord::RecordNotFound in CommentsController#create
Couldn't find Pet with 'id'=
My CommentsController looks like
def create
#pet = Pet.find(params[:pet_id])
#comment = #pet.comments.new(comment_params)
#comment.user = current_user
For further clarification, I have three models, Pets, Users and Comments, and when users make comments, the comment gets the user_id, and pet_id as its parameters.
edit:
My react component looks like
<%= react_component('CommentBox',
{:presenter => #presenter.to_json},
{:prerender => true}) %>
and my PetController looks like
def show
#comments = #pet.comments
#user = current_user
#presenter = {
:comments => #comments,
:user => current_user,
:pet_id => #pet,
:form => {
:action => comments_path,
:csrf_param => request_forgery_protection_token,
:csrf_token => form_authenticity_token
}
So there are a few issues I can see. Firstly your using ref where you should be name.
<input type="hidden" ref="pet_id" value={ this.props.pet_id } />
should be
<input type="hidden" name="pet_id" value={ this.props.pet_id } />
Your setting both an action and an onSubmit. Is there a reason to do this? Why not just read it from the props when you perform the ajax request? This is most likely causing your form to be submitted and the browser to load another page. The form submitting has nothing to do with what is on the server. The issue is in your client side code.
I would also consider putting your model values in to their own array. This is generally what rails expects back from the server. In your case it should be params[:pet][:id] not params[:pet_id]. Many of the rails active record methods such as update attributes can then be directly called giving you less verbose code.
#pet = Pet.find(params[:pet][:id])
#pet.update_attributes(prams[:pet])
#pet.save

Want to add custom header to grails application

I have Grails application there is button onclicking which it is forwarded to a action which is authenticated by spring security and first goes to login page if a user is not logged in and then come to the expected page.
I want to set header after the button is clicked and want it in login page only if it had been redirected here after clicking on the button normally it shouldn't be there on login page.
I have redirected to another function from GSP and there I am setting header and from there I have redirected to the actuall function.
For the first time when server runs the value comes but after that it dissappears.
Please guide me.
My code:-
gsp code :
<g:form controller="project" action="addFundraiserUrl" id="${project.id}" params="['fr':currentFundraiser.username]">
<input type="submit" value="Join Us" class="col-md-12 col-sm-12 col-xs-12 inviteteammember text-center btn btn-default btn-md manage-team"/>
</g:form>
controller code :-
def addFundraiserUrl () {
response.setHeader 'X-JoinUs', 'true'
redirect (action:'addFundRaiser', params:params)
}
#Secured(['IS_AUTHENTICATED_FULLY'])
def addFundRaiser(){
def project = projectService.getProjectById(params.id)
User user = userService.getCurrentUser()
def fundraiser = project.user.username
def iscampaignAdmin = userService.isCampaignBeneficiaryOrAdmin(project, user)
def message = projectService.getFundRaisersForTeam(project, user)
def title = projectService.getVanityTitleFromId(params.id)
def name = userService.getVanityNameFromUsername(fundraiser, params.id)
if (iscampaignAdmin) {
flash.prj_mngprj_message = message
redirect (action: 'manageproject', params:['projectTitle':title])
} else {
flash.prj_mngprj_message = message
redirect (action: 'show', params:['projectTitle':title,'fr':name])
}
}

Grails - custom Flash.message rendering

I am running a query to check if an entity id exists for multiple account ids. If the result set is not null, then I need to either throw an error or display a flash message.
The code for the method is as below:
def save() {
def SAMLInfoInstance = new SAMLInfo(params)
def account = Account.get(params?.accountId)
SAMLInfoInstance.setAccount(account)
def samlInfoInstanceList = SAMLInfo.executeQuery("from SAMLInfo " +
" where account.id <> ? " +
" and entityId = ?", [SAMLInfoInstance.accountId, SAMLInfoInstance.entityId])
if (samlInfoInstanceList?.size > 0){
flash.message = message(code: 'test.not.created.message', args: [message(code: 'SAMLInfo.label', default: 'SAMLInfo'), SAMLInfoInstance.entityId])
/*flash.message = "default.not.created.message"
flash.args = ["SAMLInfo", SAMLInfoInstance.entityId]
flash.default = "SAMLInfo cannot be created"
*/
render(view: "create", model: [SAMLInfoInstance: SAMLInfoInstance])
return
}
if (!SAMLInfoInstance.save(flush: true)) {
render(view: "create", model: [SAMLInfoInstance: SAMLInfoInstance])
return
}
flash.message = message(code: 'default.created.message', args: [message(code: 'SAMLInfo.label', default: 'SAMLInfo'), SAMLInfoInstance.entityId])
redirect(action: "list", id: SAMLInfoInstance.account.id)
}
In my view I render the flash message and the errors in the following manner:
<g:if test="${flash.message}">
<br/>
<div class="message" role="status">
<g:message code="${flash.message}" args="${flash.args}" default="${flash.default}"/>
</div>
<br/>
</g:if>
<br/>
<g:renderErrors bean="${SAMLInfoInstance}" as="list" />
In my message.properties file, I have the following line:
test.not.created.message=The SP url {1} is not allowed for this account. Please enter a different value.
When I run this code, the flash message displays as the string I pass as message i.e. "test.not.created.message". Also, this string is passed on to display whenever I navigate to any other page which displays flash.message.
I am new to grails and groovy and would appreciate any help with this.
Thanks!
2 problems then:
1 - the message is not being retrieved from your message.properties:
You must have other message.properties files in your project. Give it a check. Because if it's not found, grails shows the code itself instead of the message, since it did not find one. Maybe it's looking for your message in some other properties file, like the one specific for your Locale (ex: pt_BR or en_US). Other than that, you're doing it right using the message(code:...) construct.
2 - Your flash message doesn't disappear:
Instead of flash.message, use request.message.
I think the example you want to follow is here.
You only need to resolve the message once ie in the controller or in the view.
So in the controller:
flash.message = "test.not.created.message"
flash.args = ["SAMLInfo"]
flash.default = "<default text>"
And in the view:
<g:message code="${flash.message}" args="${flash.args}"
default="${flash.default}"/>
The flash scope is cleared at the end of the next request which may explain why you still see the message on the next page.

Grails Validation : renderErrors not picking up after formRemote

This is in the gsp
<g:if test="${hasError}">
<div class="errors">
<g:renderErrors bean="${eventInstance}" />
</div>
</g:if>
<g:else >
<div id="messageBox" class="message" style="display:none;">
<g:message code="legalevent.save.success" args="[entityName]" default="Event saved successfully" />
</div>
</g:else>
<g:formRemote name="eventForm" id="eventForm" url="[controller : 'search', action : 'saveLegalEvent']"
update="eventFormDiv" action="${createLink(controller: 'search', action: 'saveLegalEvent')}" method="POST"
onSuccess="jQuery('#messageBox').show()">
I am rendering a page for update with this :
def saveLegalEvent = {
def paramsView = params
def eventPattern = /(event\.).*/
def event = LegalEvent.findByLevId(params["levId"])
def corrTxt = params["corrTxt"] as CorrectionText
if(corrTxt.getCorrId()){
corrTxt = CorrectionText.findByCorrId(corrTxt.getCorrId())
}
event.setCorrTxt(corrTxt)
event.properties = params["event"]
def dataList = []
def hasError = false
def validated = event.validate()
validated &= event.validateHistoryParams()
if(validated)
event.save(flush:true)
else
hasError = true
def errorsView = event.errors
render(view:'leform', model:[attributeDataInstanceList:event.tags, lecInstance:event.leCode, eventInstance:event, hasError: hasError])
}
validateHistoryParams validate some more params that are usually not needed in the domain class.
def validateHistoryParams = { ->
if(!changeRef || !changeRef.trim()) {
this.errors.rejectValue('changeRef', 'event.changeRef.blank')
}
if(!corrTxt || !(corrTxt.corrTxt.trim() || corrTxt.corrId )) {
this.errors.rejectValue('corrTxt', 'event.corrTxt.null')
}
!(this.hasErrors())
}
The problem with all this is that the errors are not rendered in the gsp.
All other tags are rendered fine, when debugging I can see that the errors are actually in the error stack. But in the end, the tag isn't rendering them.
As you can see, there is no redirection, so I can't understand why the errors would somehow be erased between the response creation and the rendering ...
In your Groovy code, parameter returned is named hasError, and GSP checks for hasErrors. I'd recommend not to use extra variables, and just query the bean itself in GSP.
I also believe that you need to have that errors div inside the formRemote element in order to re-render after form submission.

Rendering command validation errors across a redirect

I cannot render the errors from my command object. It does the job well but my .gsp view does not render the errors I raise.
Here is my controller action:
def handleModifyProfile2 = { CreditProviderModificationCommand cpmc -> // bind params to the command object
if (cpmc.hasErrors()) {
flash.message = "Error modifying your profile:"
redirect(action: "modifyProfile", params: [creditProvider : cpmc])
} ...
Here is how I try to render the errors in my .gsp view:
<g:hasErrors bean="${creditProvider}">
<div class="errors">
<g:renderErrors bean="${creditProvider}" as="list" />
</div>
</g:hasErrors>
How can I get the errors to be displayed in the view?
You can't send the command across in a redirect using params. You have a couple options:
render() in the error condition instead of redirect()ing:
if(cpmc.hasErrors()) {
render(view: 'profile', model: [creditProvider: cpmc])
}
This is the most common idiom for what you're doing.
Store the command in the session to persist it across the redirect:
if(cpmc.hasErrors()) {
session.cpmc = cpmc
redirect(...)
}
// and in your action
def cpmc = session.cpmc ?: null
render(view: 'profile', model: [creditProvider: cpmc])
This option is somewhat questionable. If not done correctly, you can pollute the session and leave objects hanging around, taking up memory. If done correctly, though, it can be a decent way to implement a post-redirect-get.
With Grails 3 (I don't know if this worked earlier) it's possible to use the flash for this. According to the documentation, the flash will be "cleared at the end of the next request".
I like to use a pattern like this:
def save(MyDomain myDomain) {
if (myDomain.validate()) {
myDomain.save()
} else {
flash.errors = myDomain.errors
}
redirect(action: 'edit', id: myDomain.id)
}
def edit(MyDomain myDomain) {
if (flash.errors) {
myDomain.errors = (Errors) flash.errors
}
return [myDomain: myDomain]
}
I don't like to use render() for this kind of error handling, because it makes URLs shown in the browser inconsistent with the shown page. This breaks when users set bookmarks, for example.

Resources