poniedziałek, 30 czerwca 2014

Play - bonusy do ćwiczeń

Poniżej lista małych tematów, które można dorzucać do większych ćwiczeń jak jakimś cudem zostanie trochę czasu.

Globalne info o braku strony

To je dobre bo w innych frameworkach ten mechanizm jest na porządku dziennym.

//Global.scala - w katalogu app- będzie warning o  tym by nie dodawać do domyślnej paczki.
object Global extends GlobalSettings {

  override def onStart(app:Application){
    Logger.info("startujemy ku*wa") //to tak dla bajery
  }

  override def onHandlerNotFound(request: RequestHeader) = {
    Future.successful(NotFound(
      views.html.nieZnalazlem(request.path) 
    ))
  }
}

//nieznalazlem.scala.html
@* nieZnalazlem Template File *@
@(param: Any)

<h1>Nie ma takie strony ku*wa</h1>

Taki niby Quartz

Tutaj jest pierwszy kontakt z Akka.

//Global.scala
override def onStart(app:Application){
    Logger.info("startujemy ku*wa")
    
    import play.api.Play.current
    import scala.concurrent.duration._
    import scala.concurrent.ExecutionContext.Implicits.global
    
    val quartzActor=Akka.system.actorOf(Props(new quartz.QartzActor()))
    Akka.system.scheduler.schedule(0.seconds,30.seconds,quartzActor,RobCos())
  }

class QartzActor extends Actor{
 def receive={
   case RobCos()=>println("robie cos")
   case _ =>println("robie nic")
 }
}

case class RobCos()

Zabawniejszy konfig

Konfig w Playu jest bardzo wygodny w użyciu i ma kilka ciekawych sztuczek. Są takie niby przestrzenie nazw dla propertasów oraz sklejanie propertasów, które fajnie działa przy sklejaniu ścieżek. Tu dla sportu stworzymy sobie ścieżkę z warzywa.

//application.conf
warzywo: {
 kolor=zielony,
 smak=kwaśny
}

property=${warzywo.kolor}/resztaSciezki

//html
@play.api.Play.current.configuration.getString("warzywo.kolor")
<br/>
@play.api.Play.current.configuration.getString("property")

Minimalizacja javaskryptu

Ten tamat znacznie się rozwinął w Play 2.3 za sprawą sbt-web (chyba) także tutaj uboższa wersja dla wersji 2.2

w app stworzyć paczkę assets.js

//test.js
function dzialaj(a){
 var tekst="aaa"
 console.log(tekst)
}

//_notCompiled.js
function dzialaj2(a){
 var tekst="aaa"
 console.log(tekst)
}

Teraz trzeba odświeżyć stronkę bo jakoś sam z siebie tych rzeczy nie kompiluje i mamy

//test.min.js
function dzialaj(a){var tekst="aaa";console.log(tekst)};

//_notCompiled.js
function dzialaj2(a){
 var tekst="aaa"
 console.log(tekst)
}

I to to nawet nam kompiluje javascript i sprawdza składnie! Bardzo ładnie!

No i to były właśnie takie krótkie małe tematy.

niedziela, 22 czerwca 2014

Play - formularze - fotki i spostrzeżenia

Najpierw komercha

Pizza sama się nie kupuje - więc jest i logo firmowe.
Obsługa wyjątków bez wyjątków.
W sumie trzeba było zrobić dwa zdjęcia - najpierw jedno z lewej strony - a w drugim wszyscy przechodzą na prawo i zakładają dla niepoznaki ubrania na lewą stronę - frekwencja 200%.
A tak się obsługuje formularze

Co następne

Teraz po wakacjach najpewniej :

  1. Powtórka wprowadzenia - teraz być może z Play 2.3
  2. Wystawianie RESTa przy pomocy JSONa i konsumpcja przy pomocy webservisów (żeby nie bawić się w javaskrypty)
  3. Integracja Playa z Akka

Spostrzeżenie

Aby w tym wpisie pojawiło się coś poza autopromocją - czas na ciekawe spostrzeżenie jakie pojawiło się w trakcie warsztatów.

Sytuacja wygląda tak : są jakieś okoliczności i pojawia się reakcja na te okoliczności. Reakcja wtapia się w światopogląd i nawet gdy już okoliczności się zmieniają i dana reakcja jest już być może niepotrzebna to już zdążyła stać się dogmatem, którego się nie kwestionuje bo nie. Dogmat w tym przypadku brzmi "nazwy zmiennych muszą zdradzać ich przeznaczenie czyli w skrócie - być czytelne". To było zbawianie gdy trzeba było pracować z 10000 liniami "managero-helkperów" w Javie (jeśli dla kogoś nie jest to czas przeszły to współczuję) ale czy jest to aktualne gdy nie pracuje się z okaleczonym kodem?

