delta_changes 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ rvm:
2
+ - ree
3
+ - 1.9.2
4
+ - 1.9.3
data/Appraisals ADDED
@@ -0,0 +1,15 @@
1
+ appraise "rails2" do
2
+ version = "2.3.14"
3
+ gem 'activerecord', version
4
+ end
5
+
6
+ appraise "rails30" do
7
+ version = "3.2.3"
8
+ gem 'activerecord', version
9
+ end
10
+
11
+
12
+ appraise "rails32" do
13
+ version = "3.2.3"
14
+ gem 'activerecord', version
15
+ end
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source :rubygems
2
+ gemspec
3
+
4
+ gem "appraisal"
5
+ gem "rake"
6
+ gem "rspec", "~>2"
7
+ gem "sqlite3"
data/Gemfile.lock ADDED
@@ -0,0 +1,49 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ delta_changes (0.0.1)
5
+ activerecord
6
+
7
+ GEM
8
+ remote: http://rubygems.org/
9
+ specs:
10
+ activemodel (3.2.8)
11
+ activesupport (= 3.2.8)
12
+ builder (~> 3.0.0)
13
+ activerecord (3.2.8)
14
+ activemodel (= 3.2.8)
15
+ activesupport (= 3.2.8)
16
+ arel (~> 3.0.2)
17
+ tzinfo (~> 0.3.29)
18
+ activesupport (3.2.8)
19
+ i18n (~> 0.6)
20
+ multi_json (~> 1.0)
21
+ appraisal (0.4.1)
22
+ bundler
23
+ rake
24
+ arel (3.0.2)
25
+ builder (3.0.3)
26
+ diff-lcs (1.1.3)
27
+ i18n (0.6.1)
28
+ multi_json (1.3.6)
29
+ rake (0.9.2)
30
+ rspec (2.6.0)
31
+ rspec-core (~> 2.6.0)
32
+ rspec-expectations (~> 2.6.0)
33
+ rspec-mocks (~> 2.6.0)
34
+ rspec-core (2.6.4)
35
+ rspec-expectations (2.6.0)
36
+ diff-lcs (~> 1.1.2)
37
+ rspec-mocks (2.6.0)
38
+ sqlite3 (1.3.6)
39
+ tzinfo (0.3.33)
40
+
41
+ PLATFORMS
42
+ ruby
43
+
44
+ DEPENDENCIES
45
+ appraisal
46
+ delta_changes!
47
+ rake
48
+ rspec (~> 2)
49
+ sqlite3
data/Rakefile ADDED
@@ -0,0 +1,27 @@
1
+ require "bundler/gem_tasks"
2
+ require "appraisal"
3
+
4
+ task :spec do
5
+ sh "rspec spec/"
6
+ end
7
+
8
+ task :default do
9
+ sh "bundle exec rake appraisal:install && bundle exec rake appraisal spec"
10
+ end
11
+
12
+ # extracted from https://github.com/grosser/project_template
13
+ rule /^version:bump:.*/ do |t|
14
+ file = "lib/delta_changes/version.rb"
15
+
16
+ sh "git status | grep 'nothing to commit'" # ensure we are not dirty
17
+ index = ["major", "minor", "patch"].index(t.name.split(':').last)
18
+ version_file = File.read(file)
19
+ old_version, *version_parts = version_file.match(/(\d+)\.(\d+)\.(\d+)/).to_a
20
+ version_parts[index] = version_parts[index].to_i + 1
21
+ version_parts[2] = 0 if index < 2 # remove patch for minor
22
+ version_parts[1] = 0 if index < 1 # remove minor for major
23
+ new_version = version_parts * '.'
24
+
25
+ File.open(file,"w"){|f| f.write(version_file.sub(old_version, new_version)) }
26
+ sh "bundle && git add #{file} Gemfile.lock && git commit -m 'bump version to #{new_version}'"
27
+ end
data/Readme.md ADDED
@@ -0,0 +1,34 @@
1
+ Additional real/virtual attribute change tracking independent of ActiveRecords
2
+
3
+
4
+ Install
5
+ =======
6
+
7
+ gem install delta_changes
8
+
9
+ Usage
10
+ =====
11
+
12
+ class User < ActiveRecord::Base
13
+ include DeltaChanges::Extension
14
+ delta_changes :columns => [:name], :attributes => [:full_name]
15
+ end
16
+
17
+ user.name = "bar"
18
+ user.delta_changes # => {"name" => [nil, "bar"]}
19
+
20
+ user.full_name_will_change!
21
+ user.delta_changes # => {"name" => [nil, "bar"], "full_name" => [nil, "Mr. Bar"]}
22
+
23
+ user.save!
24
+ user.delta_changes # => {"name" => [nil, "bar"], "full_name" => [nil, "Mr. Bar"]}
25
+
26
+ user.reset_delta_changes!
27
+ user.delta_changes # => {}
28
+
29
+ Author
30
+ ======
31
+ [Michael Grosser](http://grosser.it)<br/>
32
+ michael@grosser.it<br/>
33
+ License: MIT<br/>
34
+ [![Build Status](https://secure.travis-ci.org/grosser/delta_changes.png)](http://travis-ci.org/grosser/delta_changes)
@@ -0,0 +1,13 @@
1
+ $LOAD_PATH.unshift File.expand_path("../lib", __FILE__)
2
+ name = "delta_changes"
3
+ require "#{name}/version"
4
+
5
+ Gem::Specification.new name, DeltaChanges::VERSION do |s|
6
+ s.summary = "Additional real/virtual attribute change tracking independent of ActiveRecords"
7
+ s.authors = ["Michael Grosser"]
8
+ s.email = "michael@grosser.it"
9
+ s.homepage = "http://github.com/grosser/#{name}"
10
+ s.files = `git ls-files`.split("\n")
11
+ s.add_runtime_dependency "activerecord"
12
+ s.license = "MIT"
13
+ end
@@ -0,0 +1,11 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source :rubygems
4
+
5
+ gem "appraisal"
6
+ gem "rake"
7
+ gem "rspec", "~>2"
8
+ gem "sqlite3"
9
+ gem "activerecord", "2.3.14"
10
+
11
+ gemspec :path=>"../"
@@ -0,0 +1,37 @@
1
+ PATH
2
+ remote: /Users/mgrosser/code/tools/delta_changes
3
+ specs:
4
+ delta_changes (0.0.0)
5
+ activerecord
6
+
7
+ GEM
8
+ remote: http://rubygems.org/
9
+ specs:
10
+ activerecord (2.3.14)
11
+ activesupport (= 2.3.14)
12
+ activesupport (2.3.14)
13
+ appraisal (0.4.1)
14
+ bundler
15
+ rake
16
+ diff-lcs (1.1.3)
17
+ rake (0.9.2.2)
18
+ rspec (2.11.0)
19
+ rspec-core (~> 2.11.0)
20
+ rspec-expectations (~> 2.11.0)
21
+ rspec-mocks (~> 2.11.0)
22
+ rspec-core (2.11.1)
23
+ rspec-expectations (2.11.3)
24
+ diff-lcs (~> 1.1.3)
25
+ rspec-mocks (2.11.2)
26
+ sqlite3 (1.3.6)
27
+
28
+ PLATFORMS
29
+ ruby
30
+
31
+ DEPENDENCIES
32
+ activerecord (= 2.3.14)
33
+ appraisal
34
+ delta_changes!
35
+ rake
36
+ rspec (~> 2)
37
+ sqlite3
@@ -0,0 +1,11 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source :rubygems
4
+
5
+ gem "appraisal"
6
+ gem "rake"
7
+ gem "rspec", "~>2"
8
+ gem "sqlite3"
9
+ gem "activerecord", "3.2.3"
10
+
11
+ gemspec :path=>"../"
@@ -0,0 +1,50 @@
1
+ PATH
2
+ remote: /Users/mgrosser/code/tools/delta_changes
3
+ specs:
4
+ delta_changes (0.0.0)
5
+ activerecord
6
+
7
+ GEM
8
+ remote: http://rubygems.org/
9
+ specs:
10
+ activemodel (3.2.3)
11
+ activesupport (= 3.2.3)
12
+ builder (~> 3.0.0)
13
+ activerecord (3.2.3)
14
+ activemodel (= 3.2.3)
15
+ activesupport (= 3.2.3)
16
+ arel (~> 3.0.2)
17
+ tzinfo (~> 0.3.29)
18
+ activesupport (3.2.3)
19
+ i18n (~> 0.6)
20
+ multi_json (~> 1.0)
21
+ appraisal (0.4.1)
22
+ bundler
23
+ rake
24
+ arel (3.0.2)
25
+ builder (3.0.3)
26
+ diff-lcs (1.1.3)
27
+ i18n (0.6.1)
28
+ multi_json (1.3.6)
29
+ rake (0.9.2.2)
30
+ rspec (2.11.0)
31
+ rspec-core (~> 2.11.0)
32
+ rspec-expectations (~> 2.11.0)
33
+ rspec-mocks (~> 2.11.0)
34
+ rspec-core (2.11.1)
35
+ rspec-expectations (2.11.3)
36
+ diff-lcs (~> 1.1.3)
37
+ rspec-mocks (2.11.2)
38
+ sqlite3 (1.3.6)
39
+ tzinfo (0.3.33)
40
+
41
+ PLATFORMS
42
+ ruby
43
+
44
+ DEPENDENCIES
45
+ activerecord (= 3.2.3)
46
+ appraisal
47
+ delta_changes!
48
+ rake
49
+ rspec (~> 2)
50
+ sqlite3
@@ -0,0 +1,11 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source :rubygems
4
+
5
+ gem "appraisal"
6
+ gem "rake"
7
+ gem "rspec", "~>2"
8
+ gem "sqlite3"
9
+ gem "activerecord", "3.2.3"
10
+
11
+ gemspec :path=>"../"
@@ -0,0 +1,50 @@
1
+ PATH
2
+ remote: /Users/mgrosser/code/tools/delta_changes
3
+ specs:
4
+ delta_changes (0.0.0)
5
+ activerecord
6
+
7
+ GEM
8
+ remote: http://rubygems.org/
9
+ specs:
10
+ activemodel (3.2.3)
11
+ activesupport (= 3.2.3)
12
+ builder (~> 3.0.0)
13
+ activerecord (3.2.3)
14
+ activemodel (= 3.2.3)
15
+ activesupport (= 3.2.3)
16
+ arel (~> 3.0.2)
17
+ tzinfo (~> 0.3.29)
18
+ activesupport (3.2.3)
19
+ i18n (~> 0.6)
20
+ multi_json (~> 1.0)
21
+ appraisal (0.4.1)
22
+ bundler
23
+ rake
24
+ arel (3.0.2)
25
+ builder (3.0.3)
26
+ diff-lcs (1.1.3)
27
+ i18n (0.6.1)
28
+ multi_json (1.3.6)
29
+ rake (0.9.2.2)
30
+ rspec (2.11.0)
31
+ rspec-core (~> 2.11.0)
32
+ rspec-expectations (~> 2.11.0)
33
+ rspec-mocks (~> 2.11.0)
34
+ rspec-core (2.11.1)
35
+ rspec-expectations (2.11.3)
36
+ diff-lcs (~> 1.1.3)
37
+ rspec-mocks (2.11.2)
38
+ sqlite3 (1.3.6)
39
+ tzinfo (0.3.33)
40
+
41
+ PLATFORMS
42
+ ruby
43
+
44
+ DEPENDENCIES
45
+ activerecord (= 3.2.3)
46
+ appraisal
47
+ delta_changes!
48
+ rake
49
+ rspec (~> 2)
50
+ sqlite3
@@ -0,0 +1,3 @@
1
+ module DeltaChanges
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,109 @@
1
+ require "delta_changes/version"
2
+
3
+ module DeltaChanges
4
+ module Extension
5
+ def self.included(base)
6
+ base.extend(ClassMethods)
7
+ base.cattr_accessor :delta_changes_options
8
+ base.attribute_method_suffix '_delta_changed?', '_delta_change', '_delta_was', '_delta_will_change!'
9
+ base.alias_method_chain :write_attribute, :delta_changes
10
+ end
11
+
12
+ module ClassMethods
13
+ def delta_changes(options)
14
+ self.delta_changes_options = options
15
+ define_virtual_attribute_delta_methods
16
+ end
17
+
18
+ #
19
+ # Provide for delta tracking of virtual (non-column) attributes.
20
+ #
21
+ def define_virtual_attribute_delta_methods
22
+ delta_changes_options[:attributes].each do |tracked_attribute|
23
+ class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
24
+ def #{tracked_attribute}_delta_changed?
25
+ attribute_delta_changed?('#{tracked_attribute}')
26
+ end
27
+
28
+ def #{tracked_attribute}_delta_change
29
+ attribute_delta_change('#{tracked_attribute}')
30
+ end
31
+
32
+ def #{tracked_attribute}_delta_was
33
+ attribute_delta_was('#{tracked_attribute}')
34
+ end
35
+
36
+ def #{tracked_attribute}_delta_will_change!
37
+ attribute_delta_will_change!('#{tracked_attribute}')
38
+ end
39
+ RUBY
40
+ end
41
+ end
42
+ end
43
+
44
+ def delta_changes
45
+ delta_changed_attributes.keys.inject({}) { |h, attr| h[attr] = attribute_delta_change(attr); h }
46
+ end
47
+
48
+ def delta_changes?
49
+ delta_changed_attributes.keys.present?
50
+ end
51
+
52
+ ####################################
53
+
54
+ def reset_delta_changes!
55
+ delta_changed_attributes.clear
56
+ end
57
+
58
+ private
59
+
60
+ # Map of change attr => original value.
61
+ def delta_changed_attributes
62
+ @delta_changed_attributes ||= {}
63
+ end
64
+
65
+ # Handle *_delta_changed? for method_missing.
66
+ def attribute_delta_changed?(attr)
67
+ delta_changed_attributes.include?(attr)
68
+ end
69
+
70
+ # Handle *_delta_change for method_missing.
71
+ def attribute_delta_change(attr)
72
+ [delta_changed_attributes[attr], __send__(attr)] if attribute_delta_changed?(attr)
73
+ end
74
+
75
+ # Handle *_delta_was for method_missing.
76
+ def attribute_delta_was(attr)
77
+ attribute_delta_changed?(attr) ? delta_changed_attributes[attr] : __send__(attr)
78
+ end
79
+
80
+ # Handle <tt>*_will_change!</tt> for +method_missing+.
81
+ def attribute_delta_will_change!(attr, options = {})
82
+ attribute_value = if self.class.delta_changes_options[:attributes].include?(attr)
83
+ value = send(attr)
84
+ value.duplicable? ? value.clone : value
85
+ else
86
+ options[:from] || clone_attribute_value(:read_attribute, attr)
87
+ end
88
+ delta_changed_attributes[attr] = attribute_value
89
+ end
90
+
91
+ # Wrap write_attribute to remember original attribute value.
92
+ def write_attribute_with_delta_changes(attr, value)
93
+ attr = attr.to_s
94
+
95
+ if self.class.delta_changes_options[:columns].include?(attr)
96
+ # The attribute already has an unsaved change.
97
+ if delta_changed_attributes.include?(attr)
98
+ old = delta_changed_attributes[attr]
99
+ delta_changed_attributes.delete(attr) unless field_changed?(attr, old, value)
100
+ else
101
+ old = clone_attribute_value(:read_attribute, attr)
102
+ delta_changed_attributes[attr] = old if field_changed?(attr, old, value)
103
+ end
104
+ end
105
+
106
+ write_attribute_without_delta_changes(attr, value)
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,99 @@
1
+ require "spec_helper"
2
+
3
+ describe DeltaChanges do
4
+ it "has a VERSION" do
5
+ DeltaChanges::VERSION.should =~ /^[\.\da-z]+$/
6
+ end
7
+
8
+ it "should not create methods on unspecified attributes" do
9
+ # TODO broken, but that might be rails 2 ...
10
+ #expect{
11
+ # User.new.email_delta_will_change!
12
+ #}.to raise_error
13
+
14
+ expect{
15
+ User.new.bar_delta_will_change!
16
+ }.to raise_error
17
+
18
+ expect{
19
+ User.new.does_not_exist_delta_will_change!
20
+ }.to raise_error
21
+ end
22
+
23
+ describe "#delta_changes" do
24
+ it "should be empty on unchanged" do
25
+ User.new.delta_changes.should == {}
26
+ end
27
+
28
+ it "should be filled by tracked column changes" do
29
+ User.new(:name => "Peter").delta_changes.should == {"name"=>[nil, "Peter"]}
30
+ end
31
+
32
+ it "should be filled by tracked number column change" do
33
+ User.new(:score => 5).delta_changes.should == {"score"=>[nil, 5]}
34
+ end
35
+
36
+ it "should be filled by tracked number column change that have the wrong type" do
37
+ User.new(:score => "5").delta_changes.should == {"score"=>[nil, 5]}
38
+ end
39
+
40
+ it "should not be filled by untracked column changes" do
41
+ User.new(:email => "Peter").delta_changes.should == {}
42
+ end
43
+
44
+ it "should not be filled implicit tracked attribute changes" do
45
+ user = User.new(:foo => 1)
46
+ user.delta_changes.should == {}
47
+ end
48
+
49
+ it "should be filled by explicit tracked attribute changes" do
50
+ user = User.new(:foo => 1)
51
+ user.foo_delta_will_change!
52
+ user.foo = 2
53
+ user.delta_changes.should == {"foo"=>[1, 2]}
54
+ end
55
+
56
+ it "should not mess with normal changes" do
57
+ User.new(:email => "EMAIL", :name => "NAME", :foo => "FOO").changes.should ==
58
+ {"email"=>[nil, "EMAIL"], "name"=>[nil, "NAME"]}
59
+ end
60
+
61
+ it "should not reset columns on save" do
62
+ user = User.create!(:name => "NAME", :foo => "FOO", :bar => "BAR")
63
+ user.delta_changes.should == {"name"=>[nil, "NAME"]}
64
+ end
65
+
66
+ it "should not reset columns on update" do
67
+ user = User.create!(:name => "NAME", :foo => "FOO", :bar => "BAR")
68
+ user.update_attributes(:name => "NAME-2")
69
+ user.delta_changes.should == {"name"=>[nil, "NAME-2"]}
70
+ end
71
+
72
+ # that might change, I'd consider this a bug ... but just documenting for now
73
+ it "should not reset columns on reload" do
74
+ user = User.create!(:name => "NAME", :foo => "FOO", :bar => "BAR")
75
+ user.reload
76
+ user.delta_changes.should == {"name"=>[nil, "NAME"]}
77
+ end
78
+
79
+ it "should have previouse value from db" do
80
+ user = User.create!(:name => "NAME", :foo => "FOO", :bar => "BAR")
81
+ user = User.find(user)
82
+ user.name = "NAME-2"
83
+ user.delta_changes.should == {"name"=>["NAME", "NAME-2"]}
84
+ end
85
+
86
+ it "should not track non-changes on tracked columns" do
87
+ user = User.create!(:score => 5).reload
88
+ user.reset_delta_changes!
89
+
90
+ user.delta_changes.should == {}
91
+
92
+ user.score = 5
93
+ user.delta_changes.should == {}
94
+
95
+ user.score = "5"
96
+ user.delta_changes.should == {}
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,31 @@
1
+ require "bundler/setup"
2
+ require "delta_changes"
3
+ require "active_record"
4
+
5
+ # connect
6
+ ActiveRecord::Base.establish_connection(
7
+ :adapter => "sqlite3",
8
+ :database => ":memory:"
9
+ )
10
+
11
+ # create tables
12
+ ActiveRecord::Schema.define(:version => 1) do
13
+ create_table :users do |t|
14
+ t.string :name
15
+ t.string :email
16
+ t.integer :score
17
+ t.timestamps
18
+ end
19
+ end
20
+
21
+ # create models
22
+ class User < ActiveRecord::Base
23
+ include DeltaChanges::Extension
24
+ attr_accessor :foo, :bar
25
+
26
+ delta_changes :columns => ["name", "score"], :attributes => ["foo"]
27
+
28
+ def full_name
29
+ "Mr. #{name}"
30
+ end
31
+ end
metadata ADDED
@@ -0,0 +1,84 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: delta_changes
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Michael Grosser
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-09-20 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: activerecord
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ description:
31
+ email: michael@grosser.it
32
+ executables: []
33
+ extensions: []
34
+ extra_rdoc_files: []
35
+ files:
36
+ - .travis.yml
37
+ - Appraisals
38
+ - Gemfile
39
+ - Gemfile.lock
40
+ - Rakefile
41
+ - Readme.md
42
+ - delta_changes.gemspec
43
+ - gemfiles/rails2.gemfile
44
+ - gemfiles/rails2.gemfile.lock
45
+ - gemfiles/rails30.gemfile
46
+ - gemfiles/rails30.gemfile.lock
47
+ - gemfiles/rails32.gemfile
48
+ - gemfiles/rails32.gemfile.lock
49
+ - lib/delta_changes.rb
50
+ - lib/delta_changes/version.rb
51
+ - spec/delta_changes_spec.rb
52
+ - spec/spec_helper.rb
53
+ homepage: http://github.com/grosser/delta_changes
54
+ licenses:
55
+ - MIT
56
+ post_install_message:
57
+ rdoc_options: []
58
+ require_paths:
59
+ - lib
60
+ required_ruby_version: !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ segments:
67
+ - 0
68
+ hash: 3940600882562868341
69
+ required_rubygems_version: !ruby/object:Gem::Requirement
70
+ none: false
71
+ requirements:
72
+ - - ! '>='
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ segments:
76
+ - 0
77
+ hash: 3940600882562868341
78
+ requirements: []
79
+ rubyforge_project:
80
+ rubygems_version: 1.8.24
81
+ signing_key:
82
+ specification_version: 3
83
+ summary: Additional real/virtual attribute change tracking independent of ActiveRecords
84
+ test_files: []