This blog post really continues on the previous one. In that post, we created a Todo app using Sinatra, Postgres but we returned ERB files. Essentially, our app returned some HTML files that contained immediately all the data in a proper Bootstrap format.
Compared to the previous post, the only change really is that we have changed the routes.rb file in the ‘routes’ folder to respond with JSON rather than referring to the ERB files. The routes.rb file now looks like:
get "/" do format_response(Todo.all, request.accept) end get "/todos" do format_response(Todo.all, request.accept) end get "/todos/:id" do todo ||= Todo.get(params[:id]) || halt(404) format_response(todo, request.accept) end post "/todos" do body = JSON.parse request.body.read todo = Todo.create( content: body['content'], created_at: Time.now, updated_at: Time.now ) status 201 format_response(todo, request.accept) end put '/todos/:id' do body = JSON.parse request.body.read todo ||= Todo.get(params[:id]) || halt(404) halt 500 unless todo.update( content: body['content'], updated_at: Time.now, completed_at: body['done'] ? Time.now : nil, done: body['done'] ? true : false ) format_response(todo, request.accept) end delete '/api/movies/:id' do todo ||= Todo.get(params[:id]) || halt(404) halt 500 unless todo.destroy end
The full application can be found here in case you want to see a completed example. Let’s go ahead an run our app.
wim@wim-mint ~/Sinatra_Todo_Postgres_Datamapper_structure_json $ rake migrate ~ (0.000721) PRAGMA table_info("todos") ~ (0.000015) SELECT sqlite_version(*) ~ (0.013478) DROP TABLE IF EXISTS "todos" ~ (0.000045) PRAGMA table_info("todos") ~ (0.004265) CREATE TABLE "todos" ("id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, "content" VARCHAR(255) NOT NULL, "done" BOOLEAN DEFAULT 'f' NOT NULL, "completed_at" TIMESTAMP, "created_at" TIMESTAMP, "updated_at" TIMESTAMP) wim@wim-mint ~/Sinatra_Todo_Postgres_Datamapper_structure_json $ shotgun == Shotgun/WEBrick on http://127.0.0.1:9393/ [2014-09-12 11:44:17] INFO WEBrick 1.3.1 [2014-09-12 11:44:17] INFO ruby 2.1.2 (2014-05-08) [x86_64-linux] [2014-09-12 11:44:17] INFO WEBrick::HTTPServer#start: pid=5161 port=9393
Going to http://localhost:9393/todos will show just some brackets. This means our app is working. Since there is no data yet in the database, it will return an empty resultset. You could also verify this with a REST client. To do so, we’ll be using the POSTMAN extension to Chrome browser, which is an excellent graphical tool in case you don’t want to use cURL (which is perfectly well suited to test our REST API if you’re more into the CLI mindset).
In the below screenshot, you can see we did a GET http://localhost:9393/todos and we got back again the . The fact you can do this using a random REST client shows that we really made our server app independent from the client app.
Let’s add some data to the database using the Postman REST client. The model we are using is in below snippet. So this means that all the data items will be added to the database. The ‘id’ will be created automatically, the content field is mandatory and the done file will be set to false by default, indicating a todo item is never completed already when it is inserted in the database the first time. The date fields are not required, but will be automatically created in our code later on.
class Todo include DataMapper::Resource property :id, Serial, key: true, unique_index: true property :content, String, required: true, length: 1..255 property :done, Boolean, :default => false, required: true property :completed_at, DateTime property :created_at, DateTime property :updated_at, DateTime end
However, in below snippet from out routes.rb file, you see that the app expects some data like the content. Note that you don’t have to supply the dates (created_at and updated_at) since they will be automatically set to the current time in our code.
post "/todos" do body = JSON.parse request.body.read todo = Todo.create( content: body['content'], created_at: Time.now, updated_at: Time.now ) status 201 format_response(todo, request.accept) end
Note that the server replies back with a “200 OK” message which means the item was inserted successfully. If you’re not convinced by the “200 OK” message from Postman, you could also see that this succeeded by doing a GET request to the /todos as shown in below example
require 'sinatra/base' module Sinatra module ResponseFormat def format_response(data, accept) accept.each do |type| return data.to_xml if type.downcase.eql? 'text/xml' return data.to_json if type.downcase.eql? 'application/json' return data.to_json end end end helpers ResponseFormat end
In the above snippet, the ‘format_response(Todo.all, request.accept)’ will provide all the todo items to the function and the format depends on the Accept header of the request. This is pretty cool. If your client application -for some reason- prefers to receive XML instead of JSON, it can be achieved easily.
This means that when we set the Accept header to ‘text/xml’ in the Rest client (can be Postman or your own client application), it will be caught by our format_response function where the accept parameter will be equal to ‘text/xml’, hence our app will return XML data via the data.to_xml return statement. Isnt’ this pretty neat?
I have made the code available on Github (Sinatra-Todo-app-with-Datamapper-using-Postgres-and-JSON) so you can have a look how it all ties together.