Эта статья является пятой в серии о моих приключениях по разработке приложения для отслеживания фитнеса для моего выступления в Devoxx за две недели. Предыдущие статьи можно найти по адресу:
- Интеграция Scalate и Jade с Play 1.2.3
- Попытка заставить CoffeeScript работать с Scalate and Play
- Интеграция HTML5 Boilerplate с Scalate и Play
- Разработка с использованием HTML5, CoffeeScript и Twitter Bootstrap
Anorm
В моей предыдущей статье я описал, как я создал функции моего приложения с использованием CoffeeScript и как он выглядит хорошо, используя Bootstrap в Twitter. Затем я перешел к сохранению этих данных с помощью Anorm .
Модуль Scala включает в себя новый уровень доступа к данным под названием Anorm, который использует простой SQL для выполнения запроса к базе данных и предоставляет несколько API для анализа и преобразования результирующего набора данных.
Я большой поклонник ORM, таких как Hibernate и JPA, поэтому поначалу изучение новой абстракции JDBC было не совсем привлекательным. Тем не менее, так как Anorm по умолчанию для Play Scala , я решил попробовать. Самый простой способ для меня изучить Anorm — это начать программировать на нем. В качестве руководства я использовал первую итерацию для модели данных и создал объекты модели, сопутствующие объекты, которые расширили Magic (с соответствующим названием), и написал несколько тестов с использованием scalatest . Я начал с модели «Спортсмен», так как знал, что «Пользователь» — это ключевое слово в PostgreSQL, и именно это Heroku использует для своей базы данных .
package models import play.db.anorm._ import play.db.anorm.defaults._ case class Athlete( id: Pk[Long], email: String, password: String, firstName: String, lastName: String ) { } object Athlete extends Magic[Athlete] { def connect(email: String, password: String) = { Athlete.find("email = {email} and password = {password}") .on("email" -> email, "password" -> password) .first() } def apply(firstName: String) = new Athlete(NotAssigned, null, null, firstName, null) }
Затем я написал пару тестов для этого в test / Tests.scala.
import play._ import play.test._ import org.scalatest._ import org.scalatest.junit._ import org.scalatest.matchers._ class BasicTests extends UnitFlatSpec with ShouldMatchers with BeforeAndAfterEach { import models._ import play.db.anorm._ override def beforeEach() { Fixtures.deleteDatabase() } it should "create and retrieve a Athlete" in { var athlete = Athlete(NotAssigned, "[email protected]", "secret", "Jim", "Smith") Athlete.create(athlete) val jim = Athlete.find( "email={email}").on("email" -> "[email protected]" ).first() jim should not be (None) jim.get.firstName should be("Jim") } it should "connect a Athlete" in { Athlete.create(Athlete(NotAssigned, "[email protected]", "secret", "Bob", "Johnson")) Athlete.connect("[email protected]", "secret") should not be (None) Athlete.connect("[email protected]", "badpassword") should be(None) Athlete.connect("[email protected]", "secret") should be(None) }
В этот момент все было хорошо и модно. Я мог запустить «play test», открыть http: // localhost / @ tests в моем браузере и запустить тесты, чтобы увидеть красивый оттенок зеленого на моем экране. Я продолжил следовать уроку, заменив «Пост» на «Тренировка», и добавил комментарии. Объект Workout показывает некоторый синтаксис сумасшедшей задницы, который Anorm увлекается Scala.
object Workout extends Magic[Workout] { def allWithAthlete: List[(Workout, Athlete)] = SQL( """ select * from Workout w join Athlete a on w.athlete_id = a.id order by w.postedAt desc """ ).as(Workout ~< Athlete ^^ flatten *) def allWithAthleteAndComments: List[(Workout, Athlete, List[Comment])] = SQL( """ select * from Workout w join Athlete a on w.athlete_id = a.id left join Comment c on c.workout_id = w.id order by w.postedAt desc """ ).as(Workout ~< Athlete ~< Workout.spanM(Comment) ^^ flatten *) def byIdWithAthleteAndComments(id: Long): Option[(Workout, Athlete, List[Comment])] = SQL( """ select * from Workout w join Athlete a on w.athlete_id = a.id left join Comment c on c.workout_id = w.id where w.id = {id} """ ).on("id" -> id).as(Workout ~< Athlete ~< Workout.spanM(Comment) ^^ flatten ?) }
Все эти методы возвращают Tuples , что сильно отличается от ORM, который возвращает объект, для которого вы вызываете методы, чтобы получить связанные с ним элементы. Ниже приведен пример того, как на это ссылаются в шаблоне Scalate:
-@ val workout:(models.Workout,models.Athlete,Seq[models.Comment]) - var commentsTitle = "No Comments" if (workout._3.size > 0) commentsTitle = workout._3.size + " comments, lastest by " + workout._3(workout._3.size - 1).author div(class="workout") h2.title a(href={action(controllers.Profile.show(workout._1.id()))}) #{workout._1.title} .metadata span.user Posted by #{workout._2.firstName} on span.date #{workout._1.postedAt} .description = workout._1.description
Эволюция в Heroku
Я был доволен своим прогрессом, пока не попытался развернуть свое приложение в Heroku. Я добавил db = $ {DATABASE_URL} в мой application.conf в соответствии с рекомендациями веб-приложений на основе базы данных с Play! на Героку / кедр . Однако при развертывании произошел сбой, поскольку таблицы базы данных не были созданы.
2011-10-05T04:08:52+00:00 app[web.1]: 04:08:52,712 WARN ~ Your database is not up to date. 2011-10-05T04:08:52+00:00 app[web.1]: 04:08:52,712 WARN ~ Use `play evolutions` command to manage database evolutions. 2011-10-05T04:08:52+00:00 app[web.1]: 04:08:52,713 ERROR ~ 2011-10-05T04:08:52+00:00 app[web.1]: 2011-10-05T04:08:52+00:00 app[web.1]: @681m15j3l 2011-10-05T04:08:52+00:00 app[web.1]: Can't start in PROD mode with errors 2011-10-05T04:08:52+00:00 app[web.1]: 2011-10-05T04:08:52+00:00 app[web.1]: Your database needs evolution! 2011-10-05T04:08:52+00:00 app[web.1]: An SQL script will be run on your database. 2011-10-05T04:08:52+00:00 app[web.1]: 2011-10-05T04:08:52+00:00 app[web.1]: play.db.Evolutions$InvalidDatabaseRevision
С помощью Джеймса Уорда я узнал, что для применения эволюции мне нужно использовать «бегство герою» . Поэтому я запустил следующую команду:
heroku run "play evolutions:apply --%prod"
К сожалению, это не удалось:
Running play evolutions:apply --%prod attached to terminal... up, run. 5 ~ _ _ ~ _ __ | | __ _ _ _| | ~ | '_ \| |/ _' | || |_| ~ | __/|_|\____|\__ (_) ~ |_| |__/ ~ ~ play! 1.2.3, http://www.playframework.org ~ framework ID is prod ~ Oct 17, 2011 7:05:46 PM play.Logger warn WARNING: Cannot replace DATABASE_URL in configuration (db=$ {DATABASE_URL}) Exception in thread "main" java.lang.NullPointerException at play.db.Evolutions.main(Evolutions.java:54)
Открыв тикет с поддержкой Heroku, я узнал, что это потому, что DATABASE_URL не был установлен («heroku config» показывает ваши переменные). Очевидно, это должно быть установлено при создании приложения, но как-то не для меня. Чтобы исправить, мне нужно было выполнить следующую команду:
$ heroku pg:promote SHARED_DATABASE -----> Promoting SHARED_DATABASE to DATABASE_URL... done
PostgreSQL и даты
Следующая проблема, с которой я столкнулся, была с загрузкой данных по умолчанию. У меня есть следующий класс BootStrap.scala в моем проекте для загрузки данных по умолчанию:
class BootStrap extends Job { override def doJob() { import models._ import play.test._ // Import initial data if the database is empty if (Athlete.count().single() == 0) { Yaml[List[Any]]("initial-data.yml").foreach { _ match { case a: Athlete => Athlete.create(a) case w: Workout => Workout.create(w) case c: Comment => Comment.create(c) } } } } }
По некоторым причинам, только моя таблица «спортсмена» была заполнена, а другие нет. Я попытался включить отладку и трассировку, но в логах ничего не появилось. Похоже, это частая проблема с Play. Когда данные не загружаются, нет регистрации, указывающей, что пошло не так. Что еще хуже с Anorm, нет способа записать SQL, который он пытается запустить. Моя работа BootStrap работала нормально при подключении к «db = mem», но остановилась после переключения на PostgreSQL. Поддержка, которую я получил для этой проблемы, была разочаровывающей, так как это вызвало сверчки в Google Group Play . Я наконец понял, что «поддержка даты для вставки» была добавлена в Anorm пару месяцев назад .
Чтобы получить последний код play-scala в свой проект, я клонировал play-scala , собрал его локально и загрузил на свой сервер. Затем я добавил следующее в dependencies.yml и запустил «play deps —sync».
require: ... - upgrades -> scala 0.9.1-20111025 ... repositories: - upgrades: type: http artifact: "http://static.raibledesigns.com/[module]-[revision].zip" contains: - upgrades -> *
Резюме
Когда я начал писать эту статью, я собирался рассказать о некоторых улучшениях, которые я внес в совместимость Scalate Play . Тем не менее, я думаю, что я сохраню это в следующий раз и, возможно, превратить его в плагин, используя play-excel в качестве примера.
Как вы можете заметить из этой статьи, мой опыт работы с Anorm был разочаровывающим — особенно из-за отсутствия сообщений об ошибках при сбое операций. Ожидание отсутствия поддержки было ожидаемым, так как обычно это происходит, когда вы живете на пределе. Однако, основываясь на этом опыте, я не могу не думать, что может пройти некоторое время, прежде чем Play 2.0 будет готов к использованию.
Хорошей новостью является то, что
IntelliJ добавляет поддержку Play . Может быть, это поможет увеличить принятие и вдохновит разработчиков платформы стабилизировать и улучшить Play Scala, прежде чем переместить всю платформу в Scala. В конце концов, кажется, они столкнулись с
некоторыми проблемами, делающими Scala столь же быстрым, как и Java .
От http://raibledesigns.com/rd/entry/play_scala_s_anorm_heroku