advertisement

Print

Shoes Meets Merb: Driving a GUI App through Web Services in Ruby
Pages: 1, 2, 3, 4, 5, 6

Fleshing Out the Rest of Our API

The rest of our work is vanilla ActiveRecord stuff. We want to be able to create, update, and delete pastes in addition to viewing them. The following three controller methods allow for that:



               
  before :retrieve_paste, :only => [:show, :update, :destroy]

  # ...

  def create               
    p = Paste.create(:title => params[:title], :text => params[:text])  
    redirect url(:paste, p)
  end    

  def update
    @paste.update_attributes(:title => params[:title], :text => params[:text])
    redirect url(:paste, @paste)
  end   

  def destroy
    @paste.destroy
    redirect url(:pastes) 
  end

In both the create and update methods, we return the results of show() for the Paste object. The url() method generates the proper location for us to redirect to, which will be something like /pastes/2.

For the earlier bits of code, we were able to download the YAML files from the browser and inspect their output. Here, we're dealing with something a little more complicated, as data is actually being posted to the server. Though we could certainly verify that this code actually works through formal tests, instant gratification comes through curl.

If you haven't heard of curl before, it's an excellent tool that should be in every web developer's toolkit.

In the quick session below, we verify that given correct data, all of the features we've implemented so far work correctly.

         
  # See the index
  $ curl -H "Accept: text/yaml"  http://localhost:4000/pastes
  ---      
    - :title: Paste 1
      :created_at: Sun Jan 06 23:34:49 EST 2008
      :id: 1
    - :title: Paste 2
      :created_at: Sun Jan 06 23:35:01 EST 2008
      :id: 2

  # List details for Paste 2 
  $ curl -H "Accept: text/yaml"  http://localhost:4000/pastes/2
  ---
  :title: Paste 2
  :created_at: Sun Jan 06 23:35:01 EST 2008
  :id: 2     
  :text: A Second paste     

  # Delete paste 2 (redirects to index)

  $ curl -H "Accept: text/yaml" -L -d "_method=delete" http://localhost:4000/pastes/2 
  ---      
    - :title: Paste 1
      :created_at: Sun Jan 06 23:34:49 EST 2008
      :id: 1

  # Add a new paste

  $ curl -H "Accept: text/yaml" -L -d "title=Paste3&text=A new paste" http://localhost:4000/pastes   
  ---
  :title: Paste3
  :created_at: Mon Jan 07 00:45:58 EST 2008
  :id: 3     
  :text: A new paste

  # Edit a paste

  $ curl -H "Accept: text/yaml" -L -d "_method=put&title=The First Paste&text=booga booga" http://localhost:4000/pastes/1
  ---
  :title: The First Paste
  :created_at: Sun Jan 06 23:34:49 EST 2008
  :id: 1     
  :text: booga booga   

  # See the index again

  $ curl -H "Accept: text/yaml"  http://localhost:4000/pastes
  ---      
    - :title: The First Paste
      :created_at: Sun Jan 06 23:34:49 EST 2008
      :id: 1
    - :title: Paste3
      :created_at: Mon Jan 07 00:45:58 EST 2008
      :id: 3

From this, you can see that we can easily create, retrieve, update, and delete pastes, so our work is essentially done here. Let's take a look at some of the interesting bits of what's going on here before we move on though.

Take a look at the first example:

   $ curl -H "Accept: text/yaml"  http://localhost:4000/pastes

Notice here that we didn't specify pastes/index.yaml, but that the server fed us back YAML anyway. That's because of our earlier definition:

  provides :yaml

This allows you to either use file extensions or the Accept header to specify which format you're interested in. In the case of clients to web services, the Accept header is almost certainly more attractive, and you'll see it used by our Shoes client in just a moment.

The other interesting bit of this curl session was that we were able to use the same trick that Merb and Rails use under the hood for faking out PUT and DELETE requests. For our update, we used _method=put and for our destroy, we used _method=delete. This allows us to use a normal POST request to still take advantage of RESTful routing. Since many apps you'll build with Merb will tend to follow this pattern, it's worth making a note of.

From here, we've got everything we need to build a client side interface to Shmerboes. Our server is admittedly a little fragile, but it implements a clean, platform agnostic Web API that can easily interact with anything that speaks HTTP and YAML. Let's now take a look at how to bridge Shoes to Merb, and learn a bit more about the fun and quirky GUI framework along the way.

Creating a User Interface with Shoes

"Shoes" is a GUI toolkit modeled after the Web, with some additional widgets inspired by native UI toolkits. It is designed to be simple, intuitive, and clean; many cross-platform interface toolkits try to do so many things that they are difficult to use. Shoes has its limitations, but it is extremely fun to hack around with. It will serve us well for our pastebin application.

Running Shoes

Shoes can be obtained from the Download Shoes page on the Shoes wiki. There are binary builds available for Windows and OS X, both with and without the heavy video libraries (yes, Shoes has a video widget!). All other platforms need to compile Shoes from source, which may be a tedious process, depending on how many of its dependencies (in particular, Cairo and Pango) are already installed.

OS X users should avoid revision 371, which, as this is being written, is the latest version available for download. That revision has several severe bugs on OS X and is unusable. Revision 327 works with the code in this article; older versions can be downloaded from _why's download page.

Shoes can be run from the command line, taking as an argument the Ruby file to run. If the filename is not provided, Shoes will pop up a dialog prompting for a file to run. Under OS X, the full path to the Shoes binary inside the application bundle is required:

  $ /Applications/Shoes.app/Contents/MacOS/shoes /path/to/shoes_application.rb

You can add that directory to your PATH to be able to run shoes directly from the command line.

Let's try a sample application; this one comes from the Shoes homepage. It is nearly the simplest Shoes application possible, and is self-explanatory:

  Shoes.app {
    button("Press Me") { alert("You pressed me") }
  }

Save that code as button.rb. We can try to run this directly from our current directory using Shoes:

  $ shoes button.rb

However, this doesn't get us very far:

It seems that Shoes is looking in its own directory for button.rb. There is a simple solution, to fully qualify the path that we pass to shoes:

  $ shoes `pwd`/button.rb

This has the expected result:

Pages: 1, 2, 3, 4, 5, 6

Next Pagearrow