Grails error handling in controller for loop - grails

I'm iterating through an uploaded CSV file within my controller with a for loop. If any of my records contain errors, I need to be able to display the entire list of errors within the GSP and not save any of the records. If no errors are present, save the entire batch of records.
I'm struggling in grails to understand how to do this. Do you iterate the CSV file twice, the first time looking for errors, the second time to handle the db commit? Do I create a list of errors and send them back as a flash.message?
My Code
def uploadFile() {
CsvToBean<EffortDetail> csvToBean = new CsvToBean<>();
Map<String, String> columnMapping = new HashMap<String, String>();
columnMapping.put("ExternalID", "alternateId");
columnMapping.put("Full Name", "fullName");
columnMapping.put("OrgKey", "orgkey");
columnMapping.put("expenseDate", "expenseDate");
columnMapping.put("projectHours", "projectHours");
columnMapping.put("totalHours", "totalHours");
HeaderColumnNameTranslateMappingStrategy<EffortDetail> strategy = new HeaderColumnNameTranslateMappingStrategy<EffortDetail>();
strategy.setType(EffortDetail.class);
strategy.setColumnMapping(columnMapping);
MultipartFile file = request.getFile('file');
if(file.empty) {
flash.message = "File cannot be empty"
} else {
List<EffortDetail> effortDetails = null;
CSVReader reader = new CSVReader(new InputStreamReader(file.getInputStream()));
effortDetails = csvToBean.parse(strategy, reader);
int count = 0;
//iterate for errors
for(EffortDetail effortDetail : effortDetails) {
println "loop 1 " + count++
def recoveryDetailInstance = recoveryDetailService.populate(effortDetail)
// Test code to try and throw a list of flash messages
flash.message = count++;
}
count = 0;
//Iterate for commit
for(EffortDetail effortDetail : effortDetails) {
println "loop 2 " + count++
def recoveryDetailInstance = recoveryDetailService.populate(effortDetail)
recoveryDetailInstance.save(flush:true,failOnError:true)
}
}
redirect (action:'upload')
}
.gsp
<div class="col-md-12">
<g:if test="${flash.message}">
<div class="message" role="status">
${flash.message}
</div>
</g:if>
<g:eachError bean="${recoveryDetailInstance}">
<li>${it}</li>
</g:eachError>
<g:uploadForm action="uploadFile">
<span class="button"> <input type="file" name="file" /> <input
type="submit" class="upload" value="upload" />
</span>
</g:uploadForm>
</div>

You need to look into transaction. Here's another Stackoverflow thread that I believe does exactly what you're looking for.
Grails - Saving multiple object, Rollback all object if one fails to save
The TLDR is to move your processing logic from the controller to a Service class method. Then combine your loop into a single loop that does both the validation and the domain save. If anything errors out you can return the list of failed domain objects after invoking the rollback.

Related

ASP.NET MVC 5 always display the first row in the table [duplicate]

this is a tricky one to explain, so I'll try bullet pointing.
Issue:
Dynamic rows (collection) available to user on View (add/delete)
User deletes row and saves (POST)
Collection passed back to controller with non-sequential indices
Stepping through code, everything looks fine, collection items, indices etc.
Once the page is rendered, items are not displaying correctly - They are all out by 1 and therefore duplicating the top item at the new 0 location.
What I've found:
This happens ONLY when using the HTML Helpers in Razor code.
If I use the traditional <input> elements (not ideal), it works fine.
Question:
Has anyone ever run into this issue before? Or does anyone know why this is happening, or what I'm doing wrong?
Please check out my code below and thanks for checking my question!
Controller:
[HttpGet]
public ActionResult Index()
{
List<Car> cars = new List<Car>
{
new Car { ID = 1, Make = "BMW 1", Model = "325" },
new Car { ID = 2, Make = "Land Rover 2", Model = "Range Rover" },
new Car { ID = 3, Make = "Audi 3", Model = "A3" },
new Car { ID = 4, Make = "Honda 4", Model = "Civic" }
};
CarModel model = new CarModel();
model.Cars = cars;
return View(model);
}
[HttpPost]
public ActionResult Index(CarModel model)
{
// This is for debugging purposes only
List<Car> savedCars = model.Cars;
return View(model);
}
Index.cshtml:
As you can see, I have "Make" and "Actual Make" inputs. One being a HTML Helper and the other a traditional HTML Input, respectively.
#using (Html.BeginForm())
{
<div class="col-md-4">
#for (int i = 0; i < Model.Cars.Count; i++)
{
<div id="car-row-#i" class="form-group row">
<br />
<hr />
<label class="control-label">Make (#i)</label>
#Html.TextBoxFor(m => m.Cars[i].Make, new { #id = "car-make-" + i, #class = "form-control" })
<label class="control-label">Actual Make</label>
<input class="form-control" id="car-make-#i" name="Cars[#i].Make" type="text" value="#Model.Cars[i].Make" />
<div>
<input type="hidden" name="Cars.Index" value="#i" />
</div>
<br />
<button id="delete-btn-#i" type="button" class="btn btn-sm btn-danger" onclick="DeleteCarRow(#i)">Delete Entry</button>
</div>
}
<div class="form-group">
<input type="submit" class="btn btn-sm btn-success" value="Submit" />
</div>
</div>
}
Javascript Delete Function
function DeleteCarRow(id) {
$("#car-row-" + id).remove();
}
What's happening in the UI:
Step 1 (delete row)
Step 2 (Submit form)
Step 3 (results)
The reason for this behavior is that the HtmlHelper methods use the value from ModelState (if one exists) to set the value attribute rather that the actual model value. The reason for this behavior is explained in the answer to TextBoxFor displaying initial value, not the value updated from code.
In your case, when you submit, the following values are added to ModelState
Cars[1].Make: Land Rover 2
Cars[2].Make: Audi 3
Cars[3].Make: Honda 4
Note that there is no value for Cars[0].Make because you deleted the first item in the view.
When you return the view, the collection now contains
Cars[0].Make: Land Rover 2
Cars[1].Make: Audi 3
Cars[2].Make: Honda 4
So in the first iteration of the loop, the TextBoxFor() method checks ModelState for a match, does not find one, and generates value="Land Rover 2" (i.e. the model value) and your manual input also reads the model value and sets value="Land Rover 2"
In the second iteration, the TextBoxFor() does find a match for Cars[1]Make in ModelState so it sets value="Land Rover 2" and manual inputs reads the model value and sets value="Audi 3".
I'm assuming this question is just to explain the behavior (in reality, you would save the data and then redirect to the GET method to display the new list), but you can generate the correct output when you return the view by calling ModelState.Clear() which will clear all ModelState values so that the TextBoxFor() generates the value attribute based on the model value.
Side note:You view contains a lot of bad practice, including polluting your markup with behavior (use Unobtrusive JavaScript), creating label element that do not behave as labels (clicking on them will not set focus to the associated control), unnecessary use of <br/> elements (use css to style your elements with margins etc) and unnecessary use of new { #id = "car-make-" + i }. The code in your loop can be
#for (int i = 0; i < Model.Cars.Count; i++)
{
<div class="form-group row">
<hr />
#Html.LabelFor(m => m.Cars[i].Make, "Make (#i)")
#Html.TextBoxFor(m => m.Cars[i].Make, new { #class = "form-control" })
....
<input type="hidden" name="Cars.Index" value="#i" />
<button type="button" class="btn btn-sm btn-danger delete">Delete Entry</button>
</div>
}
$('.delete').click(function() {
$(this).closest('.form-group').remove();
}

TextBoxFor displaying the wrong name? [duplicate]

this is a tricky one to explain, so I'll try bullet pointing.
Issue:
Dynamic rows (collection) available to user on View (add/delete)
User deletes row and saves (POST)
Collection passed back to controller with non-sequential indices
Stepping through code, everything looks fine, collection items, indices etc.
Once the page is rendered, items are not displaying correctly - They are all out by 1 and therefore duplicating the top item at the new 0 location.
What I've found:
This happens ONLY when using the HTML Helpers in Razor code.
If I use the traditional <input> elements (not ideal), it works fine.
Question:
Has anyone ever run into this issue before? Or does anyone know why this is happening, or what I'm doing wrong?
Please check out my code below and thanks for checking my question!
Controller:
[HttpGet]
public ActionResult Index()
{
List<Car> cars = new List<Car>
{
new Car { ID = 1, Make = "BMW 1", Model = "325" },
new Car { ID = 2, Make = "Land Rover 2", Model = "Range Rover" },
new Car { ID = 3, Make = "Audi 3", Model = "A3" },
new Car { ID = 4, Make = "Honda 4", Model = "Civic" }
};
CarModel model = new CarModel();
model.Cars = cars;
return View(model);
}
[HttpPost]
public ActionResult Index(CarModel model)
{
// This is for debugging purposes only
List<Car> savedCars = model.Cars;
return View(model);
}
Index.cshtml:
As you can see, I have "Make" and "Actual Make" inputs. One being a HTML Helper and the other a traditional HTML Input, respectively.
#using (Html.BeginForm())
{
<div class="col-md-4">
#for (int i = 0; i < Model.Cars.Count; i++)
{
<div id="car-row-#i" class="form-group row">
<br />
<hr />
<label class="control-label">Make (#i)</label>
#Html.TextBoxFor(m => m.Cars[i].Make, new { #id = "car-make-" + i, #class = "form-control" })
<label class="control-label">Actual Make</label>
<input class="form-control" id="car-make-#i" name="Cars[#i].Make" type="text" value="#Model.Cars[i].Make" />
<div>
<input type="hidden" name="Cars.Index" value="#i" />
</div>
<br />
<button id="delete-btn-#i" type="button" class="btn btn-sm btn-danger" onclick="DeleteCarRow(#i)">Delete Entry</button>
</div>
}
<div class="form-group">
<input type="submit" class="btn btn-sm btn-success" value="Submit" />
</div>
</div>
}
Javascript Delete Function
function DeleteCarRow(id) {
$("#car-row-" + id).remove();
}
What's happening in the UI:
Step 1 (delete row)
Step 2 (Submit form)
Step 3 (results)
The reason for this behavior is that the HtmlHelper methods use the value from ModelState (if one exists) to set the value attribute rather that the actual model value. The reason for this behavior is explained in the answer to TextBoxFor displaying initial value, not the value updated from code.
In your case, when you submit, the following values are added to ModelState
Cars[1].Make: Land Rover 2
Cars[2].Make: Audi 3
Cars[3].Make: Honda 4
Note that there is no value for Cars[0].Make because you deleted the first item in the view.
When you return the view, the collection now contains
Cars[0].Make: Land Rover 2
Cars[1].Make: Audi 3
Cars[2].Make: Honda 4
So in the first iteration of the loop, the TextBoxFor() method checks ModelState for a match, does not find one, and generates value="Land Rover 2" (i.e. the model value) and your manual input also reads the model value and sets value="Land Rover 2"
In the second iteration, the TextBoxFor() does find a match for Cars[1]Make in ModelState so it sets value="Land Rover 2" and manual inputs reads the model value and sets value="Audi 3".
I'm assuming this question is just to explain the behavior (in reality, you would save the data and then redirect to the GET method to display the new list), but you can generate the correct output when you return the view by calling ModelState.Clear() which will clear all ModelState values so that the TextBoxFor() generates the value attribute based on the model value.
Side note:You view contains a lot of bad practice, including polluting your markup with behavior (use Unobtrusive JavaScript), creating label element that do not behave as labels (clicking on them will not set focus to the associated control), unnecessary use of <br/> elements (use css to style your elements with margins etc) and unnecessary use of new { #id = "car-make-" + i }. The code in your loop can be
#for (int i = 0; i < Model.Cars.Count; i++)
{
<div class="form-group row">
<hr />
#Html.LabelFor(m => m.Cars[i].Make, "Make (#i)")
#Html.TextBoxFor(m => m.Cars[i].Make, new { #class = "form-control" })
....
<input type="hidden" name="Cars.Index" value="#i" />
<button type="button" class="btn btn-sm btn-danger delete">Delete Entry</button>
</div>
}
$('.delete').click(function() {
$(this).closest('.form-group').remove();
}

New value in HTML.DropDownListFor(...) not setting in Controller [Post] method?

Hopefully someone can see how to go about this, because I've tried everything I can think of. When the Create() View in my MVC5 application loads I first populate several [SelectList(...)]'s in my Controller (ex.):
ViewBag.Model_Id = new SelectList(db.DBT_MODELS.OrderBy(x => x.MODEL_DESCRIPTION), "MODEL_ID", "MODEL_DESCRIPTION");
I then on my Create() View use this [SelectList(...)] to Populate an Html.DropDownListFor(...):
<div class="form-group">
<span class="control-label col-md-2">Model:</span>
<div class="col-md-4">
#Html.DropDownListFor(model => model.MODEL_ID, (SelectList)ViewBag.Model_Id, htmlAttributes: new { #class = "form-control dropdown", #id = "selectModel" })
#Html.ValidationMessageFor(model => model.MODEL_ID, "", new { #class = "text-danger" })
</div>
<div class="col-md-2">
<div class="btn-group">
<button id="createNewModel" type="button" class="btn btn-success" aria-expanded="false">CREATE NEW</button>
</div>
</div>
<div class="col-md-4">
<div id="createModelFormContainer" style="display:none">
<form action="/createNewModel">
<input type="text" id="textNewModel" name="model_description" placeholder="New Model" />
<input type="button" id="submitNewModel" value="Submit" />
<input type="button" id="cancelNewModel" value="Cancel" />
</form>
</div>
</div>
</div>
Simple enough, and this all works as expected. The problem lies in a bit of extended functionality I've tried to incorporate. My main class has several of these properties which are basically Foreign Key's in my DB. When a User goes in to Create/Edit() an entity in my main Model, I wanted to allow them to be able to add new entities to these foreign tables without needing to navigate away from the current View.
As such, I added (for each foreign property, using (Model) as an example) the code shown above and again directly below with a button to Show/Hide a small form for users to insert a new value and have it added to the DropDownList:
<div class="col-md-2">
<div class="btn-group">
<button id="createNewModel" type="button" class="btn btn-success" aria-expanded="false">CREATE NEW</button>
</div>
</div>
<div class="col-md-4">
<div id="createModelFormContainer" style="display:none">
<form action="/createNewModel">
<input type="text" id="textNewModel" name="model_description" placeholder="New Model" />
<input type="button" id="submitNewModel" value="Submit" />
<input type="button" id="cancelNewModel" value="Cancel" />
</form>
</div>
</div>
My submitNewModel() click event below gets the user's inputted new value and then uses a JSON call to a Controller Method to add it in the Database Table. This new value (and new ID for it) are then returned, the form for the DropDownList is reset, and I set the DropDownList's current value as the newly added one:
$('#createNewModel').click(function () {
$('#createModelFormContainer').show();
})
$('#cancelNewModel').click(function () {
$('#createModelFormContainer').hide();
})
$('#submitNewModel').click(function () {
var form = $(this).closest('form');
var data = { description: document.getElementById('textNewModel').value };
$.ajax({
type: "POST",
dataType: "JSON",
url: '#Url.Action("createNewModel", "INV_ASSETS")',
data: data,
success: function (resp) {
if (resp.ModelExists)
{
alert("Model [" + resp.Text + "] already exists. Please select from the DropDown.");
} else {
$('#selectModel').append($('<option></option>').val(resp.MODEL_ID).text(resp.Text));
form[0].reset();
$('#createModelFormContainer').hide();
var count = $('#selectModel option').size();
$('#selectModel').prop('selectedIndex', count - 1);
$('#selectModel').val(resp.MODEL_ID);
//document.getElementById('selectModel').value = resp.MODEL_ID; - Shows dropdown as blank [ ] once executed.
}
},
error: function () {
alert("ERROR - Something went wrong adding new Model [" + resp.Text + "]!");
$('#createModelFormContainer').hide();
}
});
//reloadForNewEntity();
});
The createNewModel() method that is called in my Controller:
public JsonResult createNewModel(string description)
{
DBT_MODELS model = new DBT_MODELS()
{
// ID auto-set during save.
MODEL_DESCRIPTION = description.Trim(),
CREATED_DATE = DateTime.Now,
CREATED_BY = System.Environment.UserName
};
var duplicateModel = db.DBT_MODELS.FirstOrDefault(x => x.MODEL_DESCRIPTION.ToUpper() == model.MODEL_DESCRIPTION.ToUpper());
try
{
if (duplicateModel == null)
{
if (ModelState.IsValid)
{
db.DBT_MODELS.Add(model);
db.SaveChanges();
// Ensure the [model.ID] is properly set after having been saved to and auto-generated in the database.
model.MODEL_ID = db.DBT_MODELS.FirstOrDefault(x => x.MODEL_DESCRIPTION.ToUpper() == model.MODEL_DESCRIPTION.ToUpper()).MODEL_ID;
}
}
else
{
model = duplicateModel;
}
}
catch (Exception ex)
{
Elmah.ErrorSignal.FromCurrentContext().Raise(ex);
}
return Json(new { ID = model.MODEL_ID, Text = model.MODEL_DESCRIPTION, ModelExists = (duplicateModel != null) }, JsonRequestBehavior.AllowGet);
}
Visually speaking, everything works as intended up to this point. The problem is when I go to Save the main entity I am Creating/Editing.
Any value that was already in the Foreign Tables, and thus in the DropDownList when the View loads, saves just fine; but if I add a new Foreign Table value for these main entity properties (though visually added and the currently selected values for the individual DropDownLists) the [POST] method then executes with each foreign id value set as 0 (ex. MainClass.Model_ID = "0" vs expected MainClass.Model_ID = "625", MainClass.Type_ID = "0" vs expected MainClass.Type_ID = "17", MainClass.Location_ID = "0" vs expected MainClass.Location_ID = "82", etc.)
Basically if the value selected in the Html.DropDownListFor() is one of my newly added values, the POST controller method always renders the MainClass.*_ID value which the selected Html.DropDownListFor() value corresponds to as "0".
Can anyone point me to how to get this working? I have tried:
Changing how my JavaScript sets the value in the DropDownList after the the JSON call to my Controller Actions returns (ex): //document.getElementById('selectModel').value = resp.MODEL_ID; - Shows dropdown as blank [ ] once executed. vs $('#selectModel').val(resp.MODEL_ID); which visually renders the expected new value in the DropDownList.
On return from the Controller method, setting a new ViewBag variable and then hoping to reference the saved value in the POST method (did not work, the JavaScript rendered my #Viewbag.PostModelID = resp.ModelID as "= resp.ModelID" and threw many expected errors).
EDIT:
[Redacted for N/A]
EDIT2: Good to go. Thanks everyone for the suggestions!
The json data you are returning from your action method is in this format.
{
"ID": 24,
"Text": "IOS",
"ModelExists": false
}
But in your code, you are trying to access MODEL_ID property which does not exist in the resp object.
$('#selectModel').append($('<option></option>').val(resp.MODEL_ID).text(resp.Text));
Change your code to use ID property value
$('#selectModel').append($('<option></option>').val(resp.ID).text(resp.Text));
$('#selectModel').val(resp.ID);
In your controller where you create the new model.. your json object that you're returning is ID, Text, ModelExists, but in your javascript you're setting the val property of the new <option> to MODEL_ID.. these 2 need to match..
So change your javascript to be
.val(resp.ID)
or change the return value in your controller action to
return Json(new { MODEL_ID = model.MODEL_ID, Text = model.MODEL_DESCRIPTION
You're also referencing MODEL_ID here
$('#selectModel').val(resp.MODEL_ID);
so make sure if you don't change your controller action, you update this also

Multiple multipartfile upload testing in grails

I'm trying to write a test for file uploading in grails. I'm using spock as my testing framework.
I have a javascript function that link to a button on my view that adds an <input type="file"> to my form whenever it is clicked.
It looks something like this:
<script>
var fileNum = 1;
function addUploader() {
fileNum++;
var fileInputTag = "<input type=\"file\" name=\"myfile." +fileNum+ "\"/>";
var uploadField = document.getElementById("uploadFields");
var row = uploadField.insertRow(fileNum + 1);
var cell = row.insertCell(0);
cell.innerHTML = fileInputTag + "<br/>";
}
</script>
<tr>
<td>
<input type="file" class="required" name="myfile.1" />
</td>
</tr>
<tr>
<td> </td>
</tr>
<tr>
<td>
More Files <br />
<br/>
<input type="submit" value="Download"/>
</td>
</tr>
My controller looks something like this:
def uploadFile(){
List<MultipartFile> files = []
params.myfile.each {
if (it.value.isEmpty()) {
flash.message = message(code: 'upload.empty.message')
redirect(uri: "/")
return
}
files.add((MultipartFile) it.value)
}
def faxPreviews = []
faxPreviews = uploadFileService.generateFaxPreviews(files)
render(view:'/uploadfile/index', model:[p:uploadFileService.decodeFaxPreviews(faxPreviews)])
}
I want to test that a flash message is shown if the list is empty but I don't know how to put that in the test. I've been trying to search how to mock the view params in the test but so far no go.
This was actually very easy. D'oh! I ended up with this test.
void "updloadFile should warn when no files are attached."() {
given:
def file = new GrailsMockMultipartFile('mockFile', ''.bytes)
Map<MultipartFile> files = [:]
files[1] = file
controller.params.myfile = files
when:
controller.uploadFile()
then:
response.redirectedUrl == '/'
flash.message != null
}
My problem was that I thought the myfile in the view was a key when it is actually the name of a map. So myfile.1 would turn into a myfile[1] = "whatever value is passed to input"

How to prevent saving of duplicate data?

I am developing MVC app.
I am using Jquery in Creat-Veiw for validate the data.
I am adding the Role and checking whether that Role(or you can say UserName) already exists in DB or not.
If Role already exists then validation message should display and should not be allow to add in db (should not allow to save).
I have a text-box which accepts the Role, on the blur function I have checked whether Role already exists or not.
Every thing working ok, If Role already exists then validation message comes up.
But after clicking on save button it saves in db.
I want to prevent it ? How to do this ?
I have below code of Create View.
#model IEnumerable<PaymentAdviceEntity.Role>
<div id="roleList">
<div class="span6">
<div class="span12 HeaderField2">
Roles
<legend style="margin-bottom:2px;margin-top:5px;"></legend>
</div>
<div class="span12">
<div style="display:inline-block"></div>
<div id="addrole" style="display:none">
<span> #Html.TextBox("RoleName", String.Empty, new { #id = "RoleName",style="margin-bottom:0px;" })</span>
<span>
<input type="button" value="Save" id="btnSave"/>
<input type="button" value="Cancel" id="btnCancel" />
</span>
</div>
<div style="margin-top:5px;">
<span id="RoleNameValidation" style="display:none;color:Red;">Role already exists</span>
</div>
</div>
</div>
For this I am using the below Jquery.
$("#RoleName").blur(function ()
{
var Role_Name = $('#RoleName').val();
//alert(RoleName);
var url = "#Html.Raw(Url.Action("checkForUniqueName","Role",new {#RName = "RoleName"}))";
url = url.replace("RoleName", Role_Name);
$.post(url, function (data)
{
if (data == false) {
$("#RoleNameValidation").show();
$('#RoleName').focus();
}
else {
$("#RoleNameValidation").hide()
}
});
});
and the controller Code is....
public ActionResult checkForUniqueName(string RName)
{
bool Valid = false;
var RoleList = from e in db.Roles
where e.Name.Equals(RName)
select e;
if (RoleList.Count() > 0 )
{
Valid = false;
}
else
{
Valid = true;
}
return Json(Valid, JsonRequestBehavior.AllowGet);
}
You can use validation for this purpose if you don't want to insert duplicate records in database.
Try to create a custom rule using the jQuery Validate plugin (using addMethod) that checks if the role is already exists in database.You can find good help at http://matthewmuro.com/2012/05/08/adding-custom-jquery-validation-to-your-form/

Resources