About Rails 3.0
From its site: "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."
Version 3 of this framework is available now as stable production release.
Installation
Make sure you have installed Rails 3.0 or above and Bundler gem
$ sudo gem install rails --version 3.0.0
$ sudo gem install bundler
Create the project
A small rails application will serve as a backend for the Todos application.
Create a new Todos app by entering the following command into the console:
$ rails new todos
$ cd todos
Make sure that you do issue this command outside your SproutCore application folder!
While inside Rails Project folder, use Bundler to load correct gems for your Rails Project
WARNING: do not use the command bundle as superuser or in conjunction with sudo
$ bundle install
Setup the data model
We can use Rails generators to generate a basic RESTful interface for the Task model by typing
$ rails generate scaffold Task description:string isDone:boolean order:integer
Now you need to update your database to conform the model. For that, type
$ rake db:migrate
Writing the controller (Implementing CRUD operations)
The following sections show how to implement the CRUD (create, read, update, delete) operations for our Task model using respond_to <format> and respond_with function.
Open the file app/controllers/tasks_controllers.rb, and modify the code to obtain this:
class TasksController < ApplicationController
respond_to :json
def index
respond_with(@tasks = Task.all)
end
def show
respond_with(@task = Task.find(params[:id]))
end
def create
respond_with(@task = Task.create(:description => params[:description],
:isDone => params[:isDone],
:order => params[:order]))
end
def update
@task = Task.find(params[:id])
@task.description = params[:description]
@task.isDone = params[:isDone]
@task.order = params[:order]
@task.save
respond_with(@task)
end
def destroy
@task = Task.find(params[:id])
@task.destroy
render(:nothing => true, :status => :ok)
end
end
Fill the database
To fill your database, you can use the "seed" file. Open db/seeds.rb and add Tasks in this way:
Task.create(:description => 'This is the first task', :isDone => true, :order => 1)
Task.create(:description => 'This is the second task', :isDone => false, :order => 2)
Task.create(:description => 'This is the third task', :isDone => true, :order => 3)
After saving the file, you can populate the database with:
$ rake db:seed
Now your database is ready!
Forgery Protection
Before starting the application, we also need to disable Forgery Protection since the backend is only a stateless webservice and we need to avoid the InvalidAuthenticityToken error.
So, in ApplicationController we need to remove the line:
protect_from_forgery
Now, the entire backend is ready to start!
Starting Server
OK, the Rails application is ready to start, use the "server" command from the Rails app directory:
$ rails server
To test your server now, open the following address in your web browser: http://localhost:3000/tasks.json. You should receive a plain-text-file, with the datafields of your test data.
Sproutcore Todos Application
Sproutcore usually expects a JSON output that is not the output Ruby on Rails generates, so, there's need to manipulate data before process into Sproutcore Application.
We can create a new Class named TaskJSONProxy in this way:
Todos.TaskJSONProxy = SC.Object.create({
normalize_task_data: function(data) {
result = new Array();
if (data.length == undefined)
{
array_name = 'data.task';
eval(array_name).guid = eval(array_name).id;
result.push(eval(array_name));
}
else
{
for(var i=0; i<data.length; i++) {
array_name = 'data[i].task';
eval(array_name).guid = eval(array_name).id;
result.push(eval(array_name));
}
}
return result;
}
}) ;
Every time we get some JSON data in our SproutCore project into a Data Source we can process the body with this function (for example in fetch):
var storeKeys = store.loadRecords(Todos.Task,
Todos.TaskJSONProxy.normalize_task_data(response.get('body')));
Setup your proxy
Add the following line to the Buildfile file in your SproutCore project:
proxy "/tasks", :to => "localhost:3000"
You’re Done!
Continue to next step: Step 7: Hooking Up to the Backend »
Comments (8)
Arthur Park said
at 10:46 am on Nov 10, 2010
Uhh.. little help here. Where should I put TaskJSONProxy class?
Floris Huetink said
at 2:22 pm on Nov 10, 2010
Hi Arthur, I had this problem too, took me a while to figure out. I think a better solution is to define the following method inside ActiveRecord::Base or inside your Task model in Rails 3:
def as_json
self.attributes
end
the as_json method is used to convert a model to a JSON object. By overruling the defaults with the above, Rails simply returns the attributes as a JavaScript object and puts them in an array for you when you retrieve the index (the list of tasks). In the didFetchTask method in step 7 you can simply do
store.loadRecords(Todos.Task, response.get('body'));
(leaving out '.content' at the end) because the response body is only an array and nothing else. The TaskJSONProxy class is not needed anymore.
Arthur Park said
at 12:21 am on Nov 12, 2010
Thanks for the input. I couldn't try this until resolving proxy issue. I just needed to restart sc-server.. took me a while to realize that.
It looks like as_json will return 500 internal error. I changed it to to_json and it seems to be working, though I still need to adjust some json formatting. I probably mixed up your solution and the tutorial solution.
Floris Huetink said
at 1:34 am on Nov 12, 2010
After seeing your comment on my comment, I found out I misread my own code. In order to get as_json working in this tutorial, you should define it a little bit more explicit like this:
class Task < ActiveRecord::Base
(...)
def as_json(options = {})
ret = {
:guid => "/tasks/#{self.id}",
:id => self.id,
:description => self.description,
:isDone => self.isDone
}
end
end
You could probably leave out the "self." part for the attributes.
Some background: the as_json method is a newer feature in Rails than to_json, so you'll find less info on that when googling. They both work , but as far as i understand, the difference is this:
as_json: defined on model, you say "this is the way this model should represent itself whenever it is requested in JSON form". Returns a Ruby Hash-like object.
to_json: method to convert object into JSON. Returns a JSON formatted string.
So in your controller, each Task is first converted to its as_json representation and then converted into a JSON string. Overriding to_json means you take a shortcut: you skip as_json conversion and directly produce JSON strings.
Hope this helps!
Arthur Park said
at 9:15 am on Nov 12, 2010
Wow boom. It's working. You rock Floris!! Thanks.
Also, thanks for the insight on as_json. Good to know such thing.
shaun drong said
at 2:31 pm on Mar 23, 2011
Its seems like both solutions here kind of do a lot of workaround code to get the json output the current 1.4.x sproutcore expects ie json arrays without root class named containers. There is a configuration setting for ActiveRecord to turn this off site wide: ActiveRecord::Base.include_root_in_json = false (add to an new or existing initializer under the config folder).
You can then add the guid to your json output via a class method and a call to as_json super adding that method.
attr_accessor :guid
def guid
"/tasks/#{self.id}"
end
def as_json(options={})
super(:methods => :guid)
end
Unfortunately you still need to do a small hack to remove the guid param from data received by your update controller.
params[:<class>].delete(:guid)
if @<class>.update_attributes(params[:<class>])
This seems like a easier to maintain solution in the long run vs the sproutcore proxy or as_json override.
Eric Palmer said
at 11:03 am on Apr 1, 2011
The previous rails wiki page sets things up so you have an html-based interface, which I have found useful for generating data and debuging. If you try to access your data with browser, you are out of luck with how things are set up here.
However, if you change the respond_to line, you can get json, html and xml.
respond_to :json, :html, :xml
Eric Palmer said
at 8:18 pm on Jun 12, 2011
I followed the tutorials instructions. However, I put the Todos.TaskJSONProxy in the core.js file, after the creation of Todos.
And to be clear, in data_source/task.js, I replaced the line (about line #42)
store.loadRecords(Todos.Task, response.get('body').content);
with
var storeKeys = store.loadRecords(Todos.Task,
Todos.TaskJSONProxy.normalize_task_data(response.get('body')));
Note, I expect that people's data_source file would be called task_data_source.js, not tasks.js
You don't have permission to comment on this page.