Play á la Rails

Play á la Rails

My attempts to make my experience developing Play 2 web-applications (in Scala) more Rails-like.

I show 3 frameworks employed that draw nearer to the Ruby/Rails spirit than Play's default offerings.

7f950a34b032ab9d6eaa4530a37943be?s=128

Sebastian Nozzi

August 27, 2013
Tweet

Transcript

  1. à la Sebastian Nozzi

  2. Background • Working with Java since 2001 • Flirting with

    Smalltalk all these years • Involved with Rails since September 2012 • Hooked with Scala since January 2013 • Developing with Play since June 2013
  3. Ruby Let’s talk about

  4. None
  5. “A dynamic, open source programming language with a focus on

    simplicity and productivity. It has an elegant syntax that is natural to read and easy to write.”
  6. Let’s talk about

  7. None
  8. “Ruby on Rails is an open- source web framework that’s

    optimized for programmer happiness and sustainable productivity. It lets you write beautiful code by favoring convention over configuration.”
  9. How Ruby is (self) perceived

  10. • Heavily inspired from Rails • Fun. Productive. Save +

    Reload. • Convention over Configuration. • Routing. REST. MVC... • ... but I missed some things ...
  11. ActiveRecord db:migrate Cucumber + Capybara Anorm? Slick? Evolutions? Specs? Rails’

    vs. Play’s default offerings
  12. not “railsy” enough

  13. ActiveRecord db:migrate Cucumber + Capybara ActiveRecord (for Scala) Flyway Cucumber-JVM

    + Fluentlenium More “Rails-like” alternatives
  14. None
  15. case class User(var username: String) extends ActiveRecord { lazy val

    posts = hasMany[Post] } case class Post(var text: String) extends ActiveRecord with Timestamps { var userId: Long = _ lazy val user = belongsTo[User] } Declaring Entities
  16. object Tables extends ActiveRecordTables { val users = table[User]("users") val

    posts = table[Post]("posts") } object User extends ActiveRecordCompanion[User] object Post extends ActiveRecordCompanion[Post] Declaring the Schema
  17. val newUser = User(username=“Homer”).create() val users: List[User] = User.toList User.findBy(“username”,

    “Homer”).foreach { user => val posts = user.posts.orderBy(_.createdAt desc).toList ... ... user.posts << Post(“Ohhh donuts!”) } Basic Operations
  18. None
  19. Play Integration

  20. object Global extends GlobalSettings { override def onStart(app: Application) {

    if(!Play.isTest) { val flyway = new Flyway() // .. get values from Play’s config ... flyway.setDataSource(url, user, password) flyway.setInitOnMigrate(true) flyway.migrate() } Tables.initialize(...) // ActiveRecord } } Triggering the Migrations
  21. object Global extends GlobalSettings { override def onStart(app: Application) {

    if(!Play.isTest) { val flyway = new Flyway() // .. get values from Play’s config ... flyway.setDataSource(url, user, password) flyway.setInitOnMigrate(true) flyway.migrate() } Tables.initialize(...) // ActiveRecord } } Initializing ActiveRecord
  22. SQL-based Migrations

  23. package db.migration class V1_03__CreateSomePosts extends JdbcMigration { override def migrate(ignoredConnection:

    Connection) { ... ... ... ... ... } } Code-based Migrations
  24. package db.migration class V1_03__CreateSomePosts extends JdbcMigration { override def migrate(ignoredConnection:

    Connection) { User.findBy("username", "Homer").foreach { homer => homer.posts << Post("I'm hungry") homer.posts << Post("I should go to Moe's") homer.posts << Post("Or order some Pizza") } } } Code-based Migrations
  25. + Fluentlenium

  26. • Write in plain English • Separation of specification /

    implementation
  27. Feature: Posting status updates The goal of the system is

    keep co-workers informed by posting status updates. Background: Given that user "manager" exists And that user "manager" posted | first day at work | | meeting people | | working like crazy | Scenario: Posts are ordered chronologically (newest on top) When I go to the posts page of user "manager" Then the post nr. 1 should contain "working" And the post nr. 2 should contain "meeting" And the post nr. 3 should contain "first"
  28. When("""^I type "([^"]*)" in the "([^"]*)" field$""") { (text: String,

    fieldName: String) => ... } And("""^press "([^"]*)"$""") { (buttonLabel: String) => ... ... } Then("""^I should be on the posts page of "([^"]*)"$""") { (username: String) => ... ... ... } Step Declarations
  29. • Part of Play2 • DSL wrapping Selenium • PhantomJS

    for headless testing Fluentlenium
  30. When("""^I type "([^"]*)" in the "([^"]*)" field$""") { (text: String,

    fieldName: String) => ... } And("""^press "([^"]*)"$""") { (buttonLabel: String) => ... ... } Then("""^I should be on the posts page of "([^"]*)"$""") { (username: String) => ... ... ... }
  31. When("""^I type "([^"]*)" in the "([^"]*)" field$""") { (text: String,

    fieldName: String) => browser.fill("*", withName(fieldName)).`with`(text) } And("""^press "([^"]*)"$""") { (buttonLabel: String) => val button = browser.find("button", withText(buttonLabel)) button.click() } Then("""^I should be on the posts page of "([^"]*)"$""") { (username: String) => val user = User.findBy("username", username).get val expectedUrl = controllers.routes.UserActions.posts(user.id).url driver.getCurrentUrl() should endWith(expectedUrl) } Step Implementations
  32. Conclusions

  33. Conclusions • Satisfied with my choices (so far) • Integration

    was doable • Play flexible enough • to replace some parts • to let different libraries co-exist • Reached a more Rails-like experience
  34. Some Thoughts

  35. Some Thoughts • Programming can (and should!) be fun •

    Scala embraces some of that “fun” • ... but we can do more • Go check Ruby / Rails • Let’s steal get inspired from them ;-)
  36. Thank you