track_changes 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,4 @@
1
+ Track Changes TODO List
2
+ =======================
3
+
4
+ * Check for ivars
data/VERSION.yml ADDED
@@ -0,0 +1,4 @@
1
+ ---
2
+ :minor: 3
3
+ :patch: 0
4
+ :major: 0
@@ -0,0 +1,9 @@
1
+ require 'track_changes/instance_methods'
2
+ require 'track_changes/class_methods'
3
+
4
+ module TrackChanges
5
+ def self.included(controller)
6
+ controller.send(:include, InstanceMethods)
7
+ controller.extend(ClassMethods)
8
+ end
9
+ end
@@ -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,5 @@
1
+ module TrackChanges
2
+ module InstanceMethods
3
+ attr_accessor :track_changes_filter
4
+ end
5
+ 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,9 @@
1
+ require 'track_changes/result'
2
+ require 'track_changes/filter'
3
+ require 'track_changes/instance_methods'
4
+ require 'track_changes/class_methods'
5
+ require 'track_changes/base'
6
+
7
+ module TrackChanges
8
+
9
+ 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
@@ -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
@@ -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
@@ -0,0 +1,14 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+ require 'mocha'
5
+ require 'ostruct'
6
+
7
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
8
+ require 'track_changes'
9
+
10
+ class Test::Unit::TestCase
11
+ def test_truth
12
+ assert true
13
+ end
14
+ end
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