Issue with bindFromRequest in Play! Framework 2.3
Asked Answered
A

6

9

I'm trying to use the automatic binding feature of Play, without success. I'm developing in Java, on Eclipse 4.4 Luna.

Here is my form :

<h2>Create a new user</h2>
<form action="@routes.Backend.createUser()" method="post">
    First Name
    <input type="text" name="firstName" />
    Last Name
    <input type="text" name="lastName" />
    E-mail
    <input type="email" name="email" />
    PIN
    <input type="number" name="pin" />
    Status
    <input type="text" name="status" />
    Is guest?
    <input type="checkbox" name="isGuest" />

    <input type="submit" value="Create user" />
</form>

Here is my class "Users":

@Entity
public class Users extends Model {

// Database columns
@Id
public int userId;

public String firstName;
public String lastName;
public String email;
public int pin;
public String status;
public boolean isGuest;

}

And here is my controller:

public class Backend extends Controller {

  public static Result createUser() {
    Form<Users> form = Form.form(Users.class).bindFromRequest();
    if (form.hasErrors()) {
        // doSomething()
    } else {
        Users u = form.get();
        u.save();
    }

    // TESTING
    // Checking the content of the request
    DynamicForm requestData = Form.form().bindFromRequest();
    String firstName = requestData.get("firstName");
    String lastName = requestData.get("lastName");
    // Printing the content works, I am able to see the correct values
    System.out.println(firstName); // Bob
    System.out.println(lastName); // Smith
    // This somehow doesn't work...
    System.out.println(u.firstName); // NULL
    System.out.println(u.lastName); // NULL
    System.out.println(u.userId); // Correctly generated
    // END OF TESTING

    return redirect(routes.Backend.allUsers());
  }
}

I wonder why the automatic binding of values doesn't work. I have made sure that the fields name in my form correspond to the attributes names in the class, and this should be enough for the form binding to work, right?

I am using Eclipse Luna, and I turned off automatic project build (I do it manually from the console). I know that sometimes Eclipse can cause issues because of that auto-build feature. Note: This was the way to go, but I didn't clean the project using the activator command, as user Dmitri suggested. Also, you only have to do this once, as long as you don't turn on the automatic build feature in Eclipse.

I have tried restarting Eclipse and the application several times, without success...

EDIT: I tried using only String attributes for my Users class, since the requestData.get(String s) method returns a String. But still no success...

EDIT 2: I'm going to bind the values manually... If anyone have an idea, please post :)

EDIT 3: I've updated my code to follow the rules mentioned in the answer below

EDIT 4: I can't get autobinding working only when using my Postgresql 9.3 database. When I use in-memory database, everything works smoothly. Also, since there was no JDBC driver for Java 8 and postgresql 9.3, I'm using an older version of the driver (actually the driver is on PGSQL's website, but I couldn't get it working with Play). I will have to check what happens with another DB, then I'll report back here!

EDIT 5: I tried to create my custom data binder like this:

        Formatters.register(User.class, new Formatters.SimpleFormatter<User>() {
        @Override
        public User parse(String arg0, Locale arg1) throws ParseException {
            User u = new Model.Finder<Integer, User>(Integer.class, User.class).byId(Integer.parseInt(arg0));
            return u;
        }
        @Override
        public String print(User arg0, Locale arg1) {
            return "User : " + arg0.firstName;
        }
    });

... but it didn't work!

EDIT 6: User Dmitri has found a working solution: you have to compile the project outside of Eclipse. It seems that there is some incompatibilities between Eclipse's compilator and Play! Framework's compilator...

Agrippina answered 18/8, 2014 at 14:26 Comment(2)
Are you getting any error ?Borscht
Absolutely no errors... I know that the rest is working, since I can manually bind the values and create an instance of my Users object then save it and retrieve it, but this auto-binding feature is definitely broken for me...Agrippina
A
12

I have been struggling with exactly the same problem: bindFromRequest returned nulls for "name" field. I did exactly the same what a guy in this Play for Java introduction video did: youtube.com/watch?v=bLrmnjPQsZc . But still no luck. I've been working on Windows 7 with JDK 1.8. IDE: Eclipse 4.4.0. And I run activator through cygwin.

This is what solved the problem for me:

  1. In Eclipse: Project -> Build Automatically - > turn off
  2. In cygwin: ./activator clean; ./activator compile; ./activator run;

After this, bindFromRequest binds name correctly and puts it into the database.

Adolf answered 4/9, 2014 at 11:36 Comment(5)
I have tried to create my custom data binder, but it still didn't work, so basically I'm still struggling with the issue... I was about to go back to plain SQL for all my requests! But now I'm going to try this right away and report here!Agrippina
Unbelievable, it worked! So this was Eclipse related... right? Can you please explain what actually happened? Thank you so much! :)Agrippina
No problem :) I suspect it has to do with ebean enhancements which occur after compilation. Eclipse sees new changes and compiles again, thus messing the code.Adolf
That's very likely. IntelliJ IDEA users haven't reported that kind of problem... I don't know if a bug report has been filed, who to report the bug to (Eclipse or Play?) and if it's even worth it to file one. Anyway, thank you very much for your help, I wouldn't have found out myself!Agrippina
...Wow...mind blowing.. So the issue with Eclipse IDEBuzzer
P
5

Create getters/setters for your data model. It has solved my problem.

