Эта статья является пятой в серии о моих приключениях по разработке приложения для отслеживания фитнеса для моего выступления в 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, "jim@gmail.com", "secret", "Jim", "Smith")
Athlete.create(athlete)
val jim = Athlete.find(
"email={email}").on("email" -> "jim@gmail.com"
).first()
jim should not be (None)
jim.get.firstName should be("Jim")
}
it should "connect a Athlete" in {
Athlete.create(Athlete(NotAssigned, "bob@gmail.com", "secret", "Bob", "Johnson"))
Athlete.connect("bob@gmail.com", "secret") should not be (None)
Athlete.connect("bob@gmail.com", "badpassword") should be(None)
Athlete.connect("tom@gmail.com", "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