Monday, May 26, 2008

More Rails 2.1 changes that target JSON and therefore Ext JS

Two days ago I posted about Rails 2.1 changes that make living with Ext JS easier. Today I discovered (via Jörg Battermann) a tutorial about using Rails 2.1 (see Part 1 and Part 2).
The tutorial shows two new config options that target JSON, especially the to_json method: ActiveRecord::Base.include_root_in_json and ActiveSupport.use_standard_json_time_format

Here are the respective changesets and some more information on these changes:

Changeset 9202: Tweak ActiveRecord::Base#to_json to include a root value in the returned hash: {post: {title: ...}}

This is almost a nice change. I mean almost because it might help you when feeding your Ext JS forms via a Ext.data.JsonReader, but it'll break your Ext JS grids, when using the Ext.data.JsonReader.

Currently, you do something like the following in your javascript code:

orders_ds = new Ext.data.Store({
reader: new Ext.data.JsonReader({root: 'orders', id: 'id' }, orders_items),
proxy: new Ext.data.HttpProxy({url: orders_path_json})
});

and the following in your controller action:

render :json => { :orders => @orders }

which returns a JSON data in the following format

{"orders": [{"id": 1, ...}, {"id": 1, ...}]}

Well, after you turn on the ActiveRecord::Base.include_root_in_json option, it'll return:

{"orders": [{"order": {"id": 1, ...}}, {"order": {"id": 2, ...}}]}

which cannot be understood by your Ext.data.JsonReader by default.

It would be smarter if the include_root_in_json would differ between a single active record object and a collection of active record objects.
Therefore, if you use render :json => @orders or @orders.to_json, it would return the data in an array without a root for each object, but with the plural name of the model class name as root for the array. Just as I showed in the first JSON data example.
Well, maybe I'll write a patch for Rails for that.

Changeset 9203: Add config.active_support.use_standard_json_time_format setting so that Times and Dates export to ISO 8601 dates.

If you turn on this config option, it will probably also break your Ext JS grids. But you can easily fix that...

Right now you probably have this code in your record layout part when dealing with dates:

{ name: 'release_date', type: 'date', dateFormat: 'Y/m/d' }

Well, your JsonReader assumes that the date format is Y/m/d. But after you turned the use_standard_json_time_format option on, it will return the date in the default JSON date format.
It means you have to change the date format to the following:

{ name: 'release_date', type: 'date', dateFormat: 'Y-m-d' }


Well, these Rails changes are all JSON related changes that come in the soon to be releases Rails 2.1, but which won't really make your life easier with Ext JS. Sorry to disappoint you on that. But Rails is certainly going into the right direction.

13 comments:

Eneko said...

nice information and nice blog. I have just discovered it but it will automatically go to favorites.

Keep going!

Rails+Ext rocks!

Steffen Hiller said...

Thanks for your positive feedback, eneko! Yes, Rails and Ext rock. ;-)

Anonymous said...

I just started with Rails 2.1 and Ext.. I'm disappointed :(

however I get different results about the data "root", with false no root, with true every element have the root, but no "super plural root".

ActiveRecord::Base.include_root_in_json = false
[{"updated_at": "2008-06-17T23:05:15Z", "language": null, "body": "test", "id": 1, "created_at": "2008-06-17T23
:05:15Z"}, {"updated_at": "2008-06-17T23:13:07Z", "language": null, "body": "test2", "id": 2, "created_at"
: "2008-06-17T23:13:07Z"}]

--
ActiveRecord::Base.include_root_in_json = true
[{"medical_record": {"updated_at": "2008-06-17T23:05:15Z", "language": null, "body": "test", "id": 1
, "created_at": "2008-06-17T23:05:15Z"}}, {"medical_record": {"updated_at": "2008-06-17T23:13:07Z", "language"
: null, "body": "test2", "id": 2, "created_at": "2008-06-17T23:13:07Z"}}]

