Until now, the texts had to be hard coded into the fixture. That of course is just ridiculous for a webapp. The whole point of an application like this is that it is interactive, so that I can publish my own texts, modify my old ones and delete the garbage.
As you might know, the HTTP has four main verbs:
- GET: A content is shown, no modification should occur.
- POST: Modify a content by sending the server information.
- PUT: Create new content on the server by URL
- DELETE: What do you think?
There are some others, but these four help you design your interface. For example, our application manages texts, so the following requests should be possible:
GET /text
should display a list of all texts.PUT /text
should publish a new text.GET /text/:id
should display the text with the given id.POST /text/:id
should modify the text.DELETE /text/:id
...What do you think?
Using only meaningful URLs and HTTP methods and not cookies, etc, to navigate the user through the site is known as REST. Not only does it give an simple interface, it also helps performance, etc. Putting a cool new name on it like "REST" on it hides the fact, that this was how the makers of HTTP imagined the web all along.
Sinatra is RESTful by default, but of course, you have to take care of the
URLs yourself. The last time, we defined the two GET
methods, so lets add
publishing a new text first:
post "/text" do
text = Text.new(params[:title], params[:text])
text.save
end
Pretty easy, huh? The params
will come from a form, but that's not our
problem now.
Next, we can post changes:
post "/text/:id" do
text = Text.by_id(params[:id].to_i)
return 404 if text.nil?
text.title, text.text = params[:title], params[:text]
end
This mixes the two sources for params
.
delete "/text/:id" do
text = Text.by_id(params[:id].to_i)
return 404 if text.nil?
text.delete
end
Ok, now we can face the problem, that we can't actually access these methods, since we don't have a form for the editing part yet.
First of all, we notice that it would be stupid to write the same form twice -
once for the editing and once for the creating. We'd prefer to just show the
same form. This problem can be solved with partials: basically, we insert a
HAML call into the HAML template. Define a HAML file form.haml
, and use it
from both new.haml
and edit.haml
.
# form.haml
%form(action = actionurl method = "POST")
%label
Title:
%input(type='text' name="title" value="#{title}")
%label
Text:
%input(type='textarea' name="text" value="#{text}")
%input(type='submit')
# edit.haml
%h1
Edit
%q= @text.title
= haml :form, :locals => {:title => @text.title, :text => @text.text, :actionurl => "/text/#{@text.id}"}
# new.haml
%h1 New Txt
= haml :form, :locals => {:title => "", :text => "", :actionurl => "/text"}
We need to add URLs for the form of course, for example
get "/text/new" do
haml :new
end
get "text/:id/edit" do
@text = Text.by_id(params[:id].to_i)
haml :edit
end
- Instead of adding
:locals
, you can also define instance variables. This is often preferable.
There is only putting the respective links into the views:
# index.haml
%ul
- for text in texts
%li
%a( href="/text/#{text.id}" )= text.title
%a( href="/text/new" ) new text
# show.haml
%h1
= text.title
%div
:markdown
#{text.text}
%ul
%li
%a(href="/text/#{text.id}") edit
%li
%form(action="/text/#{text.id}" method="POST")
%input(name ="_method" type="hidden" value="delete")
%input(type="submit" value="delete")
You might be wondering, what the heck the line
%input(name ="_method" type="hidden" value="delete")
is about. Fun fact: Browsers can't use PUT
or DELETE
. It's embarrassing
but true. The fix is the following: We sent a POST
, which tells the server
that it actually should have been a DELETE
method. Sinatra understands this and
translates.
Please don't let GET
requests change anything on the server, or a google
spider might delete all your posts.
You should now know
- What REST is and how RESTful application typically build up their URLs.
- How to use partials.
- How to use the
PUT
andDELETE
methods, even though browsers don't support it.