Пишем первый проект на Play Framework 2.0

в 6:39, , рубрики: java, play framework, scala, web-framework, Веб-разработка, метки: , , ,

13 марта состоялся релиз второй версии scala/java-фреймворка Play. На хабре уже был обзор новых фич Play 2.0. В этой же статье я хочу восполнить пробел в отсутствии мануалов на русском языке по этому интересному фреймворку на примере создания простого приложения на Java, состоящего из списка категорий и привязанных к ним вакансий.

Статья рассчитана на тех, кто совсем не знаком с Play и хотел бы его «пощупать», не тратя много времени.

Для работы с Play 2.0 вам понадобиться JDK 6 и выше. Для своей ubuntu 11.10 я поставил JDK 7.

Создаем проект

1. Скачиваем Play 2.0 и распаковываем туда, где у нас есть права и на запись и на чтение, у меня это /var/www/play20.

2. Переходим в директорию, где вы держите сайты и выполняем скрипт:

/var/www/play20/play new jobs

Ответ на первый вопрос оставляем по умолчанию, на второй выбираем «2 — Create a simple Java application». Для Windows все аналогично, только вы выполняете не shell-скрипт, а .bat.

3. Переходим в созданный проект, входим в консоль play и запускаем проект:

cd jobs
/var/www/play20/play
run

4. Проверяем, что у нас получилось: http://localhost:9000.

5. Теперь для начала разработки на Play 2.0 осталось настроить IDE. Останавливаем сервер и возвращаемся в консоль командой CTRL+D и выполняем eclipsify или idea в зависимости от вашей среды разработки. Для netbeans автоматического создания проекта пока нет.

6. Открываем проект, я использую Eclipse: File > Import > General > Existing Projects into Workspace.

Список категорий

Создаем модель категории app/models/Category.java:

package models;

import java.util.List;

import javax.persistence.Entity;
import javax.persistence.Id;

import play.data.validation.*;
import play.db.ebean.Model;

@Entity
public class Category extends Model {
  
  @Id
  public Long id;
  
  @Constraints.Required
  public String title;
  
  public static Finder<Long,Category> find = new Finder (
    Long.class, Category.class
  );
  
  public static List<Category> all() {
    return find.all();
  }
}

В шаблоне главной страницы views/index.scala.html выводим все категории:

@(categories: List
Google работает над неким смартфоном под кодовым именем Taimen, который будет крупнее нового Pixel XL

Вчера мы сообщали, что новые смартфоны Google Pixel и Pixel XL имеют кодовые имена Walleye (судак) и Muskie (щука-маскинонг). Поисковый гигант уже много лет использует для своих устройств кодовые имена, выбранные из названий рыб или морской тематики в целом.

) @main("Jobs") { <ul> @for(category <- categories) { <li>@category.title</li> } </ul> }

Шаблоны в play 2.0 представляют собой html + блоки кода на Scala, которые начинаются с символа '@'. Также стоит обратить внимание, что шаблоны похожи на функции и имеют параметры, которые описываются в начале файла.

Передаем из контроллера приложения app/controllers/Application.java в отредактированный нами выше шаблон список категорий:

import models.Category;

...

public class Application extends Controller {

  public static Result index() {
    return ok(index.render(	Category.all()));
  }
}

Данные для начала будем хранить для простоты просто в памяти, для этого расскоментируем строчки в conf/application.conf:

db.default.driver=org.h2.Driver
db.default.url="jdbc:h2:mem:play"

...

ebean.default="models.*"

Жмем F5 в браузере, нам предложат выполнить SQL-запрос. Кликаем на «Apply this script now!» и мы должны увидеть пустой список ul: у нас пока нет ни одной категории.

Добавление категорий

Добавляем в шаблон app/views/index.scala.html форму добавления категории:

@(categories: List
Google работает над неким смартфоном под кодовым именем Taimen, который будет крупнее нового Pixel XL

Вчера мы сообщали, что новые смартфоны Google Pixel и Pixel XL имеют кодовые имена Walleye (судак) и Muskie (щука-маскинонг). Поисковый гигант уже много лет использует для своих устройств кодовые имена, выбранные из названий рыб или морской тематики в целом.

, categoryForm: Form
Google работает над неким смартфоном под кодовым именем Taimen, который будет крупнее нового Pixel XL

Вчера мы сообщали, что новые смартфоны Google Pixel и Pixel XL имеют кодовые имена Walleye (судак) и Muskie (щука-маскинонг). Поисковый гигант уже много лет использует для своих устройств кодовые имена, выбранные из названий рыб или морской тематики в целом.

) @import helper._ @main("Jobs") { <ul> @for(category <- categories) { <li>@category.title</li> } </ul> @form(routes.Application.add()) { @inputText(categoryForm("title")) <input type="submit" value="Создать"> } }

В шаблоне мы импользуем функции из helper._: функция form создает HTML-форму с заполненными полями action и method, функция inputText генерит текстовый input

Прописываем маршрут в conf/routes:

POST    /add                        controllers.Application.add()

Добавляем в app/controllers/Application.java экшн, который будет создавать новую категорию используя данные, полученные из формы и обновим вызов рендеринга шаблона в экшене index, добавив второй параметр (форму):

