dirty_history 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+
6
+ # Add dependencies to develop your gem here.
7
+ # Include everything needed to run rake, tests, features, etc.
8
+ group :development do
9
+ gem "shoulda", ">= 0"
10
+ gem "bundler", "~> 1.0.0"
11
+ gem "jeweler", "~> 1.5.2"
12
+ gem "rcov", ">= 0"
13
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,20 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ git (1.2.5)
5
+ jeweler (1.5.2)
6
+ bundler (~> 1.0.0)
7
+ git (>= 1.2.5)
8
+ rake
9
+ rake (0.8.7)
10
+ rcov (0.9.9)
11
+ shoulda (2.11.3)
12
+
13
+ PLATFORMS
14
+ ruby
15
+
16
+ DEPENDENCIES
17
+ bundler (~> 1.0.0)
18
+ jeweler (~> 1.5.2)
19
+ rcov
20
+ shoulda
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Gavin Todes
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.
data/README.rdoc ADDED
@@ -0,0 +1,84 @@
1
+ = Dirty History
2
+
3
+ Dirty History is a simple gem that allows you to keep track of changes to specific fields in your Rails models using the ActiveRecord::Dirty module.
4
+
5
+ == Installation
6
+
7
+ Add dirty_history to your Gemfile:
8
+
9
+ gem "dirty_history"
10
+
11
+ Install it using Bundler
12
+
13
+ bundle install
14
+
15
+ Generate the Dirty History migration and migrate your database
16
+
17
+ rails generate dirty_history:migration
18
+ rake db:migrate
19
+
20
+ == Usage
21
+
22
+ Dirty History must be set up within the ActiveRecord model (or models) you want to use. Simply call the has_dirty_history class method on your model(s), passing the attributes that you would like to track changes to.
23
+
24
+ For example, assume you want to use Dirty History in your Widget model to keep track of changes to name and price fields as outlined below:
25
+
26
+ class Widget < ActiveRecord::Base
27
+ has_dirty_history :name, :price
28
+ end
29
+
30
+ You can optionally track the creator of dirty history records by passing a creator proc that will be called when a DirtyHistoryRecord is being saved for your object.
31
+
32
+ class Widget < ActiveRecord::Base
33
+ has_dirty_history :name, :price, :creator => proc { User.current_user }
34
+ end
35
+
36
+ widget = Widget.last
37
+ widget.name
38
+ # => "Box"
39
+ widget.name = "Heart Shaped Box"
40
+ widget.save
41
+ widget.dirty_history_records
42
+ # => returns all changes to the widget
43
+
44
+ dirty_history = widget.dirty_history_records.last
45
+ dirty_history.old_value
46
+ # => "Thing"
47
+ dirty_history.new_value
48
+ # => "Heard Shaped Box"
49
+
50
+ user = User.find(123)
51
+ widget.dirty_history_records.created_by(user)
52
+ # => returns all changes to the widget performed by the specified user
53
+
54
+ class User < ActiveRecord::Base
55
+ creates_dirty_history
56
+ end
57
+
58
+ user = User.find(123)
59
+ user.dirty_history_records
60
+ # => returns changes made by the specified user
61
+
62
+ Now, suppose you want to access all of a user's changes to objects of type Foo:
63
+
64
+ user = User.find(123)
65
+ user.dirty_history_records.for_object_type("Foo")
66
+ # => returns the user's changes made only objects of type Foo
67
+
68
+ # TODO: add more documentation
69
+
70
+ == Contributing to Dirty History
71
+
72
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
73
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
74
+ * Fork the project
75
+ * Start a feature/bugfix branch
76
+ * Commit and push until you are happy with your contribution
77
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
78
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
79
+
80
+ == Copyright
81
+
82
+ Copyright (c) 2011 Gavin Todes. See LICENSE.txt for
83
+ further details.
84
+
data/Rakefile ADDED
@@ -0,0 +1,53 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'rake'
11
+
12
+ require 'jeweler'
13
+ Jeweler::Tasks.new do |gem|
14
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
15
+ gem.name = "dirty_history"
16
+ gem.homepage = "http://github.com/GAV1N/dirty_history"
17
+ gem.license = "MIT"
18
+ gem.summary = "Easily keep track of changes to specific model fields."
19
+ gem.description = "Dirty History is a simple gem that allows you to keep track of changes to specific fields in your Rails models using the ActiveRecord::Dirty module."
20
+ gem.email = "gavin.todes@gmail.com"
21
+ gem.authors = ["Gavin Todes"]
22
+ # Include your dependencies below. Runtime dependencies are required when using your gem,
23
+ # and development dependencies are only needed for development (ie running rake tasks, tests, etc)
24
+ # gem.add_runtime_dependency 'jabber4r', '> 0.1'
25
+ # gem.add_development_dependency 'rspec', '> 1.2.3'
26
+ end
27
+ Jeweler::RubygemsDotOrgTasks.new
28
+
29
+ require 'rake/testtask'
30
+ Rake::TestTask.new(:test) do |test|
31
+ test.libs << 'lib' << 'test'
32
+ test.pattern = 'test/**/test_*.rb'
33
+ test.verbose = true
34
+ end
35
+
36
+ require 'rcov/rcovtask'
37
+ Rcov::RcovTask.new do |test|
38
+ test.libs << 'test'
39
+ test.pattern = 'test/**/test_*.rb'
40
+ test.verbose = true
41
+ end
42
+
43
+ task :default => :test
44
+
45
+ require 'rake/rdoctask'
46
+ Rake::RDocTask.new do |rdoc|
47
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
48
+
49
+ rdoc.rdoc_dir = 'rdoc'
50
+ rdoc.title = "dirty_history #{version}"
51
+ rdoc.rdoc_files.include('README*')
52
+ rdoc.rdoc_files.include('lib/**/*.rb')
53
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.1
@@ -0,0 +1,69 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{dirty_history}
8
+ s.version = "0.1.1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Gavin Todes"]
12
+ s.date = %q{2011-05-01}
13
+ s.description = %q{Dirty History is a simple gem that allows you to keep track of changes to specific fields in your Rails models using the ActiveRecord::Dirty module.}
14
+ s.email = %q{gavin.todes@gmail.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE.txt",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ "Gemfile",
22
+ "Gemfile.lock",
23
+ "LICENSE.txt",
24
+ "README.rdoc",
25
+ "Rakefile",
26
+ "VERSION",
27
+ "dirty_history.gemspec",
28
+ "init.rb",
29
+ "lib/dirty_history.rb",
30
+ "lib/dirty_history/dirty_history_mixin.rb",
31
+ "lib/dirty_history/dirty_history_record.rb",
32
+ "lib/generators/dirty_history/migration/migration_generator.rb",
33
+ "lib/generators/dirty_history/migration/templates/active_record/migration.rb",
34
+ "rails/init.rb",
35
+ "test/helper.rb",
36
+ "test/test_dirty_history.rb"
37
+ ]
38
+ s.homepage = %q{http://github.com/GAV1N/dirty_history}
39
+ s.licenses = ["MIT"]
40
+ s.require_paths = ["lib"]
41
+ s.rubygems_version = %q{1.6.2}
42
+ s.summary = %q{Easily keep track of changes to specific model fields.}
43
+ s.test_files = [
44
+ "test/helper.rb",
45
+ "test/test_dirty_history.rb"
46
+ ]
47
+
48
+ if s.respond_to? :specification_version then
49
+ s.specification_version = 3
50
+
51
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
52
+ s.add_development_dependency(%q<shoulda>, [">= 0"])
53
+ s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
54
+ s.add_development_dependency(%q<jeweler>, ["~> 1.5.2"])
55
+ s.add_development_dependency(%q<rcov>, [">= 0"])
56
+ else
57
+ s.add_dependency(%q<shoulda>, [">= 0"])
58
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
59
+ s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
60
+ s.add_dependency(%q<rcov>, [">= 0"])
61
+ end
62
+ else
63
+ s.add_dependency(%q<shoulda>, [">= 0"])
64
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
65
+ s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
66
+ s.add_dependency(%q<rcov>, [">= 0"])
67
+ end
68
+ end
69
+
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/rails/init')
@@ -0,0 +1,10 @@
1
+ require "active_record"
2
+
3
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
4
+
5
+ require "dirty_history/dirty_history_record"
6
+ require "dirty_history/dirty_history_mixin"
7
+
8
+ $LOAD_PATH.shift
9
+
10
+ ActiveRecord::Base.send :include, DirtyHistory::Mixin
@@ -0,0 +1,92 @@
1
+ module DirtyHistory
2
+
3
+ module Mixin
4
+
5
+ def self.included base
6
+ base.class_eval do
7
+ extend ClassMethods
8
+ end
9
+ end
10
+
11
+ module ClassMethods
12
+
13
+ # call the dirty_history class method on models with fields that you want to track changes on.
14
+ # example usage:
15
+ # class User < ActiveRecord::Base
16
+ # has_dirty_history :email, :first_name, :last_name
17
+ # end
18
+
19
+ # pass an optional proc to assign a creator to the dirty_history object
20
+ # example usage:
21
+ # class User < ActiveRecord::Base
22
+ # has_dirty_history :email, :first_name, :last_name, :creator => proc { User.current_user }
23
+ # end
24
+
25
+ def has_dirty_history *args
26
+ # Mix in the module, but ensure to do so just once.
27
+ metaclass = (class << self; self; end)
28
+ return if metaclass.included_modules.include?(DirtyHistory::Mixin::ObjectInstanceMethods)
29
+
30
+ has_many :dirty_history_records, :as => :object
31
+ before_save :add_dirty_history
32
+ cattr_accessor :dirty_history_columns
33
+
34
+ self.dirty_history_columns ||= []
35
+
36
+ if args.present?
37
+ args.each do |arg|
38
+ if [String,Symbol].include?(arg.class)
39
+ arg = arg.to_sym
40
+ self.dirty_history_columns << arg unless self.dirty_history_columns.include?(arg)
41
+ elsif arg.is_a?(Hash)
42
+ creator_proc = arg.delete(:creator)
43
+ send :define_method, "creator_for_dirty_history" do
44
+ begin
45
+ creator_proc.is_a?(Proc) ? creator_proc.call : nil
46
+ rescue
47
+ nil
48
+ end
49
+ end
50
+ end
51
+ end
52
+ include DirtyHistory::Mixin::ObjectInstanceMethods
53
+ end
54
+ end # has_dirty_history
55
+
56
+ def creates_dirty_history
57
+ # Mix in the module, but ensure to do so just once.
58
+ metaclass = (class << self; self; end)
59
+ return if metaclass.included_modules.include?(DirtyHistory::Mixin::CreatorInstanceMethods)
60
+
61
+ has_many :dirty_history_records, :as => :creator
62
+ include DirtyHistory::Mixin::CreatorInstanceMethods
63
+ end # creates_dirty_history
64
+ end # ClassMethods
65
+
66
+ module ObjectInstanceMethods
67
+ def add_dirty_history
68
+ return true unless self.changed?
69
+ new_dirty_history = self.class.dirty_history_columns.inject({}) { |changes_hash, col|
70
+ changes_hash[col] = self.send("#{col}_change") if self.send("#{col}_changed?")
71
+ changes_hash
72
+ }
73
+ new_dirty_history.map { |col,vals|
74
+ DirtyHistoryRecord.new :creator => (self.creator_for_dirty_history rescue nil),
75
+ :column_name => col,
76
+ :column_type => self.class.columns_hash[col.to_s].type,
77
+ :old_value => vals[0],
78
+ :new_value => vals[1]
79
+ }.each { |dirty_history_record| self.dirty_history_records << dirty_history_record }
80
+ end
81
+ end # ObjectInstanceMethods
82
+
83
+ module CreatorInstanceMethods
84
+
85
+ end # CreatorInstanceMethods
86
+
87
+ end # Mixin
88
+
89
+ end # DirtyHistory
90
+
91
+
92
+ ActiveRecord::Base.send :include, DirtyHistory::Mixin
@@ -0,0 +1,38 @@
1
+ class DirtyHistoryRecord < ActiveRecord::Base
2
+ belongs_to :creator, :polymorphic => true
3
+ belongs_to :object, :polymorphic => true
4
+ validates_presence_of :object_type, :object_id, :column_name, :column_type, :old_value, :new_value
5
+
6
+ scope :created_by, lambda { |creator| where(:creator_id => creator.id, :creator_type => creator.class.name) }
7
+ scope :for_object_type, lambda { |object_type| where(:object_type => object_type.to_s.classify) }
8
+
9
+ [:new_value, :old_value].each do |attribute|
10
+ define_method "#{attribute}" do
11
+ val_to_col_type(attribute)
12
+ end
13
+ define_method "#{attribute}=" do |val|
14
+ self[attribute] = val.to_s
15
+ instance_variable_set "@#{attribute}", val
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def val_to_col_type attribute
22
+ val_as_string = self[attribute]
23
+ return nil if val_as_string.nil?
24
+ case self[:column_type].to_sym
25
+ when :integer, :boolean
26
+ val_as_string.to_i
27
+ when :decimal, :float
28
+ val_as_string.to_f
29
+ when :datetime
30
+ Time.parse val_as_string
31
+ when :date
32
+ Date.parse val_as_string
33
+ else # :string, :text
34
+ val_as_string
35
+ end
36
+ end
37
+
38
+ end
@@ -0,0 +1,32 @@
1
+ require 'rails/generators/migration'
2
+
3
+ module DirtyHistory
4
+ class MigrationGenerator < Rails::Generators::Base
5
+ include Rails::Generators::Migration
6
+
7
+ desc "Generates migration for DirtyHistoryRecord model"
8
+
9
+ def self.orm
10
+ Rails::Generators.options[:rails][:orm]
11
+ end
12
+
13
+ def self.source_root
14
+ File.join(File.dirname(__FILE__), 'templates', (orm.to_s unless orm.class.eql?(String)) )
15
+ end
16
+
17
+ def self.orm_has_migration?
18
+ [:active_record].include? orm
19
+ end
20
+
21
+ def self.next_migration_number(path)
22
+ Time.now.utc.strftime("%Y%m%d%H%M%S")
23
+ end
24
+
25
+ def create_migration_file
26
+ if self.class.orm_has_migration?
27
+ migration_template 'migration.rb', 'db/migrate/create_dirty_history_records'
28
+ end
29
+ end
30
+ end
31
+ end
32
+
@@ -0,0 +1,20 @@
1
+ class CreateDirtyHistoryRecords < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :dirty_history_records do |t|
4
+ t.references :creator, :polymorphic => true # creates creator_type and creator_id field
5
+ t.references :object, :polymorphic => true # creates object_type and object_id field
6
+ t.string :column_name, :length => 64
7
+ t.string :column_type, :length => 16 # :string, :text, :integer, :decimal, :float, :datetime, :boolean
8
+ t.text :old_value
9
+ t.text :new_value
10
+
11
+ t.datetime :created_at
12
+ end
13
+ add_index :dirty_history_records, [:creator_id, :creator_type]
14
+ add_index :dirty_history_records, [:object_id, :object_type]
15
+ end
16
+
17
+ def self.down
18
+ drop_table :dirty_history_records
19
+ end
20
+ end
data/rails/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'dirty_history'
data/test/helper.rb ADDED
@@ -0,0 +1,18 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'test/unit'
11
+ require 'shoulda'
12
+
13
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
14
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
15
+ require 'dirty_history'
16
+
17
+ class Test::Unit::TestCase
18
+ end
@@ -0,0 +1,7 @@
1
+ require 'helper'
2
+
3
+ class TestDirtyHistory < Test::Unit::TestCase
4
+ should "probably rename this file and start testing for real" do
5
+ flunk "hey buddy, you should probably rename this file and start testing for real"
6
+ end
7
+ end
metadata ADDED
@@ -0,0 +1,116 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dirty_history
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Gavin Todes
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-05-01 00:00:00.000000000 %:z
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: shoulda
17
+ requirement: &2152602180 !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ! '>='
21
+ - !ruby/object:Gem::Version
22
+ version: '0'
23
+ type: :development
24
+ prerelease: false
25
+ version_requirements: *2152602180
26
+ - !ruby/object:Gem::Dependency
27
+ name: bundler
28
+ requirement: &2152690100 !ruby/object:Gem::Requirement
29
+ none: false
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: 1.0.0
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: *2152690100
37
+ - !ruby/object:Gem::Dependency
38
+ name: jeweler
39
+ requirement: &2152883280 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ~>
43
+ - !ruby/object:Gem::Version
44
+ version: 1.5.2
45
+ type: :development
46
+ prerelease: false
47
+ version_requirements: *2152883280
48
+ - !ruby/object:Gem::Dependency
49
+ name: rcov
50
+ requirement: &2153025260 !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ! '>='
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ type: :development
57
+ prerelease: false
58
+ version_requirements: *2153025260
59
+ description: Dirty History is a simple gem that allows you to keep track of changes
60
+ to specific fields in your Rails models using the ActiveRecord::Dirty module.
61
+ email: gavin.todes@gmail.com
62
+ executables: []
63
+ extensions: []
64
+ extra_rdoc_files:
65
+ - LICENSE.txt
66
+ - README.rdoc
67
+ files:
68
+ - .document
69
+ - Gemfile
70
+ - Gemfile.lock
71
+ - LICENSE.txt
72
+ - README.rdoc
73
+ - Rakefile
74
+ - VERSION
75
+ - dirty_history.gemspec
76
+ - init.rb
77
+ - lib/dirty_history.rb
78
+ - lib/dirty_history/dirty_history_mixin.rb
79
+ - lib/dirty_history/dirty_history_record.rb
80
+ - lib/generators/dirty_history/migration/migration_generator.rb
81
+ - lib/generators/dirty_history/migration/templates/active_record/migration.rb
82
+ - rails/init.rb
83
+ - test/helper.rb
84
+ - test/test_dirty_history.rb
85
+ has_rdoc: true
86
+ homepage: http://github.com/GAV1N/dirty_history
87
+ licenses:
88
+ - MIT
89
+ post_install_message:
90
+ rdoc_options: []
91
+ require_paths:
92
+ - lib
93
+ required_ruby_version: !ruby/object:Gem::Requirement
94
+ none: false
95
+ requirements:
96
+ - - ! '>='
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ segments:
100
+ - 0
101
+ hash: 3022031599709161522
102
+ required_rubygems_version: !ruby/object:Gem::Requirement
103
+ none: false
104
+ requirements:
105
+ - - ! '>='
106
+ - !ruby/object:Gem::Version
107
+ version: '0'
108
+ requirements: []
109
+ rubyforge_project:
110
+ rubygems_version: 1.6.2
111
+ signing_key:
112
+ specification_version: 3
113
+ summary: Easily keep track of changes to specific model fields.
114
+ test_files:
115
+ - test/helper.rb
116
+ - test/test_dirty_history.rb