Skip to main content

Refactoring controllers to keep them RESTful

Rails • Asked by Nino Rosella

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.


Login or Create An Account to join the conversation.

Subscribe to the newsletter

Join 24,647+ developers who get early access to new screencasts, articles, guides, updates, and more.

    By clicking this button, you agree to the GoRails Terms of Service and Privacy Policy.

    More of a social being? We're also on Twitter and YouTube.