Note: This is part of the Production vs Tutorial Code Series.



Let’s revisit instance variables as a source of confusion.

Sometimes it doesn’t make sense to have a single memoized helper method. You want to set a bunch of instance variables all at once.

But is that really true?

What if, instead, there is a value object or process object hiding in your controller?

Consider this example:

class NotesController < ApplicationController
  def create
    if create_note
      redirect_to note_path(@note)
    else
      render :new
    end
  end

private

  def create_note
    if current_user.can?(:write_rich_text)
      @note = RichTextNote.new(note_params)
      @editable_text = @note.markdown
      @renderable_content = @note.html
    else
      @note = Note.new(note_params)
      @renderable_content = @editiable_text = @note.text
    end

    @note.save
  end

  def note_params
    params.require(:note).permit(:id, :user_id, :text)
  end
end

Why set these @editable_text and @renderable_content instance variables in the controller?

It was more convenient to only check for rich text in the controller vs the view.

I see. What if Note and RichTextNote had a better shared interface?

class NoteDecorator < SimpleDelegator
  def editable_text
    text
  end

  def renderable_content
    text
  end
end
class RichTextNoteDecorator < SimpleDelegator
  def editable_text
    markdown
  end

  def renderable_content
    html
  end
end

Then in the controller

class NotesController < ApplicationController
  def create
    if new_note.save
      redirect_to note_path(new_note)
    else
      render :new
    end
  end

protected

  def new_note
    @new_note ||=
      if current_user.can?(:write_rich_text)
        RichTextNoteDecorator.new(RichTextNote.new(note_params))
      else
        NoteDecorator(Note.new(note_params))
      end
  end

private

  def note_params
    return {} unless params.key?(:note)

    params.require(:note).permit(:id, :user_id, :text)
  end
end

There are a few changes here.

  • We have extended the memoized new_note helper from an earlier refactor. It now decorates the note object and passes in optional form params.
  • We have made calling note_params safer, it can now be called from new instead of just create.
  • Because of these two changes, create_note would have been a single line (new_note.save), so we inlined it into create.
  • In the views, you can now safely call new_note.editable_text and new_note.renderable_content without worrying about extra instance variables.