I've been using Ruby on Rails framework for four year now, and I've seen how it changed during that time. Play 2.0 is on its way to become really nice piece of software. I read play wiki and have some thoughts about it in contrast to Ruby on Rails.
I'm not saying that play sucks because it's not rails, and that it should copy everything from it (please don't!). All I want to share is that some concepts were already proven by many people over the years, and there is no point in going through that again.
So here are my thoughts:
Rails conventions are havily based on RESTful resources. While play routes are similar to Rails' there are few differencies:
- rails uses dsl written in ruby, play has custom file format (why not scala dsl?)
- play allows to define one route at a time, like: METHOD PATH ACTION while rails has some helpers that simplfy common cases
Rails routing syntax basics:
# Simplest match, /users/1 will route to users_controller.show with param :id = 1
match "/users/:id" => "users#show"
# Instead of 'match', you can specify http method
get "/foo" => "foo#get_bar"
post "/foo" => "foo#post_bar"
# etc...
Resource routes:
resources :users
will generate 7 routes:
GET /users UsersController#index
GET /users/:id UsersController#show
POST /users UsersController#create
PUT /users/:id UsersController#update
DELETE /users/:id UsersController#destroy
GET /users/new UsersController#new
GET /users/:id/edit UsersController#edit
time has shown that people were in fact writing all those routes by themself, but with different names and paths. Having one standard way of creating CRUD controller not only cleans up code, but also makes it easier to maintain. Every Rails programmer knows this convention and sticks with it, so there are no surprises when looking at other people's code. Of course, this is not a silver bullet and it is impossible to build whole application with routes like above.
resources :users do collection do get :active end
member do
get :profile
end
end
leads to:
GET /users/active UsersController#active
GET /users/:id/profile UsersController#profile
Other very useful feature of rails routing is namespacing. Consider this example:
namespace :api do
resources :users
end
This will create 7 routes as described before, but prefixed with "/api/" and pointing to Api::UsersController. Again, simple convention that saves few keystrokes and increases readability. ('namespace' feature would probably be easier to implement using scala dsl).
Resources can also be nested:
resources :users do
resources :comments
end
Will create:
GET /users/:user_id/comments CommentController#index
GET /users/:user_id/comments/:id CommentController#show
etc...
(from my experience, this is really handy)
Other interesting feature of rails routes are optional parameters:
match "/posts(/:id)" => "posts#show"
This will match both /posts and /posts/5 paths. From play perspective, this could be simply handled with Option[T] function argument.
Other usage of optional parameters:
match "/users/:id(.:format)" => "users#show"
In Rails, :format default to "html", and is havily used when creating responses (I'll talk about that later). This route will match e.g. /users/1 (format=html), /users/1.json (format=json), /users/1.xml (format=xml) etc.
Btw, why not make routing staticly typed?
If there is a function def show(id: Int)
it would be only valid if :id parameter was Int (early validation of incomming requests)
Rails routing is simple yet powerfull, even allowing routes like
match "/some_legacy_route" => redirect("/new_route")
Every modern ruby web framework is based on rack - small alyer that is interface between various servers and web frameworks. It introduces to the ruby world concept of middlewares and mountable engines - mulitple applications running at different path, sharing common data.
Rails takes care of incommint request body. By default it will parse it as form data, but if request have Content-Type header it will try to parse json or xml. On the user side, there is only one nested hash - params
and one action can work with all kinds of input data without having to explicitly define type of request body.
Play has hardcoded own templating engine, that may be good for java developers, but surely is "from 2006" for ruby community. At the beginning, rails used ERB: <div><%= some_ruby %></div>
. It worked, but wasn't good enough. Nowedays, after few years of trying different approaches HAML is happen to be the choice of most rails programmers. What's most important, rails itself doesn't really care what do you use. And you can you both erb and haml in one application (or other templating launguage). Rails simply just look at the filename, and when it finds something like "index.html.erb" it checks if there is registered "erb" processor and creates "index.html" file using that processor. Same think with "index.html.haml" or "index.html.your_processor". Again, simple convention that simplifies a lot. You could even have "index.xml.builder" - processed with xml builder library.
This also applies to assets, but this will be next point of my message.
Rendering
in rails there are several ways of creating response:
render "template_name" # well, render the template
render :json => {:foo => 1} # render some json, sets correct Content-Type
render :xml => {:bar => 2} # render some xml, sets correct Content-Type
One magical method in rails is respond_to
def index
posts = get_posts_from_database
respond_with(posts)
end
this one takes care of :format request parameter and will render html, json, xml or any other registered format - internally rails calls "to_FORMAT" method on provided object, but I think that could be resolved with proper typeclasses
I've noticed that play 2.0 has hardcoded coffeescript and lesscsss support. This is similar case as with rendering templates. Rails uses the same convention: "myapp.js.coffee" -> "myapp.js", "style.css.sass" -> "style.css". Exactly the same behavior - register processor and that's it.
Rails 3.1 introduced assets pipeline, that could be simply described as:
- process all js/css files (with coffee, sass, less, etc)
- put it in one big application.js/css file
- compress!
- attach timestamp to result
In development mode, you could see tens on <script src="...">
tags, but in production environment you will see only one with application-TIMESTAMP_HASH.js - minified (optionally gzipped).
Easy, easy to server, easy to cache.
(It also allows serving assets from 3rd party plugins)