I teraz nowa sztuczka z kołczingu - aby uratować się przed flejmwarem należy sytuacje przedstawić nie w kontekście "co jest lepsze" ale raczej "czy zabawa w szukanie dziur w dogmacie da mi nowe możliwości i rozwinie moją osobowość abym stał się bardziej kompletnym bytem"- ano dokładnie tak!

Inspiracja tym tematem pojawiła się u mnie po lekturze jakiegoś bloga, którego teraz nie mogę znaleźć ale generalna koncepcja polegała na tym aby intencje zmiennej przekazywać nie przez jej nazwę ale przez jejże typ.

I teraz zerknijmy na dwie deklaracje metodki, która tworzy mi taki mały parserek do zapisywania pliku z formularza na dysku :


//po staremu
  def saveMultipartFile(multipartFileName:String,pathOnDisc:String)(data:MultipartFormData[TemporaryFile])=
//po nowemu
  def saveMultipartFile(m:MultipartFileName,p:PathOnDisc)(data:MultipartFormData[TemporaryFile])=
No i jaki widzę problem z pierwszą wersją? Ano taki jak z dokumentacją w wordzie - pisać można tam sobie wszystko co się komu podoba. I tka naprawdę to jaka jest pewność, że pierwszy parametr to faktycznie nazwa pliku a nie nazwisko panieńskie babci? W trakcie refaktoringu kod się zmieni a nazwa zostanie taka sama i dupa.

W drugim przypadku też 100% pewności nie ma ale raz, że w trakcie refaktoringu typy - czyli zwyczajne nazwy klas - zmienia IDE a nie JAnek programista z palca - a dwa, że jako String to mogę sobie przekazać co chcę i się zesra w Runtime. No i jest jeszcze trzecia strona medalu. Tak się wywołuje tę metodkę w kodzie :

val savingFileParserFunction=saveMultipartFile(MultipartFileName("file"), PathOnDisc("/tmp/testy"))_
I tutaj od razu w kodzie widać co jest co bez potrzeby wyświetlania Popapów z docami. Dla porównania w wywołaniu klasycznym :
val savingFileParserFunction=saveMultipartFile("file", "/tmp/testy")_
Tutaj dla odmiany raczej trzeba będzie zajrzeć do doca.

Na Code Retreat jest takie ćwiczonko gdzie należy każdą koncepcję w systemie przedstawić przy pomocy tzw. "klasy domenowej" czyli np. współrzędna jest typu "Wspolrzedna" a nie typu "Integer". No i w Javie pisanie tych wszystkich klas jest niby ok ale czułem się wewnętrznie, że w życiu raczej tego nie zastosuje bo wymaga to zbyt dużo prądu. W Scali sobie gdzieś w module trzasnę :

case class MultipartFileName(name:String) extends AnyVal
case class PathOnDisc(path:String) extends AnyVal
I już.

I jeszcze jedna ciekawa rzecz

Ten mały fragmencik :

extends AnyVal
Podobno sprawia, że kompilator w trakcie kompilacji "rozpakuje" typ i stawia w jego miejsce String wywalając wrappera. Czyli nie ma narzutu na pamięć kosztem tam chwilę dłuższej kompilacji. Chyba tak to działa.

I najważniejsze, żeby nie stawiać tej koncepcji w opozycji do tego co się wie i w co się wierzy ale spróbować chwilę się nią pobawić. Być może będzie to nowy bodziec do rozwoju i usprawnienia naszej pracy? A może po chwili się go wyrzuci i wróci do dotychczasowych metod? Najważniejsze to traktować to jako ćwiczenie a nie spór o to co lepsze. Po to też między innymi kasuje się kod w Code Retreat - aby pobawić się być może niewygodnymi pomysłami ze świadomością, że i tak to pójdzie do kibla i zostaną jedynie przeżycia w mojej czy twojej głowie.

Coś z zupełnie innej beczki

Bluzganie redukuje ból oraz zmniejsza wewnętrzne napięcie :

A co najważniejsze bluzganie buduje team spirit! Wiarygodności tych badań sprawdzać nie muszę gdyż weryfikuję je od lat trzydziestu. Ewentualnie jak ktoś był chowany pod kloszem i życia doświadczył tylko z odcinków Drużyny A to może zerknąć na : http://filing.pl/ilustrowany-podrecznik-dla-mlodziezy-dobrze-wychowanej/

