ActiveModelにある程度機能を組み込んだベースクラス
更新履歴
- 2011/9/28 rails3.1対応
- 2011/6/27 ActiveModelImplの継承の継承したクラス対応。
- 2011/6/23 リリース
概要
- ActiveModelを使うとDBに保持しないモデルが作れます(ある程度ActiveRecord互換の機能が動く)
- ただしそのままでは簡単に使えなそげ。機能を作るのに必要な部品を提供するものっぽい
- なので機能をある程度組み込んだベースクラスがあると便利ではないかと作りました
組み込んだ機能
- attributes, attributes=での設定と取得
- dirty(どの属性が変更されたか等を取得)
下記は通常のActiveModelでも簡単に実装できるもの
- validation
- form_for等に渡せるように対応するため互換メソッド
ActiveModelを使うシチュエーション
- DB使わないところ
- 検索フォームのモデルに
- バックエンドがDBではない場合のモデルに。APIを叩く場合等
ベースクラスのコード
class ActiveModelImpl include ActiveModel::Validations include ActiveModel::Conversion extend ActiveModel::Naming include ActiveModel::Dirty def initialize(attributes = {}) @attributes = attributes_from_column_definition self.attributes = attributes end def persisted? false end def attributes=(attributes = {}) if attributes attributes.each do |name, value| send("#{name}=", value) end end end def attribute_names @attributes.keys.sort end def attributes attrs = {} attribute_names.each { |name| attrs[name] = read_attribute(name) } attrs end class_attribute :column_names self.column_names = [] def self.attr_accessor(*names) names.each do |attr_name| attr_name = attr_name.to_s self.column_names += [attr_name] generated_attribute_methods.module_eval("def #{attr_name}; read_attribute('#{attr_name}'); end", __FILE__, __LINE__) generated_attribute_methods.module_eval("def #{attr_name}=(new_value); write_attribute('#{attr_name}', new_value); end", __FILE__, __LINE__) end define_attribute_methods names end def write_attribute(attr_name, value) attr_name = attr_name.to_s attribute_will_change!(attr_name) unless value == read_attribute(attr_name) @attributes[attr_name] = value end def read_attribute(attr_name) if respond_to? "_#{attr_name}" send "_#{attr_name}" if @attributes.has_key?(attr_name.to_s) else _read_attribute attr_name end end def _read_attribute(attr_name) attr_name = attr_name.to_s value = @attributes[attr_name] value end # 変更した部分の変更後のHashを返します。もともとあるchanged_attributesは変更した部分の変更前のHashを返すので。Railsのdirty機能にはなかったので実装。 def changing_attributes changed.inject(HashWithIndifferentAccess.new){ |h, attr| h[attr] = __send__(attr); h } end # 変更履歴をクリアします。 # モデルでsaveやcreate、updateがあるならそのなかでこのメソッドを呼ぶ。 def clear_changed_attributes @previously_changed = changes @changed_attributes.clear end private def attributes_from_column_definition self.class.column_names.inject({}) do |attributes, column_name| attributes[column_name] = nil attributes end end end
利用コード
class User < ActiveModelImpl attr_accessor :name, :email validates_presence_of :name # 注意メモ # ・値を上書きする際は@email=とかは使わずにwrite_attributeを使う end user = User.new user.attributes = {:name => "test", :email => "hoge@hogehoge.hoge"} user.changed # ["name", "email"] user.changes # {"name"=>[nil, "test"], "email"=>[nil, "hoge@hogehoge.hoge"]} user.valid? # true user.attributes # {"email"=>"hoge@hogehoge.hoge", "name"=>"test"}