historic 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Greg Fitzgerald
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,32 @@
1
+ = historic
2
+
3
+ A gem for tracking changes of a model in a history table.
4
+ Allows fields to be marked as trigger fields that will initiate a copy of the record back to the history table.
5
+ The history table rows will be created with the data as it existed before the current modifications were made.
6
+
7
+ Usage (on the object to create history for):
8
+ =
9
+ acts_as_historic :on_change_of=>:field_1, :after => :some_method
10
+ =
11
+
12
+ Usage (on the history object):
13
+ =
14
+ acts_as_historic_record
15
+ =
16
+
17
+ By default, the History Record object is assumed to be the name of the main object plus History so:
18
+ For an object Foo, the History Record would be in a table called FooHistory.
19
+ This can be overridden using the class_name option:
20
+ =
21
+ acts_as_historic :class_name=>"BarHistory"
22
+ =
23
+
24
+ Options:
25
+ * :on_change_of - A list of fields to monitor for change. When any of these fields changes, a history row is created.
26
+ * :on_initialize - Create a History Record indicating the initial creation of the main object.
27
+ * :class_name - Specify a different History Record class name.
28
+ * :after - A symbol or proc to be called on completion of the history row being created.
29
+
30
+ == Copyright
31
+
32
+ Copyright (c) 2009 Greg Fitzgerald. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,56 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "historic"
8
+ gem.summary = %Q{A gem for tracking changes of a model in a history table.}
9
+ gem.email = "greg_fitz@yahoo.com"
10
+ gem.homepage = "http://github.com/gregfitz23/historic"
11
+ gem.authors = ["Greg Fitzgerald"]
12
+
13
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
14
+ end
15
+ rescue LoadError
16
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
17
+ end
18
+
19
+ require 'rake/testtask'
20
+ Rake::TestTask.new(:test) do |test|
21
+ test.libs << 'lib' << 'test' << 'active_record'
22
+ test.pattern = 'test/**/*_test.rb'
23
+ test.verbose = true
24
+ end
25
+
26
+ begin
27
+ require 'rcov/rcovtask'
28
+ Rcov::RcovTask.new do |test|
29
+ test.libs << 'test'
30
+ test.pattern = 'test/**/*_test.rb'
31
+ test.verbose = true
32
+ end
33
+ rescue LoadError
34
+ task :rcov do
35
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
36
+ end
37
+ end
38
+
39
+
40
+ task :default => :test
41
+
42
+ require 'rake/rdoctask'
43
+ Rake::RDocTask.new do |rdoc|
44
+ if File.exist?('VERSION.yml')
45
+ config = YAML.load(File.read('VERSION.yml'))
46
+ version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
47
+ else
48
+ version = ""
49
+ end
50
+
51
+ rdoc.rdoc_dir = 'rdoc'
52
+ rdoc.title = "historic #{version}"
53
+ rdoc.rdoc_files.include('README*')
54
+ rdoc.rdoc_files.include('lib/**/*.rb')
55
+ end
56
+
data/VERSION.yml ADDED
@@ -0,0 +1,5 @@
1
+ ---
2
+ :major: 0
3
+ :minor: 1
4
+ :patch: 0
5
+ :build:
data/historic.gemspec ADDED
@@ -0,0 +1,50 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{historic}
5
+ s.version = "0.0.1"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Greg Fitzgerald"]
9
+ s.date = %q{2009-06-11}
10
+ s.email = %q{greg_fitz@yahoo.com}
11
+ s.extra_rdoc_files = [
12
+ "LICENSE",
13
+ "README.rdoc"
14
+ ]
15
+ s.files = [
16
+ "LICENSE",
17
+ "README.rdoc",
18
+ "Rakefile",
19
+ "VERSION.yml",
20
+ "lib/historic.rb",
21
+ "lib/historic/historic.rb",
22
+ "lib/historic/historic_record.rb",
23
+ "test/historic_item.rb",
24
+ "test/historic_item_history.rb",
25
+ "test/historic_test.rb",
26
+ "test/test_helper.rb"
27
+ ]
28
+ s.has_rdoc = true
29
+ s.homepage = %q{http://github.com/gregfitz23/historic}
30
+ s.rdoc_options = ["--charset=UTF-8"]
31
+ s.require_paths = ["lib"]
32
+ s.rubygems_version = %q{1.3.1}
33
+ s.summary = %q{A gem for tracking changes of a model in a history table.}
34
+ s.test_files = [
35
+ "test/historic_item.rb",
36
+ "test/historic_item_history.rb",
37
+ "test/historic_test.rb",
38
+ "test/test_helper.rb"
39
+ ]
40
+
41
+ if s.respond_to? :specification_version then
42
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
43
+ s.specification_version = 2
44
+
45
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
46
+ else
47
+ end
48
+ else
49
+ end
50
+ end
data/lib/historic.rb ADDED
@@ -0,0 +1,3 @@
1
+ require File.join(File.dirname(__FILE__), 'historic', 'historic')
2
+ require File.join(File.dirname(__FILE__), 'historic', 'historic_record')
3
+ require "acts_as_list"
@@ -0,0 +1,89 @@
1
+ module ActiveRecord
2
+ module Acts
3
+ module Historic
4
+
5
+ def self.append_features(base) #:nodoc:
6
+ super
7
+ base.extend(Definition)
8
+ end
9
+
10
+ module Definition
11
+ # Sets up before_save and before_destroy to create HistoricRecord rows when appropriate.
12
+ def acts_as_historic(options={})
13
+ extend ActiveRecord::Acts::Historic::ClassMethods
14
+ include ActiveRecord::Acts::Historic::InstanceMethods
15
+
16
+ historic_extract_options(options)
17
+
18
+ alias_method_chain :before_save, :historic
19
+ alias_method_chain :before_destroy, :historic
20
+ end
21
+ end
22
+
23
+ module ClassMethods
24
+
25
+ def historic_record_class
26
+ @historic_record_class
27
+ end
28
+
29
+ def historic_options
30
+ @historic_options
31
+ end
32
+
33
+
34
+ private
35
+ # Extract the table class to be used or default to this class name + History.
36
+ # Ensure that on_change_of is an array.
37
+ #
38
+ def historic_extract_options(options)
39
+ @historic_record_class = (options.delete(:class_name) || "#{self.to_s}History").constantize
40
+
41
+ options[:on_change_of] = [options[:on_change_of]] if options[:on_change_of] && !options[:on_change_of].is_a?(Array)
42
+ @historic_options = options
43
+ end
44
+
45
+ end #ClassMethods
46
+
47
+ module InstanceMethods
48
+ # Chain historic_copy_to_history into before_save.
49
+ # This will only be triggered if:
50
+ # a. The current record is a new record OR the on_initialize flag is true
51
+ # b. AND on_change_of is not nil AND one of the fields specified in on_change_of has changed
52
+ def before_save_with_historic
53
+ before_save_without_historic
54
+
55
+ if historic_should_move_to_history?(historic_options)
56
+ historic_copy_to_history
57
+ end
58
+ end
59
+
60
+ # Chain historic_copy_to_history into before_destroy.
61
+ #
62
+ def before_destroy_with_historic
63
+ before_destroy_without_historic
64
+ historic_copy_to_history
65
+ end
66
+
67
+ private
68
+ def historic_should_move_to_history?(options)
69
+ (options[:on_initialize] || !new_record?) && (options[:on_change_of] && options[:on_change_of].any? {|field| self.__send__("#{field}_changed?")})
70
+ end
71
+
72
+ # Copy the current item to the HistoryRecord object and execute the :after hook, if it exists
73
+ #
74
+ def historic_copy_to_history
75
+ historic_record_class.create_history_from_current!(self)
76
+ historic_options[:after].to_proc.call(self) if historic_options[:after]
77
+ end
78
+
79
+ def historic_options
80
+ self.class.historic_options
81
+ end
82
+
83
+ def historic_record_class
84
+ self.class.historic_record_class
85
+ end
86
+ end #InstanceMethods
87
+ end #Historic
88
+ end
89
+ end
@@ -0,0 +1,53 @@
1
+ module ActiveRecord
2
+ module Acts
3
+ module HistoricRecord
4
+ def self.append_features(base)
5
+ super
6
+ base.extend(Definition)
7
+ end
8
+
9
+ module Definition
10
+ def acts_as_historic_record
11
+ extend ClassMethods
12
+ acts_as_list
13
+ end
14
+ end
15
+
16
+ module ClassMethods
17
+
18
+ # Same as create_history_from_current! but swallows exceptions and returns false.
19
+ def create_history_from_current(current)
20
+ create_history_from_current!(current) rescue false
21
+ end
22
+
23
+ # Create an HistoricRecord record based off of the passed in Object.
24
+ # Copy any fields that are on both the Object and the HistoricRecord object, preserving any changed values.
25
+ #
26
+ def create_history_from_current!(current)
27
+ history_obj = self.new
28
+ attrs_to_copy = current.attributes.reject {|key, value| !history_obj.attribute_names.include?(key)}
29
+
30
+ changed_attrs_with_old_values = {}
31
+ current.changes.each_pair {|field, value_ary| changed_attrs_with_old_values[field] = value_ary[0]}
32
+
33
+ # If a field of the form "class_name_id" exists in the history record, copy the old records id to it.
34
+ old_id_record_field = "#{current.class.to_s.underscore.singularize}_id"
35
+ if history_obj.attribute_names.include?(old_id_record_field)
36
+ attrs_to_copy.merge!(old_id_record_field.to_sym => current.id)
37
+ end
38
+
39
+ attrs_to_copy.merge!(changed_attrs_with_old_values)
40
+ self.create(attrs_to_copy)
41
+ end
42
+ end
43
+
44
+ end #HistoricRecord
45
+ end #Acts
46
+ end #ActiveRecord
47
+
48
+ ActiveRecord::Base.class_eval do
49
+ include ActiveRecord::Acts::Historic
50
+ include ActiveRecord::Acts::HistoricRecord
51
+ end
52
+
53
+
@@ -0,0 +1,8 @@
1
+ class HistoricItem < ActiveRecord::Base
2
+
3
+ acts_as_historic :on_change_of=>:field_1, :after => :clear_field_3
4
+
5
+ def clear_field_3
6
+ self.field_3 = nil
7
+ end
8
+ end
@@ -0,0 +1,5 @@
1
+ class HistoricItemHistory < ActiveRecord::Base
2
+
3
+ acts_as_historic_record
4
+
5
+ end
@@ -0,0 +1,92 @@
1
+ require 'test_helper'
2
+
3
+ class HistoricTest < Test::Unit::TestCase
4
+ context "A HistoryItem, @item" do
5
+ setup do
6
+ HistoricItemHistory.delete_all
7
+ HistoricItem.delete_all
8
+
9
+ @item = HistoricItem.new
10
+ @item.field_1, @item.field_2, @item.field_3 = 1, 2, 3
11
+ @item.save!
12
+ end
13
+
14
+ should "not create a HistoryRecord when newly created" do
15
+ assert !HistoricItemHistory.exists?(:field_1=>"1", :field_2=>"2", :field_3=>"3")
16
+ end
17
+
18
+ context "and a new value for :field_1" do
19
+ setup do
20
+ @new_field_1 = "4"
21
+ end
22
+
23
+ context "when changing the value of :field_1 to @new_field_1" do
24
+ setup do
25
+ @old_item = @item.clone
26
+ @item.update_attributes(:field_1 => @new_field_1)
27
+ end
28
+
29
+ should "create a new ItemGuideHistory for the old item guide and item" do
30
+ assert HistoricItemHistory.exists?({:field_1=>@old_item.field_1, :field_2=>@old_item.field_2, :field_3=>@old_item.field_3})
31
+ end
32
+
33
+ context ", the HistoricItemHistory" do
34
+ setup do
35
+ @item_history = HistoricItemHistory.find_by_field_1_and_field_2_and_field_3(@old_item.field_1, @old_item.field_2, @old_item.field_3)
36
+ end
37
+
38
+ should "copy field_1 from the HistoricItem" do
39
+ assert_equal @old_item.field_1, @item_history.field_1.to_i
40
+ end
41
+
42
+ should "copy field_2 from the HistoricItem" do
43
+ assert_equal @old_item.field_2, @item_history.field_2.to_i
44
+ end
45
+
46
+ should "copy field_3 from the HistoricItem" do
47
+ assert_equal @old_item.field_3, @item_history.field_3.to_i
48
+ end
49
+
50
+ should "clear c on the original HistoryItem" do
51
+ assert_equal nil, @item.field_3
52
+ end
53
+
54
+ should "set historic_item_id reference field" do
55
+ assert_equal @item.id, @item_history.historic_item_id
56
+ end
57
+ end #for the HistoricItemHistory
58
+ end #when changing field_1 of HistoricIte
59
+ end #when changing the value of :field_1 to @new_field_1
60
+
61
+ context "passed to create_from_history_item" do
62
+ setup do
63
+ @item_history = HistoricItemHistory.create_history_from_current(@item)
64
+ end
65
+
66
+ should "copy field_1 from the HistoricItem" do
67
+ assert_equal @item.field_1, @item_history.field_1
68
+ end
69
+
70
+ should "copy field_2 from the HistoricItem" do
71
+ assert_equal @item.field_2, @item_history.field_2
72
+ end
73
+
74
+ should "copy field_3 from the HistoricItem" do
75
+ assert_equal @item.field_3, @item_history.field_3
76
+ end
77
+
78
+ end #passed to create_history_from_current
79
+
80
+ context "when destroyed" do
81
+ setup do
82
+ assert_equal 0, HistoricItemHistory.count
83
+ @item.destroy
84
+ end
85
+
86
+ should "create an HistoricItemHistory row" do
87
+ assert_equal 1, HistoricItemHistory.count
88
+ # assert HistoricItemHistory.exists?({:field_1=>@item.field_1, :field_2=>@item.field_2, :field_3=>@item.field_3})
89
+ end
90
+ end #when destroyed
91
+ end # An item guide
92
+ end
@@ -0,0 +1,30 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
7
+ require "active_record"
8
+ require 'historic'
9
+
10
+ class Test::Unit::TestCase
11
+
12
+
13
+ ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :dbfile => ":memory:")
14
+
15
+ ActiveRecord::Schema.define(:version => 1) do
16
+ columns = [:field_1, :field_2, :field_3]
17
+ create_table :historic_items do |t|
18
+ t.string *columns
19
+ end
20
+
21
+ create_table :historic_item_histories do |t|
22
+ t.string *columns
23
+ t.integer :position
24
+ t.integer :historic_item_id
25
+ end
26
+ end
27
+
28
+ require "historic_item_history"
29
+ require "historic_item"
30
+ end
metadata ADDED
@@ -0,0 +1,72 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: historic
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Greg Fitzgerald
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-11-03 00:00:00 -05:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description:
17
+ email: greg_fitz@yahoo.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - LICENSE
24
+ - README.rdoc
25
+ files:
26
+ - .document
27
+ - .gitignore
28
+ - LICENSE
29
+ - README.rdoc
30
+ - Rakefile
31
+ - VERSION.yml
32
+ - historic.gemspec
33
+ - lib/historic.rb
34
+ - lib/historic/historic.rb
35
+ - lib/historic/historic_record.rb
36
+ - test/historic_item.rb
37
+ - test/historic_item_history.rb
38
+ - test/historic_test.rb
39
+ - test/test_helper.rb
40
+ has_rdoc: true
41
+ homepage: http://github.com/gregfitz23/historic
42
+ licenses: []
43
+
44
+ post_install_message:
45
+ rdoc_options:
46
+ - --charset=UTF-8
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: "0"
54
+ version:
55
+ required_rubygems_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: "0"
60
+ version:
61
+ requirements: []
62
+
63
+ rubyforge_project:
64
+ rubygems_version: 1.3.5
65
+ signing_key:
66
+ specification_version: 3
67
+ summary: A gem for tracking changes of a model in a history table.
68
+ test_files:
69
+ - test/historic_item.rb
70
+ - test/historic_item_history.rb
71
+ - test/historic_test.rb
72
+ - test/test_helper.rb