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. 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
  2. 4.
  3. 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.”
  4. 7.
  5. 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.”
  6. 10.

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

    Reload. • Convention over Configuration. • Routing. REST. MVC... • ... but I missed some things ...
  7. 14.
  8. 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
  9. 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
  10. 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
  11. 18.
  12. 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
  13. 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
  14. 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
  15. 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"
  16. 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
  17. 29.
  18. 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) => ... ... ... }
  19. 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
  20. 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
  21. 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 ;-)
  22. 36.