Статьи

Слушать проблемы Anala, Heroku и PostgreSQL в Scala

Эта статья является пятой в серии о моих приключениях по разработке приложения для отслеживания фитнеса для моего выступления в Devoxx за две недели. Предыдущие статьи можно найти по адресу:

  1. Интеграция Scalate и Jade с Play 1.2.3
  2. Попытка заставить CoffeeScript работать с Scalate and Play
  3. Интеграция HTML5 Boilerplate с Scalate и Play
  4. Разработка с использованием 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