суботу, 28 листопада 2009 р.

RESTful Sinatra

Попередні статті:
  1. Привіт, Sinatra!
  2. Привіт, DataMapper!
  3. Співаємо з Sinatra!

Прийшов час поговорити про REST архітектуру додатку та її реалізацію на Sinatra.
Особисто мені коштувало багато зусиль зрозуміти, що таке REST. Тому прошу не чіплятися до того, наскільки я спрощую термінологію, щоб сфокусуватися на самій темі.
REST - це Representational State Transfer, так звана "передача стану представлення". Фактично - це спосіб організації доступу до ресурсів. Відповідно до цієї архітектури використовується чотири типи HTTP запитів - GET, POST, PUT і DELETE.
  • GET - отримати ресурс тільки для читання
  • POST - створити новий ресурс
  • PUT - оновити існуючий ресурс
  • DELETE - видалити ресурс

Нічого не нагадує? Якщо порівняти з SQL-синтаксисом, GET - це запит SELECT, POST - це INSERT, PUT - UPDATE, і DELETE - знову просто DELETE.
Ресурси - це ті дані, з якими ми працюємо. Наприклад у нашому блозі стаття - це ресурс, коментарій - це теж ресурс. Ресурси можуть мати взаємозв'язки, та містити в собі інші ресурси. Для більшості ресурсів в нашому додатку будуть необхідні операції їх створення, отримання, оновлення та видалення.

Крім звичних GET і POST Sinatra розуміє методи PUT і DELETE. Нажаль ці методи не підтримуються броузерами, але Sinatra вміє з ними працювати через прихований елемент форми з ім'ям "_method" і значенням, рівним методу HTTP: put або delete. Наприклад:
<form method="post" action "/destroy">
  <input type="hidden" name="_method" value="delete">
  <button type="submit">Destroy it</button>
</form>

Далі ми будемо вважати клас-контролер реалізацією інтерфейсу REST.
Наш контролер буде реалізовувати наступні сім дій(методів).
Чотири цих:
  • show: обробляє GET запит для відображення індивідуального ресурсу, який ідентифікується виразом params[:id]
  • create: обробляє POST запит для створення нового екземпляру ресурсу
  • update: обробляє PUT запит для оновлення існуючого ресурсу, ідентифікованого виразом params[:id], використовуючи дані зв'язані з запитом
  • destroy: обробляє DELETE запит для видалення екземпляра ресурсу, ідентифікованого виразом params[:id]

І три цих:
  • index: обробляє GET запит для відображення колекції ресурсів
  • new: обробляє GET запит, який створює логічну структуру нового ресурсу і передає її клієнту. Цей ресурс не буде збережений на сервері. Можна вважати, що дія new створює порожню форму, яка передається клієнту для заповнення.
  • edit: обробляє GET запит, який повертає вміст ресурсу, ідентифікованого виразом params[:id], в форму, пристосовану для редагування цього вмісту

Не важко помітити, що ці сім дій включають чотири базових операції для створення, читання, оновлення і видалення ресурсу - CRUD (create, read, update, delete). Серед них також є дії для виводу переліку ресурсів та дві додаткові дії, які повертають клієнту новий та існуючий ресурси в форму, призначену для редагування.

Для Sinatra назви цих дій не мають ніякого значення, і використовуються мною тільки для зручності, і є запозиченими з Rails.
Для того щоб реалізувати REST підхід у нашому додатку треба поступити інакше ніж у попередній статті. В першу чергу один і той же URL може в залежності залежати від типу HTTP запиту відповідати різним методам. Наприклад URL
/posts/1
повинен викликати метод show(для відображення статті №1), якщо був використаний запит GET, але у випадку запиту DELETE повинен викликати метод destroy.


Все що нам залишилося зробити, це реалізувати цих сім методів у нашому контролері і оновити шаблони представлень.

blog.rb
# index
get "/posts" do
  @posts = Post.all
  haml :"posts/index"
end

# new
get "/posts/new" do
  haml :"posts/new"
end

# create
post "/posts" do
  @post = Post.create(:title => params[:title], :body => params[:body])
  if @post.save
    redirect "/posts"
  else
    redirect "/posts/new"
  end
end

# show
get "/posts/:id" do
  @post = Post.first(:id => params[:id])
  haml :"posts/show"
end

# edit
get "/posts/:id/edit" do
  @post = Post.first(:id => params[:id])
  haml :"posts/edit"
end

# update
put "/posts/:id" do
  id = params[:id]
  @post = Post.first(:id => params[:id])
  if @post.update(:title => params[:title], :body => params[:body])
    redirect "/posts/#{id}"
  else
    redirect "/posts/#{id}/edit"
  end
end

# destroy
delete "/posts/:id" do
  post = Post.first(:id => params[:id])
  post.destroy!
  redirect "/posts"
end

index.haml
#header
  %h1 Мій блог

%a{:href => "posts/new"}> New Post
#content
  - @posts.each do |post|
    .container
      %h3= post.title
      %p= post.body
      %p= post.created_at

      %table
        %tr
          %td
            %a{:href => "posts/#{post.id}"}> Show
          %td
            %a{:href => "posts/#{post.id}/edit"}> Edit
          %td
            %form{:method => 'post', :action => "/posts/#{post.id}"}
              %input{:type => "hidden", :name => "_method", :value => "delete"}
              %input{:type=>'submit', :value => 'Destroy'}

show.haml
%h3= @post.title
%p= @post.body

%a{:href => "/posts"}= "< Back"

new.haml
%h1 Write a new post

%form{:method => 'post', :action => "/posts"}
  %ul
    %li#title
      %label{:for => 'post_title'} Title:
      %br
      %input{:type=>'text', :id => 'post_title', :name => 'title'}
    %li#body
      %label{:for => 'post_body'} Body:
      %br
      %textarea{:type=>'textarea', :id => 'post_body', :cols => 80, :rows => 5, :name => 'body'}
  %input{:type=>'submit', :value => 'New'}

%a{:href => "/posts"}= "< Back"

edit.haml
%h1 Edit a post

%form{:method => 'post', :action => "/posts/#{@post.id}"}
  %input{:type => "hidden", :name => "_method", :value => "put"}
  %ul
    %li#title
      %label{:for => 'post_title'} Title:
      %br
      %input{:type=>'text', :id => 'post_title', :name => 'title', :value => @post.title}
    %li#body
      %label{:for => 'post_body'} Body:
      %br
      %textarea{:type=>'textarea', :id => 'post_body', :cols => 80, :rows => 5, :name => 'body'}= @post.body
  %input{:type=>'submit', :value => 'Edit'}

%a{:href => "/posts"}= "< Back"

Немає коментарів: