0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【Rails】Formオブジェクトでdate_selectヘルパーを利用した時に起こりえるUnknownAttributeエラーについて

Last updated at Posted at 2021-02-01

初めに

Formオブジェクトで日付などを保存する際に使われるヘルパーメソッド「date_select」で起こりやすいUnknownAttributeエラーの原因と解決方法について纏めました。

#目次

  • Formオブジェクトとは
  • ActiveModel::Model
  • attr_accessor
  • date_selectフォームヘルパ
  • UnknownAttributeエラー
  • ActiveRecord::AttributeAssignment
  • 参考サイト

#Formオブジェクトとは
一つのフォームから複数のテーブルにデータを保存・更新する時に使われるものです。railsのモデルにはデータベースのテーブルに対応する情報が入っていますが、これらのテーブル情報を一まとめにしてくれるのがFromオブジェクトです。
例えば、日付を保存するsdate.rbとスケジュールを保存するschedule.rbという二つのモデルとそれに対応するテーブルがあるとします。

①sdate.rb
スクリーンショット 2021-02-01 11.35.20.png

①-1 sdatesテーブル
スクリーンショット 2021-02-01 11.37.20.png
①-2 sdate.rbのmigrationファイル(指定したカラム「:sdate,:syear,:smonth,:sday」)

class CreateSdates < ActiveRecord::Migration[6.0]
  def change
    create_table :sdates do |t|
      t.date :sdate 
      t.integer :syear
      t.integer :smonth
      t.integer :sday
      t.timestamps
    end
  end
end

②schedule.rb
スクリーンショット 2021-02-01 11.39.55.png
②-1 scheduleテーブル
スクリーンショット 2021-02-01 11.41.59.png
※subject,start_time,end_timeなどについては、未完成なので空欄になっています。また、null:false指定をしていないので、nullも保存できる+エラーも出ないようにしています。
②-2 schedule.rbのmigrationファイル(指定したカラム「:subject,:start_time,:end_time,:sdate」)


class CreateSchedules < ActiveRecord::Migration[6.0]
  def change
    create_table :schedules do |t|
      t.string :subject
      t.time :start_time
      t.time :end_time
      t.references :sdate, foreign_key: true
      t.timestamps
    end
  end
end

③上記①と②を纏めてくれるFormオブジェクトを作成 -> sdate_schedule.rb


class SdateSchedule
  include ActiveModel::Model
  attr_accessor :subject, :start_time, :end_time, :sdate, :syear, :smonth, :sday

  def save
    sydate = Sdate.create(sdate: sdate ,syear: syear, smonth: smonth, sday: sday)
    Schedule.create(subject: subject, start_time: start_time, end_time: end_time, sdate_id: sydate.id)
  end

end

ActiveModel::Model

FormオブジェクトはRubyファイルにクラスを作り(今回の場合SdateScheduleというクラス名になります)、その中でsaveメソッドを定義します。このsaveメソッドを定義することにより、コントローラでSdateScheduleクラスのインスタンスを生成しsaveメソッドを記述するとデータが保存される仕組みになっています。この時にモデルの生成、保存・更新などを可能にしてくれるのが「ActiveModel::Model」になります。これをincludeでSdateScheduleで使えるようにする必要があります。

attr_accessor

クラス内で定義したインスタンス変数をクラスの外でも変更・保存などができるようにするためし指定するものです。Formオブジェクトでattr_accessorを利用し、保存される変数を指定しておかないとクラスの外、ここではコントローラで値を書き込むことができなくなります。入力フォームからデータはparameterとして送られてきますが、コントローラでストロングパラメーターとして受け取る処理をした後に、Formオブジェクトのインスタンスを呼び出して保存するという形になります。

  def save
    sydate = Sdate.create(sdate: sdate ,syear: syear, smonth: smonth, sday: sday)
    Schedule.create(subject: subject, start_time: start_time, end_time: end_time, sdate_id: sydate.id)
  end

