Refactoring controllers to keep them RESTful
Hi gang
I have a feature where a user can query the Google Books API with an ISBN number and get the book as a response. I have this all working nicely, but I'd like to refactor my Books controller to keep it restful.
Currently, my code looks like this:
routes.rb:
resources :books do
  collection do
    post :submit_book
    get :try_again
  end
end
BooksController.rb:
###
def submit_book
  @book = Book.new
  @book.authors.build
  isbn = Book.sanitised_isbn(params[:q])
  begin isbn
    @response = GoogleBooks.new(isbn)
  rescue => e
    redirect_to try_again_books_path(isbn: params[:q])
    nil
  end
end
###
_form.html.erb
<%= form_tag({controller: "books", action: "submit_book", method: "post"}) do %>
  <%= text_field_tag :q, "", placeholder: "ISBN-10 or ISBN-13" %>
  <%= submit_tag("Lookup book", name: nil) %>
<% end %>
What I'm trying to do now is to extract the logic from the submit_book method into it's own controller, but I'm having difficulty making it work. I'm getting bogged down in the weeds for sure. Anyone got any pointers on where I should be going?
When I submit this new form, my browser downloads an empty file lol
routes.rb:
resource :google_book, only: [:create]
resources :books
google_books_controller.rb:
class GoogleBooksController < ApplicationController
  def create
    @book = Book.new
    @book.authors.build
    isbn = Book.sanitised_isbn(params[:q])
    begin isbn
      @response = GoogleBooks.new(isbn)
    rescue => e
      redirect_to try_again_books_path(isbn: params[:q])
      nil
    end
  end
end
_form.html.erb:
<%= form_tag({controller: "google_books", action: "create", method: "post"}) do %>
  <%= text_field_tag :q, "", placeholder: "ISBN-10 or ISBN-13" %>
  <%= submit_tag("Lookup book", name: nil) %>
<% end %>
  Got it working. Will post the answer here in case anyone has the same question in the future.
My _form.html.erb became:
<%= form_with url: google_book_path, local: true do %>
  <p><small>Enter the ISBN (excluding spaces and hyphens)</small></p>
  <div class="form-group">
    <%= text_field_tag :q, "", placeholder: "ISBN-10 or ISBN-13", required: true, class: "form-control" %>
  </div>
  <%= submit_tag("Lookup book", name: nil, class: "btn btn-dark") %>
<% end %>
And I was forgetting that I obviously needed the appropriate view, so I added /views/google_books/create.html.erb
I was then able to remove the submit_book method from BooksController and everything just feels much cleaner and organised.
Thanks for sending that over 👍 That's an even cleaner solution.
Ok, so now my code looks like the following:
routes.rb:
namespace :books do
  resource :google_book, only: [:create]
end
resources :books
controllers/books/google_book.rb:
class Books::GoogleBooksController < ApplicationController
  def create
    @book = Book.new
    @book.authors.build
    isbn = Book.sanitised_isbn(params[:q])
    begin isbn
      @response = GoogleBook.new(isbn)
    rescue => e
      render 'books/google_books/try_again'
      nil
    end
  end
end
views/books/google_books/_form.html.erb:
<%= form_with url: books_google_book_path, local: true do %>
  <%= text_field_tag :q, "", placeholder: "ISBN-10 or ISBN-13" %>
  <%= submit_tag("Lookup book", name: nil) %>
<% end %>
"And what I found is that the freedom that gives you is that each controller now has its own scope with its own sets of filters that apply..." - DHH
Indeed.