--- /dev/null
+*~
+*.log
+*.db
+
--- /dev/null
+ContextMonitor
+===========
+
+This plugin provides the capability for monitoring operations (create/update).
+
+Example
+=======
+
+ ActiveRecord::Schema.define(:version => 0) do
+ create_table :articles do |t|
+ t.string :name
+ t.integer :created_by_id
+ t.integer :updated_by_id
+ t.timestamps
+ end
+ end
+
+ class Article < ActiveRecord::Base
+ context_monitor :user, :suffix => 'by'
+ end
+
+ User.current = User.find(10)
+ article = Article.create!(:name => 'example')
+ p article.created_by.id # 10
+ p article.updated_by.id # 10
+ p article.created_by == User.current # true
+ p article.updated_by == User.current # true
+
+ User.current = User.find(20)
+ article.name = 'test'
+ p article.created_by.id # 10
+ p article.updated_by.id # 20
+ p article.created_by == User.current # false
+ p article.updated_by == User.current # true
+
+
+Copyright (c) 2008 Good-Day, Inc
--- /dev/null
+ContextMonitor
+===========
+
+このプラグインはレコードに対する create/update を記録する機能を提供します。
+
+Example
+=======
+
+ ActiveRecord::Schema.define(:version => 0) do
+ create_table :articles do |t|
+ t.string :name
+ t.integer :created_by_id
+ t.integer :updated_by_id
+ t.timestamps
+ end
+ end
+
+ class Article < ActiveRecord::Base
+ context_monitor :user, :suffix => 'by'
+ end
+
+ User.current = User.find(10)
+ article = Article.create!(:name => 'example')
+ p article.created_by.id # 10
+ p article.updated_by.id # 10
+ p article.created_by == User.current # true
+ p article.updated_by == User.current # true
+
+ User.current = User.find(20)
+ article.name = 'test'
+ p article.created_by.id # 10
+ p article.updated_by.id # 20
+ p article.created_by == User.current # false
+ p article.updated_by == User.current # true
+
+
+Copyright (c) 2008 Good-Day, Inc
--- /dev/null
+require 'rake'
+require 'rake/testtask'
+require 'rake/rdoctask'
+require 'rake/gempackagetask'
+require 'spec/rake/spectask'
+
+desc 'Default: run unit tests.'
+task :default => :spec
+
+Spec::Rake::SpecTask.new do |t|
+ t.spec_files = FileList['spec/*_spec.rb']
+ t.spec_opts = ['-c']
+end
+
+PKG_FILES = FileList[
+ 'lib/monitor.rb',
+ 'rails/init.rb',
+ 'tasks/monitor_tasks.rake',
+ 'spec/*'
+]
+spec = Gem::Specification.new do |s|
+ s.name = 'monitor'
+ s.version = '0.0.1'
+ s.author = 'Good-Day, Inc.'
+ s.email = 'info@good-day.co.jp'
+ s.homepage = 'http://www.good-day.jp/'
+ s.platform = Gem::Platform::RUBY
+ s.summary = 'Add functionallity of monitoring operations'
+ s.files = PKG_FILES.to_a
+ s.require_path = 'lib'
+ s.has_rdoc = false
+ s.extra_rdoc_files = ['README']
+end
+
+desc 'Turn this plugin into a gem.'
+Rake::GemPackageTask.new(spec) do |pkg|
+ pkg.gem_spec = spec
+end
+
+desc 'Generate documentation for the context_monitor plugin.'
+Rake::RDocTask.new(:rdoc) do |rdoc|
+ rdoc.rdoc_dir = 'rdoc'
+ rdoc.title = 'Monitor'
+ rdoc.options << '--line-numbers' << '--inline-source'
+ rdoc.rdoc_files.include('README')
+ rdoc.rdoc_files.include('lib/**/*.rb')
+end
+
--- /dev/null
+# -*- coding: utf-8 -*-
+module ActiveRecord
+
+ # 作成時および更新時の情報を記録するモジュール。
+ module ContextMonitor
+
+ def self.included(base) # :ndoc:
+ base.extend(ClassMethods)
+ end
+
+ module ClassMethods
+ #
+ # モデルクラスで呼び出すことにより指定した対象の状況を記録するようになる。
+ #
+ # <tt>monitor</tt> : 記録する対象のモデルクラスの名前を指定する。
+ # デフォルトは <tt>User</tt>
+ # ここで使用するモデルクラスには current_id というクラスメソッドが
+ # 定義されている必要がある。
+ #
+ def context_monitor(target_model_name = :user, options = {})
+ model_name = target_model_name.to_s.capitalize
+ suffix = (options[:suffix] || 'by').to_s
+ created = 'created_' + suffix
+ updated = 'updated_' + suffix
+
+ class_eval do
+ callbacks = Callbacks.new(model_name, created, updated)
+ before_create callbacks
+ before_update callbacks
+ belongs_to created, :class_name => model_name, :foreign_key => "#{created}_id"
+ belongs_to updated, :class_name => model_name, :foreign_key => "#{updated}_id"
+ end
+ end
+ alias monitor context_monitor
+ end
+
+ class Callbacks # :ndoc: all
+ def initialize(model_name, created, updated)
+ @model = model_name.constantize
+ @created_id = "#{created}_id"
+ @updated_id = "#{updated}_id"
+ end
+ def before_create(record)
+ record.write_attribute(@created_id, @model.current_id) if include_column_and_nil?(record, @created_id)
+ before_update record
+ end
+ def before_update(record)
+ record.write_attribute(@updated_id, @model.current_id) if include_column?(record, @updated_id)
+ end
+
+ private
+ def column_names(record)
+ @column_names ||= record.class.column_names
+ end
+
+ def include_column?(record, column)
+ column_names(record).include? column
+ end
+
+ def include_column_and_nil?(record, column)
+ include_column?(record, column) && record.read_attribute(column).nil?
+ end
+ end
+ end
+end
+
+ActiveRecord::Base.__send__(:include, ActiveRecord::ContextMonitor)
+
--- /dev/null
+require "context_monitor"
--- /dev/null
+# -*- coding: utf-8 -*-
+
+require File.join(File.dirname(__FILE__), 'spec_helper')
+
+module CurrentMethods
+ def current=(user)
+ @current = user
+ end
+ def current
+ @current
+ end
+ def current_id
+ current.id if current
+ end
+end
+
+describe ActiveRecord::ContextMonitor do
+ module ActiveRecord::ContextMonitor
+ describe ClassMethods do
+ it 'defines context_monitor' do
+ ClassMethods.instance_methods.should be_include('context_monitor')
+ end
+
+ it 'defines alias monitor' do
+ ClassMethods.instance_methods.should be_include('monitor')
+ end
+ end
+
+ describe Callbacks do
+ before do
+ @created_by = 'created_by'
+ @updated_by = 'updated_by'
+ @callbacks = Callbacks.new('Object', @created_by, @updated_by)
+
+ @a, @b, @c = 'a', 'b', 'c'
+ @record = mock('record', :null_object => true)
+ @record.stub(:class => @record)
+ end
+
+ it 'sets model and needed ids' do
+ created_by, updated_by = @created_by, @updated_by
+ @callbacks.instance_eval do
+ @model.should == Object
+ @created_id.should == "#{created_by}_id"
+ @updated_id.should == "#{updated_by}_id"
+ end
+ end
+
+ it 'can list all column names' do
+ @record.should_receive(:column_names).and_return([@a, @b, @c])
+ column_names = @callbacks.__send__(:column_names, @record)
+ column_names.should == [@a, @b, @c]
+ end
+
+ it 'can test does record has column' do
+ @record.stub(:column_names).and_return([@a, @b, @c])
+ [@a, @b, @c].each do |target|
+ @callbacks.__send__(:include_column?, @record, target).should be_true
+ end
+ [nil, :hoge, 'test', 1].each do |target|
+ @callbacks.__send__(:include_column?, @record, target).should be_false
+ end
+ end
+
+ it 'can test does record has column and it value is nil' do
+ column = :test
+ @callbacks.should_receive(:include_column?).
+ with(@record, column).and_return(true)
+ @record.should_receive(:read_attribute).with(column).and_return(nil)
+ @callbacks.__send__(:include_column_and_nil?, @record, column).should be_true
+ end
+ end
+ end
+
+ describe 'Default user' do
+ before(:all) do
+ class User < ActiveRecord::Base
+ extend CurrentMethods
+ end
+
+ class Article < ActiveRecord::Base
+ context_monitor :user
+ end
+ end
+
+ describe 'create' do
+ before do
+ @user = User.create(:name => 'test')
+ User.current = @user
+ @article = Article.create!(:title => 'title',
+ :body => 'body' * 100)
+ end
+ after do
+ @user = nil
+ User.current = nil
+ end
+ it 'has same id' do
+ @article.created_by_id.should == @user.id
+ @article.updated_by_id.should == @user.id
+ end
+ it 'has same object' do
+ @article.created_by.should == @user
+ @article.updated_by.should == @user
+ end
+ end
+
+ describe 'update' do
+ before do
+ @user1 = ::User.create(:name => 'alice')
+ User.current = @user1
+ @article = ::Article.create!(:title => 'title',
+ :body => 'body' * 100)
+ @article.title = 'change title'
+ @user2 = ::User.create(:name => 'bob')
+ User.current = @user2
+ @article.save!
+ end
+ after do
+ @user1 = nil
+ @user2 = nil
+ User.current = nil
+ end
+ it 'has collect id' do
+ @article.created_by_id.should == @user1.id
+ @article.updated_by_id.should == @user2.id
+ end
+ it 'has collect object' do
+ @article.created_by.should == @user1
+ @article.updated_by.should == @user2
+ end
+ end
+
+ describe "no current user" do
+ before do
+ @user = User.create(:name => 'good')
+ @article = Article.create!(:title => 'title',
+ :body => 'body' * 100)
+ end
+ after do
+ @user = nil
+ User.current = nil
+ end
+ it 'all is nil' do
+ @article.created_by_id.should be_nil
+ @article.updated_by_id.should be_nil
+ @article.created_by.should be_nil
+ @article.updated_by.should be_nil
+ end
+ end
+ end
+
+ describe 'Set Class and Suffix' do
+ before(:all) do
+ class Country < ActiveRecord::Base
+ extend CurrentMethods
+ end
+
+ class Car < ActiveRecord::Base
+ context_monitor :country, :suffix => :in
+ end
+ end
+
+ before do
+ @country1 = Country.create(:name => 'Japan')
+ Country.current = @country1
+ @car = Car.create!(:name => 'XXX', :price => 100)
+ @country2 = Country.create(:name => 'America')
+ end
+
+ after do
+ @country1 = nil
+ Country.current = nil
+ end
+
+ it 'has same id' do
+ @car.created_in_id.should == @country1.id
+ @car.updated_in_id.should == @country1.id
+ end
+
+ it 'has same object' do
+ @car.created_in.should == @country1
+ @car.updated_in.should == @country1
+ end
+
+ it 'has collect id and object when update' do
+ Country.current = @country2
+ @car.price = 200
+ @car.save!
+
+ @car.created_in_id.should == @country1.id
+ @car.updated_in_id.should == @country2.id
+ @car.created_in.should == @country1
+ @car.updated_in.should == @country2
+ end
+ end
+end
+
--- /dev/null
+
+sqlite3:
+ :adapter: sqlite3
+ :database: spec/db/tiny_monitor.sqlite3.db
+
+# postgresql:
+# :adapter: postgresql
+# :username: rails
+# :password: rails
+# :database: display_monitor_test
+# :min_messages: ERROR
+#
+# mysql:
+# :adapter: mysql
+# :host: localhost
+# :username: rails
+# :password: rails
+# :database: display_monitor_test
+
--- /dev/null
+# -*- coding: utf-8 -*-
+
+ActiveRecord::Schema.define(:version => 0) do
+ create_table :users, :force => true do |t|
+ t.string :name
+ end
+ create_table :articles, :force => true do |t|
+ t.string :title
+ t.text :body
+ t.integer :created_by_id
+ t.integer :updated_by_id
+ end
+ create_table :countries, :force => true do |t|
+ t.string :name
+ end
+ create_table :cars, :force => true do |t|
+ t.string :name
+ t.integer :price
+ t.integer :created_in_id
+ t.integer :updated_in_id
+ end
+end
+
--- /dev/null
+# -*- coding: utf-8 -*-
+
+plugin_spec_dir = File.dirname(__FILE__)
+
+$:.unshift File.join(plugin_spec_dir, '..', 'lib')
+
+require 'active_record'
+require 'rubygems'
+require 'spec'
+
+ActiveRecord::Base.logger = Logger.new(plugin_spec_dir + "/debug.log")
+
+database = YAML.load(File.read(plugin_spec_dir + '/db/database.yml'))
+ActiveRecord::Base.establish_connection(database[ENV['DB'] || 'sqlite3'])
+load File.join(plugin_spec_dir, 'db', 'schema.rb')
+
+require File.join(plugin_spec_dir, '..', 'rails', 'init')
+