3

I've been working through the book "Play for Java", which is absolutely brilliant. I'm still fairly new to Java but I've been following the examples and I'm a bit stuck on Chapter 3. The code can be found here: Play for Java on GitHub.

The issue is that when I execute boundform.get(), the actual properties of the form don't seem to be making it into the "product" object. I've paused this in Eclipse's debugger and all of the values are correctly set at the line Form<Product> boundForm = productForm.bindFromRequest(); but then they just disappear by the time I get to product.save().

My controller, model, routes, and form are shown below. Please let me know if any additional information is needed.

Products.java (controller)

package controllers;

import models.Product;
import play.data.Form;
import play.mvc.Result;
import play.mvc.Controller;
import views.html.products.*;

import java.util.List;

public class Products extends Controller {

    private static final Form<Product> productForm = Form.form(Product.class);

    public static Result list() {
        List<Product> products = Product.findAll();
        return ok(list.render(products));
    }

    public static Result newProduct() {
        return ok(details.render(productForm));
    }

    public static Result details(String ean) {
        return TODO;
    }

    public static Result save() {
        Form<Product> boundForm = productForm.bindFromRequest();
        Product product = boundForm.get();
        product.save();
        return ok(String.format("Saved product %s", product));
    }

}

Product.java (model)

package models;

import java.util.ArrayList;
import java.util.List;

public class Product {

    public String ean;
    public String name;
    public String description;

    public Product() {

    }

    public Product(String ean, String name, String description) {
        this.ean = ean;
        this.name = name;
        this.description = description;
    }

    public String toString() {
        return String.format("%s - %s", this.ean, this.name);
    }

    private static List<Product> products;

    static {
        products = new ArrayList<Product>();
        products.add(new Product("1111111111111", "Paperclips 1",
                "Paperclips description 1"));
        products.add(new Product("2222222222222", "Paperclips 2",
                "Paperclips description "));
        products.add(new Product("3333333333333", "Paperclips 3",
                "Paperclips description 3"));
        products.add(new Product("4444444444444", "Paperclips 4",
                "Paperclips description 4"));
        products.add(new Product("5555555555555", "Paperclips 5",
                "Paperclips description 5"));
    }

    public static List<Product> findAll() {
        return new ArrayList<Product>(products);
    }

    public static Product findByEan(String ean) {
        for (Product candidate : products) {
            if (candidate.ean.equals(ean)) {
                return candidate;
            }
        }
        return null;
    }

    public static List<Product> findByName(String term) {
        final List<Product> results = new ArrayList<Product>();
        for (Product candidate : products) {
            if (candidate.name.toLowerCase().contains(term.toLowerCase())) {
                results.add(candidate);
            }
        }
        return results;
    }

    public static boolean remove(Product product) {
        return products.remove(product);
    }

    public void save() {
        products.remove(findByEan(this.ean));
        products.add(this);
    }
}

Routes

# Routes
# This file defines all application routes (Higher priority routes first)
# ~~~~

# Home page
GET     /                           controllers.Application.index()
GET     /products/                  controllers.Products.list()
GET     /products/new               controllers.Products.newProduct()
GET     /products/:ean              controllers.Products.details(ean: String)
POST    /products/                  controllers.Products.save()

# Map static resources from the /public folder to the /assets URL path
GET     /assets/*file               controllers.Assets.at(path="/public", file)

details.scala.html

@(productForm: Form[Product])

@import helper._

@import helper.twitterBootstrap._

@main("Product form") {
    <h1>Product form</h1>
    @helper.form(action = routes.Products.save()) {
        <fieldset>
            <legend>Product (@productForm("name").valueOr("New"))</legend>
            @helper.inputText(productForm("ean"), '_label -> "EAN")
            @helper.inputText(productForm("name"),'_label -> "Name")
            @helper.textarea(productForm("description"), '_label -> "Description")
        </fieldset>
        <input type="submit" class="btn btn-primary" value="Save">
        <a class="btn" href="@routes.Application.index()">Cancel</a>
    }
}

I'm sure this is something painfully obvious. Thank you so much!

user2864874
  • 829
  • 1
  • 9
  • 21
  • I'm afraid I know very little about Scala and Play but, you could try setting a breakpoint on the actual properties of the form. In Eclipse, if you set the breakpoint on the definition of the property and then right-click to get 'breakpoint properties', it will allow you to fire the breakpoint when the property is modified. You could then use this to see the stacktrace when something sets it to null. – matt freake Jun 23 '14 at 14:39
  • Thanks so much for the reply and the introduction to conditional breakpoints. I had no idea you could do that! Unfortunately, though, the breakpoint doesn't seem to be catching. Could this be because the value starts out as null and doesn't actually change? I tried `this.name == null` and `this.name.equals(null)` but neither of them caught. – user2864874 Jun 24 '14 at 00:22

2 Answers2

3

See the documentation's Handling Binding Failure section. Calling .get() on a Form isn't safe, because if there are validation errors it will return null. The preferred way is to check for errors first via hasErrors(), and then handle it from there.

if (boundform.hasErrors()) {
    /* There are errors somewhere in the form, 
    * return it to the view and display them there, 
    * or do whatever else you need to handle the error.
    */
    return badRequest(...);
} else {
    // A valid `Product`, proceed as normal.
    Product product = boundform.get();

    return ok(....);
}
Michael Zajac
  • 55,144
  • 7
  • 113
  • 138
2

SBT cache issue. I ran activator clean and everything worked.

I started pasting entire files from the GitHub repo to see if I could narrow it down. That led to a new set of errors which brought me to this StackOverflow question which had the SBT cache suggestion in the thread.

I appreciate the suggestion and link to the error methods, LimbSoup. Will definitely be looking into those regardless and am sure I'll probably reference your answer in the future more than once!

Thanks so much, all.

Community
  • 1
  • 1
user2864874
  • 829
  • 1
  • 9
  • 21