espinita 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +60 -0
- data/Rakefile +31 -0
- data/app/models/espinita/audit.rb +41 -0
- data/config/database.yml +24 -0
- data/config/routes.rb +2 -0
- data/db/migrate/20131029200927_create_auditable_audits.rb +15 -0
- data/lib/espinita.rb +16 -0
- data/lib/espinita/auditor.rb +18 -0
- data/lib/espinita/auditor_behavior.rb +83 -0
- data/lib/espinita/auditor_request.rb +13 -0
- data/lib/espinita/engine.rb +18 -0
- data/lib/espinita/version.rb +3 -0
- data/lib/tasks/espinita_tasks.rake +4 -0
- data/spec/controllers/audits_controller_spec.rb +47 -0
- data/spec/dummy/README.rdoc +28 -0
- data/spec/dummy/Rakefile +6 -0
- data/spec/dummy/app/assets/javascripts/application.js +13 -0
- data/spec/dummy/app/assets/javascripts/general_controller.js +2 -0
- data/spec/dummy/app/assets/stylesheets/application.css +13 -0
- data/spec/dummy/app/assets/stylesheets/general_controller.css +4 -0
- data/spec/dummy/app/controllers/application_controller.rb +5 -0
- data/spec/dummy/app/controllers/general_controller_controller.rb +2 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/helpers/general_controller_helper.rb +2 -0
- data/spec/dummy/app/models/general_model.rb +4 -0
- data/spec/dummy/app/models/user.rb +2 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/bin/bundle +3 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/application.rb +23 -0
- data/spec/dummy/config/boot.rb +5 -0
- data/spec/dummy/config/database.yml +25 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +29 -0
- data/spec/dummy/config/environments/production.rb +80 -0
- data/spec/dummy/config/environments/test.rb +36 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/dummy/config/initializers/inflections.rb +16 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +12 -0
- data/spec/dummy/config/initializers/session_store.rb +3 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +23 -0
- data/spec/dummy/config/routes.rb +4 -0
- data/spec/dummy/db/development.sqlite3 +0 -0
- data/spec/dummy/db/migrate/20131029211126_create_general_models.rb +12 -0
- data/spec/dummy/db/migrate/20131030014901_create_users.rb +11 -0
- data/spec/dummy/db/schema.rb +52 -0
- data/spec/dummy/log/development.log +28 -0
- data/spec/dummy/log/test.log +17268 -0
- data/spec/dummy/public/404.html +58 -0
- data/spec/dummy/public/422.html +58 -0
- data/spec/dummy/public/500.html +57 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/factories/auditable_audits.rb +11 -0
- data/spec/factories/general_models.rb +10 -0
- data/spec/factories/users.rb +9 -0
- data/spec/integration/navigation_test.rb +10 -0
- data/spec/models/espinita/audit_spec.rb +8 -0
- data/spec/models/models/general_model_spec.rb +160 -0
- data/spec/models/models/user_spec.rb +5 -0
- data/spec/spec_helper.rb +24 -0
- data/spec/support/models.rb +8 -0
- data/spec/support/schema.rb +34 -0
- metadata +291 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 7183b60c3fd3a7d44f78dcb555200525c5dd8030
|
4
|
+
data.tar.gz: b9bd2cf0e545a2f111082d14a094691b02aa9bca
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: bfb5056db551350c66078a54de5e644da72d58730cf6ebe06ce7aee533e5fdb76022548737755579c6edbc25c27e1c6a54325d3d9d60c2d82ad30ad021b2391b
|
7
|
+
data.tar.gz: 6899fc45eaf3413e0fd068443a20f2e4d2ac185ed535b3ef449a11ed1241fb742a6c36cbe0c1a0e49acdd704a41d4f28ae50acf7e7d491ba123fefccc8349e56
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2013 continuum.cl
|
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,60 @@
|
|
1
|
+
# Espinita
|
2
|
+
|
3
|
+
[![Build Status](https://secure.travis-ci.org/continuum/espinita.png)](http://travis-ci.org/continuum/espinita) [![Dependency Status](https://gemnasium.com/continuum/espinita.png)](https://gemnasium.com/continuum/espinita) [![Coverage Status](https://coveralls.io/repos/continuum/espinita/badge.png?branch=master)](https://coveralls.io/r/continuum/espinita?branch=master)
|
4
|
+
=======
|
5
|
+
|
6
|
+
## Audits activerecord models like a boss
|
7
|
+
|
8
|
+
![Alt text](./espinita.jpg)
|
9
|
+
|
10
|
+
Audit activerecord models like a boss, tested in rails 4 and ruby 2.0.0.
|
11
|
+
|
12
|
+
|
13
|
+
This proyect is heavily based in audited gem.
|
14
|
+
|
15
|
+
## Installation
|
16
|
+
|
17
|
+
In your gemfile
|
18
|
+
|
19
|
+
gem "espinita"
|
20
|
+
|
21
|
+
In console
|
22
|
+
|
23
|
+
$ rake espinita:install:migrations
|
24
|
+
$ rake db:migrate
|
25
|
+
|
26
|
+
## Usage
|
27
|
+
|
28
|
+
class Post < ActiveRecord::Base
|
29
|
+
auditable
|
30
|
+
end
|
31
|
+
|
32
|
+
@post.create(title: "an awesome blog post" )
|
33
|
+
|
34
|
+
Espinita will create an audit by default on creation , edition and destroy:
|
35
|
+
|
36
|
+
@post.audits.size #=> 1
|
37
|
+
|
38
|
+
Espinita provides options to include or exclude columns to trigger the creation of audit.
|
39
|
+
|
40
|
+
class Post < ActiveRecord::Base
|
41
|
+
auditable only: [:title] # except: [:some_column]
|
42
|
+
end
|
43
|
+
|
44
|
+
And let you declare the callbacks you want for audit creation:
|
45
|
+
|
46
|
+
class Post < ActiveRecord::Base
|
47
|
+
auditable on: [:create] # on: [:create, :update]
|
48
|
+
end
|
49
|
+
|
50
|
+
You can find the audits records easily:
|
51
|
+
|
52
|
+
@post.audits.first #=> #<Espinita::Audit id: 1, auditable_id: 1, auditable_type: "Post", user_id: 1, user_type: "User", audited_changes: {"title"=>[nil, "MyString"], "created_at"=>[nil, 2013-10-30 15:50:14 UTC], "updated_at"=>[nil, 2013-10-30 15:50:14 UTC], "id"=>[nil, 1]}
|
53
|
+
|
54
|
+
Espinita will save the model changes in a serialized column called audited_changes:
|
55
|
+
|
56
|
+
@post.audits.firt.audited_changed #=> {"title"=>[nil, "MyString"], "created_at"=>[nil, 2013-10-30 15:50:14 UTC], "updated_at"=>[nil, 2013-10-30 15:50:14 UTC], "id"=>[nil, 1]}
|
57
|
+
|
58
|
+
Espinita will detect current user when records saved from rails controllers. by default Espinita use current_user method but you can change it
|
59
|
+
|
60
|
+
Espinita.current_user_method = :authenticated_user
|
data/Rakefile
ADDED
@@ -0,0 +1,31 @@
|
|
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
|
+
|
8
|
+
require 'rspec/core/rake_task'
|
9
|
+
require 'bundler'
|
10
|
+
Bundler::GemHelper.install_tasks
|
11
|
+
|
12
|
+
desc 'Default: run unit specs.'
|
13
|
+
task :default => :spec
|
14
|
+
|
15
|
+
desc 'Test the lazy_high_charts plugin.'
|
16
|
+
RSpec::Core::RakeTask.new('spec') do |t|
|
17
|
+
t.pattern = FileList['spec/**/*_spec.rb']
|
18
|
+
end
|
19
|
+
|
20
|
+
require 'rdoc/task'
|
21
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
22
|
+
rdoc.rdoc_dir = 'rdoc'
|
23
|
+
rdoc.title = 'Espinita'
|
24
|
+
rdoc.options << '--line-numbers'
|
25
|
+
rdoc.rdoc_files.include('README.rdoc')
|
26
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
27
|
+
end
|
28
|
+
|
29
|
+
#APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
|
30
|
+
#load 'rails/tasks/engine.rake'
|
31
|
+
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Espinita
|
2
|
+
class Audit < ActiveRecord::Base
|
3
|
+
belongs_to :auditable, polymorphic: true
|
4
|
+
belongs_to :user, polymorphic: true
|
5
|
+
|
6
|
+
|
7
|
+
scope :descending, ->{ reorder("version DESC")}
|
8
|
+
scope :creates, ->{ where({:action => 'create'})}
|
9
|
+
scope :updates, ->{ where({:action => 'update'})}
|
10
|
+
scope :destroys, ->{ where({:action => 'destroy'})}
|
11
|
+
|
12
|
+
scope :up_until, ->(date_or_time){where("created_at <= ?", date_or_time) }
|
13
|
+
scope :from_version, ->(version){where(['version >= ?', version]) }
|
14
|
+
scope :to_version, ->(version){where(['version <= ?', version]) }
|
15
|
+
scope :auditable_finder, ->(auditable_id, auditable_type){where(auditable_id: auditable_id, auditable_type: auditable_type)}
|
16
|
+
|
17
|
+
serialize :audited_changes
|
18
|
+
|
19
|
+
before_create :set_version_number, :set_audit_user
|
20
|
+
|
21
|
+
# Return all audits older than the current one.
|
22
|
+
def ancestors
|
23
|
+
self.class.where(['auditable_id = ? and auditable_type = ? and version <= ?',
|
24
|
+
auditable_id, auditable_type, version])
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
def set_version_number
|
29
|
+
max = self.class.auditable_finder(auditable_id, auditable_type).maximum(:version) || 0
|
30
|
+
self.version = max + 1
|
31
|
+
end
|
32
|
+
|
33
|
+
def set_audit_user
|
34
|
+
self.user = RequestStore.store[:audited_user] if RequestStore.store[:audited_user]
|
35
|
+
self.remote_address = RequestStore.store[:audited_ip] if RequestStore.store[:audited_ip]
|
36
|
+
|
37
|
+
nil # prevent stopping callback chains
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
data/config/database.yml
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
sqlite3mem: &SQLITE3MEM
|
2
|
+
adapter: sqlite3
|
3
|
+
database: ":memory:"
|
4
|
+
|
5
|
+
sqlite3: &SQLITE
|
6
|
+
adapter: sqlite3
|
7
|
+
database: audited_test.sqlite3.db
|
8
|
+
|
9
|
+
postgresql: &POSTGRES
|
10
|
+
adapter: postgresql
|
11
|
+
username: postgres
|
12
|
+
password: postgres
|
13
|
+
database: audited_test
|
14
|
+
min_messages: ERROR
|
15
|
+
|
16
|
+
mysql: &MYSQL
|
17
|
+
adapter: mysql
|
18
|
+
host: localhost
|
19
|
+
username: root
|
20
|
+
password:
|
21
|
+
database: audited_test
|
22
|
+
|
23
|
+
test:
|
24
|
+
<<: *<%= ENV['DB'] || 'SQLITE3MEM' %>
|
data/config/routes.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
class CreateAuditableAudits < ActiveRecord::Migration
|
2
|
+
def change
|
3
|
+
create_table :espinita_audits do |t|
|
4
|
+
t.references :auditable, polymorphic: true, index: true
|
5
|
+
t.references :user, polymorphic: true, index: true
|
6
|
+
t.text :audited_changes
|
7
|
+
t.string :comment
|
8
|
+
t.integer :version
|
9
|
+
t.string :action
|
10
|
+
t.string :remote_address
|
11
|
+
|
12
|
+
t.timestamps
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/espinita.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require "espinita/engine"
|
2
|
+
require "request_store"
|
3
|
+
|
4
|
+
module Espinita
|
5
|
+
|
6
|
+
autoload :Auditor, "espinita/auditor"
|
7
|
+
autoload :AuditorBehavior, "espinita/auditor_behavior"
|
8
|
+
autoload :AuditorRequest, "espinita/auditor_request"
|
9
|
+
|
10
|
+
attr_accessor :current_user_method
|
11
|
+
|
12
|
+
def self.current_user_method
|
13
|
+
@current_user_method ||= :current_user
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Espinita
|
2
|
+
module Auditor
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
include Espinita::AuditorBehavior
|
5
|
+
|
6
|
+
included do
|
7
|
+
has_many :audits, :as => :auditable, :class_name => Espinita::Audit.name
|
8
|
+
#attr_accessor :audited_user, :audited_ip
|
9
|
+
accepts_nested_attributes_for :audits
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module Espinita
|
2
|
+
module AuditorBehavior
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
class_attribute :excluded_cols
|
7
|
+
class_attribute :audit_callbacks
|
8
|
+
attr_accessor :audit_comment
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
|
13
|
+
@@default_excluded = %w(lock_version created_at updated_at created_on updated_on)
|
14
|
+
|
15
|
+
def auditable(options = {})
|
16
|
+
|
17
|
+
self.audit_callbacks = []
|
18
|
+
self.audit_callbacks << options[:on] unless options[:on].blank?
|
19
|
+
self.audit_callbacks.flatten!
|
20
|
+
|
21
|
+
after_create :audit_create if self.audit_callbacks.blank? || self.audit_callbacks.include?(:create)
|
22
|
+
before_update :audit_update if self.audit_callbacks.blank? || self.audit_callbacks.include?(:update)
|
23
|
+
before_destroy :audit_destroy if self.audit_callbacks.blank? || self.audit_callbacks.include?(:destroy)
|
24
|
+
|
25
|
+
self.excluded_cols = (@@default_excluded)
|
26
|
+
|
27
|
+
if options[:only]
|
28
|
+
options[:only] = [options[:only]].flatten.map { |x| x.to_s }
|
29
|
+
self.excluded_cols = (self.column_names - options[:only] )
|
30
|
+
end
|
31
|
+
|
32
|
+
if options[:except]
|
33
|
+
options[:except] = [options[:except]].flatten.map { |x| x.to_s }
|
34
|
+
self.excluded_cols = (@@default_excluded) + options[:except]
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
def permited_columns
|
40
|
+
self.column_names - self.excluded_cols.to_a
|
41
|
+
end
|
42
|
+
|
43
|
+
# All audits made during the block called will be recorded as made
|
44
|
+
# by +user+. This method is hopefully threadsafe, making it ideal
|
45
|
+
# for background operations that require audit information.
|
46
|
+
def as_user(user, &block)
|
47
|
+
RequestStore.store[:audited_user] = user
|
48
|
+
yield
|
49
|
+
ensure
|
50
|
+
RequestStore.store[:audited_user] = nil
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
# audited attributes detected against permited columns
|
56
|
+
def audited_attributes
|
57
|
+
self.changes.keys & self.class.permited_columns
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
def audit_create
|
62
|
+
puts self.class.audit_callbacks
|
63
|
+
write_audit(:action => 'create', :audited_changes => changes,
|
64
|
+
:comment => audit_comment)
|
65
|
+
end
|
66
|
+
|
67
|
+
def audit_update
|
68
|
+
puts self.class.audit_callbacks
|
69
|
+
write_audit(:action => 'update', :audited_changes => changes,
|
70
|
+
:comment => audit_comment)
|
71
|
+
end
|
72
|
+
|
73
|
+
def audit_destroy
|
74
|
+
write_audit(:action => 'destroy', :audited_changes => changes,
|
75
|
+
:comment => audit_comment)
|
76
|
+
end
|
77
|
+
|
78
|
+
def write_audit(options)
|
79
|
+
self.audits.create(options) unless audited_attributes.blank?
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Espinita::AuditorRequest
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
before_filter :store_audited_user
|
6
|
+
end
|
7
|
+
|
8
|
+
def store_audited_user
|
9
|
+
RequestStore.store[:audited_user] = self.send(Espinita.current_user_method) #current_user
|
10
|
+
|
11
|
+
RequestStore.store[:audited_ip] = self.try(:request).try(:remote_ip)
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Espinita
|
2
|
+
class Engine < ::Rails::Engine
|
3
|
+
isolate_namespace Espinita
|
4
|
+
|
5
|
+
config.generators do |g|
|
6
|
+
g.test_framework :rspec,
|
7
|
+
:fixture_replacement => :factory_girl ,
|
8
|
+
:dir => "spec/factories"
|
9
|
+
g.integration_tool :rspec
|
10
|
+
end
|
11
|
+
|
12
|
+
initializer "include Auditor request into action controller" do |app|
|
13
|
+
ActionController::Base.send(:include, Espinita::AuditorRequest)
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class AuditsController < ActionController::Base
|
4
|
+
def audit
|
5
|
+
@general_model = FactoryGirl.create(:general_model)
|
6
|
+
render :nothing => true
|
7
|
+
end
|
8
|
+
|
9
|
+
def update_user
|
10
|
+
current_user.update_attributes( :password => 'foo')
|
11
|
+
render :nothing => true
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
attr_accessor :current_user
|
17
|
+
attr_accessor :custom_user
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
describe AuditsController do
|
22
|
+
|
23
|
+
before :each do
|
24
|
+
GeneralModel.auditable
|
25
|
+
end
|
26
|
+
|
27
|
+
let( :general_model ){
|
28
|
+
FactoryFirl.create(:general_model)
|
29
|
+
}
|
30
|
+
|
31
|
+
let( :user ) { FactoryGirl.create(:user) }
|
32
|
+
|
33
|
+
describe "POST audit" do
|
34
|
+
|
35
|
+
it "should audit user" do
|
36
|
+
controller.send(:current_user=, user)
|
37
|
+
expect {
|
38
|
+
post :audit
|
39
|
+
}.to change( Espinita::Audit, :count )
|
40
|
+
|
41
|
+
assigns(:general_model).audits.last.user.should == user
|
42
|
+
assigns(:general_model).audits.last.remote_address.should == "0.0.0.0"
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
== README
|
2
|
+
|
3
|
+
This README would normally document whatever steps are necessary to get the
|
4
|
+
application up and running.
|
5
|
+
|
6
|
+
Things you may want to cover:
|
7
|
+
|
8
|
+
* Ruby version
|
9
|
+
|
10
|
+
* System dependencies
|
11
|
+
|
12
|
+
* Configuration
|
13
|
+
|
14
|
+
* Database creation
|
15
|
+
|
16
|
+
* Database initialization
|
17
|
+
|
18
|
+
* How to run the test suite
|
19
|
+
|
20
|
+
* Services (job queues, cache servers, search engines, etc.)
|
21
|
+
|
22
|
+
* Deployment instructions
|
23
|
+
|
24
|
+
* ...
|
25
|
+
|
26
|
+
|
27
|
+
Please feel free to use a different markup language if you do not plan to run
|
28
|
+
<tt>rake doc:app</tt>.
|