Play á la Rails

1,122 views

Published on

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.

Published in: Technology
2 Comments
3 Likes
Statistics
Notes
No Downloads
Views
Total views
1,122
On SlideShare
0
From Embeds
0
Number of Embeds
14
Actions
Shares
0
Downloads
6
Comments
2
Likes
3
Embeds 0
No embeds

No notes for slide

Play á la Rails

  1. 1. à la Sebastian Nozzi
  2. 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. 3. Ruby Let’s talk about
  4. 4. “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.”
  5. 5. Let’s talk about
  6. 6. “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.”
  7. 7. How Ruby is (self) perceived
  8. 8. • Heavily inspired from Rails • Fun. Productive. Save + Reload. • Convention over Configuration. • Routing. REST. MVC... • ... but I missed some things ...
  9. 9. ActiveRecord db:migrate Cucumber + Capybara Anorm? Slick? Evolutions? Specs? Rails’ vs. Play’s default offerings
  10. 10. not “railsy” enough
  11. 11. ActiveRecord db:migrate Cucumber + Capybara ActiveRecord (for Scala) Flyway Cucumber-JVM + Fluentlenium More “Rails-like” alternatives
  12. 12. 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
  13. 13. 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
  14. 14. 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
  15. 15. Play Integration
  16. 16. 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
  17. 17. 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
  18. 18. SQL-based Migrations
  19. 19. package db.migration class V1_03__CreateSomePosts extends JdbcMigration { override def migrate(ignoredConnection: Connection) { ... ... ... ... ... } } Code-based Migrations
  20. 20. 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
  21. 21. + Fluentlenium
  22. 22. • Write in plain English • Separation of specification / implementation
  23. 23. 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"
  24. 24. 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
  25. 25. • Part of Play2 • DSL wrapping Selenium • PhantomJS for headless testing Fluentlenium
  26. 26. 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) => ... ... ... }
  27. 27. 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
  28. 28. Conclusions
  29. 29. 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
  30. 30. Some Thoughts
  31. 31. 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 ;-)
  32. 32. Thank you

×