import play.data.Form;

...

public static Result index() {
  return ok(index.render(Category.all(), form(Category.class)));
}

public static Result add() {
  Form<Category> filledForm = form(Category.class).bindFromRequest();
  if (filledForm.hasErrors()) {
    return badRequest(index.render(Category.find.all(), filledForm));
  }
  Category category = filledForm.get();
  category.save();
  return redirect(routes.Application.index());
}

Обновляем http://localhost:9000 и пробуем добавлять категории.

На этом месте у меня проект сломался. Чтобы все пересобрать выходим из консоли, вызываем /var/www/play20/play clean-all и заново запускаем.

Вакансии

Модель вакансий app/models/Job.java, связанная один-ко-многим с категориями:

package models;

import java.util.List;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.ManyToOne;

import play.data.validation.*;
import play.db.ebean.Model;
import play.db.ebean.Model.Finder;

@Entity
public class Job extends Model {
  
  @Id
  public Long id;
  
  @Constraints.Required
  public String title;
  
  @ManyToOne
  public Category category;
  
  public static Finder<Long,Job> find = new Finder(Long.class, Job.class);
}

Добавляем обратную связь в модель категорий app/models/Category.java:

import javax.persistence.OneToMany;

...

@OneToMany(mappedBy="category")
public List<Job> jobs;

Жмем F5, применяем новый SQL.

Теперь мы можем создать шаблон вывода категории views/show.scala.html со всеми ее вакансиями:

@(category: Category, jobForm: Form[Job])

@import helper._

@main("Jobs") {
  
  <h1>@category.title</h1>

  <h2>Вакансии:</h2>
  <ul>
    @for(job <- category.jobs) {
      <li>@job.title</li>
    }
  </ul>
  
  <h2>Добавить вакансию</h2>
  @form(routes.Jobs.аdd(category.id)) {
    @inputText(jobForm("title")) 
    <input type="submit" value="Создать">
  }
}

Добавляем контроллер вакансий app/controllers/Jobs.java, позволяющий добавить новую вакансию, вывод вакансии оставим на потом:

package controllers;

import java.util.List;

import play.*;
import play.data.Form;
import play.mvc.*;

import views.html.*;

import models.*;

public class Jobs extends Controller {

  public static Result add(Long categoryId) {
    Form<Job> filledForm = form(Job.class).bindFromRequest();
    if (filledForm.hasErrors()) {
      Category category = Category.find.byId(categoryId);
      return badRequest(show.render(category, filledForm));
    }
    Job job = filledForm.get();
    job.category = Category.find.ref(categoryId);
    job.save();
    return redirect(routes.Categories.show(categoryId));
  }
  
  public static Result show(Long id) {
    return TODO;
  }
}

Рефакторим категории

Осталось реализовать страницу вывода категории и всех ее вакансий, заодно вынесем из Application-контроллера добавление категорий.

Создаем отдельный контроллер для категорий app/controllers/Categories.java:

package controllers;

import java.util.List;

import play.*;
import play.data.Form;
import play.mvc.*;

import views.html.*;

import models.*;

public class Categories extends Controller {

  public static Result add() {
    Form<Category> filledForm = form(Category.class).bindFromRequest();
    if (filledForm.hasErrors()) {
      return badRequest(index.render(Category.find.all(), filledForm));
    }
    Category category = filledForm.get();
    category.save();
    return redirect(routes.Application.index());
  }
  
  public static Result show(Long id) {
    Category category = Category.find.byId(id);

    return ok(show.render(category, form(Job.class)));
  }
}

Не забываем добавить роуты добавления вакансии, вывода категории и меняем маршрут добавления новых категорий в conf/routes:

GET     /category/:id               controllers.Categories.show(id: Long)
POST    /category/add               controllers.Categories.add()
POST    /job/add                    controllers.Jobs.add(categoryId: Long)

В шаблоне главной страницы views/index.scala.html меняем параметр функции форм, добавленяем ссылки на категории:

@(categories: List
Google работает над неким смартфоном под кодовым именем Taimen, который будет крупнее нового Pixel XL

Вчера мы сообщали, что новые смартфоны Google Pixel и Pixel XL имеют кодовые имена Walleye (судак) и Muskie (щука-маскинонг). Поисковый гигант уже много лет использует для своих устройств кодовые имена, выбранные из названий рыб или морской тематики в целом.

, categoryForm: Form
Google работает над неким смартфоном под кодовым именем Taimen, который будет крупнее нового Pixel XL

Вчера мы сообщали, что новые смартфоны Google Pixel и Pixel XL имеют кодовые имена Walleye (судак) и Muskie (щука-маскинонг). Поисковый гигант уже много лет использует для своих устройств кодовые имена, выбранные из названий рыб или морской тематики в целом.

) @import helper._ @main("Jobs") { <ul> @for(category <- categories) { <li><a href="@routes.Categories.show(category.id)">@category.title</a></li> } </ul> @form(routes.Categories.add()) { @inputText(categoryForm("title")) <input type="submit" value="Создать"> } }

На этом шаге я еще раз пересобрал проект командой play clean-all.

Ресурсы для дальнейшего изучения Play 2.0:

Автор: faost

Поделиться

* - обязательные к заполнению поля