HanamiのEntityは、初期化した後で値を書き換えることができません。イミュータブル!
ちょっと個人的にインパクトが大きかったので記録を残しておきます。
どうやってupdateするのか
Hanamiでデータベースに保存されている値を更新するコードは以下のようになります
repository = UserRepository.new
user = repository.find(id)
repository.update(user.id, name: 'new name')
データの更新って↓のようなイメージだったのですが、Userエンティティの値を書き換えることはできないのでエラーになります。びっくりしてしまいました。
# うごかない
repository = UserRepository.new
user = repository.find(id)
user.name = 'new name' # NoMethodError: undefined method `name='
user.update
Entity内部から書き換えることもできない
オブジェクトの外側から値を書き換えられませんでしたが、内側からも同様です。ユーザーのパスワードを暗号化するために、以下のようなメソッドを作ったらエラーになりました。
class User < Hanami::Entity
def password=(password)
# # NoMethodError: undefined method `password_digest='
self.password_digest = BCrypt::Password.create(password).to_s
end
def password
BCrypt::Password.new(password_digest)
end
end
やっぱりセッターの password_digest=
メソッドが存在しないと言われます。
ソースを読んでみたら、コンストラクタの最後にObject#freeze
が実行されていました。どうやっても値を変更することはできないようです。
def initialize(attributes = nil)
@attributes = self.class.schema[attributes]
freeze
end
irb(main):001:0> User.new.frozen?
=> true
イミュータブルでも大丈夫
Entityがイミュータブルと聞いてショックでしたが、それは「Entityは値の入れものでしかない。Entityにビジネスロジック的なことを書いてはいけない」っていう風に誤解してしまったからで、よーく考えてみるとそんなに慌てることもないような気がしてきました。
値の書き換えができなくても処理は書けるし、データベースから取得したか、あるいはnewした時点の状態と変わってないことが保証されるので、安全性が増す気がします。
ちなみに、ユーザーのパスワードについてどう実装したらいいかググってみたら、Hanamiのチャットで中の人がコンストラクタでセットするといいよって言ってたので、コンストラクタをオーバーライドして以下のようになりました。
class User < Hanami::Entity
def initialize(attributes = {})
if attributes.has_key? :password
super attributes.merge(
password_digest: BCrypt::Password.create(attributes[:password])
)
else
super attributes
end
end
def password
BCrypt::Password.new(password_digest)
end
end