データベースにテーブルを作らずに ActiveRecord を利用する
ActiveRecord 向けのビューヘルパーメソッドを使いたい
複雑な検索条件などをフォーム上で表現するとき、ページのリロード時の値の保存や値のパースに便利なように、モデルクラスを利用したいときがあるかもしれない。たとえば、
<%= select 'search_cond', 'demographic', [["10歳未満", 1], ["10代", 2], ["20代", 3], ["30歳以上", 4]] %>
などとしたいのである。問題は当然ながら search_conds などというテーブルがデータベースに存在しないことである。text_field や select など *_tag というポストフィックスが付かないビューヘルパメソッドはすべて、ActiveRecord::Base オブジェクトの存在を前提としている。どうしたらいいだろうか?
ActiveRecord をどう騙すか?
一つの戦略は、ビューヘルパメソッドを使う範囲において、ActiveRecord があたかも search_conds というテーブルがあるかのように振舞うようにする、というものだ。ビューヘルパにとっては、ActiveRecord オブジェクトの属性にアクセスできれば満足である。そして、ActiveRecord の属性は、型キャストのためにテーブルのカラム情報を参照する。これが狙い目である。
よくよく調べてみると、ActiveRecord::Base.columns というメソッドが ActiveRecord::ConnectionAdapters::Column(のデータベース固有のサブクラス)の インスタンスの配列を返している。ここで、適当なカラムの配列を返してやれば、ActiveRecord はデータベースから取得したカラム情報だと勘違いしてくれるだろう。
実装
結論から言うと次のようにすればよいようだ。
class SearchCond < ActiveRecord::Base include ActiveRecord::ConnectionAdapters t = @@table_definition = TableDefinition.new(ActiveRecord::Base.connection) t.column :name, :string t.column :demographic, :integer t.column :written_at, :datetime def self.columns @columns ||= @@table_definition.columns.map do |c| Column.new(c.name.to_s, c.default, c.sql_type, c.null) end end end
上の例では、これで SearchCond は name:string, demographic:integer, written_at:datetime というカラムをもつ search_conds というテーブルが存在するかのように振舞う。(もちろんこれは SearchCond.columns についてだけである。SearchCond.find とか実行すれば、当然エラーになる)TableDefinition オブジェクトを使っているので、マイグレーションファイルと書式は同一である。
きちんと確認はとれていないが、実は SearchCond#valid? なども動くみたいだ。フォームの検証用にも使えるかもしれない。きちんと属性の型キャストが行われている分、ActiveForm プラグインよりマシかもしれない。