Let’s Go Corkboard
Date: 12 Jan 2011
Corkin hinder bar wearin achtung
keepin handercloppen keepin gestalt.
Lookinpeepers gewerkin waltz das
yodel auf keepin nine unter buerger.
Wunderbar er, ich heiden frau strudel
kaputt, uber wunderbar morgen,
sightseerin underbite, auf weiner ist.
Mobile developers have issues - primarily clients, but clients who have servers give that extra frisson of eyeroll, as one expects to have to deal with low-motivation IT staff in a different organization. A mobile developer would much rather have some control over a test server. If you want to make progress on your code at 3am, then you should be able to do so. Creative work is not a schedulable unit. As a pure mobile developer, maybe your server side skills are a little rusty. But you don&#x2019;t want to commit to lots of learn time to re-learn a skill of which you only really really need a smidgen. This talk is for you.\n
The solution, as always, is to replace the client&#x2019;s IT department with a shell script. Or, better still, a Sinatra script. You can consider Sinatra to be Rails&#x2019; little brother - he hasn&#x2019;t got to university yet and discovered beer and is still fairly trim. He doesn&#x2019;t have the baggage of a lengthy relationship with ActionPack either and is able to focus on one thing. \n
You have a Mac. Ergo you have Ruby and RubyGems installed already. RubyGems is the way you organize various Ruby libraries. It&#x2019;s far from perfect, but makes life a lot easier for the kind of thing we are trying to deliver here.\n
Once you have the RubyGems package manager installed, getting Sinatra is easy. Everything that Sinatra needs to run will be pulled in automagically.\n\nDSL is Domain-Specific Language - you knew that already, right?\n
&#x201C;Speed to Hello World&#x201D; is no indication of the fitness of a particular approach or technology to a large or long-term task. But! We are doing small and short-term tasks, so that&#x2019;s a WIN then. You can probably infer the rest of how the DSL works just from this teeny-tiny example, however let&#x2019;s get into it and put together something marginally more complicated.\n
The example I&#x2019;m going to use is a &#x2018;corkboard&#x2019;. You have a corkboard (the server) and you can post notes to it (and remove them, and change them, and read them), and notes have a subject, the date they were posted and a piece of text, the note content proper. What is NOT going to be in this example is users, sessions and authentication. We might do them another time.\n
The server is going to be RESTful, meaning I&#x2019;m just going to provide a set of CRUD operations on a Resource. Each note is a Resource. You have all spotted at this point that these actions map directly to a subset of the standard HTTP verbs. That&#x2019;s intentional. GET will return the details of a note, given the id in the database. PUT will insert a note in the database and then respond with the id of the new note. DELETE will remove the note from the database. POST will update the note in the database with some new content\n\nEnough! Next up is some Ruby code.\n
Here we get a note with a particular id. \n1. The &#x2018;:&#x2019; char indicates a &#x2018;symbol&#x2019;. Any symbols you put in that path up there get replaced into a dictionary called &#x2018;params&#x2019; for use in the body the get. You can be smart about the patterns you use.\n2. &#x2018;puts&#x2019; is just a print statement\n3. This loads a note from the database - we&#x2019;re trying to match the :id to the primary key.\n4. Set the status to be a HTTP 404 code, not found!\n5. Yay, we&#x2019;re good, set the status to be HTTP 200, ok.\n6. And set the body to be the JSON serialization of the note object.\nThe return just happens on exit.\n
&#x2460; Deleting a note with the given id, when we receive the HTTP delete verb. This code does a get to see if the note is there in the database so it can give a better error status return. Another option would be to catch the database exception that happens when the delete fails because the key isn&#x2019;t found. The &#x2461; destroy method removes the\nnote from the database.\n
1. Getting a little fancier now. This creates a note. We don&#x2019;t need an id in the path, and we are reacting to the \nuse of the PUT verb.\n2. Here&#x2019;s something new - we&#x2019;re asking the JSON module to parse the content of the request we have just \nreceived into an object called &#x2018;data&#x2019;. This is a dynamic language, so the type of the &#x2018;data&#x2019; is not established\nbefore execution time - in this case it&#x2019;s going to be a dictionary, or hash.\n3. Checking for missing stuff - the note has to have a subject and content to be valid, otherwise we set a HTTP 400 status, Bad Request.\n4. Actually making the model object given the subject and content and adding some time recording.\n5. Saving the model object to the database.\n6. Returning the primary key of the object we&#x2019;ve just added to the database so that the client can track it.\n
A post updates the an identified note, maybe changing the subject or the content or both. It will also\nupdate the &#x2018;updated_at&#x2019; field of the note. Nothing special to call out here, except perhaps the %w structure\nin the middle of the method. %w(subject content) just gives a collection of two strings &#x201C;subject&#x201D; and &#x201C;content&#x201D;,\nand the code iterates over these to update the target note with whatever has been part of the post body. The\nscissors is a cut error by me, sorry, there used to be a log message there that got snarfed. Next: DATABASE!\n
Sinatra can use LOTS of different data sources. But that&#x2019;s not something that concerns you. You do have \nsqlite3 on your mac AND you know how to muck about with it thanks to Core Data, so that&#x2019;s the right thing\nto pick. We&#x2019;re going to use DataMapper (another Ruby gem) to interface with it. \n1. Set up sqlite3 to point to a file in the current directory.\n2. Indicate your class is a model by just including DataMapper::Resource (easy!).\n3. You need to have a primary key in there, it will pick one called :id if it&#x2019;s there, Serial means an increasing integer value!\n4. If you have :required => true, it means this field (column) can&#x2019;t be null. There&#x2019;s lots of modifiers like this, including defaults, key status. Check the DataMapper documentation for excruciating details.\n5. This finalize tells DataMapper that all of the models have been defined and now it&#x2019;s time to nail things down.\n6. This tells DataMapper that if we make changes to the Note model and then run this again using a database that contains the *previous* Note model, then the DataMapper layer is to silently migrate the existing (old) database to put in the new information that has appeared in the changed model. This *won&#x2019;t* remove stuff that has gone away from the model, so be aware of the potential of wasted columns. Not a problem for this work!\n