対応するレコードがないフォームを使う場合、ActiveModelを使用することで、シンプルなビューを構築しつつ、処理はモデルにかけます。
しかし、ActiveModelのノウハウってあんまり落ちていません。 それなりに ActiveRecord に対する理解も必要で、難しいですね。 ハマったことなど共有していきたいと思います。
フォームからのデータは文字列ですが、ActiveRecord にはコラム自体には型があるため、型変換を自動的に行ってくれます。 これを無意識に使用していると ActiveModelではまります。
具体的には以下のテーブルがあったとします。
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :name
t.integer :age
t.boolean :is_person
t.timestamps
end
end
end
利用例を見てみましょう。
user = User.new(age: '20')
user.age # => 20
user.age.class # => Fixnum
user = User.new(is_person: "1")
user.is_person # => true
user.is_person.class # => TrueClass
文字列から作成しているけども、自動的に数値や、真偽値へと変換されています。
ActiveModel を使用する場合は以下のように実装しておくとよさそうです。
class User2
attr_reader :age, :is_person
include ActiveRecord::ConnectionAdapters
def initialize(attributes = {})
attributes.each do |key, value|
send("#{key}=",value)
end
end
def age=(age)
@age = age.to_i
end
def is_person=(is_person)
@is_person = Column.value_to_boolean(is_person)
end
end
代入する時に値を修正するのが インスタンス変数に直接アクセスした場合にも型が保証できて良いです。
「別に文字列でもいいよ。」なんてこともあると思いますが、
数値だとおもってうっかり使うと '20' * 3 -> '202020'
となって欲しい 40
とは大きく違います。
チェックボックスを利用すると "1"
などなど、値として降ってきます。
特に 真偽値への変換ですが、とりあえずわからなかったので、自前でごまかしていたのですが、調べました。
ActiveRecord::ConnectionAdapters::Column
にさままな型変換のメソッドが実装されています。
その中の value_to_boolean
を使用しました。
def value_to_boolean(value)
if value.is_a?(String) && value.blank?
nil
else
TRUE_VALUES.include?(value)
end
end
true
になる値は以下のように定義されてます。
[true, 1, '1', 't', 'T', 'true', 'TRUE', 'on', 'ON']
他にも
- value_to_integer
- value_to_decimal
- string_to_time
- string_to_date
などのメソッドがありました。
まとめ
単体テストをかくときには文字列を渡すようにしたほうが良いかもしれません。 しかし、このような状況下になることはあまりないので、そんなに気にしなくても良いかなーという感じです。
テーブルつくれることならテーブルを作るほうが手軽そうです。
サンプルコード はこちらに置いてます。
ActiveModel を使用するには 積極的にActiveRecord への理解を深める必要があります。 ソースコードもそこまで複雑ではないと思いますので、Rdocやソースコードも読んでいきたいですね。