Grails validation error from service method - service

I have a method in a service class that creates an object:
def createContent (fileName, description) {
def content = new Content(
fileName:fileName,
description:description,
).save()
}
Neither of those properties are nullable. How can I pass the validation errors back to be displayed? I've tried flash.message and render, both of which don't work from within service classes. I also tried
.save(failOnError:true)
which displayed a long list of errors.

Simplifying everything it should look like this.
Service method:
def createContent (fileName, description) {
//creating an object to save
def content = new Content(
fileName:fileName,
description:description,
)
//saving the object
//if saved then savedContent is saved domain with generated id
//if not saved then savedContent is null and content has validation information inside
def savedContent = content.save()
if (savedContent != null) {
return savedContent
} else {
return content
}
}
Now in controller:
def someAction = {
...
def content = someService.createContent (fileName, description)
if (content.hasErrors()) {
//not saved
//render create page once again and use content object to render errors
render(view:'someAction', model:[content:content])
} else {
//saved
//redirect to show page or something
redirect(action:'show', model:[id:content.id])
}
}
And someAction.gsp:
<g:hasErrors bean="${content}">
<g:renderErrors bean="${content}" as="list" />
</g:hasErrors>
And in general you should look through this: Grails validation doc

Related

Jetpack Paging3 RemoteMediator returns same PagingSatate on #load append

