Archive for the 'AIR' Category

Why your HTTPService “GET” changes to “POST” while you’re not looking

Monday, November 9th, 2009

I’m trying to get my AIR application to communicate with Basecamp. Should be easy. The guys at 37Signals have their signature REST simplicity going on and the API is just sitting there waiting for basic POST and GET requests via AIR’s HTTPService.

Ok, so I want to get my projects from the Basecamp API. Lessee here…reading through the Basecamp API (and translating to ActionScript) I just need to send an HTTP “GET” to my account’s URL, making sure to add on a “/projects” to the URL…something like https://myAccountName.basecamphq.com/projects

And, I also need to make sure that the headers include stuff like contentType=”application/xml” and I set the “Accept” property on the headers object to “application/xml” too. No prob.

And…AHA!….I need to take a little time to get the authentication header correct, as described by Alain Hufkins in this blog post.

Finally, as I mentioned before, if you want a simple list of your items (in this case projects), the convention in REST is to issue a GET request. So we’ll add this to our HTTPService’s settings as well.

Now, with just a little pompousness (soon to evaporate) we’ve got this thing down in just a few minutes. Here’s the code in my BasecampDelegate class, kind of squished together in one method so you can see it. I’m using the SWIZ framework, so the HTTPService is injected into the class’s “service” property, but you could just create the service within the method and everything would go. I also have a basecampModel class that contains the URL, username and password.

public function getProjects():AsyncToken
{
     Logger.debug(" what could be easier? ...")
     service.url = basecampModel.basecampURL + "/projects"
     service.method = "GET"
     var username:String = basecampModel.basecampLogin
     var password:String = basecampModel.basecampPassword
     var enc:Base64Encoder = new Base64Encoder();
     enc.encode(username + ":" + password);
     service.headers["Authorization"] = "Basic " + enc.toString();
     service.contentType="application/xml"
     service.headers["Accept"] = "application/xml"
     service.resultFormat = "e4x"
     return service.send()
}

Ok! (Rubbing hands gleefully). Click debug/compile and …. wait…. Flex is tracing out that the request is being sent via “POST.” But I explicitly set it to “GET.” Try re-arranging how the parameters are set. Debug/compile … same thing. Arrrrrr!!! Ok, make sure the original tag is set to “GET”. Debug/compile … same thing.

“What the…”

It turns out that the HTTPService class changes stuff around on you when you’re not looking : if you set your contentType to “application/xml”, it doesn’t matter how many times you set it to “GET,” it’s going to send off a “POST” at runtime. Big thanks to Verveguy for pointing this out.

So, the way around this is to set your content type to x-www-form-urlencoded even though the Basecamp API docs tell you you need to send application/xml. This will work when you’re doing a GET.

  service.contentType="application/x-www-form-urlencoded"

I hope this saves you some time!

Finding database record by foreign key

Monday, December 8th, 2008

I’ve been experimenting with Christopher Coenraet’s example code for a simple ORM. His code provides a simple way to load data from a table and return an ArrayCollection of typed objects. (This post won’t make much sense if you haven’t looked through his example.)

However, I found myself needing to return an ArrayCollection of objects related to another object by a “has many” relationship.  I’ve set my tables up using ruby on rails — which is a great tool for helping you get your AIR application sqlite db populated and running — so in my application the columns follow the RoR convention: since a “project” has many “scenarios” the scenarios table has a “project_id” column to represent the foreign key relationship.

So how do I get all scenarios for one project id?

Here’s my first attempt at within Coenraet’s EntityManager class to give me all the related tables:

public function findByForeignKey(c:Class, foreignKeyColumnName:String, foreignKeyID:uint):ArrayCollection
{
    if (foreignKeyColumnName=="") throw new Error("foreignKeyColumnName cannot be empty")
    if (foreignKeyID<1) throw new Error("foreignKey must be greater than 0")

    if (!_map[c]) loadMetadata(c);
    var stmt:SQLStatement = _map[c].findByForeignKeyStmt
    var identity:Object = _map[c].identity;

    stmt.parameters[":foreignKeyColumnName"] = foreignKeyColumnName
    stmt.parameters[":foreignKeyID"] = foreignKeyID
    stmt.execute()
    var result:Array = stmt.getResult().data;
    if (result==null) return null
    return typeArray(result, c);
}

Farther down in the EntityManager class, here’s the code that adds the sql statement along with the others…

var findByForeignKeySQL:String = "SELECT * FROM " + table + " WHERE :foreign_key_column_name=:foreign_key_id"

stmt = new SQLStatement()
stmt.sqlConnection = sqlConnection
stmt.text = findByForeignKeySQL
_map[c].findByForeignKeyStmt = stmt

Then, I use this method where I want to load all scenarios for certain project…

     var em:EntityManager.getInstance()
     scenariosAC = db.findByForeignKey(ScenarioVO, "project_id", project.id)

Ok. So this should all work. But…it doesn’t. It turns out that I can’t use parameter replacement within a SQLStatement for anything other than SQL values! So in my statement

var findByForeignKeySQL:String = "SELECT * FROM " + table + " WHERE :foreignKeyColumnName=:foreignKeyID"

that foreignKeyColumName doesn’t get replaced. So, I switched up the method to create a new SQLStatement each time it’s called…which is ugly, but in my application I won’t be using this too much so it’s not a big deal.

public function findByForeignKey(c:Class, foreignKeyColumnName:String, foreignKeyID:uint):ArrayCollection
{
    if (foreignKeyColumnName=="") throw new Error("foreignKeyColumnName cannot be empty")
    if (foreignKeyID<1) throw new Error("foreignKey must be greater than 0")

    if (!_map[c]) loadMetadata(c);
    var stmt:SQLStatement = new SQLStatement()
    stmt.text = "SELECT * FROM " + _map[c].table + " WHERE " + foreignKeyColumnName + "=" + foreignKeyID
    stmt.sqlConnection = sqlConnection
    stmt.execute()

    stmt.parameters[":foreignKeyColumnName"] = foreignKeyColumnName
    stmt.parameters[":foreignKeyID"] = foreignKeyID
    stmt.execute()
    var result:Array = stmt.getResult().data;
    if (result==null) return null
    return typeArray(result, c);
}

And there you go. A simple method to get your “has many” relationships.