├── init.rb ├── install.rb ├── uninstall.rb ├── .gitignore ├── lib ├── record_with_operator │ ├── version.rb │ ├── operator.rb │ └── recorder.rb ├── tasks │ └── record_with_operator_tasks.rake ├── helpers │ └── migration_helper.rb └── record_with_operator.rb ├── Gemfile ├── test ├── database.yml ├── test_helper.rb ├── record_with_operator_reflection_test.rb ├── schema.rb ├── record_with_operator_has_many_dependent_test.rb ├── record_with_operator_user_class_name_test.rb ├── record_with_operator_belongs_to_association_test.rb ├── record_with_operator_has_one_association_test.rb └── record_with_operator_test.rb ├── Rakefile ├── MIT-LICENSE ├── record_with_operator.gemspec └── README.rdoc /init.rb: -------------------------------------------------------------------------------- 1 | require 'record_with_operator' 2 | -------------------------------------------------------------------------------- /install.rb: -------------------------------------------------------------------------------- 1 | # Install hook code here 2 | -------------------------------------------------------------------------------- /uninstall.rb: -------------------------------------------------------------------------------- 1 | # Uninstall hook code here 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | .bundle 3 | Gemfile.lock 4 | pkg/* 5 | .idea/* 6 | test/debug.log 7 | -------------------------------------------------------------------------------- /lib/record_with_operator/version.rb: -------------------------------------------------------------------------------- 1 | module RecordWithOperator 2 | VERSION = "1.0.1" 3 | end 4 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | 3 | # Specify your gem's dependencies in record_with_operator.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /lib/tasks/record_with_operator_tasks.rake: -------------------------------------------------------------------------------- 1 | # desc "Explaining what the task does" 2 | # task :record_with_operator do 3 | # # Task goes here 4 | # end 5 | -------------------------------------------------------------------------------- /lib/record_with_operator/operator.rb: -------------------------------------------------------------------------------- 1 | module RecordWithOperator 2 | module Operator 3 | def operator=(o) 4 | Thread.current[:operator] = o 5 | end 6 | 7 | def operator 8 | Thread.current[:operator] 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/helpers/migration_helper.rb: -------------------------------------------------------------------------------- 1 | require 'active_record/connection_adapters/abstract/schema_definitions' 2 | ActiveRecord::ConnectionAdapters::TableDefinition.class_eval do 3 | def operator_stamps 4 | column(:created_by, :integer) 5 | column(:updated_by, :integer) 6 | column(:deleted_by, :integer) 7 | end 8 | end -------------------------------------------------------------------------------- /test/database.yml: -------------------------------------------------------------------------------- 1 | #sqlite: 2 | # :adapter: sqlite 3 | # :dbfile: record_with_operator_plugin.sqlite.db 4 | sqlite3: 5 | :adapter: sqlite3 6 | :database: ':memory:' 7 | #postgresql: 8 | # :adapter: postgresql 9 | # :username: postgres 10 | # :password: postgres 11 | # :database: record_with_operator_plugin_test 12 | # :min_messages: ERROR 13 | #mysql: 14 | # :adapter: mysql 15 | # :host: localhost 16 | # :username: 17 | # :password: 18 | # :database: record_with_operator_plugin_test 19 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | $:.push File.expand_path('../../', __FILE__) 2 | 3 | require 'logger' 4 | require 'test/unit' 5 | require 'active_record' 6 | require 'active_support' 7 | require 'active_support/test_case' 8 | 9 | require 'lib/record_with_operator' 10 | 11 | config = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml')) 12 | ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + "/debug.log") 13 | ActiveRecord::Base.establish_connection(config['sqlite3']) 14 | 15 | load(File.dirname(__FILE__) + "/schema.rb") 16 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'rake' 3 | require 'rake/clean' 4 | require 'rake/testtask' 5 | #require 'rdoc/task' 6 | #require 'rake/gempackagetask' 7 | 8 | desc 'Default: run unit tests.' 9 | task :default => :test 10 | 11 | desc 'Test the record_with_operator plugin.' 12 | Rake::TestTask.new(:test) do |t| 13 | t.libs << 'lib' 14 | t.libs << 'test' 15 | t.pattern = 'test/**/*_test.rb' 16 | t.verbose = true 17 | end 18 | 19 | #desc 'Generate documentation for the record_with_operator plugin.' 20 | #Rake::RDocTask.new(:rdoc) do |rdoc| 21 | # rdoc.rdoc_dir = 'rdoc' 22 | # rdoc.title = 'RecordWithOperator' 23 | # rdoc.options << '--line-numbers' << '--inline-source' 24 | # rdoc.rdoc_files.include('README.rdoc') 25 | # rdoc.rdoc_files.include('lib/**/*.rb') 26 | #end 27 | -------------------------------------------------------------------------------- /lib/record_with_operator.rb: -------------------------------------------------------------------------------- 1 | module RecordWithOperator 2 | def self.config 3 | @config ||= { 4 | :operator_class_name => "User", 5 | :creator_column => "created_by", 6 | :updater_column => "updated_by", 7 | :deleter_column => "deleted_by", 8 | :operator_association_options => {}} 9 | @config 10 | end 11 | 12 | def self.operator_class_name 13 | config[:operator_class_name] 14 | end 15 | 16 | def self.creator_column 17 | config[:creator_column] 18 | end 19 | 20 | def self.updater_column 21 | config[:updater_column] 22 | end 23 | 24 | def self.deleter_column 25 | config[:deleter_column] 26 | end 27 | end 28 | 29 | require 'record_with_operator/operator' 30 | require 'record_with_operator/recorder' 31 | 32 | RecordWithOperator.send :extend, RecordWithOperator::Operator 33 | 34 | ActiveRecord::Base.send :include, RecordWithOperator::Operator 35 | ActiveRecord::Base.send :include, RecordWithOperator::Recorder 36 | -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 [name of plugin creator] 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /test/record_with_operator_reflection_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class User < ActiveRecord::Base 4 | end 5 | 6 | class NoteForReflectionTest < ActiveRecord::Base 7 | set_table_name "notes" 8 | 9 | records_with_operator_on :create, :update, :destroy 10 | end 11 | 12 | class CreatorNoteForReflectionTest < ActiveRecord::Base 13 | set_table_name "creator_notes" 14 | 15 | records_with_operator_on :create 16 | end 17 | 18 | class RecordWithOperatorReflectionTest < ActiveSupport::TestCase 19 | def setup 20 | RecordWithOperator.config[:operator_class_name] = "User" 21 | @user1 = User.create!(:name => "user1") 22 | raise "@user1.id is nil" unless @user1.id 23 | @note = NoteForReflectionTest.create!(:body => "test", :operator => @user1) 24 | 25 | @creator_note = CreatorNoteForReflectionTest.create!(:body => "test", :operator => @user1) 26 | end 27 | 28 | def test_include 29 | assert NoteForReflectionTest.find(:all, :include => [:creator, :updater]) 30 | end 31 | 32 | def test_joins 33 | assert NoteForReflectionTest.find(:all, :joins => [:creator, :updater]) 34 | end 35 | 36 | def test_include_missing_association 37 | assert_raise(ActiveRecord::ConfigurationError) { CreatorNoteForReflectionTest.find(:all, :include => [:updater]) } 38 | end 39 | 40 | def test_joins_missing_association 41 | assert_raise(ActiveRecord::ConfigurationError) { CreatorNoteForReflectionTest.find(:all, :joins => [:updater]) } 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /record_with_operator.gemspec: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby; coding: utf-8 -*- 2 | $:.push File.expand_path("../lib", __FILE__) 3 | require "record_with_operator/version" 4 | 5 | Gem::Specification.new do |s| 6 | s.name = "record_with_operator" 7 | s.version = RecordWithOperator::VERSION 8 | s.platform = Gem::Platform::RUBY 9 | s.authors = ["Yasuko Ohba"] 10 | s.email = ["y.ohba@everyleaf.com"] 11 | s.homepage = "https://github.com/nay/record_with_operator" 12 | s.summary = %q{Rails plugin to set created_by, updated_by, deleted_by to ActiveRecord objects. Supports associations.} 13 | s.description = %q{RecordWithOperator is a rails plugin that makes your all active record models to be saved or logically deleted with created_by, updated_by, deleted_by automatically. Also it makes creator, updater, deleter association (belongs_to) if the class has created_by, updated_by, deleted_by.} 14 | 15 | s.rubyforge_project = "record_with_operator" 16 | 17 | s.files = `git ls-files`.split("\n") 18 | s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") 19 | s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } 20 | s.extra_rdoc_files = ['README.rdoc'] 21 | s.require_paths = ["lib"] 22 | 23 | s.licenses = ["MIT"] 24 | 25 | s.add_dependency 'activerecord' 26 | s.add_development_dependency 'bundler', ['>= 1.0.0'] 27 | s.add_development_dependency 'rake', ['>= 0.8.7'] 28 | s.add_development_dependency 'sqlite3', ['>= 0'] 29 | end 30 | -------------------------------------------------------------------------------- /test/schema.rb: -------------------------------------------------------------------------------- 1 | ActiveRecord::Schema.define(:version => 1) do 2 | 3 | create_table :simple_notes, :force => true do |t| 4 | t.column :body, :text 5 | end 6 | 7 | create_table :creator_notes, :force => true do |t| 8 | t.column :body, :text 9 | t.column :created_by, :integer 10 | t.column :created_at, :datetime 11 | end 12 | 13 | create_table :updater_notes, :force => true do |t| 14 | t.column :body, :text 15 | t.column :updated_by, :integer 16 | t.column :updated_at, :datetime 17 | end 18 | 19 | create_table :deleter_notes, :force => true do |t| 20 | t.column :body, :text 21 | t.column :deleted_by, :integer 22 | t.column :deleted_at, :datetime 23 | end 24 | 25 | create_table :notes, :force => true do |t| 26 | t.column :memo_id, :integer 27 | t.column :body, :text 28 | t.column :created_by, :integer 29 | t.column :created_at, :datetime 30 | t.column :updated_by, :integer 31 | t.column :updated_at, :datetime 32 | t.column :deleted_by, :integer 33 | t.column :deleted_at, :datetime 34 | end 35 | 36 | create_table :memos, :force => true do |t| 37 | t.column :note_id, :integer 38 | t.column :body, :text 39 | t.column :created_by, :integer 40 | t.column :created_at, :datetime 41 | t.column :updated_by, :integer 42 | t.column :updated_at, :datetime 43 | t.column :deleted_by, :integer 44 | t.column :deleted_at, :datetime 45 | end 46 | 47 | create_table :users, :force => true do |t| 48 | t.column :name, :string 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /test/record_with_operator_has_many_dependent_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class User < ActiveRecord::Base 4 | end 5 | 6 | class NoteWithUserWithDependency < ActiveRecord::Base 7 | set_table_name "notes" 8 | has_many :memos, :class_name => "MemoWithUserWithDependency", :foreign_key => "note_id", :dependent => :destroy 9 | 10 | before_destroy :check_operator 11 | 12 | private 13 | def check_operator 14 | raise "can't destroy without operator" unless operator 15 | end 16 | end 17 | 18 | class MemoWithUserWithDependency < ActiveRecord::Base 19 | set_table_name "memos" 20 | 21 | before_destroy :check_operator 22 | 23 | private 24 | def check_operator 25 | raise "can't destroy without operator" unless operator 26 | end 27 | end 28 | 29 | 30 | class RecordWithOperatorHasManyDependentTest < ActiveSupport::TestCase 31 | def setup 32 | RecordWithOperator.config[:operator_class_name] = "User" 33 | @user1 = User.create!(:name => "user1") 34 | raise "@user1.id is nil" unless @user1.id 35 | @user2 = User.create!(:name => "user2") 36 | raise "@user2.id is nil" unless @user2.id 37 | @note_created_by_user1 = NoteWithUserWithDependency.create!(:body => "test", :operator => @user1) 38 | @note_created_by_user1.memos.create! 39 | @note_created_by_user1.memos.create! 40 | @note_created_by_user1.reload 41 | end 42 | 43 | def test_memos_should_be_destroyed_when_note_is_destroyed 44 | @note_created_by_user1.destroy 45 | assert_nil NoteWithUserWithDependency.find_by_id(@note_created_by_user1.id) 46 | assert MemoWithUserWithDependency.find_all_by_note_id(@note_created_by_user1.id).empty? 47 | end 48 | 49 | end 50 | -------------------------------------------------------------------------------- /test/record_with_operator_user_class_name_test.rb: -------------------------------------------------------------------------------- 1 | 2 | require 'test_helper' 3 | 4 | RecordWithOperator.config[:operator_class_name] = "AdminUser" 5 | 6 | class NoteWithAdminUser < ActiveRecord::Base 7 | set_table_name "notes" 8 | 9 | records_with_operator_on :create, :update, :destroy 10 | 11 | def destroy_with_deleted_at 12 | NoteWithAdminUser.update_all("deleted_at = '#{Time.now.to_s(:db)}'", "id = #{self.id}") 13 | end 14 | alias_method_chain :destroy, :deleted_at 15 | def destory! 16 | destory_without_deleted_at 17 | end 18 | 19 | def deleted? 20 | self.deleted_at <= Time.now 21 | end 22 | end 23 | 24 | class AdminUser < ActiveRecord::Base 25 | set_table_name "users" 26 | end 27 | 28 | class RecordWithOperatorUserClassNameTest < ActiveSupport::TestCase 29 | def setup 30 | @user1 = AdminUser.create!(:name => "user1") 31 | @user2 = AdminUser.create!(:name => "user2") 32 | @note_created_by_user1 = NoteWithAdminUser.create!(:body => "test", :operator => @user1) 33 | end 34 | 35 | def test_note_should_be_created_with_operator 36 | assert_equal @user1, @note_created_by_user1.operator 37 | end 38 | 39 | def test_note_should_be_created_with_created_by_and_updated_by 40 | assert_equal @user1.id, @note_created_by_user1.created_by 41 | assert_equal @user1.id, @note_created_by_user1.updated_by 42 | @note_created_by_user1.reload 43 | assert_equal @user1.id, @note_created_by_user1.created_by 44 | assert_equal @user1.id, @note_created_by_user1.updated_by 45 | assert @note_created_by_user1.creator.kind_of?(AdminUser) 46 | end 47 | 48 | def test_note_should_be_found_with_for 49 | RecordWithOperator.operator = @user2 50 | note = NoteWithAdminUser.find(@note_created_by_user1.id) 51 | assert_equal(@user2, note.operator) 52 | end 53 | 54 | def test_note_should_be_updated_with_updated_by 55 | RecordWithOperator.operator = @user2 56 | note = NoteWithAdminUser.find(@note_created_by_user1.id) 57 | note.body = "changed" 58 | note.save! 59 | assert_equal(@user2.id, note.updated_by) 60 | note.reload 61 | assert_equal(@user2.id, note.updated_by) 62 | end 63 | 64 | end 65 | -------------------------------------------------------------------------------- /lib/record_with_operator/recorder.rb: -------------------------------------------------------------------------------- 1 | module RecordWithOperator 2 | module Recorder 3 | def self.included(base) 4 | base.extend(ClassMethods) 5 | end 6 | 7 | private 8 | 9 | def set_creator 10 | send("#{RecordWithOperator.creator_column}=", operator.try(:id)) 11 | end 12 | 13 | def set_updater 14 | return unless changed? # no use setting updating_by when it's not changed 15 | return unless operator # avoid changing value to be nil 16 | send("#{RecordWithOperator.updater_column}=", operator.id) 17 | end 18 | 19 | def update_deleter 20 | return if frozen? 21 | return unless operator 22 | self.class.update_all("#{RecordWithOperator.deleter_column} = #{operator.id}", ["#{self.class.primary_key} = ?", id]) 23 | send("#{RecordWithOperator.deleter_column}=", operator.id) 24 | end 25 | 26 | module ClassMethods 27 | VALID_ACTIONS = %w(create update destroy) 28 | 29 | def records_with_operator_on(*args) 30 | raise "valid actions are #{VALID_ACTIONS.inspect}." unless args.all?{|arg| VALID_ACTIONS.include?(arg.to_s) } 31 | 32 | @records_on = args.map(&:to_s) 33 | 34 | before_create :set_creator if records_creator? 35 | before_save :set_updater if records_updater? 36 | before_destroy :update_deleter if records_deleter? 37 | 38 | if self.table_exists? 39 | belongs_to :creator, {:foreign_key => "created_by", :class_name => RecordWithOperator.config[:operator_class_name]}.merge(RecordWithOperator.config[:operator_association_options]) if records_creator? 40 | belongs_to :updater, {:foreign_key => "updated_by", :class_name => RecordWithOperator.config[:operator_class_name]}.merge(RecordWithOperator.config[:operator_association_options]) if records_updater? 41 | belongs_to :deleter, {:foreign_key => "deleted_by", :class_name => RecordWithOperator.config[:operator_class_name]}.merge(RecordWithOperator.config[:operator_association_options]) if records_deleter? 42 | end 43 | end 44 | 45 | def records_creator? 46 | @records_on && @records_on.include?("create") 47 | end 48 | 49 | def records_updater? 50 | @records_on && @records_on.include?("update") 51 | end 52 | 53 | def records_deleter? 54 | @records_on && @records_on.include?("destroy") 55 | end 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /test/record_with_operator_belongs_to_association_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class User < ActiveRecord::Base 4 | end 5 | 6 | class NoteBelongsToMemo < ActiveRecord::Base 7 | set_table_name "notes" 8 | belongs_to :memo 9 | 10 | records_with_operator_on :create, :update, :destroy 11 | end 12 | 13 | class Memo < ActiveRecord::Base 14 | set_table_name "memos" 15 | 16 | records_with_operator_on :create, :update, :destroy 17 | end 18 | 19 | class RecordWithOperatorBelongsTo < ActiveSupport::TestCase 20 | def setup 21 | RecordWithOperator.config[:operator_class_name] = "User" 22 | @user1 = User.create!(:name => "user1") 23 | @user2 = User.create!(:name => "user2") 24 | @note_created_by_user1 = NoteBelongsToMemo.create!(:body => "test", :operator => @user1) 25 | end 26 | 27 | # belongs_to Association Test 28 | # build_association 29 | def test_build_memo_should_have_operator 30 | RecordWithOperator.operator = @user2 31 | note = NoteBelongsToMemo.find(@note_created_by_user1.id) 32 | memo = note.build_memo(:body => "memo") 33 | assert_equal @user2, memo.operator 34 | end 35 | 36 | # create_association 37 | def test_create_memo_should_have_operator_and_created_by 38 | RecordWithOperator.operator = @user2 39 | note = NoteBelongsToMemo.find(@note_created_by_user1.id) 40 | memo = note.create_memo(:body => "memo") 41 | assert_equal false, memo.new_record? 42 | assert_equal @user2, memo.operator 43 | assert_equal @user2.id, memo.created_by 44 | end 45 | 46 | # association= 47 | def test_memo_eql_should_have_operator_and_created_by 48 | RecordWithOperator.operator = @user2 49 | note = NoteBelongsToMemo.find(@note_created_by_user1.id) 50 | memo = Memo.new(:body => "memo") 51 | note.memo = memo # not save 52 | assert_equal @user2, memo.operator 53 | end 54 | 55 | # association 56 | def test_auto_found_memo_should_have_operator 57 | RecordWithOperator.operator = @user2 58 | note = NoteBelongsToMemo.find(@note_created_by_user1.id) 59 | note.create_memo(:body => "memo") 60 | assert_equal @user2, note.memo(true).operator 61 | end 62 | 63 | # association.nil? 64 | def test_memo_nil_should_be_false_if_note_belongs_to_memo 65 | note = NoteBelongsToMemo.find(@note_created_by_user1.id) 66 | note.create_memo(:body => "memo") 67 | assert !note.memo.nil? 68 | end 69 | 70 | # association.nil? 71 | def test_memo_nil_should_be_true_if_note_is_independent 72 | note = NoteBelongsToMemo.find(@note_created_by_user1.id) 73 | assert note.memo.nil? 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /test/record_with_operator_has_one_association_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class User < ActiveRecord::Base 4 | end 5 | 6 | class NoteHasOneMemo < ActiveRecord::Base 7 | set_table_name "notes" 8 | has_one :memo, :class_name => "Memo", :foreign_key => "note_id" 9 | 10 | records_with_operator_on :create, :update, :destroy 11 | end 12 | 13 | class Memo < ActiveRecord::Base 14 | set_table_name "memos" 15 | 16 | records_with_operator_on :create, :update, :destroy 17 | end 18 | 19 | class RecordWithOperatorHasOneAssociationTest < ActiveSupport::TestCase 20 | def setup 21 | RecordWithOperator.config[:operator_class_name] = "User" 22 | @user1 = User.create!(:name => "user1") 23 | @user2 = User.create!(:name => "user2") 24 | RecordWithOperator.operator = @user1 25 | @note_created_by_user1 = NoteHasOneMemo.create!(:body => "test") 26 | end 27 | 28 | # has_one Association Test 29 | # build_association 30 | def test_build_memo_should_have_operator 31 | RecordWithOperator.operator = @user2 32 | note = NoteHasOneMemo.find(@note_created_by_user1.id) 33 | memo = note.build_memo(:body => "memo") 34 | assert_equal @user2, memo.operator 35 | end 36 | 37 | # create_association 38 | def test_create_memo_should_have_operator_and_created_by 39 | RecordWithOperator.operator = @user2 40 | note = NoteHasOneMemo.find(@note_created_by_user1.id) 41 | memo = note.create_memo(:body => "memo") 42 | assert_equal false, memo.new_record? 43 | assert_equal @user2, memo.operator 44 | assert_equal @user2.id, memo.created_by 45 | end 46 | 47 | # association= 48 | def test_memo_eql_should_have_operator_and_created_by 49 | RecordWithOperator.operator = @user2 50 | note = NoteHasOneMemo.find(@note_created_by_user1.id) 51 | memo = Memo.new(:body => "memo") 52 | note.memo = memo 53 | assert_equal false, memo.new_record? 54 | assert_equal @user2, memo.operator 55 | assert_equal @user2.id, memo.created_by 56 | end 57 | 58 | # association 59 | def test_auto_found_memo_should_have_operator 60 | RecordWithOperator.operator = @user2 61 | note = NoteHasOneMemo.find(@note_created_by_user1.id) 62 | note.create_memo(:body => "memo") 63 | assert_equal @user2, note.memo(true).operator 64 | end 65 | 66 | # association.nil? 67 | def test_memo_nil_should_be_false_if_note_has_memo 68 | note = NoteHasOneMemo.find(@note_created_by_user1.id) 69 | note.create_memo(:body => "memo") 70 | assert !note.memo.nil? 71 | end 72 | 73 | # association.nil? 74 | def test_memo_nil_should_be_true_if_note_has_no_memo 75 | note = NoteHasOneMemo.find(@note_created_by_user1.id) 76 | assert note.memo.nil? 77 | end 78 | end 79 | -------------------------------------------------------------------------------- /README.rdoc: -------------------------------------------------------------------------------- 1 | = RecordWithOperator 2 | 3 | == Introduction 4 | 5 | RecordWithOperator is a gem that makes your active record models to be saved or logically deleted with created_by, updated_by, deleted_by (column names can be changed). 6 | Also it makes creator, updater, deleter association (belongs_to). 7 | 8 | You can specify which action you want to save operator like this. 9 | 10 | records_with_operator_on :cerate, :update, :destroy 11 | 12 | RecordWithOperator needs to know who is currently operating. 13 | For that, you need to set operator through one of the following ways. 14 | 15 | # Every ActiveRecord object has operator= and operator. 16 | record.operator = current_user 17 | record.save 18 | 19 | # RecordWithOperator module has operator= and operator. 20 | RecordWithOperator.operator = current_user 21 | obj = YourModel.find(id) 22 | obj.attributes = some_params 23 | obj.save 24 | 25 | In rails application, a nice place for setting operator whould be in filters. 26 | 27 | Operators whould be also useful for your access control features on model's side. 28 | 29 | The logical deletion function itself is out of this plugin's scope. 30 | This plugin assumes that the logical deletion is kicked by record.destory. 31 | 32 | == API Changes 33 | 34 | :for option in find method has been removed. 35 | (It's no use since operator is now global.) 36 | Please set operator before search if you need operator at that timing. 37 | 38 | Also, operator_stamps is removed. 39 | 40 | == Installation 41 | 42 | ## Gemfile 43 | gem 'record_with_operator' 44 | 45 | == Example 46 | 47 | Create Note table with nessisary attributes: 48 | class CreateNotes < ActiveRecord::Migration 49 | def change 50 | create_table :users do |t| 51 | t.string :body 52 | t.integer :created_by 53 | t.integer :updated_by 54 | t.integer :deleted_by 55 | 56 | t.boolean :deleted 57 | 58 | t.timestamps 59 | end 60 | end 61 | 62 | Set an operator in some filter 63 | 64 | def set_operator 65 | RecordWithOperator.operator = current_user 66 | end 67 | 68 | Create a new note object: 69 | 70 | note = Note.create(:body => "This is my first note.") 71 | # note.operator will be current_user 72 | # note.created_by will be current_user.id 73 | 74 | Get note objects: 75 | 76 | notes = Note.all 77 | # notes.first.operator will be current_user 78 | 79 | Create note's comments: 80 | 81 | note = Note.find(params[:id]) 82 | note.comments.create!(:body => params[:comment_body]) 83 | # comment.operator will be current_user 84 | # comment.created_by will be current_user.id 85 | 86 | == Configuration 87 | 88 | You can change the operator's class name by setting RecordWithOperator.config[:operator_class_name]. (It was renamed from :user_class_name. ) 89 | The default is 'User'. 90 | 91 | Note that it is required to change this value before your model classes are loaded. 92 | For example, you can write the code under config/initializers. 93 | 94 | RecordWithOperator.config[:operator_class_name] = "AdminUser" 95 | 96 | It's also possible to modify options for creator/updater/deleter associations. 97 | For example, in the case you use acts_as_paranoid and want to get creator/updater/deleter even if they are deleted, 98 | the configuration will be look like this. 99 | 100 | RecordWithOperator.config[:operator_association_options] = {:with_deleted => true} 101 | 102 | You can cange created_by, updated_by, deleted_by column name by changing RecordWithOperator.config with keys :creator_column, :updater_column and :deleter_column. 103 | 104 | The association names (such as creator, updater and deleter) can not be changed in this version. 105 | 106 | Copyright (c) 2009-2013 Yasuko Ohba, released under the MIT license 107 | -------------------------------------------------------------------------------- /test/record_with_operator_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class User < ActiveRecord::Base 4 | end 5 | 6 | class NoteWithUser < ActiveRecord::Base 7 | set_table_name "notes" 8 | has_many :memos, :class_name => "MemoWithUser", :foreign_key => "note_id" 9 | 10 | scope :new_arrivals, {:order => "updated_at desc"} 11 | 12 | alias_method :destroy!, :destroy 13 | 14 | records_with_operator_on :create, :update, :destroy 15 | 16 | def destroy 17 | with_transaction_returning_status do 18 | _run_destroy_callbacks do 19 | self.class.update_all({:deleted_at => Time.now}, {self.class.primary_key => self.id}) 20 | end 21 | end 22 | freeze 23 | end 24 | 25 | def deleted? 26 | !self.deleted_at.nil? 27 | end 28 | end 29 | 30 | class SimpleNoteWithUser < ActiveRecord::Base 31 | set_table_name "simple_notes" 32 | end 33 | 34 | class MemoWithUser < ActiveRecord::Base 35 | set_table_name "memos" 36 | 37 | scope :new_arrivals, {:order => "updated_at desc"} 38 | 39 | records_with_operator_on :create, :update, :destroy 40 | end 41 | 42 | class UpdaterNoteWithUser < ActiveRecord::Base 43 | set_table_name "updater_notes" 44 | 45 | records_with_operator_on :update 46 | end 47 | 48 | class DeleterNoteWithUser < ActiveRecord::Base 49 | set_table_name "deleter_notes" 50 | 51 | records_with_operator_on :destroy 52 | 53 | end 54 | 55 | class RecordWithOperatorTest < ActiveSupport::TestCase 56 | def setup 57 | RecordWithOperator.config[:operator_class_name] = "User" 58 | @user1 = User.create!(:name => "user1") 59 | raise "@user1.id is nil" unless @user1.id 60 | @user2 = User.create!(:name => "user2") 61 | raise "@user2.id is nil" unless @user2.id 62 | @note_created_by_user1 = NoteWithUser.create!(:body => "test", :operator => @user1) 63 | end 64 | 65 | # creator/updater/deleter association operation 66 | def test_note_should_be_respond_to_creator 67 | assert NoteWithUser.new.respond_to? :creator 68 | end 69 | 70 | def test_simple_note_should_not_be_respond_to_creator 71 | assert_equal false, SimpleNoteWithUser.new.respond_to?(:creator) 72 | end 73 | 74 | def test_note_should_be_respond_to_updater 75 | assert NoteWithUser.new.respond_to? :updater 76 | end 77 | 78 | def test_simple_note_should_not_be_respond_to_updater 79 | assert_equal false, SimpleNoteWithUser.new.respond_to?(:updater) 80 | end 81 | 82 | def test_note_should_be_respond_to_deleter 83 | assert NoteWithUser.new.respond_to? :deleter 84 | end 85 | 86 | def test_simple_note_should_not_be_respond_to_deleter 87 | assert_equal false, SimpleNoteWithUser.new.respond_to?(:deleter) 88 | end 89 | 90 | # test updater without creater, deleter 91 | def test_updater_note_should_not_be_respond_to_creater 92 | assert_equal false, UpdaterNoteWithUser.new.respond_to?(:creater) 93 | end 94 | def test_updater_note_should_be_respond_to_updater 95 | assert UpdaterNoteWithUser.new.respond_to? :updater 96 | end 97 | def test_updater_note_should_not_be_respond_to_deleter 98 | assert_equal false, UpdaterNoteWithUser.new.respond_to?(:deleter) 99 | end 100 | 101 | # test deleter without create, updater 102 | def test_deleter_note_should_not_be_respond_to_creater 103 | assert_equal false, DeleterNoteWithUser.new.respond_to?(:creater) 104 | end 105 | def test_deleter_note_should_not_be_respond_to_updater 106 | assert_equal false, DeleterNoteWithUser.new.respond_to?(:updater) 107 | end 108 | def test_deleter_note_should_be_respond_to_deleter 109 | assert DeleterNoteWithUser.new.respond_to?(:deleter) 110 | end 111 | 112 | # find with :for 113 | def test_note_should_be_found_with_for 114 | RecordWithOperator.operator = @user2 115 | note = NoteWithUser.find(@note_created_by_user1.id) 116 | assert_equal(@user2, note.operator) 117 | end 118 | 119 | def test_note_should_be_found_with_for_through_named_scope 120 | RecordWithOperator.operator = @user2 121 | note = NoteWithUser.new_arrivals.find(@note_created_by_user1.id) 122 | assert_equal(@user2, note.operator) 123 | end 124 | 125 | # save or destory with xxxx_by and can get as a creator/updator/deleter 126 | 127 | def test_note_should_be_created_with_operator 128 | assert_equal @user1, @note_created_by_user1.operator 129 | end 130 | 131 | def test_note_should_be_created_with_created_by_and_updated_by 132 | assert_equal @user1.id, @note_created_by_user1.created_by 133 | assert_equal @user1.id, @note_created_by_user1.updated_by 134 | @note_created_by_user1.reload 135 | assert_equal @user1.id, @note_created_by_user1.created_by 136 | assert_equal @user1.id, @note_created_by_user1.updated_by 137 | assert_equal @user1, @note_created_by_user1.creator 138 | assert_equal @user1, @note_created_by_user1.updater 139 | end 140 | 141 | def test_note_should_be_updated_with_updated_by 142 | RecordWithOperator.operator = @user2 143 | note = NoteWithUser.find(@note_created_by_user1.id) 144 | note.body = "changed" 145 | note.save! 146 | assert_equal(@user2.id, note.updated_by) 147 | note.reload 148 | assert_equal(@user2.id, note.updated_by) 149 | end 150 | 151 | def test_note_should_be_destroyed_with_deleted_by 152 | RecordWithOperator.operator = @user2 153 | note = NoteWithUser.find(@note_created_by_user1.id) 154 | note.destroy # logically 155 | note = NoteWithUser.find(@note_created_by_user1.id) 156 | raise "not deleted" unless note.deleted? 157 | assert_equal @user2.id, note.deleted_by 158 | end 159 | 160 | # reload 161 | def test_reload_should_not_change_operator 162 | @note_created_by_user1.reload 163 | assert_equal @user1, @note_created_by_user1.operator 164 | end 165 | 166 | 167 | # has_many Association Test 168 | def test_builded_memo_should_have_operator 169 | RecordWithOperator.operator = @user2 170 | note = NoteWithUser.find(@note_created_by_user1.id) 171 | memo = note.memos.build(:body => "memo") 172 | assert_equal @user2, memo.operator 173 | end 174 | 175 | def test_builded_memo_should_have_own_set_operator 176 | note = NoteWithUser.find(@note_created_by_user1.id) 177 | memo = note.memos.build(:body => "memo", :operator => @user2) 178 | assert_equal @user2, memo.operator 179 | end 180 | 181 | 182 | def test_created_memo_should_have_operator_and_created_by 183 | RecordWithOperator.operator = @user2 184 | note = NoteWithUser.find(@note_created_by_user1.id) 185 | memo = note.memos.create(:body => "memo") 186 | assert_equal false, memo.new_record? 187 | assert_equal @user2, memo.operator 188 | assert_equal @user2.id, memo.created_by 189 | end 190 | 191 | def test_auto_found_memo_should_have_operator 192 | RecordWithOperator.operator = @user2 193 | note = NoteWithUser.find(@note_created_by_user1.id) 194 | note.memos.create!(:body => "memo") 195 | assert_equal @user2, note.memos(true).first.operator 196 | end 197 | 198 | def test_manualy_found_memo_should_have_operator 199 | RecordWithOperator.operator = @user2 200 | note = NoteWithUser.find(@note_created_by_user1.id) 201 | note.memos.create!(:body => "memo") 202 | assert_equal @user2, note.memos.find(:first).operator 203 | end 204 | 205 | def test_dynamically_found_memo_should_have_operator 206 | RecordWithOperator.operator = @user2 207 | note = NoteWithUser.find(@note_created_by_user1.id) 208 | note.memos.create!(:body => "memo") 209 | assert_equal @user2, note.memos.find_by_body("memo").operator 210 | end 211 | 212 | def test_dynamically_found_memos_should_have_operator 213 | RecordWithOperator.operator = @user2 214 | note = NoteWithUser.find(@note_created_by_user1.id) 215 | note.memos.create!(:body => "memo") 216 | assert note.memos.find_all_by_body("memo").all?{|m| m.operator == @user2} 217 | end 218 | 219 | def test_found_memo_through_named_scope_should_have_operator 220 | RecordWithOperator.operator = @user2 221 | note = NoteWithUser.find(@note_created_by_user1.id) 222 | note.memos.create!(:body => "memo") 223 | assert_equal @user2, note.memos.new_arrivals.first.operator 224 | end 225 | 226 | def test_dynamically_found_memo_through_named_scope_should_have_operator 227 | RecordWithOperator.operator = @user2 228 | note = NoteWithUser.find(@note_created_by_user1.id) 229 | note.memos.create!(:body => "memo") 230 | assert_equal @user2, note.memos.new_arrivals.find_by_body("memo").operator 231 | end 232 | 233 | def test_auto_found_memo_first_should_be_nil_if_note_has_no_memo 234 | RecordWithOperator.operator = @user2 235 | note = NoteWithUser.find(@note_created_by_user1.id) 236 | assert_equal nil, note.memos.first 237 | assert_equal nil, note.memos(true).first 238 | end 239 | 240 | def test_auto_found_memo_last_should_be_nil_if_note_has_no_memo 241 | RecordWithOperator.operator = @user2 242 | note = NoteWithUser.find(@note_created_by_user1.id) 243 | assert_equal nil, note.memos.last 244 | assert_equal nil, note.memos(true).last 245 | end 246 | 247 | def test_manualy_found_memo_first_should_be_nil_if_note_has_no_memo 248 | RecordWithOperator.operator = @user2 249 | note = NoteWithUser.find(@note_created_by_user1.id) 250 | assert_equal nil, note.memos.find(:first) 251 | end 252 | 253 | def test_manualy_found_memo_last_should_be_nil_if_note_has_no_memo 254 | RecordWithOperator.operator = @user2 255 | note = NoteWithUser.find(@note_created_by_user1.id) 256 | assert_equal nil, note.memos.find(:last) 257 | end 258 | 259 | def test_dynamically_found_memos_first_should_be_nil_if_note_has_no_memo 260 | RecordWithOperator.operator = @user2 261 | note = NoteWithUser.find(@note_created_by_user1.id) 262 | assert_equal nil, note.memos.find_all_by_body("memo").first 263 | end 264 | 265 | def test_found_memo_through_named_scope_last_should_be_nil_if_note_has_no_memo 266 | RecordWithOperator.operator = @user2 267 | note = NoteWithUser.find(@note_created_by_user1.id) 268 | assert_equal nil, note.memos.new_arrivals.last 269 | end 270 | end 271 | --------------------------------------------------------------------------------