niedziela, 1 czerwca 2014

Play Formularze - opis ćwiczeń

Poniżej opis drugiego modułu (słowo "moduł" brzmi tak zajebiście) ćwiczeń z Play Framework. Pierwszy moduł pojawił się w dwóch częściach :

Poniższy materiał to krótki zbiór ćwiczeń opracowany na podstawie tutoriali i książek - przystosowany do nauki Playa w trakcie 2-3 godzinnego spotkania z przerwą na pizze czy na jakieś zdrowsze jedzenie. Program nie jest jeszcze przetestowany w życiu ale powinien płynnie pokazać uczestnikom epickość formularzy w Play gdzie testować można po prostu wszystko. Także miłej zabawy z lektury i ćwiczeń.

Gdy to tak sobie piszę jest jeszcze jedno wolne miejsca na spotkanie także jak ktoś ma ochotę niech się śmiało dopisuje -> Link do spotkania na meetupie

Co przed startem

Aby nie zamulać już na samym początku dobrze by było gdyby każdy miał już rozpakowany Play przez zipa czy activatora. Dodatkowo fajnie przypomnieć sobie podstawy w tym celu każdy musi :

  • Zrobić nową aplikację
  • Import do Eclipse czy co kto tam używa
  • Stworzyć nowy widok jako ćwiczenie - czyli kontroler,mapowanie i szablon
  • Zostawić stronę index bo tam się wyświetla link do dokumentacji
  • Jak ktoś używa eclipse to poustawiać sobie zgodnie z http://scala-ide.org/docs/tutorials/play/

Wio

Dobrze mieć pod ręką otwartą lokalną dokumentację by powklejać importy i takie tam :

import play.api.data.Forms._
import play.api.data._

Tutaj zastanawiałem się czy większą wartość edukacyjną będzie miał jakiś zabawny przykład czy też coś generycznego jak User. Trzaśniemy coś z jedzeniem to może ludzie nauczą się patrzeć na to co jedzą (no i będzie bardziej różnorodnie bo nie zerżniemy przykładu prosto z dokumentacji).

case class Meal(name: String, calories: Int)

val mealForm=Form(mealMapping)
  
lazy val mealMapping=mapping(
    "name"->text,
    "calories"->number
)(Meal.apply)(Meal.unapply)

Deklaracja mappingu na zewnątrz wydaje mi się czytelniejsza a lazy jest potrzebne bo bez tego czasami się wywala gdzieś tam wewnątrz inicjalizacja Forma.

Tak czy inaczej mając tylko deklaracje mappingu i forma można teraz pokazać na testach, iż testować można wszystko.

    "bind data to meal mapping" in {
      val data=Map("name"->"hamburger","calories"->"5000")
      val mealData=Application.mealMapping.bind(data)
      mealData.isRight must beTrue
    }
    
    "bind data to meal mapping with errors" in {
      val data=Map("name"->"hamburger","calories"->"5000b")
      val mealData=Application.mealMapping.bind(data)
      mealData.isRight must beFalse
    }
    
    "bind data to meal form with error" in {
      val data=Map("name"->"hamburger","calories"->"5000b")
      val mealData=Application.mealForm.bind(data)
      mealData.errors must have(_.key=="calories")
    }

I ogólnie fajne i czytelne asercje są. Tutaj można zrobić szybko skok do Scali i wytłumaczyć co to je to Left i Right - będzie to też dobry wstęp do walidacji formularzy.

Either

Ten temat wywołuje u niektórych dużo zabawy za pierwszym razem bo w Javie informacja o tym, że coś może się nie udać zazwyczaj nie jest zawarta w typie. I jak mamy np. coś takiego:

def divide(a:Int,b:Int)=a/b       //> divide: (a: Int, b: Int)Int
divide(4,2)                       //> res0: Int = 2
divide(4,0)                       //> java.lang.ArithmeticException: / by zero

To przez zero dzielić nie można. I można zrobić tak :

def divide(a:Int,b:Int)={
 if(b==0) throw new IllegalArgumentException("cos tam")
 a/b
}

Ale można też :

def divide(a:Int,b:Int)=
       if(b==0) None else Some(a/b)

divide(4,2).map(_+1)              //> res0: Option[Int] = Some(3)
divide(4,0).map(_+1)              //> res1: Option[Int] = None

