loading...
Cover image for Beautiful Controllers - Growing Rails applications in practice

Beautiful Controllers - Growing Rails applications in practice

brownio profile image Antonio Djigo ・4 min read

Hey 👀✨

I've been reading through a book called Growing Rails Applications in practice and I decided to make a summary to get a better understanding of every chapter. Maybe it also works for you!

Normally, the developer does not struggle to learn about what a View or a Model is. But what about the controllers?

What are the major problems of dealing with them?

  • It's often hard to decide if the logic goes into the controller or in the model.
  • Normally, sharing code between the model and the screen requires too much code.
  • Tested controller's code is hard to test and experiment with because of the parameters, sessions, requests etc they need.
  • Without a design guide to follow, it is hard to find two controllers that look alike, making it a pain to recognize who every controller work.

We cannot get rid of them, but, by following simple guidelines, we can extract business logic out of the controllers, and take them to a better place.

Consistent controller design

It is important to have a standardized controller design. A developer who has to learn how to manage a new system every time he/she edits a controller file won't be happy. A lot of time will be spent here by doing this.

By reducing the variability in our controllers, we will improve our fellow developers' mental health. Wouldn't it be awesome that, by having a default design, you could focus exactly on what you want to do, and in the other parts of the system?

Also, the development speed of new controllers would be improved. You would only decide to use CRUD, and move to the parts you really care about.

Normalizing user interactions

How could you come up with a default controller design when your site has a lot of different user interactions? A good way to manage this would be to reduce every interaction to a CRUD controller, even if you feel that it's not necessarily a typical CRUD interface (at first).

Every interaction can be modelled as a CRUD. You may have a controller that manages a subscription. Why not DESTROY a subscription instead of cancelling it? Or CREATING it instead of activating a new subscription?

By normalizing every user interaction to a CRUD interaction, we can design a beautiful controller layout and reuse it again and again with little changes.

A better controller implementation

How should a controller truly be? We have seen many unloved controllers during our careers.

Let me give you some hints.

  • They should be short, DRY and easy to read.
  • Controllers should provide the minimum amount of glue code as possible. (No intermediary code between a request and a model)
  • Always follow the standard design, unless there is a truly good reason.

A good way to do this is by creating an standart implementation of a controller that CRUDS an ActiveRecord class:

class NotesController < ApplicationController

  def index
    load_notes
  end

  def show
    load_note
  end

  def new
    build_note
  end

  def create
    build_note
    save_note or render 'new'
  end

  def edit
    load_note
    build_note
  end

  def update
    load_note
    build_note
    save_note or render 'edit'
  end

  def destroy
    load_note
    @note.destroy
    redirect_to notes_path
  end

private

  # The controller actions are delegating most of their work to helper methods like load_note or build_note. This will make our code DRY and it will be easier to change the behaviour of multiple actions by just editing our helper method.

  def load_notes
    @notes ||= note_scope.to_a
  end

  def load_note
    @note ||= note_scope.find(params[:id])
  end

  def build_note
    @note ||= note_scope.build
    @note.attributes = note_params
  end

  def save_note
    if @note.save
      redirect_to @note
    end
  end

  def note_params
    note_params = params[:note]
    note_params ? note_params.permit(:title, :text, :published) : {}
  end

  # note_params will allow us to only give access to those parameters we want the user to edit
  # authorization does not belong in a model, as no users should interact with data in there.

  def note_scope
    Note.all
  end

  # This note_scope method is used by every CRUD action. 
  # It loads a Note with a given ID, or to load the list of all notes at the index method.
  # By having this guard access to the Model, we have a central place that tells exactly which records the controller can show.
  # We could limit this, for example, to only display specific user notes, just by changing the query we make to the model.

end

Why have controllers at all?

By doing this, we are creating a simple and consistent controller design that pushes a lot of code into the model. So, why have controllers?

There are several responsibilities that belong in a controller.

  • Security (authentication, authorization)
  • Parsing and white-listing parameters
  • Loading or instantiating the model
  • Deciding which view to render

Remember that a controller never does the heavy lifting. They should contain as less amount of glue code as possible. 👀 ✨

You can follow me so you are tuned to whenever I post something through my Twitter account, hope you liked it!

Discussion

pic
Editor guide
Collapse
ben profile image
Ben Halpern

Nice post

Collapse
gjack profile image
Gabi Jack

Thanks for writing this post! I came across this book recently, but I had my doubts because it seems it was written a few years ago. Would you say it is still relevant for Rails 6 applications?

Collapse
brownio profile image
Antonio Djigo Author

Glad you liked it ✨ I'd say that yes, it is relevant, as it has general knowledge that's useful even for other frameworks. 😊