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:
### Routing
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.
### Request parameters
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.
### Views
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
### Assets
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)
### EOF // posted here:
https://gist.github.com/1791857