I tutaj ludzie, którzy robią w Javie od kilku lat maja jakaś dziwną radochę, ze typ nazywa się "Cośtam" albo "Nico". Pewnie będą jakieś pytanie "a po ch.." to w ogóle jest. Zycie pokaże co z tego wyjdzie. Tak czy inaczej mając Option chyba łatwiej będzie wytłumaczyć Either.

def divide(a:Int,b:Int)=
   if(b==0) Left(new IllegalArgumentException("cos tam"))
   else Right(a/b)           //> divide: (a: Int, b: Int)Product with Serializable with scala.util.Either[Ill
                                                  //| egalArgumentException,Int]
  divide(4,2).isRight               //> res0: Boolean = true
  divide(4,0).isRight               //> res1: Boolean = false

To będzie przykład w kontekście testów, które widzieliśmy chwilę wcześniej. A w kontekście obsługi forma, której jeszcze nie było - poniższe trzy linijki

val result=divide(4,2)
val funkcjaBiznesowa=(x:Int)=>x+1
result.fold(throw _, funkcjaBiznesowa)          //> res2: Int = 3

No i tutaj chyba każdy powinien zajażyć, że funkcjaBiznesowa nie jest sponiewierana przez wyjątki co ułatwia testowanie - z drugiej strony pewnie znowu ludzie będą śmiać się z podkreślinika throw _

No to robimy formularz

@(mealForm:Form[Meal])
<html>
<head>
<title>Formularz</title>
</head>
<body>

@import helper._

