View
 

Todos 06-Building with Rails 3

Page history last edited by Valeriano Manassero 14 years, 5 months ago

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.