Wednesday, April 29, 2009

ActiveRecord::Dirty

Another powerful feature of ActiveRecord is dirty objects. This module tracks unsaved attribute changes. This feature is available probably since March, 2008. See examples below:

A newly instantiated object is unchanged:

person = Person.find_by_name('uncle bob')
person.changed? # => false

Change the name:

person.name = 'Bob'
person.changed? # => true
person.name_changed? # => true
person.name_was # => 'uncle bob'
person.name_change # => ['uncle bob', 'Bob']
person.name = 'Bill'
person.name_change # => ['uncle bob', 'Bill']

Save the changes:

person.save
person.changed? # => false
person.name_changed? # => false

Assigning the same value leaves the attribute unchanged:

person.name = 'Bill'
person.name_changed? # => false
person.name_change # => nil

Which attributes have changed?

person.name = 'bob'
person.changed # => ['name']
person.changes # => { 'name' => ['Bill', 'bob'] }

Before modifying an attribute in-place:

person.name_will_change!
person.name << 'by'
person.name_change # => ['uncle bob', 'uncle bobby']

Tuesday, April 28, 2009

My Restful Rails Presentation

Last month, I did a presentation to my team about Restful Rails. I just uploaded to http://www.slideshare.net/cchamnap/rest-in-rails. It might be useful for newbie rails developer.

Saturday, April 11, 2009

Upload in Gears 0.5

Uploading in Gears is pretty easy since its HttpRequest object supports uploading imagery data. If you want that uploaded file could serve locally (in offline mode), you need to use LocalServer API. Moreover, it is feasible to upload in chunk for large files as well. It involves the use of Blob API as well as good handling on server side. I found two links that is able to achieve this:
http://snippets.dzone.com/posts/show/6175
http://uploadmovietool.appspot.com/. However, I just show a simple implementation both server side script (Rails) and client side script (JavaScript).

Let's start from select a file to upload with the help of Desktop API, openFiles(). You need to pass the callback, and some option attributes like filter and singleFile. After user has selected a file, the callback will be invoked with one parameter that is a File object of that file. Notice that, this file object has two properties: name and blob. Thus, you can use LocalServer API to capture this file by calling captureBlob() and passing blob object, a unique url for accessing this file, and a content type. If you don't provide a content-type, the browser doesn't know how to view this file when it has intercepted by LocalServer. Usually, it will launch a download of this file. Oop! Don't forget to create a localserver object. I assume you understand gears api good enough.

   var desktop = google.gears.factory.create('beta.desktop');
   desktop.openFiles(
      function(files) {
         var file = files[0];
         if(!file) return;

         //capture file for serving locally
         fileName = file.name;
         blobStore.captureBlob(file.blob, fileName, "image/JPEG");
      },
      { filter: ['.jpg'], singleFile: true }
   );


Next, you need to create a HttpRequest object. Then, you do a post to your server side script. You can set some request headers like: 'Content-Disposition', 'Content-Type', and 'Content-Range'. Gears provides an event handler called onprogress. When this event is triggered, you would get ProgressEvent object. This object has 3 properties: total, loaded, and lengthComputable. These 3 properties are useful in updating UI. Last, you need to pass the blob object to send() of HttpRequest object. You can do this by calling getAsBlob() and passing the url that previously called by captureBlob().

   function upload() {
      var xhrUpload = google.gears.factory.create("beta.httprequest");
      xhrUpload.open("POST", '/upload/files');
      xhrUpload.setRequestHeader('Content-Disposition', 'attachment; filename="' + fileName + '"');
      xhrUpload.setRequestHeader('Content-Type', 'image/JPEG');
      xhrUpload.setRequestHeader('Content-Range', 'bytes ' + file.blob.length);

//Update UI
      xhrUpload.upload.onprogress = function(progressEvent) {
         document.getElementById("status").innerHTML = ((progressEvent.loaded/progressEvent.total)*100).toFixed(0) + "%";
      };

      xhrUpload.onreadystatechange = function() {
         switch(xhrUpload.readyState) {
            case 4: //complete
               document.getElementById("status").innerHTML = "done";
               break;
         }
      };
      xhrUpload.send(blobStore.getAsBlob(fileName));
   }


In Rails, it is quite easy to handle actually. You just need to extract file name of request header 'HTTP_CONTENT_DISPOSITION'. Then, you perform an OS check to write a file of the whole request body. It is a method called raw_post of request object.

class UploadController < ApplicationController
   def files
      path = request.env['HTTP_CONTENT_DISPOSITION'][/^attachment\; filename="([^\"]+)"$/, 1]
      if RUBY_PLATFORM.index("win")
         File.open("public/images/#{path}", "wb") { |f| f.write(request.raw_post) }
      else
         File.open("public/images/#{path}", "w") { |f| f.write(request.raw_post) }
      end
   end
end

Thursday, April 2, 2009

JSON Handling Request in Rails

I have messed around for almost a day today just to find how to convert from json object to ruby object when posting a REST web service. However, I have known quite well a couple more features of Rails in depth like Migration, Testing, RSpec, ActiveResource,....

There are three data format we can post through AJAX (from JavaScript): serialization, and xml. Serialization is the process that formats a set of data so that the server can easily read
it from javascript object or html form.

// Serialized form
name=Chhorn&last=Chamnap&city=Cambridge&zip=02140

//XML
<name>Chhorn</name>
<last>Chamnap</last>
<city>Cambridge</city>
<zip>02140</zip>

Rails automatically recognizes these format very well. In your controller, you can access from params[:name], params[:last],.... However, you would face difficulties when you do a post request with json object. In your params variable would be {"{\"first_name\":\"chamnap
\",\"last_name\":\"chhorn\"}"=>nil. Therefore, you must parse this string to ruby object. There are two possible solutions. First solution, I got from a discussion.

irb> require 'rubygems'
=> true
irb> gem 'json'
=> true
irb> require 'json'
=> true
irb> raw = "{\"first_name\":\"chamnap\",\"last_name\":\"chhorn\"}"
=> "{\"first_name\":\"chamnap\",\"last_name\":\"chhorn\"}"
irb> puts raw
{"first_name":"chamnap","last_name":"chhorn"}
=> nil
irb> JSON(raw)
=> {"first_name"=>"chamnap", "last_name"=>"chhorn"}
irb> cooked = JSON.parse(raw)
=> {"first_name"=>"chamnap", "last_name"=>"chhorn"}
irb> raw
=> "{\"first_name\":\"chamnap\",\"last_name\":\"chhorn\"}"
irb> cooked
=> {"first_name"=>"chamnap", "last_name"=>"chhorn"}
irb> raw.class
=> String
irb> cooked.class
=> Hash
irb> cooked["first_name"]
=> "chamnap"
irb> cooked[:first_name]
=> nil

Secondly, I found by chance from my book by using ActiveSupport::JSON.decode() method.

Subscribe in a Reader