so actually Rails 2.1 and Ext grids cannot work together?

Steffen Hiller said...

Kain, I was disappointed by this change as you were.
As I said in the blog post, turning ActiveRecord::Base.include_root_in_json on is not helpful. It's good that it is turned off by default, though.
I converted a Rails 2.0.2 project just last week (to use the named_scope feature) by only changing the gem version in the environment.rb file, and so far everything including my ext js still seems to work as before. So at least 2.1 isn't breaking anything.
The only nice change in Rails 2.1 was the first one I mention here in this post: Rails 2.1 changes that make living with Ext JS easier. Though, the json_request plugin is still working fine. But the Rails solution is definitely more elegant.
To answer your question, yes Rails 2.1 and Ext JS work together, just as they did before.

kain said...

hi Steffen,
it's strange, I found out that creating a new 2.1 rails app uses the new defaults to true (check initializers/new_rails_defaults.rb) and must be turned off to false.

at this point I had to manually modify the way json data is returned by default (with settings to false) and create a new mimetype (just ext scaffold plugin does) to work with ext grids.

I'm still a bit confused as what you wrote in this blog post and comments differ enormously from the experience I had, starting with the json data structure returned by default in rails.

I would like to ask you a favor, when you have some free time, can you create a vanilla rails 2.1 (or better edge) app, a model, implement a very basic ext grid and document the effort? I think will be very, very helpful to people trying to understand what's going on.

thanks.

kain said...

mm.. I think I got it, set new options to false AND customize render :json options :)

Steffen Hiller said...

Hey kain,
you're right, my last comment was a little confusing and Rails 2.1 really turns the new "root feature" on by default IF you create a new rails app. I didn't create a new rails app with "rails appname". I only updated an existing rails app. That's why I didn't have the new_rails_defaults.rb file which sets the root feature to true and is only created when you run the "rails appname" command.
So your conclusion "set new options to false AND customize render :json options" is absolutely the way to go. I created a ext js rails plugin in my project which I'm gonna release some time in the future and which is customizing the render :json method for you to work with ext js.
Cheers,
Steffen

Anonymous said...

Good article!
Now I start a project by Rails2.1 & Extjs2.1
But I have a problem.
I want to load a static JSON file
such as "tree.json"

TreeLoader({dataUrl : 'get_nodes.json'})

It can work with PHP and by set MIME it also can work with ASP
But by use webrick, have a http 405 error.
How can I do it with Rails?

Thanks

Steffen Hiller said...

NY, look in your console where you opened the webrick from, it should tail your development log and which should tell you a more specific error message than just error 405...
I hope that helps,
Steffen

Anonymous said...

In console of my local webrick
"POST /get_nodes.json HTTP/1.1" 404 622

http://localhost:3000/main.html -> /get_nodes.json

And when I upload my project to a free host (in firebug console,it's nginx) and get 405

I put all html,javascriprt and json files in the rails public folder,is it wrong?

Can you give me a example by use treeLoader with Rails?
I need to load static json file,
It will be faster than load from database

Steffen Hiller said...

NY, well, you should look in your railsapp/log/production.log file.
It should say something like ActionController::RoutingError (No route matches "/get_nodes.json" with {:method=>:post}):
Well, the best solution would be to try TreeLoader({dataUrl : 'get_nodes.json', requestMethod : 'GET'}).
Note that the requestMethod will be GET.
I think that should help,
Steffen

Anonymous said...

Thank you very much~
You are so nice
I add GET then get it!
Thank u

Anonymous said...

A year later, I am using Rails 2.3.5 and your article has helped me remove the "order": thing from each object.

{"orders": [{"order": {"id": 1, ...}}, {"order": {"id": 2, ...}}]}

Perhaps

ActiveRecord::Base.include_root_in_json

is set to true by default in my Rails, but I didn't do it.

Many thanks, I am not sure I would have found out about this apart from your article.