DELETE action without Javascript
I have been seeing this question many times on the hanami chat. The hanami-controller offers as much as Rails in routing. So you get resources :posts
with all accompanying routes.
# apps/web/config/routes.rb
resources :posts
# $ bundle exec hanami routes
GET '/posts', to: Web::Controllers::Posts::Index
GET '/posts/new', to: Web::Controllers::Posts::New
POST '/posts', to: Web::Controllers::Posts::Create
GET '/posts/:id', to: Web::Controllers::Posts::Show
GET '/posts/:id/edit', to: Web::Controllers::Posts::Edit
PATCH '/posts/:id', to: Web::Controllers::Posts::Update
DELETE '/posts/:id', to: Web::Controllers::Posts::Destroy
DELETE’ing a resource
The problem I have seen mostly is because a lot of people expect it to work like in Rails. Rails is a very mature framework and a lot of nuances have been taken care of. Hanami is still getting its feet on the road and its ready for production if you are willing to write a little more code than in Rails.
The way Rails does it is by simulating a form
using the POST
HTTP verb. Since most browser vendors only support HTTP protocols GET, POST and PUT verbs. The DELETE verb needs a workaround. When you click a link that implements a delete action. JQueryUJS intercepts that click and presents an alert, after you confirm. The action is sent as a form post. So in the form below, your submit action is sent as a POST
, the param[:_method] is used to initiate the #destroy
action on your controller
:
<form action="/posts/1234" method="post">
<input type="hidden" name="_method" value="delete">
<!-- ... -->
</form>
In Hanami however, unless you include JQueryUJS
. You will have to do this manually. So lets take one of my favourite approach.
# action code
module Web::Controllers::Posts
class Destroy
include Web::Action
params do
required(:id).filled
end.valid?
def initialize(post: PostRepository)
@post = post.new.find(params[:id])
# @comments = CommentRepository.new.for_post(@post.id)
# @user = UserRepository.new.find(@post.user_id)
end
def call(params)
# redirect_to '/' unless authorize!(@user, @post)
if @post
@post.destroy
redirect_to '/'
else
# handle error
end
end
end
end
Its relatively straightforward. Now we just need to setup our route to:
DELETE '/posts/:id', to: Web::Controllers::Posts::Destroy
And this should work with or without Javascript. Now you just need to use a form to post to this action.
form_for post, routes.post_path(id: post.id), method: :delete, class: 'uk-form' do
submit 'Destroy', class: 'uk-button uk-button-danger'
end
This will produce a form similar to the following:
<form id="post-form" action="/posts/1234" class="uk-form">
<input type="hidden" name="_method" value="delete">
<input type="hidden" name="authenticity_token" value="f755bb0ed134b76c4" />
<input type="submit" name="commit" value="Destroy" class="uk-button uk-button-danger" />
</form>