I'm following this codelab to build an paging3 app with github API and a local DB. While the first 2 pages load fine, the mediator hits a loop when trying to load the 3rd page when scrolling to bottom - the same PagingState is passed to load() function over and over.
Just wondering if anyone knows what could be the possible root cause here?
Some implementation details:
RemoteMediator: (the prevPage and currentPage is from github API's pagination response header and saved to a local DB.)
// RepositoryMediator
override suspend fun load(
loadType: LoadType,
state: PagingState<Int, Repository>
): MediatorResult {
return when (loadType) {
LoadType.REFRESH -> {
fireRequestForPage(1, true /*clear DB*/)
return Success(endOfPaginationReached = false)
}
LoadType.APPEND -> {
// !!!!!!! kept getting the same state when APPEND is triggered, resulting in same currentPage and nextPage
// get currentPage, nextPage from state.lastItemOrNull
if(currentPage < nextPage) {
fireRequestForPage(nextPage)
Success(endOfPaginationReached = false)
} else {
return Success(endOfPaginationReached = true)
}
LoadType.PREPEND -> {
// get currentPage, prevPage from state.firstItemOrNull
if(currentPage > prevPage) {
fireRequestForPage(prevPage)
Success(endOfPaginationReached = false)
} else {
return Success(endOfPaginationReached = true)
}
}
}
}
Observable: I'm using liveData instead of flow to from the Pager:
fun searchRepositoryWithUserId(userLoginName: String): LiveData<PagingData<Repository>> {
// need to create a new Pager each time because the search query is different
return Pager(
config = PagingConfig(pageSize = PAGE_SIZE, enablePlaceholders = false),
remoteMediator = RepositoryMediator()
) {
repoDao().getRepositoriesOfUser(userLoginName)
}.liveData
}
Dao: just a plain query
#Query("SELECT * FROM repository_table WHERE login = :ownerLoginName")
fun getRepositoriesOfUser(ownerLoginName: String): PagingSource<Int, Repository>
For anyone interested, the fix is from Dao, need to update the query to sort on reponame, otherwise the query will return the same last Page for PagingSource even if there're new items inserted into DB, confusing the Mediator.
#Query("SELECT * FROM repository_table WHERE login = :ownerLoginName ORDER BY repository_name ASC")
fun getRepositoriesOfUser(ownerLoginName: String): PagingSource<Int, Repository>
Had a similar issue just now. Trying to sort by different fields had led to RemoteMediator getting stuck in a loop on different page numbers.
Turns out I couldn't rely on item ID's assigned by backend to be primary keys for my Room DB Entity. Assigning primary key ID's locally (starting from zero) seems to have fixed the issue.

Grails updates the model before saving

I am having trouble in validating and reseting some fields based on the role of a user.
I am trying to develop a rest api with grails and my problem appears when i try to reset some fields based on the role of an user. I send a json with the desired "not allowed" changes via PUT to the controller. I modify the not allowed fields to ones that are correct for me and then call .save() and the "not alowed" fields are updated with their sent value, not with the modified by me values. Here is the code.
THE MODEL
package phonebook
class User {
String firstName
String lastName
String phoneNo
String address
String email
String password
boolean active = false
String hash
String authToken = ""
String role = "user"
static hasMany = [contacts:Contact]
static constraints = {
firstName(blank: false)
lastName(blank: false)
address(blank: true)
phoneNo(unique: true)
email(blank: false, unique: true)
password(blank: false)
role(blank: false, inList: ["user", "admin"])
hash(blank: true)
authToken(blank: true)
active(inList:[true,false])
}
}
THE METHOD FROM CONTROLLER:
#Transactional
def update(User userInstance) {
if (!isAuthenticated()){
notAllowed()
return
}
if (userInstance == null) {
notFound()
return
}
//if(isAdmin()){
def userBackup = User.findById(userInstance.id)
userInstance.role = userBackup.role
userInstance.active = userBackup.active
userInstance.hash = userBackup.hash
userInstance.authToken = userBackup.authToken
//}
if (userInstance.hasErrors()) {
respond userInstance.errors, view:'edit'
return
}
userInstance.save flush:false
request.withFormat {
'*'{ respond userInstance, [status: OK] }
}
}
THE JSON SENT VIA PUT
{
"id":"1",
"firstName": "Modified Name 23",
"role":"admin",
"active":"true",
"hash":"asdasd"
}
The above code should not modify my values for hash or active or role even if they are sent.
Any ideas?
Thanks.
The reason your changes are being saved is because by default any changes made to a domain instance will be flushed at the end of the session. This is known as open session in view with automatic session flushing. I recommend you do some reading on some of the main issues people face with GORM.
Proper use of discard may solve your issue. Discard your instance changes before you exit your controller.
For example:
if (!isAuthenticated()){
notAllowed()
userInstance.discard()
return
}
Edit
Based on conversation in the comments this perhaps may be the way to address your issue. A combination of discard and attach.
userInstance.discard()
def userBackup = User.findById(userInstance.id)
userInstance.role = userBackup.role
userInstance.active = userBackup.active
userInstance.hash = userBackup.hash
userInstance.authToken = userBackup.authToken
userInstance.attach()
I was helped by this method.
getPersistentValue
Example
def update(ShopItem shopItemInstance) {
if (shopItemInstance == null) {
notFound()
return
}
if (!shopItemInstance.itemPhoto){
shopItemInstance.itemPhoto =
shopItemInstance.getPersistentValue("itemPhoto");
}
if (shopItemInstance.hasErrors()) {
respond shopItemInstance.errors, view:'edit'
return
}
shopItemInstance.save flush:true
redirect(action: "show", id: shopItemInstance.id)
}
In your case:
userInstance.role = userInstance.getPersistentValue("role")
userInstance.active = userInstance.getPersistentValue("active")
userInstance.hash = userInstance.getPersistentValue("hash")
userInstance.authToken = userInstance.getPersistentValue("authToken")
It's better if you'll use the command objects feature. You can bind a command object with the request payload, validate it and than find and update the domain object.
You can find more details here:
http://grails.org/doc/2.3.x/guide/theWebLayer.html#commandObjects
And off the record you shoudn't use #Transactional in your controller. You can move that code into a service.
Eq:
def update(Long id, UserCommand cmd){
// Grails will map the json object into the command object and will call the validate() method if the class is annotated with #Validatable
}

Grails URL id field not getting mapped to params

Here is my URLmappings.groovy
class UrlMappings {
static mappings = {
"/$controller/$action?/$id?(.${format})?" {
constraints {
// apply constraints here
}
}
"/ewhet/$id"(controller : "ewhet", action : "show")
"/"(view: "/index")
"500"(view: '/error')
}
}
Here is my ewhetController's show action:
class EwhetController {
def index(){
}
def show(){
def ctx = startAsync()
ctx.start {
render params
//render "this invoked!!"
ctx.complete()
}
}
}
Now when I enter the url as: http://localhost:8080/g24/ewhet/abc
The abc does not get mapped to the params.id and when I render params, I get an empty map [:] . In case if url is entered as http://localhost:8080/g24/ewhet/show?id=abc the id field gets mapped to the params.id and I get:
['id':'abc']
So I just want to get the last part of the url mapped to the id parameter in params map without using any map in the url (like id=abc) as per Section 7.4.3 in Grails documentation So how is that possible and why is my approach not working?
Kindly note that I do not have any domain classes as I am using schemaless mongodb at my backend.
Try to reload the app after changing the UrlMappings.groovy to assure the new config is correctly loaded.

MVC How To Pass Url Values as Well as a Model From Action to a View

I am looking for a way to preserve a url parameter after posting through a form. For example my GET method takes a string "type" and uses that to determine the type of report to render in the View. The url looks like this:
http://mysite/Reports/Report?type=1
[HttpGet]
public ActionResult Report(string type)
{
var model = new ReportsModel()
{
Report = ReportList.Find(o => o.ReportType == type)
};
return View(model);
}
The View has a form that has start/end date filters used to determine the date range of the date to be displayed for the type of report:
#using (Html.BeginForm("Report", "Reports"))
{
Report.ReportName
#Html.HiddenFor(o => o.Report.ReportType)
#Html.EditorFor(o => o.Report.StartDate )<br/>
#Html.EditorFor(o => o.Report.EndDate )<br/>
<button id="reports">Report</button>
}
The above form posts to an action that gets report data from the database based on the specified report type, start/end dates, and returns back to the view.
[HttpPost]
public ActionResult Report(GenericReportsModel model)
{
switch (model.Report.ReportType)
{
case ReportType.ReportType1:
model.Result = ReportRepository.GetReport<ReportType1>(model.StartDate, model.EndDate);
break;
case ReportType.ReportType2:
model.Result = ReportRepository.GetReport<ReportType2>(model.StartDate, model.EndDate);
break;
}
return View(model);
}
The problem is that after the post, the "type" parameter is lost from the url.
Before the post: http://mysite/Reports/Report?type=1
After the post: http://mysite/Reports/Report
I need to be able to do something like this (which doesn't work):
return View(model, new {ReportType = model.ReportType);
How can I preserve the type parameter in the url after the post, in case someone wants to copy and paste the url to send to someone else?
You need to update Html.BeginForm and your HttpPost version of Report method.
#using(Html.BeginForm("Report", "Report", "YourController", new { type = model.ReportType})
{
// I am assuming that model.ReportType == type argument
// in your HttpGet Report action
// The rest of the form goes here
}
Your action should look like:
[HttpPost]
public ActionResult Report(string type, GenericReportsModel model)
{
switch (model.Report.ReportType)
{
case ReportType.ReportType1:
model.Result = ReportRepository.GetReport<ReportType1>(model.StartDate, model.EndDate);
break;
case ReportType.ReportType2:
model.Result = ReportRepository.GetReport<ReportType2>(model.StartDate, model.EndDate);
break;
}
return View(model);
}
If type is not equal to model.ReportType then you should create a ViewModel that contains the values from your GenericsReportModel and this other Report type.

How to include a picture type in a form in Play!2 in Scala?

According to this guide, one can upload files by writing the html form by hand. I want to handle file upload as part of a bigger form that includes text fields (for example name and email). Here is what I have to far (quite ugly):
def newUser = Action(parse.multipartFormData) { implicit request =>{
//handle file
import play.api.mvc.MultipartFormData.FilePart
import play.api.libs.Files.TemporaryFile
var uploadSuccessful = true
var localPicture: FilePart[TemporaryFile] = null
request.body.file("picture").map { picture =>
localPicture = picture }.getOrElse {
uploadSuccessful = false }
//process the rest of the form
signupForm.bindFromRequest.fold(
errors => BadRequest(views.html.signup(errors)),
label => {
//file uploading code here(see guide), including error checking for the file.
if(uploadSuccesful){
User.create(label._1, label._2, label._3._1, 0, "NO PHOTO", label._4)
Redirect(routes.Application.homepage).withSession("email" -> label._2)
} else {
Redirect(routes.Application.index).flashing(
"error" -> "Missing file"
}
})
} }
This looks tremendously ugly to me. Note that I have defined a signupForm somewhere that includes all fields (apart from the file upload one). My question is: Is there a prettier way of going about this? Perhaps by including the file field in the signupForm and then handling errors uniformly.
So far I think it's not possible to bind binary data to a form directly, you can only bind the reference (e.g. the picture's ID or name). You could however reformulate your code a bit:
def newUser() = Action(parse.multipartFormData) { implicit request =>
import play.api.mvc.MultipartFormData.FilePart
import play.api.libs.Files.TemporaryFile
request.body.file("picture").map { picture =>
signupForm.bindFromRequest.fold(
errors => BadRequest(views.html.signup(errors)),
label => {
User.create(label._1, label._2, label._3._1, 0, picture.absolutePath(), label._4)
Redirect(routes.Application.homepage).withSession("email" -> label._2)
}
)
}.getOrElse(Redirect(routes.Application.index).flashing("error" -> "Missing file"))
}
You can use the asFormUlrEncoded, like below:
def upload = Action(parse.multipartFormData) { request =>
val formField1 = request.body.asFormUrlEncoded("formField1").head;
val someOtherField = request.body.asFormUrlEncoded("someOtherField").head;
request.body.file("photo").map { picture =>
...
}
}