From 38841736f969c386661fc0bc13526b842d260880 Mon Sep 17 00:00:00 2001 From: okimoto Date: Wed, 9 Dec 2009 14:58:16 +0900 Subject: [PATCH 1/1] initialize repository --- .gitignore | 4 + README | 37 ++++++++ README.ja | 37 ++++++++ Rakefile | 48 +++++++++++ lib/context_monitor.rb | 68 +++++++++++++++ rails/init.rb | 1 + spec/context_monitor_spec.rb | 197 +++++++++++++++++++++++++++++++++++++++++++ spec/db/database.yml | 19 +++++ spec/db/schema.rb | 23 +++++ spec/spec_helper.rb | 18 ++++ 10 files changed, 452 insertions(+) create mode 100644 .gitignore create mode 100644 README create mode 100644 README.ja create mode 100644 Rakefile create mode 100644 lib/context_monitor.rb create mode 100644 rails/init.rb create mode 100644 spec/context_monitor_spec.rb create mode 100644 spec/db/database.yml create mode 100644 spec/db/schema.rb create mode 100644 spec/spec_helper.rb diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3b53699 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*~ +*.log +*.db + diff --git a/README b/README new file mode 100644 index 0000000..364d619 --- /dev/null +++ b/README @@ -0,0 +1,37 @@ +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 diff --git a/README.ja b/README.ja new file mode 100644 index 0000000..1d436a0 --- /dev/null +++ b/README.ja @@ -0,0 +1,37 @@ +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 diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..e1724b5 --- /dev/null +++ b/Rakefile @@ -0,0 +1,48 @@ +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 + diff --git a/lib/context_monitor.rb b/lib/context_monitor.rb new file mode 100644 index 0000000..edcfc31 --- /dev/null +++ b/lib/context_monitor.rb @@ -0,0 +1,68 @@ +# -*- coding: utf-8 -*- +module ActiveRecord + + # 作成時および更新時の情報を記録するモジュール。 + module ContextMonitor + + def self.included(base) # :ndoc: + base.extend(ClassMethods) + end + + module ClassMethods + # + # モデルクラスで呼び出すことにより指定した対象の状況を記録するようになる。 + # + # monitor : 記録する対象のモデルクラスの名前を指定する。 + # デフォルトは User + # ここで使用するモデルクラスには 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) + diff --git a/rails/init.rb b/rails/init.rb new file mode 100644 index 0000000..2038d66 --- /dev/null +++ b/rails/init.rb @@ -0,0 +1 @@ +require "context_monitor" diff --git a/spec/context_monitor_spec.rb b/spec/context_monitor_spec.rb new file mode 100644 index 0000000..3a272be --- /dev/null +++ b/spec/context_monitor_spec.rb @@ -0,0 +1,197 @@ +# -*- 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 + diff --git a/spec/db/database.yml b/spec/db/database.yml new file mode 100644 index 0000000..e7da6f8 --- /dev/null +++ b/spec/db/database.yml @@ -0,0 +1,19 @@ + +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 + diff --git a/spec/db/schema.rb b/spec/db/schema.rb new file mode 100644 index 0000000..baac4b8 --- /dev/null +++ b/spec/db/schema.rb @@ -0,0 +1,23 @@ +# -*- 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 + diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..36ba9d2 --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,18 @@ +# -*- 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') + -- 2.11.0