As a spike for my pipeline dashboard project I would like to have a page with some drawn rectangles which I can move around. Furthermore, the position of the moved objects should be persisted.
Outline
Since I like to get more hands on experience with Ajax websites which communicate with the server using JSON, the outline of my solution is the following:
- The rectangles are drawn using SVG.
- The rectangles are moved around using drag-and-drop.
- There is a Rails application which provides a RESTful web service to retrieve the positions of the rectangles and of course, to persist changes of the positions. (This is inspired by an example in "RESTful web services" by Richardson and Ruby.)
- To simplify the usage the example page is provided as the index page of the Rails web application.
Creating the RESTful service
Creating a RESTful web service with rails is very simple. You will see, it takes just a few minutes.
$ rails spike-dnd-with-svg-and-rails
create
create app/controllers
create app/helpers
create app/models
create app/views/layouts
create config/environments
create config/initializers
create config/locales
create db
create doc
create lib
create lib/tasks
create log
create public/images
create public/javascripts
create public/stylesheets
create script/performance
create test/fixtures
create test/functional
create test/integration
create test/performance
create test/unit
create vendor
create vendor/plugins
create tmp/sessions
create tmp/sockets
create tmp/cache
create tmp/pids
create Rakefile
create README
create app/controllers/application_controller.rb
create app/helpers/application_helper.rb
create config/database.yml
create config/routes.rb
create config/locales/en.yml
create db/seeds.rb
create config/initializers/backtrace_silencers.rb
create config/initializers/inflections.rb
create config/initializers/mime_types.rb
create config/initializers/new_rails_defaults.rb
create config/initializers/session_store.rb
create config/environment.rb
create config/boot.rb
create config/environments/production.rb
create config/environments/development.rb
create config/environments/test.rb
create script/about
create script/console
create script/dbconsole
create script/destroy
create script/generate
create script/runner
create script/server
create script/plugin
create script/performance/benchmarker
create script/performance/profiler
create test/test_helper.rb
create test/performance/browsing_test.rb
create public/404.html
create public/422.html
create public/500.html
create public/index.html
create public/favicon.ico
create public/robots.txt
create public/images/rails.png
create public/javascripts/prototype.js
create public/javascripts/effects.js
create public/javascripts/dragdrop.js
create public/javascripts/controls.js
create public/javascripts/application.js
create doc/README_FOR_APP
create log/server.log
create log/production.log
create log/development.log
create log/test.log
$ cd spike-dnd-with-svg-and-rails
Now we use the scaffolding to create our domain object "item", which represents a rectangle to be drawn. An item has x and y coordinates, a name and an id.
$ ruby script/generate scaffold item id:integer name:text x:integer y:integer
exists app/models/
exists app/controllers/
exists app/helpers/
create app/views/items
exists app/views/layouts/
exists test/functional/
exists test/unit/
create test/unit/helpers/
exists public/stylesheets/
create app/views/items/index.html.erb
create app/views/items/show.html.erb
create app/views/items/new.html.erb
create app/views/items/edit.html.erb
create app/views/layouts/items.html.erb
create public/stylesheets/scaffold.css
create app/controllers/items_controller.rb
create test/functional/items_controller_test.rb
create app/helpers/items_helper.rb
create test/unit/helpers/items_helper_test.rb
route map.resources :items
dependency model
exists app/models/
exists test/unit/
exists test/fixtures/
create app/models/item.rb
create test/unit/item_test.rb
create test/fixtures/items.yml
create db/migrate
create db/migrate/20110514212212_create_items.rb
Before starting the service we init the database as follows.
$ rake db:migrate
(in /Users/alex/SWDEV/spike-dnd-with-svg-and-rails)
== CreateItems: migrating ====================================================
-- create_table(:items)
-> 0.0014s
== CreateItems: migrated (0.0015s) ===========================================
The service is started with a script contained in the folder script.
$ script/server --daemon
=> Booting Mongrel
=> Rails 2.3.5 application starting on http://0.0.0.0:3000
What we have so far?
We have a very simple web application running on http://localhost:3000 which allows for managing our domain objects. Its index page shows some welcome message with some hints. The interesting page is http://localhost:3000/items. It returns a list of our domain objects, which we called "item". Of course the list is empty.
To create our first entry we klick on "New item" and fill the form with id=1 and some fantasy. Now http://localhost:3000/items returns a list containing one item. Beside the HTML pages we can also request XML data:
$ curl -i http://localhost:3000/items.xml
TTP/1.1 200 OK
Connection: close
Date: Sun, 15 May 2011 14:00:00 GMT
ETag: "8fc46ac5da86d8127536a43e164a4878"
Content-Type: application/xml; charset=utf-8
X-Runtime: 7
Content-Length: 339
Cache-Control: private, max-age=0, must-revalidate
<?xml version="1.0" encoding="UTF-8"?>
<items type="array">
<item>
<created-at type="datetime">2011-05-14T21:40:20Z</created-at>
<id type="integer">1</id>
<name>my first rect</name>
<updated-at type="datetime">2011-05-15T10:39:21Z</updated-at>
<x type="integer">3</x>
<y type="integer">4</y>
</item>
</items>
Using the verbose option -i of curl tells us a lot more details about the request, especially its return code.
As the Rails application provides a RESTful service we can access the "item" with id 1 using
$ curl -i http://localhost:3000/items/1.xml
HTTP/1.1 200 OK
Connection: close
Date: Sun, 15 May 2011 14:01:24 GMT
ETag: "a388de3aec9d892bc1b1af2f81194701"
Content-Type: application/xml; charset=utf-8
X-Runtime: 7
Content-Length: 293
Cache-Control: private, max-age=0, must-revalidate
<?xml version="1.0" encoding="UTF-8"?>
<item>
<created-at type="datetime">2011-05-14T21:40:20Z</created-at>
<id type="integer">1</id>
<name>my first rect</name>
<updated-at type="datetime">2011-05-15T10:39:21Z</updated-at>
<x type="integer">3</x>
<y type="integer">4</y>
</item>
What about JSON?
Cool, out of the box we got HTML and XML. Let's try to use json. We either can tell cURL that we would like to get JSON results or we append the postfix ".json" to the URL,
pre>
$ curl -i http://localhost:3000/items.jsonl
HTTP/1.1 406 Not Acceptable
Connection: close
Date: Sun, 15 May 2011 14:10:41 GMT
Content-Type: text/html; charset=utf-8
X-Runtime: 5
Content-Length: 1
Cache-Control: no-cache
$ curl -i -H "Accept: application/json" http://localhost:3000/items
HTTP/1.1 406 Not Acceptable
Connection: close
Date: Sun, 15 May 2011 14:13:27 GMT
Content-Type: text/html; charset=utf-8
X-Runtime: 5
Content-Length: 1
Cache-Control: no-cache
Both calls are returning 406 and no content. So, there is something to do to enable JSON support.
How to ad JSON support?
To provide JSO we have to extend our controller
./app/controllers/items_controller.rb
. The controller has code like this:
def index
@items = Item.all
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => @items }
end
end
To add json support we simply copy each "format.xml" line and exchange "xml" with "json". That's it. (Of course, it would be very nice if an option for scaffolding could enable the json support.)
Let's verify whether the JSON support works.
$ curl http://localhost:3000/items/1.json
{"item":{"name":"my first rect","created_at":"2011-05-14T21:40:20Z","x":3,"y":4,"updated_at":"2011-05-14T21:40:20Z","id":1}}
Conclusion
Cool. we have out first RESTful web service with JSON, XML (and also HTML) support... written in less than 10 minutes. Not so bad.
With the next post I try to cover the manipulation of data using PUT and DELETE requests rather than GET requests.