@form(action=controllers.routes.Application.submitForma){
@inputText(mealForm("name"),'id->"name")
@inputText(mealForm("calories"))
<input type="submit">
}

</body>
</html>

I zmiana w routach oraz kontrolerze :

//routes
POST   /submitForma    controllers.Application.submit()

//Application
def form = Action {
    Ok(views.html.formularz(mealForm))
  }
  
  def submitForma=TODO

Czas na obsługę POSTA - tutaj wąłśnie przydaje się wiedza z ćwiczenia "Either".

def submitForma = Action { implicit request =>
    mealForm.bindFromRequest.fold(
      badForm => BadRequest(views.html.formularz(badForm)),
      _ => Ok("mealOK")
    )

  }

Na razie bez styli wygląda to koślawo ale na prostszych przykładach łatwiej się uczyć.

Dokladniejsza walidacja

Na razie walidacja jest uboga dlatego dodamy sobie kilka parametrów aby ograniczyć to co można wprowadzić i do tego wrzucimy trzecie pole opcjonalne aby pokazać jak ładnie wszystko nam się skomponuje. Warto dodać, że wszystkie validacje są w obiekcie Forms

lazy val mealMapping = mapping(
    "name" -> nonEmptyText(3, 10),
    "calories" -> number(1000, 8000),
    "description" -> optional(text)
  )(Meal.apply)(Meal.unapply)


case class Meal(name: String, calories: Int,description:Option[String])

I w ten czas jest dobry moment aby uaktualnić testy o sprawdzenie obsługi pełnego żądania http.

//routes
POST   /submitForma    controllers.Application.submitForma()

//testy
"submit user form successfully" in new WithApplication{
     val formRequest=FakeRequest(POST,"/submitForma").withFormUrlEncodedBody("name"->"hamburger","calories"->"5000")
     val result=route(formRequest).get
     status(result) must equalTo(OK)
    }
    
    "return bad request when form data is missing" in new WithApplication{
      val formRequest=FakeRequest(POST,"/submitForma").withFormUrlEncodedBody("name"->"hamburger")
      val result=route(formRequest).get
      status(result) must equalTo(BAD_REQUEST)
    }

No i technicznie nawet nie musieliśmy odpalać formularza aby wiedzieć, że wsio działa :D. Teraz czas na jeszcze jeden patent - globalna walidacja!

//Application.scala
lazy val mealMapping = mapping(
    "name" -> nonEmptyText(3, 10),
    "calories" -> number(1000, 8000),
    "description" -> optional(text)
  )(Meal.apply)(Meal.unapply) verifying("zle dane tak ogolnie", mealData=>mealData.calories>2000 && mealData.name!="Warzywo")


//test
 "bind data to user form with global error" in {
     val data=Map("name"->"Warzywo","calories"->"5000")
     val mealData=Application.mealForm.bind(data)
     mealData.errors must have(_.message=="zle dane tak ogolnie")
    }

//html
@if(mealForm.hasGlobalErrors) {
<ul>
@for(error<-mealForm.globalErrors){
<li>@error.message</li>
}
</ul>
}

I można też verifying pojedyncze pola -

"name"->text(3, 10).verifying("can not be Warzywo", _!="Warzywo")

No i pewnie padnie pytanie jak te napisy pod polami pozmieniać. To można na szybko tak :

@inputText(mealForm("calories"),'_showConstraints->false,'_help->"moj koment")

Ale zapewne to nie wystarczy i czas przejść do definiowania własnych komunikatów.

Custom messages

I teraz jest taka opcja - odpowiednie komentarze muszą pojawić się w pliku conf/messages, którego jeszcze nie ma. Wszystkie możliwe komunikaty zaś znajdziemy tam gdzie rozpakowaliśmy archiwum playa - $PLAY_HOME/framework/src/play/src/main/resources/messages

Co by ni szukać poniżej pełna lista - za długa to ona nie jest

# Default messages

# --- Constraints
constraint.required=Required
constraint.min=Minimum value: {0}
constraint.max=Maximum value: {0}
constraint.minLength=Minimum length: {0}
constraint.maxLength=Maximum length: {0}
constraint.email=Email

# --- Formats
format.date=Date (''{0}'')
format.numeric=Numeric
format.real=Real

# --- Errors
error.invalid=Invalid value
error.invalid.java.util.Date=Invalid date value
error.required=This field is required
error.number=Numeric value expected
error.real=Real number value expected
error.real.precision=Real number value with no more than {0} digit(s) including {1} decimal(s) expected
error.min=Must be greater or equal to {0}
error.min.strict=Must be strictly greater than {0}
error.max=Must be less or equal to {0}
error.max.strict=Must be strictly less than {0}
error.minLength=Minimum length is {0}
error.maxLength=Maximum length is {0}
error.email=Valid email required
error.pattern=Must satisfy {0}

error.expected.date=Date value expected
error.expected.date.isoformat=Iso date value expected
error.expected.jodadate.format=Joda date value expected
error.expected.jsarray=Array value expected
error.expected.jsboolean=Boolean value expected
error.expected.jsnumber=Number value expected
error.expected.jsobject=Object value expected
error.expected.jsstring=String value expected
error.expected.jsnumberorjsstring=String or number expected
error.expected.keypathnode=Node value expected

error.path.empty=Empty path
error.path.missing=Missing path
error.path.result.multiple=Multiple results for the given path

Walnijmy sobie takie zestawik jak poniżej i zerknijmy co wyjdzie.

#forms
constraint.required=wymagane
format.numeric=musi być numerem

custom template

Czas by bliżej przyjrzeć temu co jest generowane przez te funkcje "@inputText" - ...i to zrobimy na zajęciach a nie tutaj. W tym miejscu po prostu poznamy patent na zmianę domyślnej struktury html jeśli nam nie pasuje.

// tworzymy template playowy z htmlem
@* myFormField Template File *@
@(elements: views.html.helper.FieldElements)

<div class="formGroupMoje">
<label for="@elements.id"></label>
<div class="formInputMoje">
@elements.input
</div>
</div> //formatter pola z wykorzystaniem szablonu object MyFormConstructor { import views.html.helper.FieldConstructor implicit val myFields = FieldConstructor(myFormField.f) } // jest implicit to sam import w stronie forma wystarczy @import helper._ @import views.MyFormConstructor._

I od razu wygląd forma się zmienił. Czy na lepsze? Każda zmiana jest na lepsze poza zmianami na gorsze.

upload pliku

To ćwiczenie jest w całości zerżnięte z książki "Play for Scala developers". Wygodnie jest zadeklarować najpierw
//Application
def fileUpload=TODO
//routes
POST   /uploadFile     controllers.Application.fileUpload()
bo wtedy kompilator nie pluje się przy używaniu backward routing.
<form action="@routes.Application.fileUpload" method="post" enctype="multipart/form-data">
<input type="file" name="image">
<input type="submit">
</form>
I tutaj ważna i ciekawa rzecz - po raz pierwszy uczestnicy kursu spotykają się z parserami requestów - co będzie tematyką trzecich albo czwartych zajęć.
  def pokazPlikForm = Action {
    Ok(html.fileForm())
  }

  def fileUpload = Action(parse.multipartFormData) { request =>
    request.body.file("image").map { file =>
      file.ref.moveTo(new File("/tmp/image"),true)
      Ok(s"retrieved file ${file.filename}")
    }.getOrElse(BadRequest("file mising."))
  }

No i to tyle

No i to tyle