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 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,2 @@
1
+ TrackChanges::Engine.routes.draw do
2
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :track_changes do
3
+ # # Task goes here
4
+ # end
@@ -0,0 +1,6 @@
1
+ require 'track_changes/track_changes'
2
+
3
+ module TrackChanges
4
+ class Engine < ::Rails::Engine
5
+ end
6
+ end
@@ -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
@@ -0,0 +1,3 @@
1
+ module TrackChanges
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,4 @@
1
+ require "track_changes/engine"
2
+
3
+ module TrackChanges
4
+ 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: []