LoginSignup
3
2

More than 3 years have passed since last update.

Railsで関連に更新履歴を残しつつ1件ずつ登録する

Posted at

データに履歴を残したい

データの履歴はとっても大事だなと感じ始めた昨今、履歴を残しつつ、しかし普段は最新のデータのみ参照したい。
そして、データの更新時は既存のデータを更新ではなく、新しい履歴を作成したい。

やってみる

Modelの設定

モデルにFooHistoryがあって、1対多の関係であるとする。
Fooには、nameという属性があるとする。
Historyには、memoという属性があり、それの履歴が保存されていくとする。

fields_forで更新したいため、accept_nested_attributes_forを使う。ミソなのは、limit: 1にしておくことだ。

class Foo < ApplicationRecord
  has_many :histories, inverse_of: :foo
  has_one :latest_history, -> { order(created_at: :desc) }, class_name: :History

  accepts_nested_attributes_for :histories, limit: 1

  validates :name, presence: true
end

class History < ApplicationRecord
  belongs_to :foo

  validates :foo, presence: true
  validates :memo, presence: true
end

Viewの設定

Viewでは、fields_forを使って表現する。1件だけを対象にしたいので、@foo.histories.lastを使う。
なお、Viewはslimで記述、simple_formを使っている前提で書く。

= simple_form_for @foo do |f|
  = f.input :name
  = f.simple_fields_for :histories, @foo.histories.last do |fh|
    = fh.input :memo
  = f.button :submit

Controllerの設定

登録の場合

Controller側では、actionがnewの場合は、@foo.histories.buildするだけでよい。

class FoosController < ApplicationController
  # ~ 略
  def new
    @foo = Foo.new
    @foo.histories.build
  end

  def create
    @foo = Foo.new(foo_params)
    if @foo.save
      redirect_to @foo, notice: '成功'
    else
      flash.now[:alert] = '失敗'
      render :new
    end
  end
  # ~ 略

  private

    def foo_params
      params.require(:foo).permit(
        :name,
        histories_attributes: [:memo]
      )
    end
end

更新の場合

問題はeditの時である。単純に@foo.histories.buildするとmemoが空になる。
editの場合は、latest_historyからデータを取ってきて、それを使ってbuildする。

class FoosController < ApplicationController
  before_action :set_foo, only: [:show, :edit, :update, :destroy]
  # ~ 略
  def edit
    @foo.histories.build(
      # 自動で値が入るカラムを除外してbuildのパラメータとして利用する
      @foo.latest_history.attributes.except('id', 'created_at', 'updated_at')
    )
  end

  def update
    if @foo.update(foo_params)
      redirect_to @foo, notice: '成功'
    else
      flash.now[:alert] = '失敗'
      render :edit
    end
  end
  # ~ 略

  private

    def set_foo
      @foo = Foo.find params[:id]
    end

    def foo_params
      params.require(:foo).permit(
        :name,
        histories_attributes: [:memo]
      )
    end
end

これで、普段は最新の情報のみが閲覧できるが、フォームを開いたらメモの履歴は取れる状態となった。

学び

  • fields_forの引数にオブジェクトを渡すことで、数を制限できる
  • accept_nested_attributesには件数制限のできるlimitオプションがある
  • ActiveSupportにあるHashの指定キーを取り除くexceptメソッドが便利
3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2