recorder 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 2b3955ec8b5c54010caff1d1e24aa92bcb2c5b43
4
+ data.tar.gz: ded550166b4025bc612fe913d4e79a480f2d06f4
5
+ SHA512:
6
+ metadata.gz: 3b77a10f6e0ded0fc17d52ac885b9a89a7cb70b6052d616f45c33a189e15d3f3aba2c6db97b4fac9689daf121d020f65ab4afa61bf9b291c601de5c61928be94
7
+ data.tar.gz: 3b1ff7aa9ee10e24babbe23ae278c4047e64c26e4ee45f5128107872f05d30f7679d800a019656c9172567f34e5f4be16e1319ed0a512a2e5f7bb334183537b8
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.4
4
+ before_install: gem install bundler -v 1.11.2
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in recorder.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 igor
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.
data/README.md ADDED
@@ -0,0 +1,39 @@
1
+ # Recorder
2
+
3
+ Recorder tracks changes of your Rails models
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'recorder'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install recorder
20
+
21
+ ## Usage
22
+
23
+ TODO: Write usage instructions here
24
+
25
+ ## Development
26
+
27
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
28
+
29
+ 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).
30
+
31
+ ## Contributing
32
+
33
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/recorder.
34
+
35
+
36
+ ## License
37
+
38
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
39
+
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "recorder"
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
data/bin/setup ADDED
@@ -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,2 @@
1
+ Description:
2
+ Generates (but does not run) a migration to add a revisions table.
@@ -0,0 +1,40 @@
1
+ require "rails/generators"
2
+ require "rails/generators/active_record"
3
+
4
+ module Recorder
5
+ # Installs Recorder in a rails app.
6
+ class InstallGenerator < ::Rails::Generators::Base
7
+ include ::Rails::Generators::Migration
8
+
9
+ source_root File.expand_path("../templates", __FILE__)
10
+
11
+ class_option(
12
+ :with_partitions,
13
+ type: :boolean,
14
+ default: false,
15
+ desc: "Create partitions for `revisions` table"
16
+ )
17
+
18
+ desc "Generates (but does not run) a migration to add a `revisions` table."
19
+
20
+ def create_migration_file
21
+ self.add_or_skip_recorder_migration('create_recorder_revisions')
22
+ self.add_or_skip_recorder_migration('add_partitions_to_recorder_revisions_table') if options.with_partitions?
23
+ end
24
+
25
+ def self.next_migration_number(dirname)
26
+ ::ActiveRecord::Generators::Base.next_migration_number(dirname)
27
+ end
28
+
29
+ protected
30
+
31
+ def add_or_skip_recorder_migration(template)
32
+ migration_dir = File.expand_path('db/migrate')
33
+ if self.class.migration_exists?(migration_dir, template)
34
+ ::Kernel.warn "Migration already exists: #{template}"
35
+ else
36
+ migration_template "#{template}.rb", "db/migrate/#{template}.rb"
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,18 @@
1
+ # This migration creates the `revisions` table.
2
+ class CreateRecorderRevisions < ActiveRecord::Migration
3
+ def change
4
+ create_table :recorder_revisions do |t|
5
+ t.string :item_type, null: false
6
+ t.integer :item_id, null: false
7
+ t.string :event, null: false
8
+ t.jsonb :data, null: false
9
+ t.inet :ip
10
+ t.date :action_date, null: false
11
+ t.integer :user_id
12
+ t.jsonb :meta
13
+ t.datetime :created_at, null: false
14
+ end
15
+
16
+ add_index :recorder_revisions, [:item_type, :item_id]
17
+ end
18
+ end
@@ -0,0 +1,13 @@
1
+ module Recorder
2
+ module Draper
3
+ module DecoratorConcern
4
+ def self.included(base)
5
+ base.decorates_association :item
6
+ end
7
+
8
+ def item_human_attribute_name(attribute)
9
+ self.item.source.class.human_attribute_name(attribute)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,26 @@
1
+ require 'recorder/tape'
2
+ require 'active_support/concern'
3
+
4
+ module Recorder::Observer
5
+ extend ::ActiveSupport::Concern
6
+
7
+ included do
8
+ has_many :revisions, :class_name => '::Recorder::Revision', :inverse_of => :item, :as => :item
9
+ end
10
+
11
+ module ClassMethods
12
+ def record_changes(options = {})
13
+ after_commit :on => :create do
14
+ Recorder::Tape.new(self, options).record_create
15
+ end
16
+
17
+ after_commit :on => :update do
18
+ Recorder::Tape.new(self, options).record_update
19
+ end
20
+
21
+ after_commit :on => :destroy do
22
+ Recorder::Tape.new(self, options).record_destroy
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,51 @@
1
+ module Recorder
2
+ module Rails
3
+ # Extensions to rails controllers. Provides convenient ways to pass certain
4
+ # information to the model layer, with `recorder_meta`.
5
+ module ControllerConcern
6
+ def self.included(base)
7
+ base.before_action :set_recorder_info
8
+ base.before_action :set_recorder_meta
9
+ end
10
+
11
+ protected
12
+
13
+ # Returns the user who is responsible for any changes that occur.
14
+ # By default this calls `current_user` and returns the result.
15
+ #
16
+ # Override this method in your controller to call a different
17
+ # method, e.g. `current_person`, or anything you like.
18
+ def recorder_user_id
19
+ return unless defined?(current_user)
20
+ current_user.try!(:id)
21
+ rescue NoMethodError
22
+ current_user
23
+ end
24
+
25
+ def recorder_info
26
+ {
27
+ :user_id => self.recorder_user_id,
28
+ :ip => request.remote_ip,
29
+ :action_date => Date.current,
30
+ :meta => self.recorder_meta
31
+ }
32
+ end
33
+
34
+ # Override this method in your controller to return a hash of any
35
+ # information you need.
36
+ def recorder_meta
37
+ nil
38
+ end
39
+
40
+ # Tells Recorder any information from the controller you want to store
41
+ # alongside any changes that occur.
42
+ def set_recorder_info
43
+ ::Recorder.info = self.recorder_info
44
+ end
45
+
46
+ def set_recorder_meta
47
+ ::Recorder.meta = self.recorder_meta
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,27 @@
1
+ require 'active_record'
2
+
3
+ class Recorder::Revision < ActiveRecord::Base
4
+ self.table_name = 'recorder_revisions'
5
+
6
+ if ::Recorder.active_record_protected_attributes?
7
+ attr_accessible(
8
+ :event,
9
+ :user_id,
10
+ :ip,
11
+ :user_agent,
12
+ :action_date,
13
+ :data,
14
+ :meta
15
+ )
16
+ end
17
+
18
+ belongs_to :item, :polymorphic => true, :inverse_of => :revisions
19
+ belongs_to :user
20
+
21
+ validates :item, :presence => true
22
+ validates :event, :presence => true
23
+ validates :action_date, :presence => true
24
+ validates :data, :presence => true
25
+
26
+ scope :ordered_by_created_at, -> { order(:created_at => :desc) }
27
+ end
@@ -0,0 +1,85 @@
1
+ class Recorder::Tape
2
+
3
+ attr_reader :item, :options
4
+
5
+ def initialize(item, options = {})
6
+ @item = item
7
+ @options = options
8
+ end
9
+
10
+ def record_create
11
+ data = {
12
+ :changes => self.sanitize_attributes(self.item.attributes)
13
+ }
14
+
15
+ # changes.merge!(self.parse_associations_attributes)
16
+
17
+ if data.any?
18
+ self.record(
19
+ Recorder.store.merge({
20
+ :event => :create,
21
+ :data => data
22
+ })
23
+ )
24
+ end
25
+ end
26
+
27
+ def record_update
28
+ data = {
29
+ :changes => self.sanitize_attributes(self.item.previous_changes)
30
+ }
31
+
32
+ associations = self.parse_associations_attributes
33
+ data.merge!(:associations => self.parse_associations_attributes) if associations.any?
34
+
35
+ if data.any?
36
+ self.record(
37
+ Recorder.store.merge({
38
+ :event => :update,
39
+ :data => data
40
+ })
41
+ )
42
+ end
43
+ end
44
+
45
+ def record_destroy
46
+ end
47
+
48
+ protected
49
+
50
+ def record(params)
51
+ params.merge!({
52
+ :action_date => Date.today
53
+ })
54
+
55
+ self.item.revisions.create(params)
56
+ end
57
+
58
+ def sanitize_attributes(attributes = {})
59
+ # attributes.symbolize_keys.delete_if{ |key, value| [:created_at, :updated_at, :delta].include?(key) || (value[0] == value[1]) }
60
+ attributes.symbolize_keys.except(:created_at, :updated_at, :delta)
61
+ end
62
+
63
+ def parse_associations_attributes
64
+ if self.options[:associations].any?
65
+ self.options[:associations].inject({}) do |hash, association|
66
+ reflection = self.item.class.reflect_on_association(association)
67
+ if reflection.present?
68
+ if reflection.collection?
69
+
70
+ else
71
+ object = self.item.send(association)
72
+
73
+ if object.present? && self.sanitize_attributes(object.previous_changes).any?
74
+ hash[reflection.name] = {
75
+ :changes => self.sanitize_attributes(object.previous_changes)
76
+ }
77
+ end
78
+ end
79
+ end
80
+
81
+ hash
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,15 @@
1
+ module Recorder
2
+ # :nodoc:
3
+ module VERSION
4
+ MAJOR = 0
5
+ MINOR = 1
6
+ TINY = 0
7
+ PRE = nil
8
+
9
+ STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".").freeze
10
+
11
+ def self.to_s
12
+ STRING
13
+ end
14
+ end
15
+ end
data/lib/recorder.rb ADDED
@@ -0,0 +1,43 @@
1
+ require 'recorder/version'
2
+ require 'recorder/observer'
3
+ require 'recorder/rails/controller_concern'
4
+
5
+ require 'request_store'
6
+
7
+ module Recorder
8
+ class << self
9
+ # Sets Recorder information from the controller.
10
+ # @api public
11
+ def info=(hash)
12
+ self.store.merge!(hash)
13
+ end
14
+
15
+ # Sets Recorder meta information.
16
+ # @api public
17
+ def meta=(hash)
18
+ self.store[:meta] = hash
19
+ end
20
+
21
+ # Returns a boolean indicating whether "protected attibutes" should be
22
+ # configured, e.g. attr_accessible.
23
+ def active_record_protected_attributes?
24
+ @active_record_protected_attributes ||= !!defined?(ProtectedAttributes)
25
+ end
26
+
27
+ # Thread-safe hash to hold Recorder's data.
28
+ # @api private
29
+ def store
30
+ RequestStore.store[:recorder] ||= { }
31
+ end
32
+
33
+ def version
34
+ VERSION::STRING
35
+ end
36
+ end
37
+ end
38
+
39
+ if defined?(Draper)
40
+ require 'recorder/draper/decorator_concern'
41
+ end
42
+
43
+ require 'recorder/revision'
data/recorder.gemspec ADDED
@@ -0,0 +1,30 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'recorder/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'recorder'
8
+ spec.version = Recorder::VERSION::STRING
9
+ spec.authors = ["Igor Alexandrov"]
10
+ spec.email = ["igor.alexandrov@jetrockets.ru"]
11
+
12
+ spec.summary = %q{Rails model auditor}
13
+ spec.description = %q{Recorder tracks changes of your Rails models}
14
+ spec.homepage = "https://github.com/jetrockets/recorder"
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 'activerecord', '>= 4.0', '< 5.1'
23
+ spec.add_dependency 'activesupport', '>= 4.0', '< 5.1'
24
+ spec.add_dependency 'request_store'
25
+ spec.add_dependency 'pg'
26
+
27
+ spec.add_development_dependency "bundler", "~> 1.11"
28
+ spec.add_development_dependency "rake", "~> 10.0"
29
+ spec.add_development_dependency "rspec", "~> 3.0"
30
+ end
metadata ADDED
@@ -0,0 +1,174 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: recorder
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Igor Alexandrov
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-07-06 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activerecord
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '4.0'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '5.1'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: '4.0'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '5.1'
33
+ - !ruby/object:Gem::Dependency
34
+ name: activesupport
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '4.0'
40
+ - - "<"
41
+ - !ruby/object:Gem::Version
42
+ version: '5.1'
43
+ type: :runtime
44
+ prerelease: false
45
+ version_requirements: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: '4.0'
50
+ - - "<"
51
+ - !ruby/object:Gem::Version
52
+ version: '5.1'
53
+ - !ruby/object:Gem::Dependency
54
+ name: request_store
55
+ requirement: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: '0'
60
+ type: :runtime
61
+ prerelease: false
62
+ version_requirements: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: '0'
67
+ - !ruby/object:Gem::Dependency
68
+ name: pg
69
+ requirement: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ type: :runtime
75
+ prerelease: false
76
+ version_requirements: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: '0'
81
+ - !ruby/object:Gem::Dependency
82
+ name: bundler
83
+ requirement: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - "~>"
86
+ - !ruby/object:Gem::Version
87
+ version: '1.11'
88
+ type: :development
89
+ prerelease: false
90
+ version_requirements: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - "~>"
93
+ - !ruby/object:Gem::Version
94
+ version: '1.11'
95
+ - !ruby/object:Gem::Dependency
96
+ name: rake
97
+ requirement: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - "~>"
100
+ - !ruby/object:Gem::Version
101
+ version: '10.0'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - "~>"
107
+ - !ruby/object:Gem::Version
108
+ version: '10.0'
109
+ - !ruby/object:Gem::Dependency
110
+ name: rspec
111
+ requirement: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - "~>"
114
+ - !ruby/object:Gem::Version
115
+ version: '3.0'
116
+ type: :development
117
+ prerelease: false
118
+ version_requirements: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - "~>"
121
+ - !ruby/object:Gem::Version
122
+ version: '3.0'
123
+ description: Recorder tracks changes of your Rails models
124
+ email:
125
+ - igor.alexandrov@jetrockets.ru
126
+ executables: []
127
+ extensions: []
128
+ extra_rdoc_files: []
129
+ files:
130
+ - ".gitignore"
131
+ - ".rspec"
132
+ - ".travis.yml"
133
+ - Gemfile
134
+ - LICENSE.txt
135
+ - README.md
136
+ - Rakefile
137
+ - bin/console
138
+ - bin/setup
139
+ - lib/generators/recorder/USAGE
140
+ - lib/generators/recorder/install_generator.rb
141
+ - lib/generators/recorder/templates/create_recorder_revisions.rb
142
+ - lib/recorder.rb
143
+ - lib/recorder/draper/decorator_concern.rb
144
+ - lib/recorder/observer.rb
145
+ - lib/recorder/rails/controller_concern.rb
146
+ - lib/recorder/revision.rb
147
+ - lib/recorder/tape.rb
148
+ - lib/recorder/version.rb
149
+ - recorder.gemspec
150
+ homepage: https://github.com/jetrockets/recorder
151
+ licenses:
152
+ - MIT
153
+ metadata: {}
154
+ post_install_message:
155
+ rdoc_options: []
156
+ require_paths:
157
+ - lib
158
+ required_ruby_version: !ruby/object:Gem::Requirement
159
+ requirements:
160
+ - - ">="
161
+ - !ruby/object:Gem::Version
162
+ version: '0'
163
+ required_rubygems_version: !ruby/object:Gem::Requirement
164
+ requirements:
165
+ - - ">="
166
+ - !ruby/object:Gem::Version
167
+ version: '0'
168
+ requirements: []
169
+ rubyforge_project:
170
+ rubygems_version: 2.4.5.1
171
+ signing_key:
172
+ specification_version: 4
173
+ summary: Rails model auditor
174
+ test_files: []