Reputation: 5951
I'm here again with a question about Play2.0 :-).
I have a model called Task
. This task contains a field called createdBy
. This field must be populated with the logged in user.
My field declaration looks like this:
@ManyToOne
@Constraints.Required
public User createdBy;
I render a form where the logged in user can enter a description and a priority from a drop down box. When the form is submited I call my save()
method. In the save method I have this code:
Form<Task> taskForm = form(Task.class).bindFromRequest();
if (taskForm.hasErrors()) {
return badRequest(views.html.tasks.taskCreate.render(taskForm));
}
taskForm.get().save();
Obviously the Task
is never saved because the field createdBy
does not contain any (valid) user. The only reference to the user is that I save his unique CODE and not the primary key in the session.
I can only get it work when I remove the Required constraint and postfill the user to the saved task. There must be a better way to bind the form and user together, right?
Upvotes: 0
Views: 1421
Reputation: 1051
What Play2.0 wants you to do is making a distinction between your 'database model' and your 'form bean'. I also think there should be a better solution. But on the other hand, it is pretty clean. So what you should do is:
TaskForm
, for example. This class only contains the fields that are displayed in your form, with the right annotations and validation.toTask(User user)
method in your TaskForm
which returns a Task
.Change the code in your controller to:
Form<TaskForm> taskForm = form(TaskForm.class).bindFromRequest();
if (taskForm.hasErrors()) {
return badRequest(views.html.tasks.taskCreate.render(taskForm));
}
taskForm.get().toTask(user).save();
Keep the annotations/validation in your 'model class' Task
, but look at them as database validation, not form validation.
Edit:
Some extra explanation: I had several discussions about this validation/binding problems on the Play group: discussion1, discussion2. The idea of the Play Developers, as I understand it, is to use the Form class for binding/validation. This Form class wraps an existing class. I think there is nothing wrong with their idea that this wrapped class is a 1-to-1 representation of a form. Only the problem is, the Scala language makes this a lot easier then the Java language. in Java you have to create lots of 'form representation classes' where in Scala you can use things like 'mapping' and 'case classes' (see ScalaForms). So I totally agree with your points in the comments that it is probably not worthy to write a new 'form bean' for one different property, and you would rather have your validation in one place. But that is how it is designed, yet. I hope that either they change the API by themself, or someone else takes a good look at it and submits a pull request. But right now, this is how the framework wants you to do your binding/validation.
Example:
public class Task extends Model {
@Constraints.Required
public String description;
@Constraints.Required
public Integer priority;
@ManyToOne
@Constraints.Required
public User createdBy;
public Task() {
}
public Task(final TaskForm taskForm, final Long userCode) {
description = taskForm.description;
userCode = taskForm.userCode;
createdBy = User.findByCode(userCode);
}
}
public class TaskForm {
@Constraints.Required
public String description;
@Constraints.Required
public Integer priority;
public toTask(final Long userCode) {
return new Task(this, userCode);
}
}
public class Tasks extends Controller {
public static Result save() {
Form<TaskForm> taskForm = form(TaskForm.class).bindFromRequest();
if (taskForm.hasErrors()) {
return badRequest(views.html.tasks.taskCreate.render(taskForm));
}
taskForm.get().toTask(session("userCode")).save();
}
}
Upvotes: 2
Reputation: 55798
Remove the required annotation from your model and bind new task in two steps:
Form<Task> taskForm = form(Task.class).bindFromRequest();
if (taskForm.hasErrors()) {
return badRequest(views.html.tasks.taskCreate.render(taskForm));
}
Task newTask = taskForm.get();
newTask.createdBy = someObjectOfUserModel;
newTask.save();
return ok("New user added");
Upvotes: 0