Saturday, May 14, 2011

HTC Incredible internal memory

My phone, an HTC Droid Incredible, may be hopelessly antiquated by the standards of true mobile hipsters. Still, it came with a generous 8GB internal storage. Too bad the SD card is a chintzy 2GB. These days, you get more than 2 gigs on an abacus. It seems like Android wants to use internal storage for the OS and apps, reserving the SD card for media, which makes that 8GB/2GB split a little awkward. I filled that 2GB right up with choice sides of Miles and 'Trane in no time. And, what do I need with 8 gigs worth of apps? What am I running, Bloatpad 2.0? So, anyway, I decided I wanted to use the empty 6 plus gigs on the internal storage for some Thelonious. So, can I do that?

Well, the Help/How to thing at HTC says, "...Music only plays audio files saved on the storage card...". Well, I use another media player anyway - Meridian. Then there's an article called Programmitically accessing internal storage (not SD card) on Verizon HTC Droid Incredible (Android), which says, "To be quite honest, the internal storage is a joke. Just think of it as a flash drive..."

But, it turns out, you can access music and other media on the internal storage. You just have to know that the internal storage is mounted as /emmc. Maybe they shoulda called it /WTF.

Saturday, May 07, 2011

Rails 3 MongoDB recipe

Here's a quick and dirty recipe for getting started with MongoDB and Rails 3 using mongo_mapper. We'll get set up and test out a Restful web service.

I installed MongoDB with MacPorts.

sudo port selfupdate
sudo port install mongodb

Start a rails project. You may want to leave out ActiveRecord using the switch --skip-active-record as suggested by the Getting Started guide. That's optional. You might want to leave it in, if you intend to use a relational DB alongside Mongo.

  rails new <myproject>

Add to Gemfile

source 'http://gemcutter.org'
gem 'mongrel'
gem 'mongo_mapper'
gem 'bson'
gem 'bson_ext'
gem 'SystemTimer'
gem 'rails3-generators'
gem 'json'

Bundler happily installs all this stuff.

bundle install

Create config/initializers/mongodb.rb

MongoMapper.connection = Mongo::Connection.new('localhost', 27017)
MongoMapper.database = "#myapp-#{Rails.env}"

if defined?(PhusionPassenger)
   PhusionPassenger.on_event(:starting_worker_process) do |forked|
     MongoMapper.connection.connect if forked
   end
end

Start the MongoDB server, giving it a place to put data files:

mkdir <path>/data
mongod --dbpath <path>/data

Create an entity to be stored in MongoDB. I like to use a database of Nerds as my test application.

rails generate scaffold Nerd name:string description:string iq:integer --orm=mongo_mapper 

I originally gave description type "text", but that didn't work for me, producing, "NameError in NerdsController#show, uninitialized constant Nerd::Text". So, I edited the model file to look like this:

class Nerd
  include MongoMapper::Document         
  key :name, String
  key :description, String
  key :iq, Integer
end

Getting mongodb objects as XML doesn't work?

I saw an error: undefined method `each' with mongo_mapper (0.8.6), which was fixed by upgrading to (0.9.0)

Started GET "/nerds/4dc5c41a1ff2367744000004.xml" for 127.0.0.1 at Sat May 07 15:41:54 -0700 2011
  Processing by NerdsController#show as XML
  Parameters: {"id"=>"4dc5c41a1ff2367744000004"}
Completed 200 OK in 9ms (Views: 2.6ms | ActiveRecord: 0.0ms)
Sat May 07 15:41:54 -0700 2011: Read error: #<NoMethodError: undefined method `each' for #<Nerd:0x1040bfb80>>

JSON web services in Rails

You'll likely want to serve up and accept JSON in HTTP requests. For some crazy reason, Rails doesn't generate code for JSON in the controller, only HTML and XML. You have to add a line to the respond_to code block, like this:

class NerdsController < ApplicationController
  # GET /nerds
  # GET /nerds.xml
  def index
    @nerds = Nerd.all

    respond_to do |format|
      format.html # index.html.erb
      format.xml  { render :xml => @nerds }
      format.json { render :json => @nerds }
    end
  end

  # GET /nerds/1
  # GET /nerds/1.xml
  def show
    @nerd = Nerd.find(params[:id])

    respond_to do |format|
      format.html # show.html.erb
      format.xml  { render :xml => @nerd }
      format.json { render :json => @nerds }
    end
  end
  ...
end

Use curl to test that this works. First, get HTML, then ask for JSON using the accept header.

curl --request GET -H "accept: application/json" http://localhost:3000/nerds/4dc5c41a1ff2367744000004

OK, now we can serve JSON, how about receiving it in POST requests? Another couple quick additions and we're in business.

# POST /nerds
# POST /nerds.xml
def create
  @nerd = Nerd.new(params[:nerd])

  respond_to do |format|
    if @nerd.save
      format.html { redirect_to(@nerd, :notice => 'Nerd was successfully created.') }
      format.xml  { render :xml => @nerd, :status => :created, :location => @nerd }
      format.json  { render :json => @nerd, :status => :created, :location => @nerd }
    else
      format.html { render :action => "new" }
      format.xml  { render :xml => @nerd.errors, :status => :unprocessable_entity }
      format.json  { render :json => @nerd.errors, :status => :unprocessable_entity }
    end
  end
end
curl --request POST -H "Content-Type: application/json" -H "Accept: application/json" --data '{"nerd":{"name":"Donald Knuth", "iq":199, "description":"Writes hard books."}}' http://localhost:3000/nerds

If you have an entity named "nerd" it's pretty reasonable to expect the XML representation to look something like <nerd>...</nerd>. By analogy to that, they expect your JSON to look like this {"nerd":{...}}, which I'm not sure I like. I coded around the issue, which is might be unwise, by making the controller's create method look like this:

# POST /nerds
# POST /nerds.xml
def create

  input = params[:nerd] || request.body.read
  if request.content_type == 'application/json'
    @nerd = Nerd.new(JSON.parse(input))
  else
    @nerd = Nerd.new(input)
  end

  respond_to do |format|
    if @nerd.save
      format.html { redirect_to(@nerd, :notice => 'Nerd was successfully created.') }
      format.xml  { render :xml => @nerd, :status => :created, :location => @nerd }
      format.json  { render :json => @nerd, :status => :created, :location => @nerd }
    else
      format.html { render :action => "new" }
      format.xml  { render :xml => @nerd.errors, :status => :unprocessable_entity }
      format.json  { render :json => @nerd.errors, :status => :unprocessable_entity }
    end
  end
end

...which you can test with curl like so:

curl --request POST -H "content-type: application/json" --data '{"name":"Bozo", "iq":178, "description":"A very smart clown."}' http://localhost:3000/nerds

So, there you have it - Rails 3 and MongoDB playing ReSTfully together.

Warning: Current release versions of MongoDB have a 4MB maximum document size. This makes some sense as documents are often serialized and deserialized in memory. Apparently, this is being raised in later versions. Fully streaming APIs would certainly help, but that brings up the question of how much of the XML dog-pile of technologies will (or should) be replicated in JSON. Guess we'll see.