Peony answered 19/9, 2015 at 3:13 Comment(2)
This is actually the correct answer. The error occurs because the scala compiler adds default getters and setters which are not being created when compiling it with eclipse.Flit
Even today, I believe this is the correct answer, had exactly the same problem. Creating getters and setters solves the issue.Offstage
B
2

In your code you have :

Users u = Form.form(Users.class).bindFromRequest().get(); 

Try with this instead :

Users user = new Users();
Form <Users> u = Form.form(Users.class).fill(user).bindFromRequest();

EDIT :

May be the problem is the input types you're using. Try to generate your form like this :

@form(routes.Backend.createUser()) {

            <label>First Name:</label> @inputText(userForm("first_name")) <br />
            <label>Last Name:</label> @inputText(userForm("first_name")) <br />
            <label>Email:</label> @inputText(userForm("email")) <br />
            <label>Pin:</label> @inputText(userForm("pin")) <br />
            <label>Status:</label> @inputText(userForm("status")) <br />
            <label>Is Guest:</label> @checkbox(userForm("is_guest")) <br />
            <input type="submit" value="Create user" />
    }

Then in User Entity : try to change all columns type to String

@Entity
public class Users extends Model {

// Database columns
@Id
public int user_id;

public String first_name;
public String last_name;
public String email;
public String pin;
public String status;
public String is_guest;

}

In your controller :

public class Backend extends Controller {

  public static Result createUser() {
    Form <Users> userForm = Form.form(Users.class).bindFromRequest();
    Users user = userForm .get();
    user.save();
}
}  
Borscht answered 18/8, 2014 at 14:51 Comment(6)
I use the ".get()" method at the end, which is supposed to return an object of type "Users". This is exactly what is done in the Play! Framework for Java Developers introduction video : youtube.com/watch?v=bLrmnjPQsZcAgrippina
What is "userForm" in your code? I checked on play's documentation but there is nothing on it... I just know that it's a parameter passed to the template, like @(userForm: Form[Users]), but what is it exactly?Agrippina
please see this link : playframework.com/documentation/2.2.x/ScalaFormsBorscht
Ok, so I assumed it was just a simple Form<Users> userForm = Form.form(Users.class); that I passed to my template. And I still cannot get the auto-binding working... The generated form is fine, everything is OK when I manually check the values.Agrippina
here's a good postBorscht
Thanks for the info. I was able to get autobinding working on another test project, following exactly the instructions on the video tutorial. I think that my issue might be related to the fact that I have a more complex database than the simpler one used in the example. I guess I'm going to bind the values manually for the moment, and I'll look into it later. Thanks for your help :)Agrippina
G
1

There is absolutely no link between the binding and your database. Do not follow @blackbishop's advice telling you to change all the fields of your model to String. That's a very bad idea, if there are different types, there is a reason...

Moreover, Ebean (supposing you're using it) or JPA generate database column types according to your Java properties type. Why would you store a 0 or a 1 in a varchar column ?

Follow these rules :

  1. Use a singular name for your models (User instead of Users)
  2. Always use camel case for your properties, no underscores (firstName instead of first_name, lastName instead of last_name...)
  3. Check errors before getting the value after binding

That should give you this :

public static Result createUser() {
    Form<User> form = Form.form(User.class).bindFromRequest();
    if (form.hasErrors()) {
        // Do what you have to do (i.e. : redirect to the form with a flash message)
    }
    User u = form.get();
    u.save();
    return redirect(routes.Backend.allUsers());
}

By the way, in your testing lines, the user_id is correctly generated because you have no validation rules and this data comes from the database, not the form.

Gerbil answered 20/8, 2014 at 9:39 Comment(5)
I've always used these rules, but for this project, if I name my model "User", the database name will be "user", which is a reserved SQL name... Anyway, I'll try using camelCase and report back. Thanks!Agrippina
I tried with camelCase... without success. I really have no idea what's happening, I still get the same result :/Agrippina
What's really bothering me is that if I print the content of the form (form.toString()), I get this : Form(of=class models.system.Users, data={firstName=Bob, lastName=Smith, pin=567, isGuest=on, [email protected], status=active}, value=Some(models.system.Users@5d482b), errors={}), which clearly shows that the form.get() method doesn't work for me...Agrippina
For user being a reserved word in MySQL, simply use @Table(name = "users") in your model class. What is inserted in the database after you submit the form ?Gerbil
Yeah, I'll use the @Table annotation, it's a better solution. After I submit the form, "null" is inserted. I might have a lead: check my edit!Agrippina
W
1

I solved this by Adding Setters and Getters. If you have Entity/Model class you should add setters and getters. If you have FormData classes add setters and getters for it as well.

So when you call

Form<YourFormData> formData =       Form.form(YourFormData.class).bindFromRequest(); 
 YourFormData formData = formData.get(); 

Your formData will now have all the values set. Hope this helps!

Wilburn answered 23/6, 2016 at 14:21 Comment(0)
L
0

I know this post has an accepted answer but wanted to post my experience with this issue.

I had the same issue and performed all the steps mentioned in the answer above i.e. Eclipse build automatically and then clean and compile through the activator. However, this did not work. I tried many different ways and even created a project from scratch without creating any Eclipse dependencies. Still, it did not work.

Then, I looked at my model and decided to try by changing the case of my property names, and then voila! It worked!

Here is what my model looked like BEFORE:

public class Test {
 public String FirstName;
 public String LastName;}

Now, I changed it to look like this:

public class Test {
  public String firstName;
  public String lastName;}

Just wanted to point this out since it is not obvious and I am coming from .Net

Lawlor answered 5/12, 2014 at 14:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.