ActionView のフォームヘルパーには #date_select や #datetime_select といったヘルパーが用意されています。これらのヘルパーを利用すると、日付や日時の入力に次のようなセレクトボックスを利用した UI が表示されます。

最新の Rails では、scaffold で入力ページを作成すると #date_select や #datetime_select は使われず、代わりに #date_field や #datetime_field が使われるので、この形式のフォームはちょっと懐かしい感じがしますね。
生成された HTML を確認すると、次のような select タグが生成されていることがわかります。

ここでは birthday という datetime カラムに対して年、月、日、時、分を表す 5つのセレクトボックスが生成されています。そして、それぞれのセレクトボックスには user[birthday(1i)] から user[birthday(5i)] というインデックス付きの名前が与えられています。
日時以外を扱う、 #text_field や #number_field といったフォームでは 1カラム 1フォームが生成されますが、日時を扱う場合はこのように複数のフォームを利用して、データを入力できるようです。
実際に、このフォームを submit すると Rails は入力データから datetime データを組み立てて、ちゃんと保存してくれます。
Started POST "/users" for 127.0.0.1 at 2025-12-04 03:07:38 +0900
ActiveRecord::SchemaMigration Load (1.8ms) SELECT "schema_migrations"."version" FROM "schema_migrations" ORDER BY "schema_migrations"."version" ASC /*application='MultiparameterAssignmentTest'*/
Processing by UsersController#create as TURBO_STREAM
Parameters: {"authenticity_token" => "[FILTERED]", "user" => {"name" => "tk0miya", "birthday(1i)" => "2025", "birthday(2i)" => "12", "birthday(3i)" => "3", "birthday(4i)" => "17", "birthday(5i)" => "05"}, "commit" => "Create User"}
TRANSACTION (0.2ms) BEGIN immediate TRANSACTION /*action='create',application='MultiparameterAssignmentTest',controller='users'*/
↳ app/controllers/users_controller.rb:27:in 'block in UsersController#create'
User Create (3.3ms) INSERT INTO "users" ("birthday", "created_at", "name", "updated_at") VALUES ('2025-12-03 17:05:00', '2025-12-03 18:07:39.272852', 'tk0miya', '2025-12-03 18:07:39.272852') RETURNING "id" /*action='create',application='MultiparameterAssignmentTest',controller='users'*/
↳ app/controllers/users_controller.rb:27:in 'block in UsersController#create'
TRANSACTION (0.7ms) COMMIT TRANSACTION /*action='create',application='MultiparameterAssignmentTest',controller='users'*/
↳ app/controllers/users_controller.rb:27:in 'block in UsersController#create'
redirected to http://127.0.0.1:3000/users/1
↳ app/controllers/users_controller.rb:28:in 'block (2 levels) in UsersController#create'
Completed 302 Found in 192ms (ActiveRecord: 12.4ms (1 query, 0 cached) | GC: 0.0ms)Parameters の行を見ると、予想通り birthday(1i) や birthday(2i) といった値が個別に送信されていますね。そして、INSERT 文では birthday カラムに 2025-12-03 17:05:00 という値が設定されています。
では、この挙動はどのように実現されているのでしょうか。
この仕組みは multiparameter assignment と呼ばれているようです。「複数のパラメータを使った代入」という意味ですね。この multiparameter assignment は ActiveRecord のふたつの実装の組み合わせで実現されています。
#assign_attributes メソッドによるパラメータの集約それでは、この仕組みについてそれぞれ見ていきましょう。
#assign_attributes メソッドによるパラメータの集約ひとつめは #assign_attributes メソッドです。このメソッドは使ったことがある人も多いかもしれません。
こんな感じでモデルの属性をまとめて上書きする際に利用します。
user = User.first
user.assign_attributes(name: "tk0miya", birthday: "2025-12-03 17:05:00")この #assign_attributes メソッドは代入文と読み替えることができます。
先ほどのコードは次のコードと等価です。
user.name = "tk0miya"
user.birthday = "2025-12-03 17:05:00"#assign_attributes メソッドは ActiveRecord モデルの .new メソッドや #update メソッドなどから利用されているので、
多くの人が間接的にお世話になっているはずです。
この #assign_attributes には、multiparameter assignment を支える仕組みが組み込まれています。#assign_attributes に渡されたパラメータに (1i) や (2i) といったインデックス付きの属性名が含まれている場合、これらの値をグルーピングしてから、該当の属性に代入してくれます。
たとえば、以下のような呼び出しについて考えてみましょう。
params = {
"name" => "tk0miya",
"birthday(1i)" => "2025",
"birthday(2i)" => "12",
"birthday(3i)" => "3",
"birthday(4i)" => "17",
"birthday(5i)" => "05"
}
user.assign_attributes(params)このコードは、内部的には次のような代入処理に読み替えられます。
user.name = "tk0miya"
user.birthday = {
1 => 2025,
2 => 12,
3 => 3,
4 => 17,
5 => 5
}name キーのデータは name 属性に代入されていますが、 birthday(1i) から birthday(5i) までのデータはひとつの Hash データに変換されたうえで、birthday 属性に代入されています。
代入されるデータに着目すると、Hash のキーにはインデックスの数値の部分が利用されます。 (1i) は 1 に、 (2i) は 2 に変換されます。また、Hash の値は文字列データが数値データに変換されています。これはインデックスの i という添字によるものです。 birthday(1i) => “2025” というデータは 1 => 2025 に変換されているのです。
添字に i がついていない場合、変換は行われず文字列のまま格納されます。他には浮動小数点に変換する f という添字もサポートしているようです。
この処理は ActiveRecord::AttributeAssignment によって実現されています。
#assign_attributes メソッドによって、複数のフォームから送信された値がグルーピングされて、ひとつの属性に代入されることがわかりました。今度は代入されたあとに、どのように扱われるのかを見ていきましょう。
multiparameter assignment を支えるもうひとつの仕組みに属性へのデータ代入時の変換処理があります。ActiveRecord では、モデルの属性に値を代入する際にデータ変換が行われるのをご存知でしょうか。
たとえば、Integer 型の age というカラムを持つ User モデルに対して、文字列の “18” を代入してみます。
user = User.new
user.age = "18" # 文字列の "18" を代入
user.age #=> 18 (Integer)代入したあとに、 user.age を参照すると、数値の 18 が返ってきます。この例では age に文字列の “18” を代入していますが、属性を参照すると数値の 18 に変換されていますね。
これと同様の変換が datetime でも行われます。
user = User.new
user.birthday = {
1 => 2025,
2 => 12,
3 => 3,
4 => 17,
5 => 5
}
user.birthday #=> 2025-12-03 17:05:00 UTCこのように、datetime 型の属性に 1, 2, 3, … というキーを持つ Hash を代入すると、ActiveRecord は Hash 値をもとに datetime データを組み立ててくれます。
この処理は ActiveModel::Type::Helpers::AcceptsMultiparameterTime によって実現されています。
先ほど紹介した #assign_attributes の仕組みと組み合わせることで、 #datetime_select のような複数のセレクトボックスからの入力が実現されていることがわかりました。
実は ActiveModel で Form Object を作った場合、この multiparameter assignment は動作しません。
なぜかというと、この multiparameter assignment を実現するふたつの仕組みのうち、「#assign_attributes メソッドによるパラメータの集約」機能は ActiveRecord にしか実装されていないためです。ActiveModel のモデルには #assign_attributes メソッドが定義されているのですが、multiparameter assignment 用の実装が欠けているのです。
もう一方の「属性へのデータ代入時の変換処理」は ActiveModel::Attributes モジュールで属性の型を定義していれば利用できるのですが、肝心の機能が欠けているため意図通りに動きません。
Rails のイシューでは、これまでに何度か ActiveModel にも multiparameter assignment を導入したいという提案がされているようですが、いまのところ実現には至っていません。
10年以上話が進んでいないこともあり、しばらくは改善を見込めそうにないですね。
当面の回避策は、ActiveModel 製のモデルに ActiveRecord::AttributeAssignment モジュールを読み込ませることです。これで、ActiveModel でも multiparameter assignment を利用できるようになります。
class UserForm
include ActiveModel::Model
include ActiveModel::Attributes
include ActiveRecord::AttributeAssignment # assign_attributes を multiparameter assignment に対応させる
attribute :name, :string
attribute :birthday, :datetime # 複数フォームから datetime のデータへ変換が行われるように型を明示する
endこのようにすると、ActiveModel でも multiparameter assignment を利用できるようになります。
本記事では #datetime_select を実現している multiparameter assignment と、それを実現する裏側のふたつの仕組みについて紹介しました。
#datetime_select は複数のフォームを使ってひとつのカラムにデータを保存している#assign_attributes メソッドによるパラメータの集約ActiveRecord::AttributeAssignment を include するとよい本当はこの multiparameter assignment の応用をお見せしようと思ったのですが、紙面の都合で今回はここまでとさせてください。次回、「date picker と multiparameter assignment」(仮) でお会いしましょう。