変数sydateにSdateモデルの各値をActiveModel::Modelのcreateメソッドで保存し代入します。Scheduleモデルも同じようにモデルの値をcreateメソッドで保存します。Sdateモデルと違う点はScheduleはSdateの外部キーも保存しなければならないので、上記のsydateのid情報も「sdate_id: sydate.id」というふうに外部キーとして保存します。

④コントローラ(一部)
2つのモデルsdate.rbとschedule.rbのどちらかにコントローラを用意し、Fromオブジェクトを実行するようにします。今回の場合、schedule.rbに対応するshcedulesコントローラで処理しました。

class SchedulesController < ApplicationController
(・・・前略)

def new
  @sdate_schedule = SdateSchedule.new
end

def create
  @sdate_schedule = SdateSchedule.new(schedule_params)
  @sdate_schedule.save
  redirect_to schedules_path
end

private

def schedule_params
  params.require(:sdate_schedule).permit(:subject, :start_time, :end_time, :sdate, :syear, :smonth, :sday)
end

schedule_paramsというメソッドでパラメーターを受け取り、newメソッドでインスタンス@sdate_scheduleを生成します。createメソッドでは生成したインスタンスにschedule_paramsを渡し、Formオブジェクトで定義したsaveメソッドを使いデータベースにデータを保存するようにしています。

#date_selectフォームヘルパ
入力フォームに日付を入力し、保存できるようにするために使うのがdate_selectです。今回の場合、sdateモデルにdate型のデータを保存するためにdate_selectフォームヘルパを使いました。

<h1>授業新規登録</h1>
<%= form_with model: @sdate_schedule, url: schedules_path, local: true do |f| %>
  <%= raw sprintf(
                  f.date_select(
                                :sdate,
                                {:discard_year => true,:use_month_numbers => 
                                 true,date_separator: '%s'},
                               ),
                                "<p></p>") + "<p></p>" %>
   <%= f.submit "新規登録" ,class:"#" %>
<% end %>

入力フォームで日付を入力し、新規登録ボタンを押します。
スクリーンショット 2021-02-01 12.51.10.png

これでうまくいくはずだと思いきや、次のようなエラーが出ました。
スクリーンショット 2021-02-01 12.53.38.png

UnknownAttributeエラー

UnknownAttributeエラーとはつまり未知の属性ですよという意味になります。入力フォームからparameterとしてデータが送られてきたものの、該当するデータがないということです。なぜこのようなことが起こるのでしょうか。原因を調べるためにparamsと入力し中身をみてみました。

スクリーンショット 2021-02-01 12.57.44.png
requestとして送られているparametersには先ほど入力した日付のパラメーターがちゃんと届いています。しかし、送られてきたパラメーターの形がちょっと違います。 「"sdate(1i)"=>"2021", "sdate(2i)"=>"2", "sdate(3i)"=>"1"」というふうにsdateカラムを年「sdate(1i)」、月「sdate(2i)」、日「sdate(3i)」に分解してきています。sdateテーブルにはsdateカラムしかないのに3つもカラムを分けて届いているから保存ができなかったんですね。

ActiveRecord::AttributeAssignment

これを解決してくれるのが「ActiveRecord::AttributeAssignment」です。これは公式サイトで確認すると「Allows you to set all the attributes by passing in a hash of attributes with keys matching the attribute names.」となっています。つまり、属性名と一致するキーを持つ属性の塊を渡し、そのすべての属性を設定できるとなっています。このActiveRecord::AttributeAssignmentをFromオブジェクトに記述するとsdate(1i)などでバラバラになっている属性値を一塊にして渡してくれます。
スクリーンショット 2021-02-01 13.09.52.png
sdate_schedule.rbに「include ActiveRecord::AttributeAssignment」を追記します。
これでエラーが解消し、テーブルに保存できるようになりました。

  • 参考サイト

https://api.rubyonrails.org/classes/ActiveModel/AttributeAssignment.html#method-i-assign_attributes
https://railsguides.jp/active_model_basics.html
https://qiita.com/Tiima/items/b14c73df98d0465cbb52
https://naokirin.hatenablog.com/entry/2019/02/20/231317
https://qiita.com/ngron/items/dd3cd8eb8ef58bd1c1fc

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?