auditable 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm --create use 1.9.3@auditable
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in auditable.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Harley Trung
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,152 @@
1
+ # Auditable
2
+
3
+ There are a lot of gems under https://www.ruby-toolbox.com/categories/Active_Record_Versioning and https://www.ruby-toolbox.com/categories/Active_Record_User_Stamping but there are various issues with them (as of this writing):
4
+
5
+ * Almost all of them are outdated and not working with Rails 3.2.2. Some of the popular ones such as `papertrail` and `vestal_versions` have many issues and pull requests but haven't been addressed or merged. Based on the large number of forks of these popular gems, people seem to be OK with their own gem tweaks. Some tweaks are good, but some are too hackish (to deal with recent Rails changes) based on the commits that I have read. I have tried some of the more popular ones but they don't work reliably for something simple I'm trying to achieve.
6
+ * Many of these gems have evolved overtime and become rather (very) cumbersome.
7
+ * Most or all of them don't seem to support beyond the database columns, i.e. not working (or working well) with virtual methods or associations. `papertrail` supports `has_one` but the author said it's not easy to go further than that.
8
+ * I need something simple and lightweight.
9
+
10
+ A lot of the gems in the above category are great. I'm just aiming to create a dead simple gem that lets you easily diff the changes on a model's attributes or methods. Yes, methods, not just attributes. Here's the key difference in my approach:
11
+
12
+ If you want to track changes to complicated stuff such as associated records, just define a method that returns some representation of the associated records and let `auditable` keeps track of the changes in those values over time.
13
+
14
+ Basically:
15
+
16
+ * I don't want the default to track all my columns. Only the attributes or methods I specify please.
17
+ * I don't want to deal with association mess. Use methods instead.
18
+ * I care about tracking the values of certain virtual attributes or methods, not just database columns
19
+ * I want something simple, similar to [ActiveRecord::Dirty#changes](http://ar.rubyonrails.org/classes/ActiveRecord/Dirty.html#M000291) but persistent across saves. See `Auditable::Auditing#audited_changes` below.
20
+
21
+ See examples under Usage section.
22
+
23
+ ## Installation
24
+
25
+ Add this line to your application's Gemfile:
26
+
27
+ gem 'auditable'
28
+
29
+ And then execute:
30
+
31
+ $ bundle
32
+
33
+ Or install it yourself as:
34
+
35
+ $ gem install auditable
36
+
37
+ ## Usage
38
+
39
+ First, add the necessary `audits` table to your database with:
40
+
41
+ rails generate auditable:migration
42
+ rake db:migrate
43
+
44
+ Then, provide a list of methods you'd like to audit to the `audit` method in your model.
45
+
46
+ class Survey
47
+ has_many :questions
48
+ attr_accessor :current_page
49
+
50
+ audit :title, :current_page, :question_ids
51
+ end
52
+
53
+ ## Demo
54
+
55
+ I'm going to demo with the test models from the test suite. You probably want to use 'rails console' and test with the model that you want to audit.
56
+
57
+ For more details, I suggest you check out the test examples in the `spec` folder itself.
58
+
59
+ $ bundle console
60
+ >> require(File.expand_path "../spec/spec_helper", __FILE__)
61
+ => true
62
+
63
+ >> s = Survey.create :title => "demo"
64
+ => #<Survey id: 1, title: "demo">
65
+
66
+ >> Survey.audited_attributes
67
+ => [:title, :current_page]
68
+
69
+ >> s.audited_changes
70
+ => {"title"=>[nil, "demo"]}
71
+
72
+ >> s.update_attributes(:title => "new title", :current_page => 2)
73
+ => true
74
+
75
+ >> s.audited_changes
76
+ => {"title"=>["demo", "new title"], "current_page"=>[nil, 2]}
77
+
78
+ >> s.update_attributes(:current_page => 3, :action => "modified", :changed_by => User.create(:name => "someone"))
79
+ => true
80
+
81
+ >> s.audited_changes
82
+ => {"current_page"=>[2, 3]}
83
+
84
+ >> s.audits.last
85
+ => #<Auditable::Audit id: 3, auditable_id: 1, auditable_type: "Survey", user_id: 1, user_type: "User", modifications: {"title"=>"new title", "current_page"=>3}, action: "modified", created_at: ...>
86
+
87
+ >> s.tag_with(:tag => "something memorable")
88
+ # we just tagged the latest audit, now then do make changes with s
89
+ # ...
90
+ # assuming you've made some changes to s
91
+
92
+ >> s.audited_changes(:tag => "something memorable")
93
+ # return the changes against the tagged version above
94
+ # note s.audited_changes still diff against the second latest audit
95
+ # you can also pass in other filters, such as s.audited_changes(:changed_by => some_user, :audit_action => "modified")
96
+ # note that it always uses the latest audit to diff against an earlier audit matching the arguments to audited_changes
97
+
98
+ ## How it works
99
+ ### Audit Model
100
+
101
+ As seen above, I intend to have a migration file like this for the Audit model:
102
+
103
+ class CreateAudits < ActiveRecord::Migration
104
+ def change
105
+ create_table :audits do |t|
106
+ t.belongs_to :auditable, :polymorphic => true
107
+ t.belongs_to :user, :polymorphic => true
108
+ t.text :modifications
109
+ t.string :action
110
+ t.timestamps
111
+ end
112
+ end
113
+ end
114
+
115
+ It guessable from the above that `audits.modifications` will just be a serialized representation of keys and values of the audited attributes.
116
+
117
+ ### Who changed it and what was that action?
118
+
119
+ If you want to store the user who made the changed to the record, just assigned it to the record's `changed_by` attribute, like so:
120
+
121
+ # note you have to define `attr_accessor :changed_by` yourself
122
+ >> @survey.update_attributes(:changed_by => current_user, # and other attributes you want to set)
123
+ # then @surveys.audits.last.user will be set to current_user
124
+ # also works when you set changed_by and call save later, of course
125
+
126
+ `action` will just be `create` or `update` depending on the operation on the record, but you can also override it with another virtual attribute, call it `change_action`
127
+
128
+ >> @survey.changed_action = "add page"
129
+ >> @survey.update_attribute :page_count, 2
130
+
131
+ **Don't store a new row in `audits` table if the `modifications` column is the same as the one immediately before it. This makes it easier to review change**
132
+
133
+ That's all I can do for this README Driven approach. Back soon.
134
+
135
+ ## TODO
136
+
137
+ * improve api (still clumsy) -- come up with better syntax
138
+ * get some suggestions and feedback
139
+ * update README
140
+
141
+ e.g. right now, changes are serialized into `audits.modifications` column, but what if we what to do multiple sets of audits at each save. I'm thinking of supporting syntax like this:
142
+
143
+ # store snapshots of certain methods to audits.trivial_changes column (that you can easily add yourself)
144
+ audit :modifications => [:method_1, :method_2], :trivial_changes => [:method_3, :method_4, :method_5]
145
+
146
+ ## Contributing
147
+
148
+ 1. Fork it
149
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
150
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
151
+ 4. Push to the branch (`git push origin my-new-feature`)
152
+ 5. Create new Pull Request
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+ require 'rspec/core/rake_task'
4
+
5
+ RSpec::Core::RakeTask.new(:rspec) do |spec|
6
+ spec.pattern = 'spec/**/*_spec.rb'
7
+ spec.rspec_opts = ['-cfs --backtrace']
8
+ end
9
+
10
+ desc "Default: run specs"
11
+ task :default => :rspec
12
+
13
+
@@ -0,0 +1,28 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/auditable/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Harley Trung"]
6
+ gem.email = ["harley@socialsci.com"]
7
+ gem.description = %q{A simple gem that audit models' attributes or methods by taking snapshots and diff them for you. Starting from scratch to work with Rails 3.2.2 onwards}
8
+ gem.summary = %q{A simple gem to audit attributes and methods in ActiveRecord models.}
9
+ gem.homepage = ""
10
+
11
+ gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
12
+ gem.files = `git ls-files`.split("\n")
13
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
14
+ gem.name = "auditable"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = Auditable::VERSION
17
+
18
+ gem.add_development_dependency 'rake'
19
+ gem.add_development_dependency 'rspec'
20
+ gem.add_development_dependency 'watchr'
21
+ gem.add_development_dependency 'sqlite3'
22
+ # documetation stuff
23
+ gem.add_development_dependency 'yard'
24
+ gem.add_development_dependency 'rdiscount'
25
+
26
+ gem.add_runtime_dependency 'activesupport'
27
+ gem.add_runtime_dependency 'activerecord'
28
+ end
@@ -0,0 +1,27 @@
1
+ require 'rails/generators'
2
+ require 'rails/generators/migration'
3
+
4
+ module Auditable
5
+ module Generators
6
+ class MigrationGenerator < ::Rails::Generators::Base
7
+ include Rails::Generators::Migration
8
+
9
+ def self.source_root
10
+ @source_root ||= File.join(File.dirname(__FILE__), 'templates')
11
+ end
12
+
13
+ # Rails expects us to override/implement this ourself
14
+ def self.next_migration_number(dirname)
15
+ if ActiveRecord::Base.timestamped_migrations
16
+ Time.new.utc.strftime("%Y%m%d%H%M%S")
17
+ else
18
+ "%.3d" % (current_migration_number(dirname) + 1)
19
+ end
20
+ end
21
+
22
+ def generate_files
23
+ migration_template 'migration.rb', 'db/migrate/create_audits.rb'
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,12 @@
1
+ class CreateAudits < ActiveRecord::Migration
2
+ def change
3
+ create_table :audits do |t|
4
+ t.belongs_to :auditable, :polymorphic => true
5
+ t.belongs_to :user, :polymorphic => true
6
+ t.text :modifications
7
+ t.string :action
8
+ t.string :tag
9
+ t.timestamps
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,8 @@
1
+ require 'active_record'
2
+ require 'active_support/concern'
3
+
4
+ require "auditable/version"
5
+ require 'auditable/audit'
6
+ require 'auditable/auditing'
7
+
8
+ ActiveRecord::Base.send :include, Auditable::Auditing
@@ -0,0 +1,66 @@
1
+ module Auditable
2
+ class Audit < ActiveRecord::Base
3
+ belongs_to :auditable, :polymorphic => true
4
+ belongs_to :user, :polymorphic => true
5
+ serialize :modifications
6
+
7
+ # Diffing two audits' modifications
8
+ #
9
+ # Returns a hash containing arrays of the form
10
+ # {
11
+ # :key_1 => [<value_in_other_audit>, <value_in_this_audit>],
12
+ # :key_2 => [<value_in_other_audit>, <value_in_this_audit>],
13
+ # :other_audit_own_key => [<value_in_other_audit>, nil],
14
+ # :this_audio_own_key => [nil, <value_in_this_audit>]
15
+ # }
16
+ def diff(other_audit)
17
+ other_modifications = other_audit ? other_audit.modifications : {}
18
+
19
+ {}.tap do |d|
20
+ # find keys present only in this audit
21
+ (self.modifications.keys - other_modifications.keys).each do |k|
22
+ d[k] = [nil, self.modifications[k]] if self.modifications[k]
23
+ end
24
+
25
+ # find keys present only in other audit
26
+ (other_modifications.keys - self.modifications.keys).each do |k|
27
+ d[k] = [other_modifications[k], nil] if other_modifications[k]
28
+ end
29
+
30
+ # find common keys and diff values
31
+ self.modifications.keys.each do |k|
32
+ if self.modifications[k] != other_modifications[k]
33
+ d[k] = [other_modifications[k], self.modifications[k]]
34
+ end
35
+ end
36
+ end
37
+ end
38
+
39
+ # Diff this audit with the one created immediately before it
40
+ #
41
+ # See #diff for more details
42
+ def latest_diff(options = {})
43
+ if options.present?
44
+ scoped = auditable.audits.order("created_at DESC")
45
+ if tag = options.delete(:tag)
46
+ scoped = scoped.where(:tag => tag)
47
+ end
48
+ if changed_by = options.delete(:changed_by)
49
+ scoped = scoped.where(:user_id => changed_by.id, :user_type => changed_by.class.name)
50
+ end
51
+ if audit_tag = options.delete(:audit_tag)
52
+ scoped = scoped.where(:tag => audit_tag)
53
+ end
54
+ diff scoped.first
55
+ else
56
+ diff_since(created_at)
57
+ end
58
+ end
59
+
60
+ # Diff this audit with the latest audit created before the `time` variable passed
61
+ def diff_since(time)
62
+ other_audit = self.class.where("created_at < ?", time).order("created_at DESC").limit(1).first
63
+ diff(other_audit)
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,71 @@
1
+ #require 'active_support/concern'
2
+
3
+ module Auditable
4
+ module Auditing
5
+ extend ActiveSupport::Concern
6
+
7
+ module ClassMethods
8
+ attr_writer :audited_attributes
9
+
10
+ # Get the list of methods to track over record saves, including those inherited from parent
11
+ def audited_attributes
12
+ attrs = @audited_attributes || []
13
+ # handle STI case: include parent's audited_attributes
14
+ if superclass != ActiveRecord::Base and superclass.respond_to?(:audited_attributes)
15
+ attrs.push(*superclass.audited_attributes)
16
+ end
17
+ attrs
18
+ end
19
+
20
+ # Set the list of methods to track over record saves
21
+ #
22
+ # Example:
23
+ #
24
+ # class Survey < ActiveRecord::Base
25
+ # audit :page_count, :question_ids
26
+ # end
27
+ def audit(*options)
28
+ has_many :audits, :class_name => "Auditable::Audit", :as => :auditable
29
+ after_create {|record| record.snap!("create")}
30
+ after_update {|record| record.snap!("update")}
31
+
32
+ self.audited_attributes = Array.wrap options
33
+ end
34
+ end
35
+
36
+ # INSTANCE METHODS
37
+
38
+ # Get the latest audit record
39
+ def last_audit
40
+ audits.last
41
+ end
42
+
43
+ # Mark the latest record in order to easily find and perform diff against later
44
+ def tag_with(tag)
45
+ last_audit.update_attribute(:tag, tag)
46
+ end
47
+
48
+ # Take a snapshot of and save the current state of the audited record's audited attributes
49
+ def snap!(action_default = "update")
50
+ snap = {}.tap do |s|
51
+ self.class.audited_attributes.each do |attr|
52
+ s[attr.to_s] = self.send attr
53
+ end
54
+ end
55
+ audits.create! do |audit|
56
+ audit.modifications = snap
57
+ audit.tag = self.audit_tag if self.respond_to?(:audit_tag)
58
+ audit.action = (self.respond_to?(:audit_action) && self.audit_action) || action_default
59
+ audit.user = self.changed_by if self.respond_to?(:changed_by)
60
+ end
61
+ end
62
+
63
+ # Get the latest changes by comparing the latest two audits
64
+ def audited_changes(options = {})
65
+ audits.last.latest_diff(options)
66
+ end
67
+
68
+ #def self.included(base)
69
+ #end
70
+ end
71
+ end
@@ -0,0 +1,3 @@
1
+ module Auditable
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,119 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Model.audited_attributes" do
4
+ it "should be available to models using audit" do
5
+ Survey.audited_attributes.should include :title
6
+ end
7
+ end
8
+
9
+ describe Auditable do
10
+ let(:survey) { Survey.create :title => "test survey" }
11
+ let(:user) { User.create(:name => "test user") }
12
+ let(:another_user) { User.create(:name => "another user") }
13
+
14
+ it "should have a valid audit to start with" do
15
+ survey.title.should == "test survey"
16
+ survey.audited_changes.should == {"title" => [nil, "test survey"]}
17
+ survey.audits.last.action.should == "create"
18
+ end
19
+
20
+ it "should behave similar to ActiveRecord::Dirty#changes" do
21
+ # first, let's see how ActiveRecord::Dirty#changes behave
22
+ survey.title = "new title"
23
+ survey.changes.should == {"title" => ["test survey", "new title"]}
24
+ survey.save!
25
+ survey.changes.should == {}
26
+ # .audited_changes to the rescue:
27
+ survey.audited_changes.should == {"title" => ["test survey", "new title"]}
28
+ survey.audits.last.action.should == "update"
29
+ end
30
+
31
+ it "should handle virtual attributes" do
32
+ survey.current_page = 1
33
+ # ActiveRecord::Dirty#changes is not meant to track virtual attributes
34
+ survey.changes.should == {}
35
+ survey.save!
36
+ # but we do because we were told to track it
37
+ survey.audited_changes.should == {"current_page" => [nil, 1]}
38
+ end
39
+
40
+ it "should handle multiple keys" do
41
+ survey.current_page = 1
42
+ survey.title = "new title"
43
+ survey.save
44
+ survey.audited_changes.should == {
45
+ "title" => ["test survey", "new title"],
46
+ "current_page" => [nil, 1]
47
+ }
48
+ end
49
+
50
+ context "setting additional attributes" do
51
+ it "should set changed_by" do
52
+ survey.update_attributes(:title => "another title", :changed_by => user)
53
+ survey.audits.last.user.should == user
54
+ end
55
+
56
+ it "should set audit_action" do
57
+ survey.update_attributes(:audit_action => "modified")
58
+ survey.audits.last.action.should == "modified"
59
+ end
60
+
61
+ it "should set audit_tag" do
62
+ survey.update_attributes(:audit_tag => "some tag")
63
+ survey.audits.last.tag.should == "some tag"
64
+ end
65
+ end
66
+
67
+ describe ".last_audit" do
68
+ it "should be the same as audits.last" do
69
+ survey.audits.last.should == survey.last_audit
70
+ end
71
+ end
72
+
73
+ context "tagging" do
74
+ describe ".tag_with" do
75
+ it "should tag the latest audit" do
76
+ survey.audits.last.tag.should_not == "hey"
77
+ survey.tag_with("hey")
78
+ survey.audits.last.tag.should == "hey"
79
+ end
80
+ end
81
+
82
+ describe ".audited_changes" do
83
+ context "using :tag and :changed_by arguments" do
84
+ before do
85
+ survey.tag_with("original")
86
+ survey.update_attributes :title => "new title 1", :changed_by => user
87
+ survey.tag_with("locked")
88
+ survey.update_attributes :title => "new title 2", :changed_by => another_user
89
+ survey.tag_with("locked")
90
+ survey.update_attributes :title => "new title 3", :changed_by => user
91
+ survey.update_attributes :title => "new title 4", :changed_by => another_user
92
+ end
93
+
94
+ it "should diff with latest audit matching a tag" do
95
+ survey.audited_changes(:tag => "original").should == {"title" => ["test survey", "new title 4"]}
96
+ survey.audited_changes(:tag => "locked").should == {"title" => ["new title 2", "new title 4"]}
97
+ end
98
+
99
+ it "should diff with latest audit matching user" do
100
+ survey.audited_changes(:changed_by => user).should == {"title" => ["new title 3", "new title 4"]}
101
+ end
102
+
103
+ it "should diff under the same user and tag" do
104
+ survey.audited_changes(:tag => "locked", :changed_by => user).should == {"title" => ["new title 1", "new title 4"]}
105
+ end
106
+ end
107
+ end
108
+ end
109
+
110
+ context "distinguishing between audited records" do
111
+ let(:another_survey) { Survey.create :title => "another survey" }
112
+ it "should audit changes pertaining to each record only" do
113
+ survey.update_attributes :title => "new title 1"
114
+ another_survey.update_attributes :title => "another title 1"
115
+ survey.audited_changes.should == {"title" => ["test survey", "new title 1"]}
116
+ another_survey.audited_changes.should == {"title" => ["another survey", "another title 1"]}
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,6 @@
1
+ require 'bundler'
2
+ Bundler.require
3
+
4
+ require 'auditable'
5
+
6
+ Dir[File.expand_path("../support/*.rb", __FILE__)].each {|f| require f}
@@ -0,0 +1,24 @@
1
+ class Survey < ActiveRecord::Base
2
+ attr_accessor :current_page
3
+ attr_accessor :changed_by, :audit_action, :audit_tag
4
+
5
+ audit :title, :current_page
6
+ end
7
+
8
+ class User < ActiveRecord::Base
9
+ end
10
+
11
+ # TODO add Question class to give examples on association stuff
12
+
13
+ # prepare test data
14
+ class CreateTestSchema < ActiveRecord::Migration
15
+ def change
16
+ create_table "surveys", :force => true do |t|
17
+ t.string "title"
18
+ end
19
+ create_table "users", :force => true do |t|
20
+ t.string "name"
21
+ end
22
+ end
23
+ end
24
+
@@ -0,0 +1,10 @@
1
+ ActiveRecord::Base.establish_connection({
2
+ :adapter => 'sqlite3',
3
+ :database => ':memory:'
4
+ })
5
+
6
+ CreateTestSchema.migrate(:up)
7
+
8
+ # run gem's required migration
9
+ require(File.expand_path("../../../generators/auditable/templates/migration.rb", __FILE__))
10
+ CreateAudits.migrate(:up)
@@ -0,0 +1,61 @@
1
+ # Run me with:
2
+ #
3
+ # $ watchr specs.watchr
4
+
5
+ # --------------------------------------------------
6
+ # Convenience Methods
7
+ # --------------------------------------------------
8
+ def all_spec_files
9
+ Dir['spec/**/*_spec.rb']
10
+ end
11
+
12
+ def run_spec_matching(thing_to_match)
13
+ matches = all_spec_files.grep(/#{thing_to_match}/i)
14
+ if matches.empty?
15
+ puts "Sorry, thanks for playing, but there were no matches for #{thing_to_match}"
16
+ else
17
+ run matches.join(' ')
18
+ end
19
+ end
20
+
21
+ def run(files_to_run)
22
+ puts("Running: #{files_to_run}")
23
+ system("clear;rspec #{files_to_run}")
24
+ no_int_for_you
25
+ end
26
+
27
+ def run_all_specs
28
+ run(all_spec_files.join(' '))
29
+ end
30
+
31
+ # --------------------------------------------------
32
+ # Watchr Rules
33
+ # --------------------------------------------------
34
+ watch('^spec/(.*)_spec\.rb') { |m| run_spec_matching(m[1]) }
35
+ watch('^lib/(.*)\.rb') { |m| run_spec_matching(m[1]) }
36
+ watch('^lib/(.*)/.*\.rb') { |m| run_spec_matching(m[1]) }
37
+ watch('^spec/spec_helper\.rb') { run_all_specs }
38
+ watch('^spec/support/.*\.rb') { run_all_specs }
39
+
40
+ # --------------------------------------------------
41
+ # Signal Handling
42
+ # --------------------------------------------------
43
+
44
+ def no_int_for_you
45
+ @sent_an_int = nil
46
+ end
47
+
48
+ Signal.trap 'INT' do
49
+ if @sent_an_int then
50
+ puts " A second INT? Ok, I get the message. Shutting down now."
51
+ exit
52
+ else
53
+ puts " Did you just send me an INT? Ugh. I'll quit for real if you do it again."
54
+ @sent_an_int = true
55
+ Kernel.sleep 1.5
56
+ run_all_specs
57
+ end
58
+ end
59
+
60
+ # vim:ft=ruby
61
+
metadata ADDED
@@ -0,0 +1,164 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: auditable
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Harley Trung
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-03-12 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rake
16
+ requirement: &2157976740 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *2157976740
25
+ - !ruby/object:Gem::Dependency
26
+ name: rspec
27
+ requirement: &2157976240 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *2157976240
36
+ - !ruby/object:Gem::Dependency
37
+ name: watchr
38
+ requirement: &2157975760 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *2157975760
47
+ - !ruby/object:Gem::Dependency
48
+ name: sqlite3
49
+ requirement: &2157975300 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *2157975300
58
+ - !ruby/object:Gem::Dependency
59
+ name: yard
60
+ requirement: &2157974760 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ type: :development
67
+ prerelease: false
68
+ version_requirements: *2157974760
69
+ - !ruby/object:Gem::Dependency
70
+ name: rdiscount
71
+ requirement: &2157974220 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ! '>='
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: *2157974220
80
+ - !ruby/object:Gem::Dependency
81
+ name: activesupport
82
+ requirement: &2157973780 !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ! '>='
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ type: :runtime
89
+ prerelease: false
90
+ version_requirements: *2157973780
91
+ - !ruby/object:Gem::Dependency
92
+ name: activerecord
93
+ requirement: &2157973340 !ruby/object:Gem::Requirement
94
+ none: false
95
+ requirements:
96
+ - - ! '>='
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ type: :runtime
100
+ prerelease: false
101
+ version_requirements: *2157973340
102
+ description: A simple gem that audit models' attributes or methods by taking snapshots
103
+ and diff them for you. Starting from scratch to work with Rails 3.2.2 onwards
104
+ email:
105
+ - harley@socialsci.com
106
+ executables: []
107
+ extensions: []
108
+ extra_rdoc_files: []
109
+ files:
110
+ - .gitignore
111
+ - .rspec
112
+ - .rvmrc
113
+ - Gemfile
114
+ - LICENSE
115
+ - README.md
116
+ - Rakefile
117
+ - auditable.gemspec
118
+ - generators/auditable/migration_generator.rb
119
+ - generators/auditable/templates/migration.rb
120
+ - lib/auditable.rb
121
+ - lib/auditable/audit.rb
122
+ - lib/auditable/auditing.rb
123
+ - lib/auditable/version.rb
124
+ - spec/lib/auditable_spec.rb
125
+ - spec/spec_helper.rb
126
+ - spec/support/models.rb
127
+ - spec/support/schema.rb
128
+ - specs.watchr
129
+ homepage: ''
130
+ licenses: []
131
+ post_install_message:
132
+ rdoc_options: []
133
+ require_paths:
134
+ - lib
135
+ required_ruby_version: !ruby/object:Gem::Requirement
136
+ none: false
137
+ requirements:
138
+ - - ! '>='
139
+ - !ruby/object:Gem::Version
140
+ version: '0'
141
+ segments:
142
+ - 0
143
+ hash: -1046713668439752865
144
+ required_rubygems_version: !ruby/object:Gem::Requirement
145
+ none: false
146
+ requirements:
147
+ - - ! '>='
148
+ - !ruby/object:Gem::Version
149
+ version: '0'
150
+ segments:
151
+ - 0
152
+ hash: -1046713668439752865
153
+ requirements: []
154
+ rubyforge_project:
155
+ rubygems_version: 1.8.17
156
+ signing_key:
157
+ specification_version: 3
158
+ summary: A simple gem to audit attributes and methods in ActiveRecord models.
159
+ test_files:
160
+ - spec/lib/auditable_spec.rb
161
+ - spec/spec_helper.rb
162
+ - spec/support/models.rb
163
+ - spec/support/schema.rb
164
+ has_rdoc: