Статьи

Миграция в Play 2 и презентация My ÜberConf


В моем
последнем посте о переходе на Play 2 я сказал, что напишу еще один пост на оставшейся части моего опыта. Хотя я еще не закончил переход на Play 2, я чувствую, что сделал достаточно, чтобы поговорить о возникших проблемах.

Проверка и отображение ошибок
С Play 1 я не могу не думать, что проверка была немного более интуитивной. Например, вот как я заполнил объект из параметров запроса, преобразовал значение и проверил его пригодность для помещения в базу данных.

var workout = params.get("workout", classOf[Workout])

// change duration to time
var duration = params.get("workout.duration")
workout.duration = convertWatchToTime(duration)

Validation.valid("workout", workout)

if (Validation.hasErrors) {
  renderArgs.put("template", "Profile/edit")
  edit(id);
  ...
} else { // put into db

В Play Scala 2 вы должны определить структуру формы и связать ее с запросом. Основываясь на том, что я смог придумать, я написал следующий код, чтобы выполнить то же самое:

val workoutForm = Form(
  mapping(
    "id" -> ignored(NotAssigned: anorm.Pk[Long]),
    "title" -> text,
    "description" -> text,
    "duration" -> nonEmptyText,
    "distance" -> nonEmptyText,
    "postedAt" -> optional(date),
    "athleteId" -> optional(longNumber)
  )((id, title, description, duration, distance, postedAt, athleteId) =>
    Workout(id, title, description, convertWatchToTime(duration), distance.toDouble, null, 0))
    ((w: Workout) =>
      Some((w.id, w.title, w.description, w.duration.toString, w.distance.toString, null, Some(0))))
)
...
workoutForm.bindFromRequest.fold(
  form => {
    Ok(Scalate("/Profile/edit.jade").render(request, 'errors -> form.errors))
  },
  workout => { // put into db

Во-первых, версия Play 2 более многословна, но в основном это происходит из-за переопределения моего модельного объекта как формы. Кажется странным, что Java API позволяет делать это в одну строку, а версия Scala — нет. Кроме того, мне не удалось выяснить, как вернуть данные из моей «формы» обратно в запрос, чтобы я мог заполнить поля ввода. Я признаю, я не тратил много времени, пытаясь понять это, но он провалил 10-минутный тест. Примечание для себя: используйте обязательный атрибут HTML5, чтобы уменьшить необходимость проверки на стороне сервера в современных браузерах.

С другой стороны, мне понравилось, как я могу использовать маршруты в своих шаблонах Jade. Это было так же просто, как импортировать класс маршрутов и использовать его так же, как в Scala Templates Play:

-import controllers._

form(method="post" class="form-stacked" id="workoutForm"
  action={routes.Profile.postWorkout(workout.map(_.id.get))})
  input(type="hidden" name="id" value="#{workout.map(_.id)}")

Безопасное общение
После того, как большая часть моего пользовательского интерфейса заработала, я начал смотреть на Безопасный социальный модуль для Play 2 . Ниже приведены шаги, которые мне пришлось пройти, чтобы установить его:

  1. Клонированный проект GitHub на мой жесткий диск.
  2. Скопировал код модуля / * в директорию modules / securesocial моих проектов.
  3. Модифицированный проект / Build.scala для добавления secureSocial и зависит от моего проекта.
    val secureSocial = PlayProject(
      appName + "-securesocial", appVersion, mainLang = SCALA, path = file("modules/securesocial")
    )
    
    val main = PlayProject(appName, appVersion, appDependencies, mainLang = SCALA).settings(
      // Add your own project settings here
    ).dependsOn(secureSocial).aggregate(secureSocial)
    
  4. Добавил conf / securesocial.conf и включил его в мой application.conf со следующей строкой:
    include "securesocial.conf"
    
  5. Добавлен файл conf / play.plugins со следующим, чтобы Twitter загружался в качестве провайдера:
    10000:securesocial.core.providers.TwitterProvider
    
  6. Создал InMemoryUserService.scala и сослался на него в моем файле play.plugins:
    9999:services.InMemoryUserService
    
  7. Маршруты Secure Social добавлены в мой файл conf / route.

 

 

 

 

После того, как я закончил все эти шаги, я запустил свое приложение и был приятно удивлен, обнаружив, что я могу перейти к / войти и успешно пройти аутентификацию через Twitter. Установить Secure Social в приложении Play 2 немного сложнее, чем добавить его в качестве зависимости в Play 1, но я был благодарен, что заставил его работать менее чем за 10 минут.

Heroku
Следующее, что я сделал, это попытался развернуть мое приложение в Heroku. Я знал, что могут быть некоторые проблемы со Scalate после прочтения блога Яна Хелвича о Scalate на Heroku. Первые вещи, с которыми я столкнулся, были 1) успешный запуск и 2) ошибка в моем браузере.

Действие не найдено

Я смог воспроизвести эту проблему локально, запустив «play clean stage» и запустив приложение с «target / start». После 30 минут удара головой о стену, я догадался, что это может быть вызвано безопасным социальным. Удаление Secure Social решило проблему, и я вернулся в бизнес. Однако на этот раз при развертывании я получил сообщение об ошибке, о которой упоминал Ян.

2012-06-21T07:07:12+00:00 app[web.1]: [error] o.f.s.l.DefaultLayoutStrategy - Unhandled: org.fusesource.scalate.TemplateException: target/../tmp/src/app/target/../app/views/layouts/default.jade.scala (No such file or directory)
2012-06-21T07:07:12+00:00 app[web.1]: [error] application - 
2012-06-21T07:07:12+00:00 app[web.1]: 
2012-06-21T07:07:12+00:00 app[web.1]: ! @6amfgf02h - Internal server error, for request [GET /] ->
2012-06-21T07:07:12+00:00 app[web.1]: 
2012-06-21T07:07:12+00:00 app[web.1]: play.core.ActionInvoker$$anonfun$receive$1$$anon$1: Execution exception [[TemplateException: target/../tmp/src/app/target/../app/views/layouts/default.jade.scala (No such file or directory)]]
2012-06-21T07:07:12+00:00 app[web.1]:   at play.core.ActionInvoker$$anonfun$receive$1.apply(Invoker.scala:134) [play_2.9.1-2.0.1.jar:2.0.1]
2012-06-21T07:07:12+00:00 app[web.1]:   at play.core.ActionInvoker$$anonfun$receive$1.apply(Invoker.scala:115) [play_2.9.1-2.0.1.jar:2.0.1]
2012-06-21T07:07:12+00:00 app[web.1]:   at akka.actor.Actor$class.apply(Actor.scala:311) [akka-actor-2.0.1.jar:2.0.1]
2012-06-21T07:07:12+00:00 app[web.1]:   at play.core.ActionInvoker.apply(Invoker.scala:113) [play_2.9.1-2.0.1.jar:2.0.1]
2012-06-21T07:07:12+00:00 app[web.1]:   at akka.actor.ActorCell.invoke(ActorCell.scala:619) [akka-actor-2.0.1.jar:2.0.1]
2012-06-21T07:07:12+00:00 app[web.1]:   at akka.dispatch.Mailbox.processMailbox(Mailbox.scala:196) [akka-actor-2.0.1.jar:2.0.1]
2012-06-21T07:07:12+00:00 app[web.1]: Caused by: org.fusesource.scalate.TemplateException: target/../tmp/src/app/target/../app/views/layouts/default.jade.scala (No such file or directory)
2012-06-21T07:07:12+00:00 app[web.1]:   at org.fusesource.scalate.TemplateEngine.compileAndLoad(TemplateEngine.scala:834) ~[scalate-core-1.5.3.jar:1.5.3]
2012-06-21T07:07:12+00:00 app[web.1]:   at org.fusesource.scalate.TemplateEngine.compileAndLoadEntry(TemplateEngine.scala:691) ~[scalate-core-1.5.3.jar:1.5.3]
2012-06-21T07:07:12+00:00 app[web.1]:   at org.fusesource.scalate.TemplateEngine.liftedTree1$1(TemplateEngine.scala:411) ~[scalate-core-1.5.3.jar:1.5.3]
2012-06-21T07:07:12+00:00 app[web.1]:   at org.fusesource.scalate.TemplateEngine.load(TemplateEngine.scala:405) ~[scalate-core-1.5.3.jar:1.5.3]
2012-06-21T07:07:12+00:00 app[web.1]:   at org.fusesource.scalate.TemplateEngine.load(TemplateEngine.scala:475) ~[scalate-core-1.5.3.jar:1.5.3]
2012-06-21T07:07:12+00:00 app[web.1]:   at org.fusesource.scalate.layout.DefaultLayoutStrategy.org$fusesource$scalate$layout$DefaultLayoutStrategy$$tryLayout(DefaultLayoutStrategy.scala:77) ~[scalate-core-1.5.3.jar:1.5.3]
2012-06-21T07:07:12+00:00 app[web.1]: Caused by: java.io.FileNotFoundException: target/../tmp/src/app/target/../app/views/layouts/default.jade.scala (No such file or directory)
2012-06-21T07:07:12+00:00 app[web.1]:   at java.io.FileOutputStream.open(Native Method) ~[na:1.6.0_20]
2012-06-21T07:07:12+00:00 app[web.1]:   at java.io.FileOutputStream.<init>(FileOutputStream.java:209) ~[na:1.6.0_20]
2012-06-21T07:07:12+00:00 app[web.1]:   at java.io.FileOutputStream.<init>(FileOutputStream.java:160) ~[na:1.6.0_20]
2012-06-21T07:07:12+00:00 app[web.1]:   at org.fusesource.scalate.util.IOUtil$.writeBinaryFile(IOUtil.scala:111) ~[scalate-util-1.5.3.jar:1.5.3]
2012-06-21T07:07:12+00:00 app[web.1]:   at org.fusesource.scalate.TemplateEngine.compileAndLoad(TemplateEngine.scala:747) ~[scalate-core-1.5.3.jar:1.5.3]
2012-06-21T07:07:12+00:00 app[web.1]:   at org.fusesource.scalate.TemplateEngine.compileAndLoadEntry(TemplateEngine.scala:691) ~[scalate-core-1.5.3.jar:1.5.3]
</init></init>

Я попробовал его предложение (убрав первую косую черту в моих путях Scalate), но оно не сработало. Я попытался добавить прекомпиляцию Scalate, но это тоже не решило проблему. Хорошая новость заключается в том, что я решил эту проблему сегодня днем, изменив свой объект Scalate, чтобы использовать канонический путь вместо абсолютного.

Приложение для iPhone
В дополнение к упомянутым здесь изменениям я переписал приложение для iPhone для Play More. Я обновил его до PhoneGap 1.8.1 , использовал jQTouch , разработал с помощью AppCode (вместо XCode ) и получил довольно хороший опыт. Единственная проблема, с которой я столкнулся, была с расширением jqt.bars от DataZombies . Я кратко попытался интегрировать это и затем решил не делать этого. Тем не менее, я оставил все свои JS и CSS на своей странице, и это привело к тому, что прокрутка не сработала и приложение замедлилось. Удаление файлов решило проблему. Другое большое улучшение, которое я сделал, — это перемещение всех статических ресурсов (JS, CSS, изображений) в мобильное приложение вместо ссылки на них с http://play-more.com. Это уменьшило время запуска с 30-40 секунд до 3-4 секунд!

 

Презентация и исходный код
Я представил все эти выводы и рассказал свою историю на ÜberConf сегодня утром. Кроме того, я объявил, что код теперь с открытым исходным кодом и доступен на GitHub . Вы можете просмотреть мою презентацию ниже или на Slideshare .

Заключение
Буду ли я делать это снова? Изучение Scala было моим основным мотиватором для изучения Play. Когда было объявлено Play 2, я подумал, что перенести мое приложение на новую версию будет легко. К сожалению, разработчики Play решили нарушить обратную совместимость и написали совершенно новый фреймворк, который все еще находится в зачаточном состоянии. Я думаю, вы можете видеть из моих последних постов, что переход с Play 1.x на 2.x был непростой задачей. Было приятно узнать больше о Play и Scala в процессе, но жизнь на переднем крае также была довольно разочаровывающей. Play Scala 1.x показался немного более производительным, чем Play 2, особенно из-за Magic [T] в Anorm, но также потому, что для него требовалось меньше кода в контроллерах.

Я нашел Anorm и Scalate огромными временными затратами и не знаю, рекомендую ли я использовать один из них в проекте Play 2. Я уверен, что Scalate будет проще в использовании, поскольку его интеграция с Play 2 становится более совершенной, но я не знаю, есть ли надежда на абстракцию JDBC, которая не выдает сообщения об ошибках, когда дела идут на юг.

С другой стороны, мой опыт работы с HTML5 и CoffeeScript был замечательным. Они сделали то, что я попросил их сделать, и не причиняли много боли. Когда веб-приложение на основе браузера не может обрабатывать гео-фон в фоновом режиме, PhoneGap пришел на помощь.