scaffoldで生成されたようなコントローラーを書こう

流しのRails業でチームの中で共有したtipsをちょっとずつブログにも書いていくぞという。気持ち。
ちょっと初心者向けの話題のように思うかもしれないけれど、私もなんども苦しんだことなので。

$ rails g scaffold Book name:string price:integer

scaffoldでこのように生成したとしよう。
そうすると、BooksController#create は以下のように生成される。

# POST /books
# POST /books.json
def create
  @book = Book.new(book_params)

  respond_to do |format|
    if @book.save
      format.html { redirect_to @book, notice: 'Book was successfully created.' }
      format.json { render :show, status: :created, location: @book }
    else
      format.html { render :new }
      format.json { render json: @book.errors, status: :unprocessable_entity }
    end
  end
end

もう少し簡易化してみましょう。

# POST /books
def create
  @book = Book.new(book_params)

   if @book.save
     redirect_to @book, notice: 'Book was successfully created.'
   else
    render :new
  end
end

respond_toを排除し、HTMLしか返さないようにしてみました。
何の変哲も無いcreateアクションに見えるでしょうが、これがrailsのcreateの基本形です。全てのPOSTをこの形で書けるようにすべきです。

例えば、Serviceクラスを使う時はこうなっていると綺麗です。

def create
  if BookRegisterService.call(book_params)
    redirect_to :show, notice: 'Book was successfully created.'
  else
    render :new
  end
end

save modelのインターフェースを委譲されたmodelでデコレーターパターンを用いて実装すると、もっと元の形に近いままです。

def create
  @book = Manga.new(Book.new(book_params))
  if @book.save
    redirect_to @book, notice: 'Book was successfully created.'
  else
    render :new
  end
end

MangaはBookクラスから委譲されたインターフェースを持っています。

class Manga
  delegate_missing_to :@book
  def initialize(book)
    @book = book
  end
  def save
    # Manga固有のロジックをここに書く
    @book.save
  end
end

Formオブジェクトも似たような形にできます。

def create
  @book = BookForm.new(book_params)
  if @book.save
    redirect_to @book, notice: 'Book was successfully created.'
  else
    render :new
  end
end
class BookForm
  include ActiveModel::Model
  attr_accessor :name, :price
  def save
    if valid?
      # フォームが正しかった時の処理
      true
    else
      false
    end
  end
end

とりあえず、今日はここまでにしておきます。