espinita 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +60 -0
  4. data/Rakefile +31 -0
  5. data/app/models/espinita/audit.rb +41 -0
  6. data/config/database.yml +24 -0
  7. data/config/routes.rb +2 -0
  8. data/db/migrate/20131029200927_create_auditable_audits.rb +15 -0
  9. data/lib/espinita.rb +16 -0
  10. data/lib/espinita/auditor.rb +18 -0
  11. data/lib/espinita/auditor_behavior.rb +83 -0
  12. data/lib/espinita/auditor_request.rb +13 -0
  13. data/lib/espinita/engine.rb +18 -0
  14. data/lib/espinita/version.rb +3 -0
  15. data/lib/tasks/espinita_tasks.rake +4 -0
  16. data/spec/controllers/audits_controller_spec.rb +47 -0
  17. data/spec/dummy/README.rdoc +28 -0
  18. data/spec/dummy/Rakefile +6 -0
  19. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  20. data/spec/dummy/app/assets/javascripts/general_controller.js +2 -0
  21. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  22. data/spec/dummy/app/assets/stylesheets/general_controller.css +4 -0
  23. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  24. data/spec/dummy/app/controllers/general_controller_controller.rb +2 -0
  25. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  26. data/spec/dummy/app/helpers/general_controller_helper.rb +2 -0
  27. data/spec/dummy/app/models/general_model.rb +4 -0
  28. data/spec/dummy/app/models/user.rb +2 -0
  29. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  30. data/spec/dummy/bin/bundle +3 -0
  31. data/spec/dummy/bin/rails +4 -0
  32. data/spec/dummy/bin/rake +4 -0
  33. data/spec/dummy/config.ru +4 -0
  34. data/spec/dummy/config/application.rb +23 -0
  35. data/spec/dummy/config/boot.rb +5 -0
  36. data/spec/dummy/config/database.yml +25 -0
  37. data/spec/dummy/config/environment.rb +5 -0
  38. data/spec/dummy/config/environments/development.rb +29 -0
  39. data/spec/dummy/config/environments/production.rb +80 -0
  40. data/spec/dummy/config/environments/test.rb +36 -0
  41. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  42. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  43. data/spec/dummy/config/initializers/inflections.rb +16 -0
  44. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  45. data/spec/dummy/config/initializers/secret_token.rb +12 -0
  46. data/spec/dummy/config/initializers/session_store.rb +3 -0
  47. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  48. data/spec/dummy/config/locales/en.yml +23 -0
  49. data/spec/dummy/config/routes.rb +4 -0
  50. data/spec/dummy/db/development.sqlite3 +0 -0
  51. data/spec/dummy/db/migrate/20131029211126_create_general_models.rb +12 -0
  52. data/spec/dummy/db/migrate/20131030014901_create_users.rb +11 -0
  53. data/spec/dummy/db/schema.rb +52 -0
  54. data/spec/dummy/log/development.log +28 -0
  55. data/spec/dummy/log/test.log +17268 -0
  56. data/spec/dummy/public/404.html +58 -0
  57. data/spec/dummy/public/422.html +58 -0
  58. data/spec/dummy/public/500.html +57 -0
  59. data/spec/dummy/public/favicon.ico +0 -0
  60. data/spec/factories/auditable_audits.rb +11 -0
  61. data/spec/factories/general_models.rb +10 -0
  62. data/spec/factories/users.rb +9 -0
  63. data/spec/integration/navigation_test.rb +10 -0
  64. data/spec/models/espinita/audit_spec.rb +8 -0
  65. data/spec/models/models/general_model_spec.rb +160 -0
  66. data/spec/models/models/user_spec.rb +5 -0
  67. data/spec/spec_helper.rb +24 -0
  68. data/spec/support/models.rb +8 -0
  69. data/spec/support/schema.rb +34 -0
  70. 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
@@ -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,2 @@
1
+ Espinita::Engine.routes.draw do
2
+ end
@@ -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,3 @@
1
+ module Espinita
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :espinita do
3
+ # # Task goes here
4
+ # 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>.