assign_attributesメソッド
記事投稿アプリで公開日が過去の日付なら「公開」、未来の日付なら「公開待ち」と状態を変えるのにつまづいたので忘備録として残しておきます。
前提として記事の状態state
はenumで定義されています。
# draft: 下書き, published: 公開, publish_wait: 公開待ち enum state: { draft: 0, published: 1, publish_wait: 2 }
article_controller.rbのupdateアクションがこのようになっています。
before_action :set_article, only: %i[edit update destroy] def update authorize(@article) if @article.update(article_params) flash[:notice] = '更新しました' redirect_to edit_article_path(@article.uuid) else render :edit end end def article_params params.require(:article).permit( :title, :description, :state, :published_at, :author_id ) end def set_article @article = Article.find_by!(uuid: params[:uuid]) end
@article.update(article_params)
で情報を受け取り更新処理しています。
しかし下記のように、状態を公開、日付を未来の日付にして更新した場合は、状態は公開ではなく公開待ちになるようにしたいのですが、現状のupdateアクションでは公開日がまだ来ていないにも関わらず、公開されてしまいます。
これは@article.update(article_params)
のupdateメソッド
でarticle_paramsを受け取ったときにDBにstateが公開という情報が保存されるからです。
ユーザーが、公開日を未来の日付、状態を公開で更新してしまうことは考えられるので対策が必要です。
ここでassign_attributes
メソッドを使います。
assign_attributesメソッドとは
引数で指定したattributes(属性)を変更するメソッドです。
ただしDBへの保存はされないので別途saveメソッド
で保存します。
これを踏まえ、このような流れにしたいです。
① assign_attributesメソッドで変更を受け取る
② 現在時刻が公開日を過ぎているか、いないかでstateを公開、公開待ち、と振り分ける
③ saveメソッドで保存
ではまずupdateアクションに①、③を書いていきます。
def update authorize(@article) @article.assign_attributes(article_params) # ① # ② if @article.save # ③ flash[:notice] = '更新しました' redirect_to edit_article_path(@article.uuid) else render :edit end end
こうなりました。
あとは②の処理をどのようにするかですが、FatControllerにならないようmodelにメソッドとして切り出しました。
models/article.rb def publishable? # 現在時刻が公開日を過ぎていればtrueを返す Time.current >= published_at end def change_state return if draft? # draft?はstateがdraftかどうか判定する trueならreturnによりここで処理が終わる(nilが返る) self.state = if publishable? # stateがpublishedなら:published(公開)を返す :published else # それ以外(publish_waitしか残ってないのでpublish_wait)なら:publish_wait(公開待ち)を返す :publish_wait end end
enumで定義しているのでdraft?のように直感的にstateの状態を確認できます。(draftならtrue、違えばfalseを返す)
あとはこれを②の位置に入れます。
def update authorize(@article) @article.assign_attributes(article_params) # ① @article.change_state # ② if @article.save # ③ flash[:notice] = '更新しました' redirect_to edit_article_path(@article.uuid) else render :edit end end
ではこれで再度確認してみます。
未来の日付で状態を公開にし
更新すると
無事、状態が公開待ちに変わりました。
最後に
updateメソッドとはassign_attributes + saveだったんですね。
間に何か処理を挟みたいときにassign_attributesを使うと良さそうです。
updateもupdate_attributesのalias(別名)なので
・update_attributes(DB更新あり)
・assign_attributes(DB更新なし)
のように並べてみると分かりやすいですね。
では今回は以上です。ありがとうございました。
参考サイト
まとめてオブジェクトの属性を変更したい時に便利!assign_attributesメソッド - その辺にいるWebエンジニアの備忘録
ActiveModel::AttributeAssignment