culturecode-track_changes 0.0.1
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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +44 -0
- data/Rakefile +37 -0
- data/app/helpers/track_changes/diff_helper.rb +49 -0
- data/app/models/track_changes/diff.rb +18 -0
- data/app/models/track_changes/snapshot.rb +42 -0
- data/config/initializers/extend_activerecord.rb +1 -0
- data/config/routes.rb +2 -0
- data/lib/tasks/track_changes_tasks.rake +4 -0
- data/lib/track_changes/engine.rb +6 -0
- data/lib/track_changes/track_changes.rb +54 -0
- data/lib/track_changes/version.rb +3 -0
- data/lib/track_changes.rb +4 -0
- metadata +86 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: d9f210dc9b044e9606eaceda2cb73ee6b16d41ba
|
4
|
+
data.tar.gz: 381b4764ef7f663a87482ce71d91510769a422b2
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ebe1afa939c705490216f2b144a359cf44d18c59d694745d6d314ca3e848e70e97f8831a5c743f4be10ac2e824c1ac2c69c4288389eef68a1e6eb6340997c9cb
|
7
|
+
data.tar.gz: 235713afc3708a6b5208350fb201e53cde080630779003f14a4034704cf248cbca2e0e390a9bf4b2be3584924c14b98842b42525d3472f987d2d93fc3a0b749f
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2015 Nicholas Jakobsen
|
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.md
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
# Track Changes
|
2
|
+
|
3
|
+
## Installation
|
4
|
+
|
5
|
+
Migration
|
6
|
+
```ruby
|
7
|
+
class CreateTrackChangesTables < ActiveRecord::Migration
|
8
|
+
def change
|
9
|
+
create_table :track_changes_snapshots do |t|
|
10
|
+
t.references :record, :polymorphic => true
|
11
|
+
t.text :state
|
12
|
+
t.timestamps
|
13
|
+
end
|
14
|
+
|
15
|
+
create_table :track_changes_diffs do |t|
|
16
|
+
t.references :record, :polymorphic => true
|
17
|
+
t.text :from
|
18
|
+
t.text :to
|
19
|
+
t.string :action
|
20
|
+
t.string :changes_by
|
21
|
+
t.timestamps
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
```
|
26
|
+
|
27
|
+
## Usage
|
28
|
+
|
29
|
+
In your model
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
class Person < ActiveRecord::Base
|
33
|
+
tracks_changes # may also pass an options hash
|
34
|
+
end
|
35
|
+
```
|
36
|
+
|
37
|
+
### Options
|
38
|
+
By default all model attributes are tracked, except the primary_key, usually ```id```, ```created_at```, and ```updated_at```.
|
39
|
+
|
40
|
+
- ```:only``` accepts a field name or array of field names to track instead of the default fields
|
41
|
+
- ```:except``` accepts a field name or array of field names to ignore
|
42
|
+
- ```:methods``` accepts a field name or array of field names to track in addition to the default fields
|
43
|
+
- ```:track_timestamps``` accepts a boolean, enabling or disabling tracking of ```created_at``` and ```updated_at```
|
44
|
+
- ```:track_primary_key``` accepts a boolean, enabling or disabling tracking of the model's primary key
|
data/Rakefile
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'rdoc/task'
|
8
|
+
|
9
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
10
|
+
rdoc.rdoc_dir = 'rdoc'
|
11
|
+
rdoc.title = 'TrackChanges'
|
12
|
+
rdoc.options << '--line-numbers'
|
13
|
+
rdoc.rdoc_files.include('README.rdoc')
|
14
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
15
|
+
end
|
16
|
+
|
17
|
+
APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
|
18
|
+
load 'rails/tasks/engine.rake'
|
19
|
+
|
20
|
+
|
21
|
+
load 'rails/tasks/statistics.rake'
|
22
|
+
|
23
|
+
|
24
|
+
|
25
|
+
Bundler::GemHelper.install_tasks
|
26
|
+
|
27
|
+
require 'rake/testtask'
|
28
|
+
|
29
|
+
Rake::TestTask.new(:test) do |t|
|
30
|
+
t.libs << 'lib'
|
31
|
+
t.libs << 'test'
|
32
|
+
t.pattern = 'test/**/*_test.rb'
|
33
|
+
t.verbose = false
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
task default: :test
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module TrackChanges
|
2
|
+
module DiffHelper
|
3
|
+
def diff_action(diff)
|
4
|
+
case diff.action
|
5
|
+
when 'create'
|
6
|
+
'added'
|
7
|
+
when 'update'
|
8
|
+
'updated'
|
9
|
+
when 'destroy'
|
10
|
+
'deleted'
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def diff_change_sentence(diff, field, changes, link_models = [])
|
15
|
+
from, to = changes.is_a?(Array) ? changes : [nil, changes]
|
16
|
+
# return if diff.action == 'destroy'
|
17
|
+
return if from.blank? && to.blank?
|
18
|
+
|
19
|
+
if record = diff.record
|
20
|
+
field_name = diff.record.class.human_attribute_name(field)
|
21
|
+
reflection = diff.record.class.reflections.values.detect {|reflection| reflection.foreign_key == field.to_s }
|
22
|
+
end
|
23
|
+
|
24
|
+
if reflection
|
25
|
+
from = reflection.klass.find(from) if from.present?
|
26
|
+
to = reflection.klass.find(to) if to.present?
|
27
|
+
end
|
28
|
+
|
29
|
+
if from.blank?
|
30
|
+
content_tag(:span, field_name, :class => 'field_name') + " set to " + content_tag(:span, link_diff_field_value(to, link_models), :class => 'field_value')
|
31
|
+
elsif to.blank?
|
32
|
+
content_tag(:span, field_name, :class => 'field_name') + " removed"
|
33
|
+
else
|
34
|
+
content_tag(:span, field_name, :class => 'field_name') + " changed from " + content_tag(:span, link_diff_field_value(from, link_models), :class => 'field_value') + " to " + content_tag(:span, link_diff_field_value(to, link_models), :class => 'field_value')
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def link_diff_field_value(value, link_models = [])
|
39
|
+
case value
|
40
|
+
when Array
|
41
|
+
value.collect{|v| link_diff_field_value(v, link_models) }.to_sentence.html_safe
|
42
|
+
when *link_models
|
43
|
+
link_to(value.to_s, value)
|
44
|
+
else
|
45
|
+
value
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module TrackChanges
|
2
|
+
class Diff < ActiveRecord::Base
|
3
|
+
self.table_name = "track_changes_diffs"
|
4
|
+
|
5
|
+
belongs_to :record, :polymorphic => true
|
6
|
+
|
7
|
+
serialize :from, Hash
|
8
|
+
serialize :to, Hash
|
9
|
+
|
10
|
+
# Returns a hash of changes where the key is the field name
|
11
|
+
# and the value is an array of the from value and the to value
|
12
|
+
def changes
|
13
|
+
Hash[(from.keys + to.keys).collect do |key|
|
14
|
+
[key, [from[key], to[key]]]
|
15
|
+
end]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module TrackChanges
|
2
|
+
class Snapshot < ActiveRecord::Base
|
3
|
+
self.table_name = "track_changes_snapshots"
|
4
|
+
|
5
|
+
belongs_to :record, :polymorphic => true
|
6
|
+
|
7
|
+
serialize :state, Hash
|
8
|
+
|
9
|
+
before_save :capture_record_state
|
10
|
+
|
11
|
+
# Creates a diff object that shows the changes between this snapshot and the record's state
|
12
|
+
def create_diff(diff_attributes = {})
|
13
|
+
record_state = self.class.record_state(record)
|
14
|
+
snapshot_state = self.state
|
15
|
+
from = {}
|
16
|
+
to = {}
|
17
|
+
|
18
|
+
record.class.track_changes_fields.each do |key|
|
19
|
+
if snapshot_state.key?(key) && snapshot_state[key] != record_state[key]
|
20
|
+
from[key] = snapshot_state[key]
|
21
|
+
to[key] = record_state[key]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
diff_attributes = diff_attributes.reverse_merge(:from => from, :to => to)
|
25
|
+
record.diffs.create!(diff_attributes) unless diff_attributes[:from].empty? && diff_attributes[:to].empty?
|
26
|
+
end
|
27
|
+
|
28
|
+
# Updates the snapshot to the current record state
|
29
|
+
def update
|
30
|
+
save
|
31
|
+
end
|
32
|
+
|
33
|
+
def capture_record_state
|
34
|
+
self.state = self.class.record_state(record)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Returns a hash of the current values for all tracked fields on the record
|
38
|
+
def self.record_state(record)
|
39
|
+
Hash[record.class.track_changes_fields.collect {|method_name| [method_name, record.send(method_name)] }]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
ActiveRecord::Base.send :extend, TrackChanges::ActsMethod
|
data/config/routes.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
module TrackChanges
|
2
|
+
module ActsMethod
|
3
|
+
def tracks_changes(options = {})
|
4
|
+
extend ClassMethods
|
5
|
+
include InstanceMethods
|
6
|
+
|
7
|
+
class_attribute :track_changes_options
|
8
|
+
self.track_changes_options = options
|
9
|
+
|
10
|
+
attr_accessor :track_changes # Faux attribute to allow disabling of change tracking on this record
|
11
|
+
attr_accessor :track_changes_by # Faux attribute to store who made the changes so we can save it in the diff
|
12
|
+
|
13
|
+
has_one :snapshot, :as => :record, :class_name => 'TrackChanges::Snapshot' # A representation of this record as it was last saved
|
14
|
+
has_many :diffs, :as => :record, :class_name => 'TrackChanges::Diff' # A representation of changes made between saves through this record's lifetime
|
15
|
+
|
16
|
+
after_save :persist_tracked_changes
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
module ClassMethods
|
21
|
+
# Returns the method names to call to fetch the fields tracked for changes
|
22
|
+
def track_changes_fields
|
23
|
+
fields = Array(track_changes_options[:only]).collect(&:to_s).presence || self.attribute_names
|
24
|
+
fields -= Array(track_changes_options[:except]).collect(&:to_s)
|
25
|
+
fields += Array(track_changes_options[:methods]).collect(&:to_s)
|
26
|
+
fields -= ['created_at', 'updated_at'] unless track_changes_options[:track_timestamps]
|
27
|
+
fields -= [primary_key] unless track_changes_options[:track_primary_key]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
module InstanceMethods
|
32
|
+
private
|
33
|
+
|
34
|
+
# Compares the last tracked changes to the current state and saves a diff of the changes
|
35
|
+
def persist_tracked_changes
|
36
|
+
return if track_changes == false
|
37
|
+
|
38
|
+
new_record = id_was.blank?
|
39
|
+
action = new_record ? 'create' : 'update'
|
40
|
+
changes_by = track_changes_by.is_a?(ActiveRecord::Base) ? track_changes_by.id : track_changes_by
|
41
|
+
|
42
|
+
if snapshot
|
43
|
+
snapshot.create_diff(:action => action, :changes_by => changes_by)
|
44
|
+
snapshot.update
|
45
|
+
elsif new_record
|
46
|
+
create_snapshot
|
47
|
+
snapshot.create_diff(:action => action, :changes_by => changes_by, :from => {}, :to => snapshot.state)
|
48
|
+
else # We started tracking changes after the item was created
|
49
|
+
create_snapshot
|
50
|
+
snapshot.create_diff(:action => action, :changes_by => changes_by, :from => {})
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
metadata
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: culturecode-track_changes
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Nicholas Jakobsen, Ryan Wallace
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-08-24 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 4.2.1
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 4.2.1
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: sqlite3
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
description: Easily track changes to various ActiveRecord models. Supports both attribute
|
42
|
+
and method tracking.
|
43
|
+
email:
|
44
|
+
- contact@culturecode.ca
|
45
|
+
executables: []
|
46
|
+
extensions: []
|
47
|
+
extra_rdoc_files: []
|
48
|
+
files:
|
49
|
+
- MIT-LICENSE
|
50
|
+
- README.md
|
51
|
+
- Rakefile
|
52
|
+
- app/helpers/track_changes/diff_helper.rb
|
53
|
+
- app/models/track_changes/diff.rb
|
54
|
+
- app/models/track_changes/snapshot.rb
|
55
|
+
- config/initializers/extend_activerecord.rb
|
56
|
+
- config/routes.rb
|
57
|
+
- lib/tasks/track_changes_tasks.rake
|
58
|
+
- lib/track_changes.rb
|
59
|
+
- lib/track_changes/engine.rb
|
60
|
+
- lib/track_changes/track_changes.rb
|
61
|
+
- lib/track_changes/version.rb
|
62
|
+
homepage: https://github.com/culturecode/track_changes
|
63
|
+
licenses:
|
64
|
+
- MIT
|
65
|
+
metadata: {}
|
66
|
+
post_install_message:
|
67
|
+
rdoc_options: []
|
68
|
+
require_paths:
|
69
|
+
- lib
|
70
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0'
|
75
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
76
|
+
requirements:
|
77
|
+
- - ">="
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: '0'
|
80
|
+
requirements: []
|
81
|
+
rubyforge_project:
|
82
|
+
rubygems_version: 2.4.7
|
83
|
+
signing_key:
|
84
|
+
specification_version: 4
|
85
|
+
summary: Easily track changes to various ActiveRecord models
|
86
|
+
test_files: []
|