activeadmin-audit 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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 2eea25e963a62183de6ecfa5dbbb1ccdf1fe5fd2
4
+ data.tar.gz: 26600c5639afb004e950447a9104c9a8600ae083
5
+ SHA512:
6
+ metadata.gz: 50af6f9fb5257b18c7845fd982aea7a3a618f7b5e0648799f538edb7f4afbf7b213efef0f5f15ed9aff11d141ddeeb5b46f8eb103072a4d5956097e0de4bbe5d
7
+ data.tar.gz: b63c0beff362edc36238acb03e9b3a48a812982969bfd24605b3e65597ded857f2db5da579ef4247a3bedb24d07ba733d0fc0118439172973b24f404a4532bde
@@ -0,0 +1,13 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/dummy/tmp/
9
+ /spec/dummy/public/assets
10
+ /spec/dummy/.sass-cache
11
+ /spec/dummy/log
12
+ /spec/reports/
13
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require rails_helper
@@ -0,0 +1,24 @@
1
+ sudo: false
2
+ language: ruby
3
+
4
+ rvm:
5
+ - 2.1.5
6
+ - 2.2.4
7
+ - 2.3.1
8
+ - ruby-head
9
+
10
+ matrix:
11
+ allow_failures:
12
+ - rvm: ruby-head
13
+ fast_finish: true
14
+
15
+ before_install: gem update --remote bundler
16
+ install:
17
+ - bundle install --retry=3
18
+ script:
19
+ - bundle exec rake dummy:prepare
20
+ - bundle exec rspec
21
+
22
+ addons:
23
+ code_climate:
24
+ repo_token: fa6eecd14a238a6a4326b5b001bab6b0acf5170da237779800fa4935ad1c0026
@@ -0,0 +1,49 @@
1
+ # Contributor Code of Conduct
2
+
3
+ As contributors and maintainers of this project, and in the interest of
4
+ fostering an open and welcoming community, we pledge to respect all people who
5
+ contribute through reporting issues, posting feature requests, updating
6
+ documentation, submitting pull requests or patches, and other activities.
7
+
8
+ We are committed to making participation in this project a harassment-free
9
+ experience for everyone, regardless of level of experience, gender, gender
10
+ identity and expression, sexual orientation, disability, personal appearance,
11
+ body size, race, ethnicity, age, religion, or nationality.
12
+
13
+ Examples of unacceptable behavior by participants include:
14
+
15
+ * The use of sexualized language or imagery
16
+ * Personal attacks
17
+ * Trolling or insulting/derogatory comments
18
+ * Public or private harassment
19
+ * Publishing other's private information, such as physical or electronic
20
+ addresses, without explicit permission
21
+ * Other unethical or unprofessional conduct
22
+
23
+ Project maintainers have the right and responsibility to remove, edit, or
24
+ reject comments, commits, code, wiki edits, issues, and other contributions
25
+ that are not aligned to this Code of Conduct, or to ban temporarily or
26
+ permanently any contributor for other behaviors that they deem inappropriate,
27
+ threatening, offensive, or harmful.
28
+
29
+ By adopting this Code of Conduct, project maintainers commit themselves to
30
+ fairly and consistently applying these principles to every aspect of managing
31
+ this project. Project maintainers who do not follow or enforce the Code of
32
+ Conduct may be permanently removed from the project team.
33
+
34
+ This code of conduct applies both within project spaces and in public spaces
35
+ when an individual is representing the project or its community.
36
+
37
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
38
+ reported by contacting a project maintainer at aemelyanov@spbtv.com. All
39
+ complaints will be reviewed and investigated and will result in a response that
40
+ is deemed necessary and appropriate to the circumstances. Maintainers are
41
+ obligated to maintain confidentiality with regard to the reporter of an
42
+ incident.
43
+
44
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage],
45
+ version 1.3.0, available at
46
+ [http://contributor-covenant.org/version/1/3/0/][version]
47
+
48
+ [homepage]: http://contributor-covenant.org
49
+ [version]: http://contributor-covenant.org/version/1/3/0/
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in activeadmin-audit.gemspec
4
+ gemspec
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Alex Emelyanov
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,91 @@
1
+ [![Gem Version](https://badge.fury.io/rb/activeadmin-audit.svg)](https://badge.fury.io/rb/activeadmin-audit)
2
+ [![Build Status](https://travis-ci.org/holyketzer/activeadmin-audit.svg?branch=master)](https://travis-ci.org/holyketzer/activeadmin-audit)
3
+ [![Code Climate](https://codeclimate.com/github/holyketzer/activeadmin-audit/badges/gpa.svg)](https://codeclimate.com/github/holyketzer/activeadmin-audit)
4
+ [![Test Coverage](https://codeclimate.com/github/holyketzer/activeadmin-audit/badges/coverage.svg)](https://codeclimate.com/github/holyketzer/activeadmin-audit/coverage)
5
+
6
+ # Activeadmin::Audit
7
+
8
+ This gem allows you to track changes of records which done through ActiveAdmin panel. Also works with has_many relations
9
+
10
+ ## Installation
11
+
12
+ Add this line to your application's Gemfile:
13
+
14
+ ```ruby
15
+ gem 'activeadmin-audit'
16
+ ```
17
+
18
+ And then execute:
19
+
20
+ $ bundle
21
+
22
+ Or install it yourself as:
23
+
24
+ $ gem install activeadmin-audit
25
+
26
+ ## Usage
27
+
28
+ Copy and apply migrations
29
+
30
+ ```bash
31
+ rake active_admin_audit:install:migrations
32
+ rake db:migrate
33
+ ```
34
+
35
+ Include this line in your CSS code (active_admin.scss)
36
+
37
+ ```scss
38
+ @import "activeadmin-audit";
39
+ ```
40
+
41
+ Include this module in your `ApplicationController`
42
+
43
+ ```ruby
44
+ class ApplicationController < ActionController::Base
45
+ include ActiveAdmin::Audit::ControllerHelper
46
+ end
47
+ ```
48
+
49
+ From model that you want to auditing call `has_versions` method
50
+
51
+ ```ruby
52
+ class Movie < ActiveRecord::Base
53
+ has_many :genres
54
+ has_many :images
55
+
56
+ has_versions skip: [:comment], also_include: {
57
+ genres: [:id],
58
+ images: [:url, :width, :height, :kind],
59
+ }
60
+ ```
61
+
62
+ By default `has_versions` take care about all record attributes including belongs_to references. If you don't want to include some attribute you can pass it name to `skip` options. If you want to include has_many relation pass it's name with attributes to `also_include` option.
63
+
64
+ To display table with latest changes on the ActiveAdmin resource page use helper `latest_versions`:
65
+
66
+ ```ruby
67
+ ActiveAdmin.register Movie do
68
+ # ...
69
+
70
+ show do |movie|
71
+ # ...
72
+ latest_versions(movie)
73
+ end
74
+ end
75
+ ```
76
+
77
+ ## Development
78
+
79
+ After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
80
+
81
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
82
+
83
+ ## Contributing
84
+
85
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/activeadmin-audit. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
86
+
87
+
88
+ ## License
89
+
90
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
91
+
@@ -0,0 +1,41 @@
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
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'ActiveAdminAudit'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.md')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
18
+
19
+ load 'rails/tasks/engine.rake'
20
+ load 'rails/tasks/statistics.rake'
21
+
22
+ require 'bundler/gem_tasks'
23
+
24
+ namespace :dummy do
25
+ desc 'Setup dummy app database'
26
+ task :prepare do
27
+ # File.expand_path is executed directory of generated Rails app
28
+ rakefile = File.expand_path('Rakefile', dummy_path)
29
+ command = "rake -f '%s' db:create db:schema:load RAILS_ENV=test" % rakefile
30
+ sh(command) unless ENV['DISABLE_CREATE']
31
+ end
32
+
33
+ def dummy_path
34
+ rel_path = ENV['DUMMY_APP_PATH'] || 'spec/dummy'
35
+ if @current_path.to_s.include?(rel_path)
36
+ @current_path
37
+ else
38
+ @current_path = File.expand_path(rel_path)
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,35 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'active_admin/audit/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'activeadmin-audit'
8
+ spec.version = ActiveAdmin::Audit::VERSION
9
+ spec.authors = ["Alex Emelyanov"]
10
+ spec.email = ["aemelyanov@spbtv.com"]
11
+
12
+ spec.summary = 'PaperTrail based audit for ActiveAdmin'
13
+ spec.description = 'Allow to track changes of records which done through ActiveAdmin'
14
+ spec.homepage = 'https://github.com/holyketzer/activeadmin-audit'
15
+ spec.license = 'MIT'
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
+ spec.bindir = 'exe'
19
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
+ spec.require_paths = ['lib']
21
+
22
+ spec.add_dependency 'activeadmin', '>= 1.0.0.pre1'
23
+ spec.add_dependency 'paper_trail', '~> 4.0.0'
24
+ spec.add_dependency 'rails', '>= 4.0.0'
25
+
26
+ spec.add_development_dependency 'bundler', '~> 1.12'
27
+ spec.add_development_dependency 'codeclimate-test-reporter', '~> 0.4'
28
+ spec.add_development_dependency 'database_cleaner', '~> 1.5.0'
29
+ spec.add_development_dependency 'factory_girl_rails'
30
+ spec.add_development_dependency 'pg', '~> 0.18.1'
31
+ spec.add_development_dependency 'rake', '~> 10.0'
32
+ spec.add_development_dependency 'rspec', '~> 3.3', '>= 3.3.0'
33
+ spec.add_development_dependency 'rspec-rails', '~> 3.3'
34
+ spec.add_development_dependency 'temping', '~> 3.7', '>= 3.3.0'
35
+ end
@@ -0,0 +1,56 @@
1
+ ActiveAdmin.register ActiveAdmin::Audit::ContentVersion, as: 'ContentVersion' do
2
+ menu parent: 'System'
3
+
4
+ actions :index, :show
5
+
6
+ filter :item_type, input_html: { class: 'chosen' }, as: :select
7
+ filter :event, input_html: { class: 'chosen' }, as: :select
8
+ filter :whodunnit, input_html: { class: 'chosen' }, as: :select, collection: -> { AdminUser.all.map { |u| [u.email, u.id] } }
9
+ filter :created_at
10
+
11
+ index do
12
+ id_column
13
+ column :item
14
+ column :item_type
15
+ column :event
16
+ column :who
17
+ column :object_changes do |version|
18
+ version_attributes_diff(version.object_changes)
19
+ end
20
+ column :additional_objects_changes do |version|
21
+ version_attributes_diff(version.additional_objects_changes)
22
+ end
23
+ column :created_at
24
+ actions
25
+ end
26
+
27
+ show do |version|
28
+ panel version.item_type do
29
+ attributes_table_for version do
30
+ row :item
31
+ row :item_type
32
+ row :event
33
+ row :who
34
+ row :created_at
35
+ end
36
+ end
37
+
38
+ render partial: 'object_changes', locals: {
39
+ event: version.event,
40
+ object_changes: version.object_snapshot_changes,
41
+ }
42
+
43
+ render partial: 'additional_objects_changes', locals: {
44
+ event: version.event,
45
+ additional_objects_changes: version.additional_objects_snapshot_changes,
46
+ }
47
+
48
+ render partial: 'object_snapshot', locals: {
49
+ object_snapshot: version.object_snapshot,
50
+ }
51
+
52
+ render partial: 'additional_objects_snapshot', locals: {
53
+ additional_objects_snapshot: version.additional_objects_snapshot,
54
+ }
55
+ end
56
+ end
@@ -0,0 +1,37 @@
1
+ .diff {
2
+ background: #F4F4F4;
3
+ width: auto;
4
+
5
+ table, th, td {
6
+ border: 0.5px solid #aaa;
7
+ }
8
+
9
+ thead > tr > th {
10
+ background: #E4E4E4;
11
+ padding: 7px;
12
+ border-width: 0.5px 0.5px 2px;
13
+ }
14
+
15
+ .attr-name {
16
+ font-weight: bold;
17
+ }
18
+
19
+ .added {
20
+ background: #EEFFE7;
21
+ color: #3C763D;
22
+ }
23
+
24
+ .changed {
25
+ background: #FFFBE6;
26
+ color: #7D6C00;
27
+ }
28
+
29
+ .removed {
30
+ background: #FFF0F0;
31
+ color: #CD2929;
32
+ }
33
+
34
+ img {
35
+ width: 350px;
36
+ }
37
+ }
@@ -0,0 +1,48 @@
1
+ module ActiveAdmin
2
+ module Audit
3
+ class ContentVersion < PaperTrail::Version
4
+ serialize :object, VersionSnapshot
5
+ serialize :object_changes, VersionSnapshot
6
+
7
+ serialize :additional_objects, VersionSnapshot
8
+ serialize :additional_objects_changes, VersionSnapshot
9
+
10
+ def object_changes
11
+ ignore = %w(id created_at updated_at)
12
+ super.reject { |k, _| ignore.include?(k) }
13
+ end
14
+
15
+ def object_snapshot
16
+ object.materialize(item_class)
17
+ end
18
+
19
+ def additional_objects_snapshot
20
+ additional_objects.materialize(item_class)
21
+ end
22
+
23
+ def object_snapshot_changes
24
+ object_changes.materialize(item_class)
25
+ end
26
+
27
+ def additional_objects_snapshot_changes
28
+ additional_objects_changes.materialize(item_class)
29
+ end
30
+
31
+ def who
32
+ AdminUser.find_by(id: whodunnit)
33
+ end
34
+
35
+ def item_class
36
+ item_type.constantize
37
+ rescue NameError
38
+ ActiveRecord::Base
39
+ end
40
+
41
+ def item
42
+ super
43
+ rescue NameError
44
+ nil
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,26 @@
1
+ - if additional_objects_changes.any?
2
+ h3 Related object changes
3
+ table.diff
4
+ thead: tr
5
+ th Attribute
6
+ th Removed
7
+ th Added
8
+ tbody
9
+ - additional_objects_changes.each do |attr, changes|
10
+ - added = changes['+']
11
+ - removed = changes['-']
12
+
13
+ - old_class, new_class = versions_diff_classes(removed, added)
14
+
15
+ tr
16
+ td.attr-name = attr.humanize
17
+ td class=old_class
18
+ - if removed
19
+ ul
20
+ - removed.each do |removed_item|
21
+ li = pretty_version_attribute_value(attr, removed_item)
22
+ td class=new_class
23
+ - if added
24
+ ul
25
+ - added.each do |added_item|
26
+ li = pretty_version_attribute_value(attr, added_item)
@@ -0,0 +1,13 @@
1
+ - if additional_objects_snapshot.any?
2
+ h3 Related objects snapshot
3
+ table.diff
4
+ thead: tr
5
+ th Attribute
6
+ th Value
7
+ tbody
8
+ - additional_objects_snapshot.each do |attr, values|
9
+ tr
10
+ td.attr-name = attr.humanize
11
+ td: ul
12
+ - values.each do |value|
13
+ li = pretty_version_attribute_value(attr, value)
@@ -0,0 +1,16 @@
1
+ - if object_changes.any?
2
+ h3 Object changes
3
+ table.diff
4
+ thead: tr
5
+ th Attribute
6
+ th Old value
7
+ th New value
8
+ tbody
9
+ - object_changes.each do |attr, diff|
10
+ - old_value, new_value = diff
11
+ - old_class, new_class = versions_diff_classes(old_value, new_value)
12
+
13
+ tr
14
+ td.attr-name = attr.humanize
15
+ td class=old_class = pretty_version_attribute_value(attr, old_value)
16
+ td class=new_class = pretty_version_attribute_value(attr, new_value)
@@ -0,0 +1,11 @@
1
+ - if object_snapshot.any?
2
+ h3 Object snapshot
3
+ table.diff
4
+ thead: tr
5
+ th = 'Attribute'
6
+ th = 'Value'
7
+ tbody
8
+ - object_snapshot.each do |attr, value|
9
+ tr
10
+ td.attr-name = attr.humanize
11
+ td = pretty_version_attribute_value(attr, value)
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "activeadmin/audit"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env ruby
2
+ # This command will automatically be run when you run "rails" with Rails gems
3
+ # installed from the root of your application.
4
+
5
+ ENGINE_ROOT = File.expand_path('../..', __FILE__)
6
+ ENGINE_PATH = File.expand_path('../../lib/active_admin/audit/engine', __FILE__)
7
+
8
+ # Set up gems listed in the Gemfile.
9
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
10
+ require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
11
+
12
+ require 'rails/all'
13
+ require 'rails/engine/commands'
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,16 @@
1
+ class CreateVersions < ActiveRecord::Migration
2
+ def change
3
+ create_table :versions do |t|
4
+ t.string :item_type, :null => false
5
+ t.integer :item_id, :null => false
6
+ t.string :event, :null => false
7
+ t.string :whodunnit
8
+ t.text :object
9
+ t.text :object_changes
10
+ t.text :additional_objects
11
+ t.text :additional_objects_changes
12
+ t.datetime :created_at
13
+ end
14
+ add_index :versions, [:item_type, :item_id]
15
+ end
16
+ end
@@ -0,0 +1,13 @@
1
+ require 'active_admin'
2
+ require 'active_admin/audit/version'
3
+ require 'active_admin/audit/engine'
4
+ require 'active_admin/audit/controller_helper'
5
+ require 'active_admin/audit/has_versions'
6
+ require 'active_admin/audit/version_snapshot'
7
+ require 'active_admin/views/latest_versions'
8
+ require 'active_admin/versions_helper'
9
+
10
+ module ActiveAdmin
11
+ module Audit
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ module ActiveAdmin
2
+ module Audit
3
+ module ControllerHelper
4
+ def user_for_paper_trail
5
+ current_admin_user
6
+ end
7
+
8
+ def paper_trail_enabled_for_controller
9
+ request.fullpath.start_with?('/admin')
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,26 @@
1
+ require 'rails/engine'
2
+
3
+ module ActiveAdmin
4
+ module Audit
5
+ class Engine < Rails::Engine
6
+ isolate_namespace ActiveAdmin::Audit
7
+
8
+ initializer 'load_config_initializers' do |app|
9
+ PaperTrail.serializer = PaperTrail::Serializers::JSON
10
+
11
+ app_path = File.expand_path('../../../../app/admin', __FILE__)
12
+ ActiveAdmin.application.load_paths.unshift(app_path)
13
+
14
+ module ActiveAdmin::ViewHelpers
15
+ include ActiveAdmin::VersionsHelper
16
+ end
17
+ end
18
+
19
+ initializer 'active_record.set_configs' do
20
+ ActiveSupport.on_load(:active_record) do
21
+ include ActiveAdmin::Audit::HasVersions
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,133 @@
1
+ require 'paper_trail'
2
+
3
+ module ActiveAdmin
4
+ module Audit
5
+ module HasVersions
6
+ extend ActiveSupport::Concern
7
+
8
+ module ClassMethods
9
+ def has_versions(options = {})
10
+ options[:also_include] ||= {}
11
+ options[:skip] ||= []
12
+ options[:skip] += options[:also_include].keys
13
+ if respond_to?(:translated_attrs)
14
+ options[:skip] += translated_attrs.map { |attr| "#{attr}_translations" }
15
+ end
16
+
17
+ has_paper_trail options.merge(on: [:nothing], class_name: 'ActiveAdmin::Audit::ContentVersion', meta: {
18
+ additional_objects: ->(record) { record.additional_objects_snapshot.to_json },
19
+ additional_objects_changes: ->(record) { record.additional_objects_snapshot_changes.to_json },
20
+ })
21
+
22
+ class_eval do
23
+ define_method(:additional_objects_snapshot) do
24
+ options[:also_include].each_with_object(VersionSnapshot.new) do |(attr, scheme), snapshot|
25
+ snapshot[attr] =
26
+ if scheme.is_a? Symbol
27
+ send(scheme)
28
+ elsif scheme.empty?
29
+ send(attr)
30
+ else
31
+ Array(send(attr)).map do |item|
32
+ scheme.each_with_object({}) do |item_attr, item_snapshot|
33
+ item_snapshot[item_attr] = item.send(item_attr)
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ # Will save new version of the object
41
+ after_commit do
42
+ if paper_trail_switched_on?
43
+ if @event_for_paper_trail
44
+ generate_version!
45
+ end
46
+ end
47
+ end
48
+
49
+ options_on = Array(options.fetch(:on, [:create, :update, :destroy]))
50
+
51
+ if options_on.include?(:create)
52
+ after_create do
53
+ if paper_trail_switched_on?
54
+ @event_for_paper_trail = 'create'
55
+ end
56
+ end
57
+ end
58
+
59
+ if options_on.include?(:update)
60
+ # Cache object changes to access it from after_commit
61
+ after_update do
62
+ if paper_trail_switched_on?
63
+ @event_for_paper_trail = 'update'
64
+ cache_version_object_changes
65
+ end
66
+ end
67
+ end
68
+
69
+ if options_on.include?(:destroy)
70
+ # Cache all details to access it from after_commit
71
+ before_destroy do
72
+ if paper_trail_switched_on?
73
+ @event_for_paper_trail = 'destroy'
74
+ cache_version_object
75
+ cache_version_object_changes
76
+ cache_version_additional_objects_and_changes
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+
84
+ def latest_versions(count = 5)
85
+ versions.reorder(created_at: :desc).limit(count).rewhere(item_type: self.class.name)
86
+ end
87
+
88
+ def additional_objects_snapshot_changes
89
+ prev_version = versions.last
90
+
91
+ old_snapshot = prev_version.try(:additional_objects) || VersionSnapshot.new
92
+ new_snapshot = additional_objects_snapshot
93
+
94
+ old_snapshot.diff(new_snapshot)
95
+ end
96
+
97
+ private
98
+
99
+ def cache_version_object
100
+ @version_object_cache ||= object_attrs_for_paper_trail(self.attributes)
101
+ end
102
+
103
+ def cache_version_object_changes
104
+ @version_object_changes_cache ||= changes_for_paper_trail
105
+ end
106
+
107
+ def cache_version_additional_objects_and_changes
108
+ @version_additional_objects_and_changes_cache ||= merge_metadata({})
109
+ end
110
+
111
+ def clear_version_cache
112
+ @version_object_cache = nil
113
+ @version_object_changes_cache = nil
114
+ @version_additional_objects_and_changes_cache = nil
115
+ end
116
+
117
+ def generate_version!
118
+ data = {
119
+ event: @event_for_paper_trail,
120
+ object: cache_version_object.to_json,
121
+ object_changes: cache_version_object_changes.to_json,
122
+ whodunnit: PaperTrail.whodunnit.try(:id),
123
+ item_type: self.class.name,
124
+ item_id: id,
125
+ }
126
+
127
+ PaperTrail::Version.create! data.merge!(cache_version_additional_objects_and_changes)
128
+
129
+ clear_version_cache
130
+ end
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,5 @@
1
+ module ActiveAdmin
2
+ module Audit
3
+ VERSION = '0.1.0'
4
+ end
5
+ end
@@ -0,0 +1,80 @@
1
+ module ActiveAdmin
2
+ module Audit
3
+ class VersionSnapshot < HashWithIndifferentAccess
4
+ def self.dump(snapshot)
5
+ snapshot.to_json
6
+ end
7
+
8
+ def self.load(string)
9
+ self[JSON.parse(string || '{}')]
10
+ end
11
+
12
+ def diff(other_snapshot)
13
+ keys = (self.keys + other_snapshot.keys).uniq
14
+
15
+ keys.each_with_object({}) do |key, diff|
16
+ old_value = self[key]
17
+ new_value = other_snapshot[key]
18
+
19
+ if old_value.class == new_value.class
20
+ item_diff = {}
21
+
22
+ added = new_value - old_value
23
+ item_diff[:+] = added unless added.empty?
24
+
25
+ removed = old_value - new_value
26
+ item_diff[:-] = removed unless removed.empty?
27
+
28
+ diff[key] = item_diff unless item_diff.empty?
29
+ end
30
+ end
31
+ end
32
+
33
+ def materialize(klass)
34
+ each do |attr, values|
35
+ self[attr] =
36
+ if values.is_a? Array
37
+ # array of any values
38
+ values.map { |value| materialize_item(klass, attr, value) }
39
+ elsif values.is_a? Hash
40
+ # hash with diff in '+'/'-'
41
+ values.each do |k, items|
42
+ values[k] = items.map { |value| materialize_item(klass, attr, value) }
43
+ end
44
+ else
45
+ # any values
46
+ materialize_item(klass, attr, values)
47
+ end
48
+ end
49
+ end
50
+
51
+ private
52
+
53
+ def materialize_item(klass, attr, value)
54
+ if (association = klass.reflect_on_all_associations.find { |a| [a.foreign_key, a.plural_name, a.name.to_s].include?(attr) })
55
+ # attr is association in klass
56
+ if value.is_a?(Hash)
57
+ if value.size == 1 && value.keys.first.to_s == association.klass.primary_key.to_s
58
+ # has_many/has_one
59
+ materialize_record_value(association.klass, value.values.first)
60
+ else
61
+ # nested item
62
+ value.each do |key, v|
63
+ value[key] = materialize_item(association.klass, key, v)
64
+ end
65
+ end
66
+ elsif value.is_a?(Integer) || value =~ /\d+/
67
+ # belongs_to
68
+ materialize_record_value(association.klass, value)
69
+ end
70
+ else
71
+ value
72
+ end
73
+ end
74
+
75
+ def materialize_record_value(klass, value)
76
+ klass.find_by(id: value) || "#{klass} ##{value} was removed"
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,55 @@
1
+ module ActiveAdmin
2
+ module VersionsHelper
3
+ BOOLEAN_VALUES = [true, false].freeze
4
+
5
+ def pretty_version_attribute_value(attr, value)
6
+ case value
7
+ when Array
8
+ Arbre::Context.new(context: self) do
9
+ text_node attr.humanize.singularize
10
+ ul do
11
+ value.each do |v|
12
+ li { context.pretty_version_attribute_value(attr, v).html_safe }
13
+ end
14
+ end
15
+ end.to_s
16
+ when Hash
17
+ Arbre::Context.new(context: self) do
18
+ text_node attr.humanize.singularize
19
+ ul do
20
+ value.each do |k, v|
21
+ li { "#{k.humanize}: #{context.pretty_version_attribute_value(k, v)}".html_safe }
22
+ end
23
+ end
24
+ end.to_s
25
+ when /\.(jpg|png|gif)\z/i
26
+ image_tag value
27
+ when ActiveRecord::Base
28
+ pretty_format(value)
29
+ else
30
+ value
31
+ end
32
+ end
33
+
34
+ def version_attributes_diff(changes)
35
+ Arbre::Context.new do
36
+ ul do
37
+ changes.keys.sort.each { |attr| li(attr.humanize) }
38
+ end
39
+ end.to_s
40
+ end
41
+
42
+ def versions_diff_classes(removed, added)
43
+ if (added.present? && removed.present?) || (BOOLEAN_VALUES.include?(added) && BOOLEAN_VALUES.include?(removed))
44
+ old_class = 'changed'
45
+ new_class = old_class
46
+ elsif added.present? || BOOLEAN_VALUES.include?(added)
47
+ new_class = 'added'
48
+ elsif removed.present? || BOOLEAN_VALUES.include?(removed)
49
+ old_class = 'removed'
50
+ end
51
+
52
+ [old_class, new_class]
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,34 @@
1
+ module ActiveAdmin
2
+ module Views
3
+ class LatestVersions < ActiveAdmin::Component
4
+ builder_method :latest_versions
5
+
6
+ def build(resource, _attributes = {})
7
+ panel 'Latest versions' do
8
+ table_for resource.latest_versions do
9
+ column :id
10
+ column :event
11
+ column :who
12
+ column :object_changes do |version|
13
+ version_attributes_diff(version.object_changes)
14
+ end
15
+ column :additional_objects_changes do |version|
16
+ version_attributes_diff(version.additional_objects_changes)
17
+ end
18
+ column :created_at
19
+ column :actions do |version|
20
+ link_to 'View', admin_content_version_path(version)
21
+ end
22
+ end
23
+
24
+ div do
25
+ link_to 'View all versions', admin_content_versions_path({
26
+ 'q[item_type_eq]' => resource.class.name,
27
+ 'q[item_id_eq]' => resource.id,
28
+ })
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1 @@
1
+ require 'active_admin/audit'
metadata ADDED
@@ -0,0 +1,253 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: activeadmin-audit
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Alex Emelyanov
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-09-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activeadmin
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 1.0.0.pre1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 1.0.0.pre1
27
+ - !ruby/object:Gem::Dependency
28
+ name: paper_trail
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 4.0.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 4.0.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: rails
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: 4.0.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: 4.0.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.12'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.12'
69
+ - !ruby/object:Gem::Dependency
70
+ name: codeclimate-test-reporter
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.4'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.4'
83
+ - !ruby/object:Gem::Dependency
84
+ name: database_cleaner
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 1.5.0
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 1.5.0
97
+ - !ruby/object:Gem::Dependency
98
+ name: factory_girl_rails
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: pg
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: 0.18.1
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: 0.18.1
125
+ - !ruby/object:Gem::Dependency
126
+ name: rake
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '10.0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '10.0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: rspec
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: '3.3'
146
+ - - ">="
147
+ - !ruby/object:Gem::Version
148
+ version: 3.3.0
149
+ type: :development
150
+ prerelease: false
151
+ version_requirements: !ruby/object:Gem::Requirement
152
+ requirements:
153
+ - - "~>"
154
+ - !ruby/object:Gem::Version
155
+ version: '3.3'
156
+ - - ">="
157
+ - !ruby/object:Gem::Version
158
+ version: 3.3.0
159
+ - !ruby/object:Gem::Dependency
160
+ name: rspec-rails
161
+ requirement: !ruby/object:Gem::Requirement
162
+ requirements:
163
+ - - "~>"
164
+ - !ruby/object:Gem::Version
165
+ version: '3.3'
166
+ type: :development
167
+ prerelease: false
168
+ version_requirements: !ruby/object:Gem::Requirement
169
+ requirements:
170
+ - - "~>"
171
+ - !ruby/object:Gem::Version
172
+ version: '3.3'
173
+ - !ruby/object:Gem::Dependency
174
+ name: temping
175
+ requirement: !ruby/object:Gem::Requirement
176
+ requirements:
177
+ - - "~>"
178
+ - !ruby/object:Gem::Version
179
+ version: '3.7'
180
+ - - ">="
181
+ - !ruby/object:Gem::Version
182
+ version: 3.3.0
183
+ type: :development
184
+ prerelease: false
185
+ version_requirements: !ruby/object:Gem::Requirement
186
+ requirements:
187
+ - - "~>"
188
+ - !ruby/object:Gem::Version
189
+ version: '3.7'
190
+ - - ">="
191
+ - !ruby/object:Gem::Version
192
+ version: 3.3.0
193
+ description: Allow to track changes of records which done through ActiveAdmin
194
+ email:
195
+ - aemelyanov@spbtv.com
196
+ executables: []
197
+ extensions: []
198
+ extra_rdoc_files: []
199
+ files:
200
+ - ".gitignore"
201
+ - ".rspec"
202
+ - ".travis.yml"
203
+ - CODE_OF_CONDUCT.md
204
+ - Gemfile
205
+ - LICENSE.txt
206
+ - README.md
207
+ - Rakefile
208
+ - activeadmin-audit.gemspec
209
+ - app/admin/content_versions.rb
210
+ - app/assets/stylesheets/activeadmin-audit.css
211
+ - app/models/active_admin/audit/content_version.rb
212
+ - app/views/admin/content_versions/_additional_objects_changes.html.slim
213
+ - app/views/admin/content_versions/_additional_objects_snapshot.html.slim
214
+ - app/views/admin/content_versions/_object_changes.html.slim
215
+ - app/views/admin/content_versions/_object_snapshot.html.slim
216
+ - bin/console
217
+ - bin/rails
218
+ - bin/setup
219
+ - db/migrate/20141219113159_create_versions.rb
220
+ - lib/active_admin/audit.rb
221
+ - lib/active_admin/audit/controller_helper.rb
222
+ - lib/active_admin/audit/engine.rb
223
+ - lib/active_admin/audit/has_versions.rb
224
+ - lib/active_admin/audit/version.rb
225
+ - lib/active_admin/audit/version_snapshot.rb
226
+ - lib/active_admin/versions_helper.rb
227
+ - lib/active_admin/views/latest_versions.rb
228
+ - lib/activeadmin/audit.rb
229
+ homepage: https://github.com/holyketzer/activeadmin-audit
230
+ licenses:
231
+ - MIT
232
+ metadata: {}
233
+ post_install_message:
234
+ rdoc_options: []
235
+ require_paths:
236
+ - lib
237
+ required_ruby_version: !ruby/object:Gem::Requirement
238
+ requirements:
239
+ - - ">="
240
+ - !ruby/object:Gem::Version
241
+ version: '0'
242
+ required_rubygems_version: !ruby/object:Gem::Requirement
243
+ requirements:
244
+ - - ">="
245
+ - !ruby/object:Gem::Version
246
+ version: '0'
247
+ requirements: []
248
+ rubyforge_project:
249
+ rubygems_version: 2.5.1
250
+ signing_key:
251
+ specification_version: 4
252
+ summary: PaperTrail based audit for ActiveAdmin
253
+ test_files: []