Sunday, March 04, 2007

Rails 1.2 changes

Formats and respond_to

In earlier versions of rails you could have your actions behave differently depending on what content type the web browser was expecting – eg:

respond_to do |format|

    format.xml{ render :xml=>image.to_xml }

    format.jpg{ self.image }

end

However to make this work you needed to set the HTTP Accept header in the HTTP web request. This is hard to do outside of tests. A new default route has now been added

map.connect ':controller/:action/:id.:format'

The additional format parameter lets you override the format so you can now load people/12345.xml or image/12345.jpg in your web browser to test what happens instead of mucking about with HTTP headers.

Note you still have to register MIME types for the formats you need – for format.jpg I had to put

Mime::Type.register 'image/jpeg', :jpg

In my environment.rb, as jpg is not noticed by default

Named Routes

map.index '/', :controller=>'home', :action=>'index'

map.home '/:action/:id', :controller=>'home'

These create a bunch of helper methods which you can use anywhere you'd supply a URL or parameters for a redirect – eg:

def first_action

    redirect_to index_url # redirects to /

end



def second_action

    redirect_to home_url( :action=>'second' ) # redirects to /second

    # which is the 'home' controller.

end





<%= link_to 'home', index_url %>

<%= link_to 'test', home_path( :action=>'test' ) %>

 

The difference between foo_url and foo_path is that foo_url gives the entire url eg: http://www.site.com/people/12345 whereas foo_path just gives /people/12345

Gives your code lots more meaning and makes it shorter. Definite win for commonly used things.

Resources

CRUD means Create, Read, Update, Delete.

These map to the four HTTP methods – POST, GET, PUT, DELETE.

HTTP methods let you have shortcuts, so instead of /people/create you can just do an HTTP POST to /people. Also /people/show/1 maps to GET /people/1, etc etc

Routes are created differently – for the above it is

map.resources :people.

Run script/generate scaffold_resource people to have a look

NOTE: Rails expects resources in both the routes.rb and controller names to be named in plural - eg:

www.example.com/people/1 instead of www.example.com/people/1

Philosophy

Basically they are encouraging you to write your controllers and app so that everything revolves around either a create, read, update, or delete of some resource.

Contrived Example: User Login sessions:

Old way – Revolves around action:

User Logs in – post a form to /users/login – this sticks a 'Login Token' of some sort in the session to identify them.

User does stuff – look up the session and link it back – might put an is_logged_in? method on your user model or something.

User Logs out – posts a form to /users/logout – this removes thing from the session.

New way – Revolves around resources

Identify what the 'resource' is – in this case it's the Login Token.

User Logs in – Create a LoginToken by POSTing a form to /LoginTokens – stick it's id in the session or something

User does stuff – Find the correct LoginToken based on it's id, check it's valid etc.

User Logs out – Delete the LoginToken by DELETEing /LoginTokens/1

Conflict of interest?

This LoginToken is behaving a lot like a model even though it's a controller. In fact you should create a model for it. The LoginTokensController should only be a lightweight wrapper around this model. This is a definite win if you can structure your app like this because it seperates the different areas of code out.

In the old way we had the users controller handling login, logout, and whatever else it needed to do – probably about half a dozen other unrelated things. This gets you messy code which is hard to understand/modify. By moving each part out to its own controller we end up with several separate nice clean controllers instead of one big messy one – easier to maintain and to see who's responsible for what. Very important!

REST API's

Many blog entries talk about how you can get an externally accessible API 'for free' by extending these CRUD controllers. The standard example is something like:

Now that your users are accessible via GET,POST,etc to /users/1, we can extend that controller using respond_to so that you can also query it for an XML or JSON representation of the user – this can then be used by other websites/apps for free! Hooray!

This is nice in theory but not so nice in practice. Why?

If you are in a webapp, doing a POST to create a new LoginSession will result in a redirect_to home_url or something like that. However for an external API, you're meant to return an http response code of 201 – Created to indicate the create was successful. Trying to jam these 2 things into the same controller is a mess.

This does not mean the REST idea is bad, only that you need to think a bit more.

If you've done the right thing and created a LoginSession model, then you can just create 2 lightweight controllers – one which fits into your web app, and another if you like which processes XML/JSON.

You still get the major benefit which is that by thinking of stuff as resources, you get a much better design/structure of your app.

ActiveResource

If you have an external XML REST api, these resources end up looking a lot like some data that you might want to load, update, store, etc, like a kind of remote database.

They therefore decided to make something called ActiveResource which would do for XML REST resources what ActiveRecord does for databases (in a limited fashion)

For Example:

class RemotePerson < ActiveResource::Base

    set_site http://localhost/rest_people ## the base URI

    set_credentials :username => "some_user", :password => "abcdefg" ## for HTTP basic authentication

    set_object_name 'person' ## the other end will expect data called 'person' not 'RemotePerson'

end

You can then do

RemotePerson.find( 1 )

This will fire an HTTP request at http://localhost/rest_people/1. It will load the resulting XML and convert it into an object. You can change its data, and call save, etc like you would with a piece of data from the database. When you have 2 sites that need to communicate with each other, this makes it a WHOLE lot easier

The bad news – This isn't in rails 1.2 They pulled it out in one of the beta versions and there's no indication as to when it's coming back

The good news – I wrote one (a limited version thereof anyway) to replace what didn't ship with rails.

to_xml, from_xml, to_json, etc

For all these active resource things to work, they need an easy way to convert data to and from XML so it can be sent over the HTTP request. There are now new methods - to_xml, from_xml, to_json, and other stuff like that which will convert the object to and from xml. These have been added to Hash, ActiveRecord, and other things like that.

Multibyte Support

Rails 1.2 hacks the string class so it is now sort of Unicode (UTF-8) aware.

TextHelper#truncate/excerpt and String#at/from/to/first/last will now automatically work with Unicode strings, but for everything else you need to call .chars or you will break the string.

In other words, If you need to deal with foreign characters (and for the US we probably will) String.length is broken and so is string[x..y] or just about anything else you'd want to do! Beware!

I have no idea how this is going to impact storing strings in mysql etc.

Tons of other bits and pieces

Lots of things have now been deprecated doing stuff like referencing @params instead of params etc etc – these all get dumped to the logs as deprecation warnings – they are still ok now but will break in rails 2.0

image_tag without a file extension is also deprecated. You should always specify one.

 

See here:

http://www.railtie.net/articles/2006/09/05/rush-to-rails-1-2-adds-tons-of-new-features