├── .gitignore ├── .travis.yml ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── lib ├── paper_trail-globalid.rb └── paper_trail_globalid │ ├── paper_trail.rb │ ├── version.rb │ └── version_concern.rb ├── paper_trail_globalid.gemspec └── spec ├── paper_trail_globalid_spec.rb ├── spec_helper.rb └── support ├── admin.rb ├── database_connection.rb └── order.rb /.gitignore: -------------------------------------------------------------------------------- 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 18 | *.bundle 19 | *.so 20 | *.o 21 | *.a 22 | mkmf.log 23 | .DS_Store 24 | .byebug_history 25 | paper_trail_globalid_test 26 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | script: "bundle exec rspec spec" 3 | rvm: 4 | - 2.1.5 5 | gemfile: 6 | - Gemfile 7 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in paper_trail_globalid.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 ankit1910 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. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PaperTrailGlobalid 2 | [![Code Climate](https://codeclimate.com/github/ankit1910/paper_trail-globalid/badges/gpa.svg)](https://codeclimate.com/github/ankit1910/paper_trail-globalid) 3 | [![Build Status](https://travis-ci.org/ankit1910/paper_trail-globalid.png?branch=master)](https://travis-ci.org/ankit1910/paper_trail-globalid) 4 | 5 | This gem is an extension to paper_trail gem https://github.com/airblade/paper_trail. That means you need to pre install `paper_trail` gem. This gem will add one more method `actor` to instances of `PaperTrail::Version` that will return you the `ActiveRecord` object who was responsible for change. 6 | 7 | 8 | ## Supported Rails 9 | 10 | This Gem only support `>= rails 4.1.0` and above versions. 11 | 12 | ## Installation 13 | 14 | 1. Add PaperTrailGlobalid to your `Gemfile`. 15 | 16 | `gem 'paper_trail-globalid'` 17 | 18 | 1. And then execute: 19 | 20 | ``` 21 | bundle install 22 | ``` 23 | 24 | ## Basic Usage 25 | 26 | Basically this gem works on stroring global_id to whodunnit field of `PaperTrail::Version table`. As this is an additional extension installed, this will not hinder / break existing paper_trail functionalities. 27 | 28 | ```ruby 29 | widget = Widget.find 42 30 | widget.versions # [, , ...] 31 | v = widget.versions.last 32 | ``` 33 | Now you can also store object to `PaperTrail.whodunnit=`, and if object will be instance of `ActiveRecord::Base` it will store the global id in the version's `whodunnit` column. 34 | 35 | And you can also retrieve the actually object later just by using method `actor`. 36 | 37 | ```ruby 38 | admin = Admin.find(1) # 39 | 40 | PaperTrail.whodunnit = admin 41 | PaperTrail.actor # actual object 42 | 43 | widget.update_attributes :name => 'Wibble' 44 | widget.versions.last.whodunnit # "gid://app/Admin/1" 45 | widget.versions.last.actor # returns the actual object 46 | ``` 47 | 48 | Method `actor` will return the whodunnit value if we pass value another than `ActiveRecord` object. 49 | 50 | ```ruby 51 | PaperTrail.whodunnit = 'admin_name' 52 | PaperTrail.actor # "admin_name" 53 | 54 | widget.update_attributes :name => 'Wibble' 55 | widget.versions.last.whodunnit # "admin_name" 56 | widget.versions.last.actor # "admin_name" 57 | ``` 58 | 59 | ## Contributing 60 | 61 | 1. Fork it ( https://github.com/[my-github-username]/paper_trail-globalid/fork ) 62 | 2. Create your feature branch (`git checkout -b my-new-feature`) 63 | 3. Commit your changes (`git commit -am 'Add some feature'`) 64 | 4. Push to the branch (`git push origin my-new-feature`) 65 | 5. Create a new Pull Request 66 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | 3 | -------------------------------------------------------------------------------- /lib/paper_trail-globalid.rb: -------------------------------------------------------------------------------- 1 | require "paper_trail_globalid/paper_trail" 2 | require "paper_trail_globalid/version" 3 | require "paper_trail_globalid/version_concern" 4 | 5 | module PaperTrailGlobalid 6 | module ::PaperTrail 7 | class << self 8 | prepend ::PaperTrailGlobalid::PaperTrail 9 | end 10 | end 11 | 12 | module ::PaperTrail 13 | module VersionConcern 14 | include ::PaperTrailGlobalid::VersionConcern 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/paper_trail_globalid/paper_trail.rb: -------------------------------------------------------------------------------- 1 | module PaperTrailGlobalid 2 | module PaperTrail 3 | def whodunnit=(value) 4 | if value.is_a? ActiveRecord::Base 5 | paper_trail_store[:whodunnit] = value.to_gid 6 | else 7 | paper_trail_store[:whodunnit] = value 8 | end 9 | end 10 | 11 | def actor 12 | ::GlobalID::Locator.locate(paper_trail_store[:whodunnit]) || whodunnit 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/paper_trail_globalid/version.rb: -------------------------------------------------------------------------------- 1 | module PaperTrailGlobalid 2 | VERSION = "0.2.0" 3 | end 4 | -------------------------------------------------------------------------------- /lib/paper_trail_globalid/version_concern.rb: -------------------------------------------------------------------------------- 1 | module PaperTrailGlobalid 2 | module VersionConcern 3 | def whodunnit=(value) 4 | if value.is_a? ActiveRecord::Base 5 | super(value.to_gid) 6 | else 7 | super 8 | end 9 | end 10 | 11 | # Returns an object which was responsible for a change 12 | # you need to store global_id to whodunnit field to make this method return the object(who was responsible) 13 | # for example, whodunnit => "gid://app/Order/1" then 14 | # whodunnit_user will return Order.find_by(id: 1) in application scope. 15 | def actor 16 | ::GlobalID::Locator.locate(whodunnit) || whodunnit 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /paper_trail_globalid.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'paper_trail_globalid/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "paper_trail-globalid" 8 | spec.version = PaperTrailGlobalid::VERSION 9 | spec.authors = ["Ankit"] 10 | spec.email = ["ankit.bansal1910@gmail.com"] 11 | spec.summary = "An extension to paper_trail, using this you can fetch actual object who was responsible for this change" 12 | spec.description = "An extension to paper_trail, using this you can fetch actual object who was responsible for this change" 13 | spec.homepage = "https://github.com/ankit1910/paper_trail-globalid" 14 | spec.license = "MIT" 15 | 16 | spec.files = `git ls-files -z`.split("\x0") 17 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } 18 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) 19 | spec.require_paths = ["lib"] 20 | 21 | spec.add_dependency 'paper_trail', '>= 3.0.0' 22 | spec.add_dependency 'globalid' 23 | 24 | spec.add_development_dependency 'appraisal', '~> 2.1' 25 | spec.add_development_dependency "bundler", "~> 1.6" 26 | spec.add_development_dependency "rake" 27 | spec.add_development_dependency "rspec", "~> 3.3.0" 28 | spec.add_development_dependency "coveralls", "~> 0.8.2" 29 | spec.add_development_dependency "activerecord", ">=3.2.0" 30 | spec.add_development_dependency "activesupport", ">=3.2.0" 31 | spec.add_development_dependency "sqlite3" 32 | spec.add_development_dependency "byebug" 33 | end 34 | -------------------------------------------------------------------------------- /spec/paper_trail_globalid_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require_relative '../spec/support/order.rb' 3 | require_relative '../spec/support/admin.rb' 4 | 5 | 6 | describe PaperTrailGlobalid do 7 | before(:all) do 8 | ActiveRecord::Migration.verbose = false 9 | ActiveRecord::Schema.define do 10 | create_table :orders, force: true do |t| 11 | t.string :order_number 12 | t.integer :total 13 | end 14 | 15 | create_table :admins, force: true do |t| 16 | t.string :name 17 | end 18 | 19 | create_table :versions do |t| 20 | t.string :item_type, :null => false 21 | t.integer :item_id, :null => false 22 | t.string :event, :null => false 23 | t.string :whodunnit 24 | t.text :object, :limit => 1_073_741_823 25 | t.datetime :created_at 26 | end 27 | end 28 | end 29 | 30 | after(:all) do 31 | ActiveRecord::Schema.define do 32 | drop_table :orders 33 | drop_table :versions 34 | end 35 | ActiveRecord::Migration.verbose = true 36 | end 37 | 38 | describe 'paper_trail_globalid' do 39 | before do 40 | GlobalID.app = "App" 41 | @admin = Admin.create(name: 'admin') 42 | @order = Order.create!(order_number: 'ord_1', total: 100) 43 | @version = @order.versions.last 44 | end 45 | 46 | describe 'paper_trail' do 47 | describe 'class_methods' do 48 | describe 'PaperTrail.actor' do 49 | context 'when value for whodunnit is object of ActiveRecord' do 50 | it 'returns object' do 51 | PaperTrail.whodunnit=@admin 52 | expect(PaperTrail.actor).to eq(@admin) 53 | end 54 | end 55 | 56 | context 'when value for whodunnit is not an object of ActiveRecord' do 57 | it 'returns value itself' do 58 | PaperTrail.whodunnit="test" 59 | expect(PaperTrail.actor).to eq("test") 60 | end 61 | end 62 | end 63 | 64 | describe '.whodunnit' do 65 | context 'when value for whodunnit is object of ActiveRecord' do 66 | it 'returns global id' do 67 | PaperTrail.whodunnit=@admin 68 | expect(PaperTrail.paper_trail_store[:whodunnit]).to eq(@admin.to_gid) 69 | end 70 | end 71 | 72 | context 'when value for whodunnit is not an object of ActiveRecord' do 73 | it 'returns value itself' do 74 | PaperTrail.whodunnit="test" 75 | expect(PaperTrail.paper_trail_store[:whodunnit]).to eq("test") 76 | end 77 | end 78 | end 79 | end 80 | end 81 | 82 | describe 'version_concern' do 83 | describe 'instance_methods' do 84 | describe '#actor' do 85 | context 'when value for whodunnit is object of ActiveRecord' do 86 | it 'returns object' do 87 | @version.whodunnit = @admin 88 | @version.save 89 | expect(@version.actor).to eq(@admin) 90 | end 91 | end 92 | 93 | context 'when value for whodunnit is not an object of ActiveRecord' do 94 | it 'returns value itself' do 95 | @version.whodunnit = "test" 96 | @version.save 97 | expect(@version.actor).to eq("test") 98 | end 99 | end 100 | end 101 | 102 | describe '.whodunnit' do 103 | context 'when value for whodunnit is object of ActiveRecord' do 104 | it 'returns global id' do 105 | @version.whodunnit = @admin 106 | @version.save 107 | expect(@version.whodunnit).to eq(@admin.to_gid.to_s) 108 | end 109 | end 110 | 111 | context 'when value for whodunnit is not an object of ActiveRecord' do 112 | it 'returns value itself' do 113 | @version.whodunnit = "test" 114 | @version.save 115 | expect(@version.whodunnit).to eq("test") 116 | end 117 | end 118 | end 119 | end 120 | end 121 | end 122 | end 123 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # This file was generated by the `rspec --init` command. Conventionally, all 2 | # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. 3 | # The generated `.rspec` file contains `--require spec_helper` which will cause 4 | # this file to always be loaded, without a need to explicitly require it in any 5 | # files. 6 | # 7 | # Given that it is always loaded, you are encouraged to keep this file as 8 | # light-weight as possible. Requiring heavyweight dependencies from this file 9 | # will add to the boot time of your test suite on EVERY test run, even for an 10 | # individual file that may not need all of that loaded. Instead, consider making 11 | # a separate helper file that requires the additional dependencies and performs 12 | # the additional setup, and require it from the spec files that actually need 13 | # it. 14 | # 15 | # The `.rspec` file also contains a few flags that are not defaults but that 16 | # users commonly want. 17 | # 18 | # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration 19 | require 'sqlite3' 20 | require 'active_record' 21 | require 'byebug' 22 | require_relative './../lib/paper_trail-globalid.rb' 23 | require_relative './support/database_connection.rb' 24 | RSpec.configure do |config| 25 | # rspec-expectations config goes here. You can use an alternate 26 | # assertion/expectation library such as wrong or the stdlib/minitest 27 | # assertions if you prefer. 28 | config.expect_with :rspec do |expectations| 29 | # This option will default to `true` in RSpec 4. It makes the `description` 30 | # and `failure_message` of custom matchers include text for helper methods 31 | # defined using `chain`, e.g.: 32 | # be_bigger_than(2).and_smaller_than(4).description 33 | # # => "be bigger than 2 and smaller than 4" 34 | # ...rather than: 35 | # # => "be bigger than 2" 36 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true 37 | end 38 | 39 | # rspec-mocks config goes here. You can use an alternate test double 40 | # library (such as bogus or mocha) by changing the `mock_with` option here. 41 | config.mock_with :rspec do |mocks| 42 | # Prevents you from mocking or stubbing a method that does not exist on 43 | # a real object. This is generally recommended, and will default to 44 | # `true` in RSpec 4. 45 | mocks.verify_partial_doubles = true 46 | end 47 | 48 | # The settings below are suggested to provide a good initial experience 49 | # with RSpec, but feel free to customize to your heart's content. 50 | =begin 51 | # These two settings work together to allow you to limit a spec run 52 | # to individual examples or groups you care about by tagging them with 53 | # `:focus` metadata. When nothing is tagged with `:focus`, all examples 54 | # get run. 55 | config.filter_run :focus 56 | config.run_all_when_everything_filtered = true 57 | 58 | # Allows RSpec to persist some state between runs in order to support 59 | # the `--only-failures` and `--next-failure` CLI options. We recommend 60 | # you configure your source control system to ignore this file. 61 | config.example_status_persistence_file_path = "spec/examples.txt" 62 | 63 | # Limits the available syntax to the non-monkey patched syntax that is 64 | # recommended. For more details, see: 65 | # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax 66 | # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ 67 | # - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching 68 | config.disable_monkey_patching! 69 | 70 | # This setting enables warnings. It's recommended, but in some cases may 71 | # be too noisy due to issues in dependencies. 72 | config.warnings = true 73 | 74 | # Many RSpec users commonly either run the entire suite or an individual 75 | # file, and it's useful to allow more verbose output when running an 76 | # individual spec file. 77 | if config.files_to_run.one? 78 | # Use the documentation formatter for detailed output, 79 | # unless a formatter has already been configured 80 | # (e.g. via a command-line flag). 81 | config.default_formatter = 'doc' 82 | end 83 | 84 | # Print the 10 slowest examples and example groups at the 85 | # end of the spec run, to help surface which specs are running 86 | # particularly slow. 87 | config.profile_examples = 10 88 | 89 | # Run specs in random order to surface order dependencies. If you find an 90 | # order dependency and want to debug it, you can fix the order by providing 91 | # the seed, which is printed after each run. 92 | # --seed 1234 93 | config.order = :random 94 | 95 | # Seed global randomization in this process using the `--seed` CLI option. 96 | # Setting this allows you to use `--seed` to deterministically reproduce 97 | # test failures related to randomization by passing the same `--seed` value 98 | # as the one that triggered the failure. 99 | Kernel.srand config.seed 100 | =end 101 | end 102 | -------------------------------------------------------------------------------- /spec/support/admin.rb: -------------------------------------------------------------------------------- 1 | require 'globalid' 2 | class Admin < ActiveRecord::Base 3 | include ::GlobalID::Identification 4 | end 5 | -------------------------------------------------------------------------------- /spec/support/database_connection.rb: -------------------------------------------------------------------------------- 1 | ActiveRecord::Base.establish_connection( 2 | :adapter => "sqlite3", 3 | :database => "paper_trail_globalid_test" 4 | ) 5 | -------------------------------------------------------------------------------- /spec/support/order.rb: -------------------------------------------------------------------------------- 1 | require 'paper_trail' 2 | require 'paper_trail-globalid' 3 | class Order < ActiveRecord::Base 4 | has_paper_trail 5 | end 6 | --------------------------------------------------------------------------------