trackable 0.1.0

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.
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 [name of plugin creator]
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 ADDED
@@ -0,0 +1,13 @@
1
+ ActsAsEventable
2
+ ===============
3
+
4
+ Introduction goes here.
5
+
6
+
7
+ Example
8
+ =======
9
+
10
+ Example goes here.
11
+
12
+
13
+ Copyright (c) 2010 [name of plugin creator], released under the MIT license
data/README.textile ADDED
@@ -0,0 +1 @@
1
+ h2. acts_as_eventable
data/Rakefile ADDED
@@ -0,0 +1,40 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+
5
+ begin
6
+ require 'jeweler'
7
+ Jeweler::Tasks.new do |gem|
8
+ gem.name = "trackable"
9
+ gem.summary = %Q{Add a readable event history to your models}
10
+ gem.description = %Q{Add a readable event history to your models}
11
+ gem.email = "jim@jimvanfleet.com"
12
+ gem.homepage = "http://github.com/bigfleet/trackable"
13
+ gem.authors = ["bigfleet"]
14
+ gem.add_development_dependency "ruby-sqlite", ">= 0"
15
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
16
+ end
17
+ Jeweler::GemcutterTasks.new
18
+ rescue LoadError
19
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
20
+ end
21
+
22
+ desc 'Default: run unit tests.'
23
+ task :default => :test
24
+
25
+ desc 'Test the acts_as_eventable plugin.'
26
+ Rake::TestTask.new(:test) do |t|
27
+ t.libs << 'lib'
28
+ t.libs << 'test'
29
+ t.pattern = 'test/**/*_test.rb'
30
+ t.verbose = true
31
+ end
32
+
33
+ desc 'Generate documentation for the acts_as_eventable plugin.'
34
+ Rake::RDocTask.new(:rdoc) do |rdoc|
35
+ rdoc.rdoc_dir = 'rdoc'
36
+ rdoc.title = 'Trackable'
37
+ rdoc.options << '--line-numbers' << '--inline-source'
38
+ rdoc.rdoc_files.include('README')
39
+ rdoc.rdoc_files.include('lib/**/*.rb')
40
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,17 @@
1
+ class TrackableMigration < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :events do |t|
4
+ t.string :eventable_type, :null => false
5
+ t.integer :eventable_id, :null => false
6
+ t.string :field_name, :null => false
7
+ t.string :whodunnit
8
+ t.string :message
9
+ t.datetime :created_at
10
+ end
11
+ add_index :events, [:eventable_id, :eventable_type, :field_name]
12
+ end
13
+
14
+ def self.down
15
+ drop_table :events
16
+ end
17
+ end
@@ -0,0 +1,11 @@
1
+ class TrackableMigrationGenerator < Rails::Generator::Base
2
+ def manifest
3
+ record do |m|
4
+ m.migration_template 'migration.rb', 'db/migrate'
5
+ end
6
+ end
7
+
8
+ def file_name
9
+ "trackable_migration"
10
+ end
11
+ end
data/init.rb ADDED
@@ -0,0 +1,2 @@
1
+ # Include hook code here
2
+ require 'trackable'
data/install.rb ADDED
@@ -0,0 +1 @@
1
+ # Install hook code here
@@ -0,0 +1,32 @@
1
+ module ActsAsTrackable
2
+ def self.included(base)
3
+ base.send :extend, ClassMethods
4
+ base.instance_eval{
5
+ cattr_accessor :eventable_options
6
+ }
7
+ end
8
+
9
+ module ClassMethods
10
+
11
+ def trackable(options)
12
+ instance_eval{
13
+ self.eventable_options = options
14
+ }
15
+ send :include, InstanceMethods
16
+ has_many :events, :as => :eventable, :dependent => :destroy, :order => "created_at desc"
17
+ after_save :record_events
18
+ end
19
+ end
20
+
21
+ module InstanceMethods
22
+ def record_events
23
+ active_keys = changes.keys.reject{ |key| %w{id created_at updated_at}.include?(key)}
24
+ active_keys.map do |key|
25
+ old_val, new_val = changes[key]
26
+ events.create(Event.attributes_from(self, key, old_val, new_val))
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ ActiveRecord::Base.send :include, ActsAsTrackable
@@ -0,0 +1,23 @@
1
+ class Event < ActiveRecord::Base
2
+
3
+ def self.attributes_from(model, key, old_val, new_val)
4
+ eventable_options = model.class.eventable_options
5
+ msg = if eventable_options[:events][key.to_sym] && eventable_options[:events][key.to_sym][new_val]
6
+ eventable_options[:events][key.to_sym][new_val]
7
+ elsif eventable_options[:events][key.to_sym] && eventable_options[:events][key.to_sym][:message]
8
+ the_proc = eventable_options[:events][key.to_sym][:message]
9
+ if key.index("_id") # hackish, but this is a convention
10
+ reference = key[0..-4].to_sym
11
+ the_proc.call(model.send(reference).to_s)
12
+ else
13
+ the_proc.call(new_val)
14
+ end
15
+ elsif key.index("_id") # hackish, but this is a convention
16
+ reference = key[0..-4].to_sym
17
+ "#{key.titleize} changed to #{model.send(reference).to_s}"
18
+ else
19
+ "#{key.titleize} changed to #{new_val}"
20
+ end
21
+ {:field_name => key, :message => msg, :whodunnit => Trackable.whodunnit}
22
+ end
23
+ end
data/lib/trackable.rb ADDED
@@ -0,0 +1,29 @@
1
+ # Trackable
2
+ require 'trackable/acts_as_trackable'
3
+ require 'trackable/event'
4
+
5
+ module Trackable
6
+ @@whodunnit = nil
7
+
8
+ def self.included(base)
9
+ base.before_filter :set_whodunnit
10
+ end
11
+
12
+ def self.whodunnit
13
+ @@whodunnit.respond_to?(:call) ? @@whodunnit.call : @@whodunnit
14
+ end
15
+
16
+ def self.whodunnit=(value)
17
+ @@whodunnit = value
18
+ end
19
+
20
+ private
21
+
22
+ def set_whodunnit
23
+ @@whodunnit = lambda {
24
+ self.send :current_user rescue nil
25
+ }
26
+ end
27
+ end
28
+
29
+ ActionController::Base.send :include, Trackable
data/rails/init.rb ADDED
@@ -0,0 +1 @@
1
+ require File.join(File.dirname(__FILE__), '/../lib/trackable')
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :trackable do
3
+ # # Task goes here
4
+ # end
data/test/database.yml ADDED
@@ -0,0 +1,3 @@
1
+ sqlite:
2
+ :adapter: sqlite
3
+ :dbfile: vendor/plugins/trackable/test/trackable_plugin.sqlite.db
@@ -0,0 +1,33 @@
1
+ require File.dirname(__FILE__) + '/test_helper.rb'
2
+ require 'rails_generator'
3
+ require 'rails_generator/scripts/generate'
4
+
5
+ class MigrationTest < Test::Unit::TestCase
6
+
7
+ def setup
8
+ FileUtils.mkdir_p(fake_rails_root)
9
+ @original_files = file_list
10
+ end
11
+
12
+ def teardown
13
+ ActiveRecord::Base.pluralize_table_names = true
14
+ FileUtils.rm_r(fake_rails_root)
15
+ end
16
+
17
+ def test_generates_correct_file_name
18
+ Rails::Generator::Scripts::Generate.new.run(["trackable_migration"], :destination => fake_rails_root)
19
+ new_file = (file_list - @original_files).first
20
+ assert_match /trackable_migration/, new_file
21
+ assert File.read(new_file) =~ /create_table :events do |t|/
22
+ end
23
+
24
+ private
25
+ def fake_rails_root
26
+ File.join(File.dirname(__FILE__), 'rails_root')
27
+ end
28
+
29
+ def file_list
30
+ Dir.glob(File.join(fake_rails_root, "db", "migrate", "*"))
31
+ end
32
+
33
+ end
data/test/models.rb ADDED
@@ -0,0 +1,15 @@
1
+ class Foo < ActiveRecord::Base
2
+ trackable :events =>{
3
+ :no_homers => {true => "Homers have been barred.", false => "Homers have been allowed."},
4
+ :custom_status => {:message => Proc.new {|n| "The value of a custom string field changed to #{n}" }},
5
+ :custom_bar_id => {:message => Proc.new{|n| "Active Bar set to #{n}"}}
6
+ }
7
+ belongs_to :bar, :class_name => "Bar"
8
+ belongs_to :custom_bar, :class_name => "Bar"
9
+ end
10
+
11
+ class Bar < ActiveRecord::Base
12
+ def to_s
13
+ name
14
+ end
15
+ end
data/test/schema.rb ADDED
@@ -0,0 +1,25 @@
1
+ ActiveRecord::Schema.define(:version => 0) do
2
+ create_table :foos, :force => true do |t|
3
+ t.boolean :no_homers
4
+ t.string :status
5
+ t.string :custom_status
6
+ t.integer :bar_id
7
+ t.integer :custom_bar_id
8
+
9
+ t.timestamps
10
+ end
11
+ create_table :bars, :force => true do |t|
12
+ t.string :name
13
+
14
+ t.timestamps
15
+ end
16
+ # This is the active table
17
+ create_table :events, :force => true do |t|
18
+ t.string :eventable_type, :null => false
19
+ t.integer :eventable_id, :null => false
20
+ t.string :field_name, :null => false
21
+ t.string :whodunnit
22
+ t.string :message
23
+ t.datetime :created_at
24
+ end
25
+ end
@@ -0,0 +1,37 @@
1
+ ENV['RAILS_ENV'] = 'test'
2
+ ENV['RAILS_ROOT'] ||= File.dirname(__FILE__) + '/../../../..'
3
+
4
+ require 'rubygems'
5
+ require 'test/unit'
6
+
7
+ require File.expand_path(File.join(ENV['RAILS_ROOT'], 'config/environment.rb'))
8
+ require 'models'
9
+
10
+ def load_schema
11
+ config = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml'))
12
+ ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + "/debug.log")
13
+
14
+ db_adapter = ENV['DB']
15
+
16
+ # no db passed, try one of these fine config-free DBs before bombing.
17
+ db_adapter ||=
18
+ begin
19
+ require 'rubygems'
20
+ require 'sqlite'
21
+ 'sqlite'
22
+ rescue MissingSourceFile
23
+ begin
24
+ require 'sqlite3'
25
+ 'sqlite3'
26
+ rescue MissingSourceFile
27
+ end
28
+ end
29
+
30
+ if db_adapter.nil?
31
+ raise "No DB Adapter selected. Pass the DB= option to pick one, or install Sqlite or Sqlite3."
32
+ end
33
+
34
+ ActiveRecord::Base.establish_connection(config[db_adapter])
35
+ load(File.dirname(__FILE__) + "/schema.rb")
36
+ require File.dirname(__FILE__) + '/../init.rb'
37
+ end
@@ -0,0 +1,56 @@
1
+ require File.dirname(__FILE__) + '/test_helper.rb'
2
+
3
+ class ApplicationController < ActionController::Base
4
+ def rescue_action(e)
5
+ raise e
6
+ end
7
+
8
+ # Returns id of hypothetical current user
9
+ def current_user
10
+ 153
11
+ end
12
+ end
13
+
14
+ class FoosController < ApplicationController
15
+ def create
16
+ @foo = Foo.create params[:foo]
17
+ head :ok
18
+ end
19
+
20
+ def update
21
+ @foo = Foo.find params[:id]
22
+ @foo.update_attributes params[:foo]
23
+ head :ok
24
+ end
25
+ end
26
+
27
+
28
+ class TrackableControllerTest < ActionController::TestCase #Test::Unit::TestCase
29
+ def setup
30
+ @controller = FoosController.new
31
+ @request = ActionController::TestRequest.new
32
+ @response = ActionController::TestResponse.new
33
+
34
+ ActionController::Routing::Routes.draw do |map|
35
+ map.resources :foos
36
+ end
37
+ end
38
+
39
+ test 'create' do
40
+ post :create, :foo => { :status => 'Flugel' }
41
+ foo = assigns(:foo)
42
+ assert_equal 1, foo.events.length
43
+ assert_equal 153, foo.events.last.whodunnit.to_i
44
+ end
45
+
46
+ test 'update' do
47
+ w = Foo.create :status => 'Duvel'
48
+ assert_equal 1, w.events.length
49
+ put :update, :id => w.id, :foo => { :status => 'Bugle' }
50
+ foo = assigns(:foo)
51
+ assert_equal 2, foo.events.length
52
+ assert_equal 153, foo.events.last.whodunnit.to_i
53
+ end
54
+
55
+ end
56
+
@@ -0,0 +1,131 @@
1
+ require 'test_helper'
2
+
3
+ class TrackableTest < Test::Unit::TestCase
4
+
5
+ load_schema
6
+
7
+ def test_schema_has_loaded_correctly
8
+ Foo.create
9
+ Bar.create
10
+ assert Foo.count >= 1
11
+ assert Bar.count >= 1
12
+ end
13
+
14
+ def test_cattr_accessor_installed
15
+ assert_not_nil Foo.eventable_options
16
+ end
17
+
18
+ def test_desired_boolean_change_trigger
19
+ foo = Foo.create
20
+ foo.update_attribute(:no_homers, true)
21
+ assert_equal 1, foo.events.size
22
+ end
23
+
24
+ def test_desired_boolean_messaging
25
+ foo = Foo.create
26
+ foo.update_attribute(:no_homers, true)
27
+ assert_equal "Homers have been barred.", foo.events.first.message
28
+ end
29
+
30
+ def test_desired_boolean_messaging_stackable
31
+ foo = Foo.create
32
+ foo.update_attribute(:no_homers, true)
33
+ sleep 1 #mur
34
+ foo.update_attribute(:no_homers, false)
35
+ assert_equal "Homers have been allowed.", foo.events.first.message
36
+ assert_equal "Homers have been barred.", foo.events.last.message
37
+ end
38
+
39
+ def test_desired_string_change_trigger
40
+ foo = Foo.create
41
+ foo.update_attribute(:status, "New")
42
+ assert_equal 1, foo.events.size
43
+ end
44
+
45
+ def test_desired_string_messaging
46
+ foo = Foo.create
47
+ foo.update_attribute(:status, "New")
48
+ assert_equal "Status changed to New", foo.events.first.message
49
+ end
50
+
51
+ def test_desired_string_stackability
52
+ foo = Foo.create
53
+ foo.update_attribute(:status, "Old")
54
+ sleep 1 #mur
55
+ foo.update_attribute(:status, "New")
56
+ assert_equal "Status changed to New", foo.events.first.message
57
+ assert_equal "Status changed to Old", foo.events.last.message
58
+ end
59
+
60
+ def test_desired_custom_string_change_trigger
61
+ foo = Foo.create
62
+ foo.update_attribute(:custom_status, "New")
63
+ assert_equal 1, foo.events.size
64
+ end
65
+
66
+ def test_desired_custom_string_messaging
67
+ foo = Foo.create
68
+ foo.update_attribute(:custom_status, "New")
69
+ assert_equal "The value of a custom string field changed to New", foo.events.first.message
70
+ end
71
+
72
+ def test_desired_custom_string_stackability
73
+ foo = Foo.create
74
+ foo.update_attribute(:custom_status, "Old")
75
+ sleep 1 #mur
76
+ foo.update_attribute(:custom_status, "New")
77
+ assert_equal "The value of a custom string field changed to New", foo.events.first.message
78
+ assert_equal "The value of a custom string field changed to Old", foo.events.last.message
79
+ end
80
+
81
+ def test_desired_reference_change_trigger
82
+ foo = Foo.create
83
+ bar = Bar.create(:name => "Baloney")
84
+ foo.bar = bar; foo.save
85
+ assert_equal 1, foo.events.size
86
+ end
87
+
88
+ def test_desired_reference_messaging
89
+ foo = Foo.create
90
+ bar = Bar.create(:name => "Baloney")
91
+ foo.bar = bar; foo.save
92
+ assert_equal "Bar changed to Baloney", foo.events.first.message
93
+ end
94
+
95
+ def test_desired_reference_stackability
96
+ foo = Foo.create
97
+ bar1 = Bar.create(:name => "Peanut Butter")
98
+ bar2 = Bar.create(:name => "Jelly")
99
+ foo.bar = bar1; foo.save
100
+ sleep 1 #mur
101
+ foo.bar = bar2; foo.save
102
+ assert_equal "Bar changed to Jelly", foo.events.first.message
103
+ assert_equal "Bar changed to Peanut Butter", foo.events.last.message
104
+ end
105
+
106
+ def test_desired_custom_reference_change_trigger
107
+ foo = Foo.create
108
+ bar = Bar.create(:name => "Baloney")
109
+ foo.custom_bar = bar; foo.save
110
+ assert_equal 1, foo.events.size
111
+ end
112
+
113
+ def test_desired_custom_reference_messaging
114
+ foo = Foo.create
115
+ bar = Bar.create(:name => "Baloney")
116
+ foo.custom_bar = bar; foo.save
117
+ assert_equal "Active Bar set to Baloney", foo.events.first.message
118
+ end
119
+
120
+ def test_desired_custom_reference_stackability
121
+ foo = Foo.create
122
+ bar1 = Bar.create(:name => "Peanut Butter")
123
+ bar2 = Bar.create(:name => "Jelly")
124
+ foo.custom_bar = bar1; foo.save
125
+ sleep 1 #mur
126
+ foo.custom_bar = bar2; foo.save
127
+ assert_equal "Active Bar set to Jelly", foo.events.first.message
128
+ assert_equal "Active Bar set to Peanut Butter", foo.events.last.message
129
+ end
130
+
131
+ end
data/uninstall.rb ADDED
@@ -0,0 +1 @@
1
+ # Uninstall hook code here
metadata ADDED
@@ -0,0 +1,99 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: trackable
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 0
9
+ version: 0.1.0
10
+ platform: ruby
11
+ authors:
12
+ - bigfleet
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-02-23 00:00:00 -05:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: ruby-sqlite
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 0
29
+ version: "0"
30
+ type: :development
31
+ version_requirements: *id001
32
+ description: Add a readable event history to your models
33
+ email: jim@jimvanfleet.com
34
+ executables: []
35
+
36
+ extensions: []
37
+
38
+ extra_rdoc_files:
39
+ - README
40
+ - README.textile
41
+ files:
42
+ - MIT-LICENSE
43
+ - README
44
+ - Rakefile
45
+ - VERSION
46
+ - generators/trackable_migration/templates/migration.rb
47
+ - generators/trackable_migration/trackable_migration_generator.rb
48
+ - init.rb
49
+ - install.rb
50
+ - lib/trackable.rb
51
+ - lib/trackable/acts_as_trackable.rb
52
+ - lib/trackable/event.rb
53
+ - rails/init.rb
54
+ - tasks/acts_as_eventable_tasks.rake
55
+ - test/database.yml
56
+ - test/models.rb
57
+ - test/schema.rb
58
+ - test/test_helper.rb
59
+ - test/trackable_controller_test.rb
60
+ - test/trackable_test.rb
61
+ - uninstall.rb
62
+ - README.textile
63
+ has_rdoc: true
64
+ homepage: http://github.com/bigfleet/trackable
65
+ licenses: []
66
+
67
+ post_install_message:
68
+ rdoc_options:
69
+ - --charset=UTF-8
70
+ require_paths:
71
+ - lib
72
+ required_ruby_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ segments:
77
+ - 0
78
+ version: "0"
79
+ required_rubygems_version: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ segments:
84
+ - 0
85
+ version: "0"
86
+ requirements: []
87
+
88
+ rubyforge_project:
89
+ rubygems_version: 1.3.6
90
+ signing_key:
91
+ specification_version: 3
92
+ summary: Add a readable event history to your models
93
+ test_files:
94
+ - test/migration_test.rb
95
+ - test/models.rb
96
+ - test/schema.rb
97
+ - test/test_helper.rb
98
+ - test/trackable_controller_test.rb
99
+ - test/trackable_test.rb