(スキルレベル:知識ゼロ。プラグインてなに?)
少し調べてみたところ、ActiveRecordを拡張する acts_as_xxx という手法(フレームワーク?)が適当そうだったため、その作法に則ります。テーブルやカラムの追加等、あまり複雑なことはせず、ActiveRecord作成日付フィールド(普通は created_at)だけを参照します。
参考にしたのは、↓の記事と acts_as_taggable_on_steroidsプラグインのソース。
プラグイン作成
まずは、pluginをgenerateします。プラグイン名は acts_as_archivable。
$ script/generate plugin acts_as_archivablegenerate を実行すると、vendor/plugin 以下に acts_as_archivable ディレクトリ(と中にプラグインの雛形)が作られます。
generateされたファイルのうち、編集したのは以下の2つだけです。
- init.rb
- lib/acts_as_archivable.rb
# init.rb
require 'acts_as_archivable'
ActiveRecord::Base.class_eval do
include ActiveRecord::Acts::Archivable
end
init.rb は Railsによりアプリケーション初期化時に読み込まれます(西和則著「Ruby on Rails入門―優しいRailsの育て方」)。ここでは、ActiveRecord::Baseクラスに、ActiveRecord::Acts::Archivable モジュールをMix-inしているだけです。
Archivableモジュールの実体は、lib/acts_as_archivable.rb の中で定義します。
# acts_as_archivable.rb
module ActiveRecord
module Acts
module Archivable
def self.included(base)
base.extend(ActMacro)
end
end
module ActMacro
# acts_as_ メソッド(ActiveRecordクラス内で呼び出す)
# 例外処理、クラスメソッドのインクルード
def acts_as_archivable(col_name = "created_at")
# 指定された作成日カラムがActiveRecordクラスになければ例外を発生させる
raise NoCreatedAtColumnError unless self.column_names.include? col_name
# 指定された作成日カラムの型が:datetimeでなければ例外を発生させる
raise TypeError unless self.columns_hash[col_name].type.equal? :datetime
# クラスメソッドをインクルード
self.extend(ClassMethods)
self.set_created_col_name col_name
end
class NoCreatedAtColumnError < StandardError; end
class TypeError < StandardError; end
end
module ClassMethods
# ActiveRecordと、作成日カラム名を紐付けるマップ
@@class_col_hash = Hash.new
def set_created_col_name col_name
@@class_col_hash.store self.class.name, col_name
end
def get_created_col_name
@@class_col_hash[self.class.name]
end
# 月別アーカイブ作成支援メソッド
# ここでは、月ごとに作成されたActiveRecordの件数をもつハッシュを作成して返している
# ex. {2008=>{5=>10,8=>3,11=>6},2009=>{1=>5,3=>5,...}}
# パフォーマンス/使い易さを考えると、もう少しやりようがあると思うけどとりあえず。。。
def monthly_archive_counts
counts = Hash.new
records = self.find(:all)
records.each do |record|
year = record[get_created_col_name].year
month = record[get_created_col_name].month
if counts[year] == nil
counts.store year, Hash.new(0)
end
counts[year][month] += 1
end
return counts
end
end
end
end
self.includedというのはModuleクラスのメソッドで、リファレンスマニュアルの説明には
self が include されたときに対象のクラスまたはモジュールを引数にインタプリタから呼び出されます。
とあります。
acts_as_archivableが、(プラグインに慣れている人にはたぶんおなじみの)、拡張対象のActiveRecordクラスから呼び出すメソッドになります。
作成日カラムとして"created_at"以外を使う場合に対応できるよう、作成日カラム名を与えるようにしています(デフォルト値は"created_at")。
- 指定された作成日カラムが存在しない場合
- 存在するが :datetime タイプでない場合
例外処理のあと、ActiveRecordと作成日カラム名の対応づけと、目的となるクラスメソッドのインクルードを行います。
使いかた
準備として
class Bookmark < ActiveRecord::Base
acts_as_archivable
end
のように ActiveRecord クラス内で acts_as_archivable メソッドを呼ぶだけです。これで、Bookmarkクラスに monthly_archive_counts メソッドが追加されます。
bookmarksテーブルに
sqlite> select id, created_at from bookmarks;
5|2008-10-01 09:00:00
6|2008-10-01 09:00:00
7|2008-10-01 09:00:00
8|2008-11-01 09:00:00
9|2009-02-01 09:00:00
10|2009-02-01 09:00:00
11|2009-04-01 09:00:00
12|2009-04-01 09:00:00
13|2009-04-01 09:00:00
14|2009-04-01 09:00:00
16|2009-06-01 09:00:00
というデータが入っているとき、Bookmark.monthly_archive_counts は次のハッシュを返します。
$ script/console
Loading development environment (Rails 2.3.3)
>> counts = Bookmark.monthly_archive_counts
=> {2008=>{10=>3, 11=>1}, 2009=>{2=>2, 4=>4, 6=>1}}
>> counts[2009][4]
=> 4
ビューでの使用イメージはこんな感じ。

ちゃんと使えるプラグインを作ろうとすると、テーブルやカラムを追加したり色々複雑になりますが、acts_asプラグインの骨格としてはこんな感じでしょうか。
よくできてるなー。
Mix-inの強力さを(あらためて)実感。
0 件のコメント:
コメントを投稿