track_changes 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README.rdoc +47 -0
- data/TODO +4 -0
- data/VERSION.yml +4 -0
- data/lib/track_changes/base.rb +9 -0
- data/lib/track_changes/class_methods.rb +28 -0
- data/lib/track_changes/filter.rb +43 -0
- data/lib/track_changes/instance_methods.rb +5 -0
- data/lib/track_changes/result.rb +36 -0
- data/lib/track_changes.rb +9 -0
- data/test/class_methods_test.rb +29 -0
- data/test/filter_test.rb +73 -0
- data/test/result_test.rb +35 -0
- data/test/test_helper.rb +14 -0
- metadata +72 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2008 Matt Haley
|
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,47 @@
|
|
1
|
+
= track_changes
|
2
|
+
|
3
|
+
TrackChanges is a Rails plugin to facilitate tracking
|
4
|
+
changes made to an ActiveRecord model in your
|
5
|
+
Controller#update actions. By default, the block is
|
6
|
+
only called if the changes hash is not empty.
|
7
|
+
|
8
|
+
A TrackChanges::Filter is instantiated on each run of the <tt>before_filter</tt>
|
9
|
+
so it should be thread-safe.
|
10
|
+
|
11
|
+
Consult the generated rdoc for more information.
|
12
|
+
|
13
|
+
== Example
|
14
|
+
|
15
|
+
class PostController < ApplicationController
|
16
|
+
include TrackChanges
|
17
|
+
|
18
|
+
before_filter :get_post, :except => [:index, :new]
|
19
|
+
|
20
|
+
track_changes_to :post do |r|
|
21
|
+
# r.changes => Model#changes made during update action
|
22
|
+
RAILS_DEFAULT_LOGGER.debug(r.changes.inspect)
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
def update
|
27
|
+
if @post.update_attributes(params[:post])
|
28
|
+
flash[:notice] = "Success."
|
29
|
+
else
|
30
|
+
render :action => "edit"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Normal controller actions
|
35
|
+
# ...
|
36
|
+
|
37
|
+
protected
|
38
|
+
|
39
|
+
def get_post
|
40
|
+
@post = Post.find(params[:id])
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
== COPYRIGHT
|
46
|
+
|
47
|
+
Copyright (c) 2008 Matt Haley. See LICENSE for details.
|
data/TODO
ADDED
data/VERSION.yml
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'track_changes/filter'
|
2
|
+
|
3
|
+
module TrackChanges
|
4
|
+
module ClassMethods
|
5
|
+
# Uses a combination of <tt>before_filter</tt> and <tt>after_filter</tt>
|
6
|
+
# to act on changes made to a module during a controllers <tt>update</tt>
|
7
|
+
# action.
|
8
|
+
#
|
9
|
+
# Parameters:
|
10
|
+
#
|
11
|
+
# * <tt>model</tt>: A symbol of the model instance name to track changes to
|
12
|
+
# * <tt>only_if_changed</tt>: If true, will only yield block if changes is not empty. Defaults to <tt>true</tt>.
|
13
|
+
# * <tt>options</tt>: An options hash the will be supplied to <tt>before_filter</tt> and <tt>after_filter</tt>. Defaults to <tt>:only => :update</tt>.
|
14
|
+
# * <tt>&block</tt>: The supplied block is called with an instance of Result as its parameter.
|
15
|
+
def track_changes_to(models, only_if_changed = true, options = {:only => :update}, &block)
|
16
|
+
append_before_filter(options) do |controller|
|
17
|
+
track_filter = Filter.new(models, only_if_changed, block)
|
18
|
+
controller.track_changes_filter = track_filter
|
19
|
+
|
20
|
+
track_filter.before(controller)
|
21
|
+
end
|
22
|
+
|
23
|
+
append_after_filter(options)do |controller|
|
24
|
+
controller.track_changes_filter.after(controller)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'track_changes/result'
|
2
|
+
|
3
|
+
module TrackChanges
|
4
|
+
class Filter
|
5
|
+
attr_accessor :call_proc, :changes, :model, :original
|
6
|
+
|
7
|
+
def initialize(model, yield_only_changed, call_proc)
|
8
|
+
@call_proc = call_proc
|
9
|
+
@model = model
|
10
|
+
@yield_only_changed = yield_only_changed
|
11
|
+
@changes = []
|
12
|
+
@original = []
|
13
|
+
end
|
14
|
+
|
15
|
+
def before(controller)
|
16
|
+
@original = clone_model(controller)
|
17
|
+
end
|
18
|
+
|
19
|
+
def after(controller)
|
20
|
+
cur_model = controller.instance_variable_get("@#{@model}")
|
21
|
+
# If changed? returns true, then the update didn't succeed.
|
22
|
+
unless cur_model.changed?
|
23
|
+
@changes = changes_between(@original, cur_model)
|
24
|
+
unless @yield_only_changed && @changes.empty?
|
25
|
+
@call_proc.call(Result.new(@model, controller, @changes))
|
26
|
+
end
|
27
|
+
end
|
28
|
+
nil
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def changes_between(old, new)
|
34
|
+
old.attributes = new.attributes
|
35
|
+
old.changes
|
36
|
+
end
|
37
|
+
|
38
|
+
def clone_model(controller)
|
39
|
+
ivar = controller.instance_variable_get("@#{@model}")
|
40
|
+
ivar.clone if ivar.respond_to?(:clone)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module TrackChanges
|
2
|
+
# A Result instance is yielded by ClassMethods#track_changes_to.
|
3
|
+
#
|
4
|
+
# Example:
|
5
|
+
#
|
6
|
+
# track_changes_to :post do |result|
|
7
|
+
# result.post # => The instance variable in the current_controler
|
8
|
+
# # given by the :post option.
|
9
|
+
# result.controller # => The instance of the current controller
|
10
|
+
# result.current_user # => The current controllers @current_user
|
11
|
+
# result.some_method # => Call some_method on the current controller instance
|
12
|
+
# end
|
13
|
+
class Result
|
14
|
+
attr_accessor :changes, :controller
|
15
|
+
|
16
|
+
def initialize(model_name, controller, changes)
|
17
|
+
@changes = changes
|
18
|
+
@controller = controller
|
19
|
+
|
20
|
+
self.class.send(:define_method, model_name) do
|
21
|
+
@controller.instance_variable_get("@#{model_name}")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Pass all other methods to the controller.
|
26
|
+
def method_missing(method_sym, *args)
|
27
|
+
controller.send(method_sym, *args)
|
28
|
+
end
|
29
|
+
|
30
|
+
# A convienence method that returns the <tt>@current_user</tt>
|
31
|
+
# in the current controller.
|
32
|
+
def current_user
|
33
|
+
user = controller.instance_variable_get("@current_user")
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class ClassMethodsTest < Test::Unit::TestCase
|
4
|
+
include TrackChanges::ClassMethods
|
5
|
+
|
6
|
+
context "a ClassMethods module" do
|
7
|
+
context "when track_changes_to method called" do
|
8
|
+
setup { track_changes_to(:something) {} }
|
9
|
+
|
10
|
+
should "call append_before_filter" do
|
11
|
+
assert @before_filter
|
12
|
+
end
|
13
|
+
|
14
|
+
should "call append_after_filter" do
|
15
|
+
assert @after_filter
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
protected
|
21
|
+
|
22
|
+
def append_before_filter(options = {}, &block)
|
23
|
+
@before_filter = block
|
24
|
+
end
|
25
|
+
|
26
|
+
def append_after_filter(options = {}, &block)
|
27
|
+
@after_filter = block
|
28
|
+
end
|
29
|
+
end
|
data/test/filter_test.rb
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class FilterTest < Test::Unit::TestCase
|
4
|
+
context "a Filter instance" do
|
5
|
+
setup do
|
6
|
+
@controller = mock('@controller')
|
7
|
+
@model = mock('@model')
|
8
|
+
@proc_result = nil
|
9
|
+
@proc = Proc.new { |value| @proc_result = value }
|
10
|
+
|
11
|
+
@filter = TrackChanges::Filter.new(:model, true, @proc)
|
12
|
+
end
|
13
|
+
|
14
|
+
should "set @original when method before called" do
|
15
|
+
@model.expects(:clone).returns(mock('@model cloned', :my_mock? => true))
|
16
|
+
@controller.expects(:instance_variable_get).returns(@model)
|
17
|
+
@filter.before(@controller)
|
18
|
+
|
19
|
+
assert @filter.original.my_mock?
|
20
|
+
end
|
21
|
+
|
22
|
+
should "return a Result when after called" do
|
23
|
+
@filter.original = mock('original', :attributes= => {"attr" => "new"},
|
24
|
+
:changes => {"attr" => ["old", "new"]})
|
25
|
+
@new_model = mock('new_model', :attributes => {"attr" => "new"},
|
26
|
+
:changed? => false)
|
27
|
+
|
28
|
+
@controller.expects(:instance_variable_get).once.returns(@new_model)
|
29
|
+
|
30
|
+
@filter.after(@controller)
|
31
|
+
assert @proc_result.is_a?(TrackChanges::Result)
|
32
|
+
assert_equal({"attr" => ["old", "new"]}, @proc_result.changes)
|
33
|
+
end
|
34
|
+
|
35
|
+
context "when yield_only_changed is true" do
|
36
|
+
setup do
|
37
|
+
@proc_result = nil
|
38
|
+
@filter = TrackChanges::Filter.new(:model, true, @proc)
|
39
|
+
end
|
40
|
+
|
41
|
+
should "not call block when method after called and changes is empty" do
|
42
|
+
@filter.original = mock('original', :attributes= => {"attr" => "new"},
|
43
|
+
:changes => {})
|
44
|
+
@new_model = mock('new_model', :attributes => {"attr" => "new"},
|
45
|
+
:changed? => false)
|
46
|
+
|
47
|
+
@controller.expects(:instance_variable_get).once.returns(@new_model)
|
48
|
+
|
49
|
+
@filter.after(@controller)
|
50
|
+
assert_nil @proc_result
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context "when yield_only_changed is false" do
|
55
|
+
setup do
|
56
|
+
@proc_result = nil
|
57
|
+
@filter = TrackChanges::Filter.new(:model, false, @proc)
|
58
|
+
end
|
59
|
+
|
60
|
+
should "call block when method after called and changes is empty" do
|
61
|
+
@filter.original = mock('original', :attributes= => {"attr" => "new"},
|
62
|
+
:changes => {})
|
63
|
+
@new_model = mock('new_model', :attributes => {"attr" => "new"},
|
64
|
+
:changed? => false)
|
65
|
+
|
66
|
+
@controller.expects(:instance_variable_get).once.returns(@new_model)
|
67
|
+
|
68
|
+
@filter.after(@controller)
|
69
|
+
assert_not_nil @proc_result
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
data/test/result_test.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper'
|
2
|
+
|
3
|
+
class ResultTest < Test::Unit::TestCase
|
4
|
+
context "a Result instance" do
|
5
|
+
setup do
|
6
|
+
@controller = mock()
|
7
|
+
@changes = { "attr" => ["old", "new"] }
|
8
|
+
|
9
|
+
@filter = TrackChanges::Result.new(:model, @controller, @changes)
|
10
|
+
end
|
11
|
+
|
12
|
+
should "define given symbol as method" do
|
13
|
+
@controller.expects(:instance_variable_get).with() {|v| v == "@model"}.returns(:ivar_result)
|
14
|
+
assert_equal :ivar_result, @filter.model
|
15
|
+
end
|
16
|
+
|
17
|
+
should "send method_missing calls to controller" do
|
18
|
+
@controller.expects(:bogus_method).returns(:got_it)
|
19
|
+
assert_equal :got_it, @filter.bogus_method
|
20
|
+
end
|
21
|
+
|
22
|
+
should "get @current_user from controller" do
|
23
|
+
@controller.expects(:instance_variable_get).with() {|v| v == "@current_user"}.returns(:ivar_result)
|
24
|
+
assert_equal :ivar_result, @filter.current_user
|
25
|
+
end
|
26
|
+
|
27
|
+
should "return changes" do
|
28
|
+
assert_equal @changes, @filter.changes
|
29
|
+
end
|
30
|
+
|
31
|
+
should "return controller" do
|
32
|
+
assert_equal @controller, @filter.controller
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/test/test_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: track_changes
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.3.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Matt Haley
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-10-12 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: Easier auditing of Rails model changes in your controllers.
|
17
|
+
email: matt@smajn.net
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- LICENSE
|
24
|
+
- README.rdoc
|
25
|
+
files:
|
26
|
+
- LICENSE
|
27
|
+
- TODO
|
28
|
+
- VERSION.yml
|
29
|
+
- lib/track_changes.rb
|
30
|
+
- lib/track_changes/base.rb
|
31
|
+
- lib/track_changes/class_methods.rb
|
32
|
+
- lib/track_changes/filter.rb
|
33
|
+
- lib/track_changes/instance_methods.rb
|
34
|
+
- lib/track_changes/result.rb
|
35
|
+
- test/class_methods_test.rb
|
36
|
+
- test/filter_test.rb
|
37
|
+
- test/result_test.rb
|
38
|
+
- test/test_helper.rb
|
39
|
+
- README.rdoc
|
40
|
+
has_rdoc: true
|
41
|
+
homepage: http://github.com/smajn/track_changes
|
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: mhaley
|
64
|
+
rubygems_version: 1.3.5
|
65
|
+
signing_key:
|
66
|
+
specification_version: 3
|
67
|
+
summary: Easier auditing of Rails model changes in your controllers.
|
68
|
+
test_files:
|
69
|
+
- test/result_test.rb
|
70
|
+
- test/class_methods_test.rb
|
71
|
+
- test/filter_test.rb
|
72
|
+
- test/test_helper.rb
|