recorder 0.1.23 → 1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8ca2c4ebb8079516d7eb4720436938dbb781dbf93d4ac9f970b3245cb04601af
4
- data.tar.gz: 39e4b51785486dfb7e865561a37a503271d6ad34430e6cb1096ef6d2fcb79c80
3
+ metadata.gz: 4af26e2ed8bedd59ea727cc40967738a53967495d7109401f3698895e4cc394e
4
+ data.tar.gz: 356249bc613ab922c13deb3d9b8f91fd82fd99984b3e44e258b4d04720ab49c8
5
5
  SHA512:
6
- metadata.gz: 91294088c5a975d6604dec889882f675cdc2b2d2514448035e257600c7d9e31ff1ca7402d6c85a39d2b623ea06d6148f6766a15a338e95d3cd6a19a880d2e001
7
- data.tar.gz: d248d7914f3134f59202efa1a9d6519ae770183bb1f2296456e82956a0c2b7552291cecbfc960ca32893f90ce2f2c3eb4649fef6892664d1d3a3603b19993596
6
+ metadata.gz: 27b9d07aa2667fefb5d6f5f2acba706a1397da9c37af28bcf8f775753bf183507acbd4a5cf6604c16e5b7017b34a7b1c430d13db0407144038d46807a6ced3aa
7
+ data.tar.gz: 456c8e4ccffd562f766a732940b1c0378806bd71b18b223de6d3926ef0fafc956856d1bfbdde93ff9287857a5908bc1ac8715218dc88d5fed5b15b3ac6c9c5dc
data/.gitignore CHANGED
@@ -9,3 +9,7 @@
9
9
  /tmp/
10
10
 
11
11
  /spec/dummy/config/database.yml
12
+
13
+ # Ignore vim swap files
14
+ *.swp
15
+ *.swo
data/.rubocop.yml ADDED
@@ -0,0 +1,27 @@
1
+ inherit_gem:
2
+ jetrockets-standard: config/gems.yml
3
+
4
+ AllCops:
5
+ Exclude:
6
+ - 'bin/*'
7
+ - 'tmp/**/*'
8
+ - 'docs/**/*'
9
+ - 'Gemfile'
10
+ - 'vendor/**/*'
11
+ - 'gemfiles/**/*'
12
+ DisplayCopNames: true
13
+ TargetRubyVersion: 2.5
14
+
15
+ Style/TrailingCommaInArrayLiteral:
16
+ EnforcedStyleForMultiline: no_comma
17
+
18
+ Style/TrailingCommaInHashLiteral:
19
+ EnforcedStyleForMultiline: no_comma
20
+
21
+ Layout/ParameterAlignment:
22
+ EnforcedStyle: with_first_parameter
23
+
24
+ # See https://github.com/rubocop-hq/rubocop/issues/4222
25
+ Lint/AmbiguousBlockAssociation:
26
+ Exclude:
27
+ - 'spec/**/*'
data/Gemfile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source 'https://rubygems.org'
2
4
 
3
5
  # Specify your gem's dependencies in recorder.gemspec
data/README.md CHANGED
@@ -20,7 +20,37 @@ Or install it yourself as:
20
20
 
21
21
  ## Usage
22
22
 
23
- TODO: Write usage instructions here
23
+ To enable logging on a model you just need to include `Recorder::Observer` into the model and configure logging options for it:
24
+
25
+ ``` ruby
26
+ class Post < ActiveRecord::Base
27
+ include ::Recorder::Observer
28
+
29
+ recorder only: %i[title tags],
30
+ associations: {
31
+ author: { only: %i[full_name] },
32
+ category: { only: %i[name slug] }
33
+ }
34
+ end
35
+ ```
36
+
37
+ Recorder supports the following options:
38
+
39
+ * `ignore: [array]` - attributes that are ignored on logging;
40
+ * `only: [array]` - only these attributes are logged, other attributes are ingored;
41
+ * `associations: {hash} (hash)` - allows to set what associations will be logged alongside with the model. For each association you can also set ignore and only options;
42
+ * `async: bool` - a logging strategy (true - asynchronous, false - synchronous).
43
+
44
+ There are two strategies for logging: synchronous and asynchronous. When the synchronous strategy is used, a revision record is saved immediately after a model is saved, and the async strategy moves creating of revision records to [Sidekiq](https://github.com/mperham/sidekiq).
45
+
46
+ To enable storing of such data as user_id and ip, you need to include `Recorder::Rails::ControllerConcern` to `ApplicationController`. Recorder uses [request_store](https://github.com/steveklabnik/request_store) to safely store these data on a thread level.
47
+
48
+ ``` ruby
49
+ class ApplicationController < ActionController::Base
50
+ include Recorder::Rails::ControllerConcern
51
+ ...
52
+ end
53
+ ```
24
54
 
25
55
  ## Development
26
56
 
@@ -34,9 +64,9 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/jetroc
34
64
 
35
65
  ## Credits
36
66
 
37
- ![JetRockets](http://www.jetrockets.ru/jetrockets.png)
67
+ ![JetRockets](https://media.jetrockets.pro/jetrockets-white.png)
38
68
 
39
- Recorder is maintained by [JetRockets](http://www.jetrockets.ru).
69
+ Recorder is maintained by [JetRockets](https://www.jetrockets.pro]).
40
70
 
41
71
  ## License
42
72
 
data/Rakefile CHANGED
@@ -1,6 +1,8 @@
1
- require "bundler/gem_tasks"
2
- require "rspec/core/rake_task"
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
3
5
 
4
6
  RSpec::Core::RakeTask.new(:spec)
5
7
 
6
- task :default => :spec
8
+ task default: :spec
data/bin/console CHANGED
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
- require "bundler/setup"
4
- require "recorder"
4
+ require 'bundler/setup'
5
+ require 'recorder'
5
6
 
6
7
  # You can add fixtures and/or initialization code here to make experimenting
7
8
  # with your gem easier. You can also use a different console, if you like.
@@ -10,5 +11,5 @@ require "recorder"
10
11
  # require "pry"
11
12
  # Pry.start
12
13
 
13
- require "irb"
14
+ require 'irb'
14
15
  IRB.start
@@ -1,48 +1,50 @@
1
- require "rails/generators"
2
- require "rails/generators/active_record"
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators'
4
+ require 'rails/generators/active_record'
3
5
 
4
6
  module Recorder
5
7
  # Installs Recorder in a rails app.
6
8
  class InstallGenerator < ::Rails::Generators::Base
7
9
  include ::Rails::Generators::Migration
8
10
 
9
- source_root File.expand_path("../templates", __FILE__)
11
+ source_root File.expand_path('templates', __dir__)
10
12
 
11
13
  class_option(
12
14
  :with_partitions,
13
15
  type: :boolean,
14
16
  default: false,
15
- desc: "Create partitions to `recorder_revisions` table"
17
+ desc: 'Create partitions to `recorder_revisions` table'
16
18
  )
17
19
 
18
20
  class_option(
19
21
  :with_number_column,
20
22
  type: :boolean,
21
23
  default: false,
22
- desc: "Add `number` column to `recorder_revisions` table"
24
+ desc: 'Add `number` column to `recorder_revisions` table'
23
25
  )
24
26
 
25
27
  class_option(
26
28
  :with_index_by_user_id,
27
29
  type: :boolean,
28
30
  default: false,
29
- desc: "Add index by `user_id` column to `recorder_revisions` table"
31
+ desc: 'Add index by `user_id` column to `recorder_revisions` table'
30
32
  )
31
33
 
32
- desc "Generates (but does not run) a migration to add a `recorder_revisions` table."
34
+ desc 'Generates (but does not run) a migration to add a `recorder_revisions` table.'
33
35
 
34
36
  def create_migration_file
35
- self.add_or_skip_recorder_migration('create_recorder_revisions')
36
- self.add_or_skip_recorder_migration('add_number_column_to_recorder_revisions') if options.with_number_column?
37
- self.add_or_skip_recorder_migration('add_index_by_user_id_to_recorder_revisions') if options.with_index_by_user_id?
38
- self.add_or_skip_recorder_migration('add_partitions_to_recorder_revisions') if options.with_partitions?
37
+ add_or_skip_recorder_migration('create_recorder_revisions')
38
+ add_or_skip_recorder_migration('add_number_column_to_recorder_revisions') if options.with_number_column?
39
+ add_or_skip_recorder_migration('add_index_by_user_id_to_recorder_revisions') if options.with_index_by_user_id?
40
+ add_or_skip_recorder_migration('add_partitions_to_recorder_revisions') if options.with_partitions?
39
41
  end
40
42
 
41
43
  def self.next_migration_number(dirname)
42
44
  ::ActiveRecord::Generators::Base.next_migration_number(dirname)
43
45
  end
44
46
 
45
- protected
47
+ protected
46
48
 
47
49
  def add_or_skip_recorder_migration(template)
48
50
  migration_dir = File.expand_path('db/migrate')
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # This migration adds number column to the `revisions` table.
2
4
  class AddIndexByUserIdToRecorderRevisions < ActiveRecord::Migration
3
5
  def change
@@ -1,30 +1,32 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # This migration adds number column to the `revisions` table.
2
4
  class AddNumberColumnToRecorderRevisions < ActiveRecord::Migration
3
5
  def up
4
- add_column :recorder_revisions, :number, :integer, :null => false, :default => 0
5
-
6
- execute <<-SQL
7
- CREATE OR REPLACE FUNCTION get_recorder_revisions_number()
8
- RETURNS trigger AS
9
- $BODY$
10
- BEGIN
11
- SELECT COALESCE(MAX(recorder_revisions.number), 0) + 1
12
- INTO NEW.number
13
- FROM
14
- recorder_revisions
15
- WHERE
16
- recorder_revisions.item_type = NEW.item_type
17
- AND recorder_revisions.item_id = NEW.item_id;
6
+ add_column :recorder_revisions, :number, :integer, null: false, default: 0
18
7
 
19
- RETURN NEW;
20
- END;
21
- $BODY$ LANGUAGE plpgsql;
8
+ execute <<~SQL
9
+ CREATE OR REPLACE FUNCTION get_recorder_revisions_number()
10
+ RETURNS trigger AS
11
+ $BODY$
12
+ BEGIN
13
+ SELECT COALESCE(MAX(recorder_revisions.number), 0) + 1
14
+ INTO NEW.number
15
+ FROM
16
+ recorder_revisions
17
+ WHERE
18
+ recorder_revisions.item_type = NEW.item_type
19
+ AND recorder_revisions.item_id = NEW.item_id;
20
+ #{" "}
21
+ RETURN NEW;
22
+ END;
23
+ $BODY$ LANGUAGE plpgsql;
22
24
  SQL
23
25
 
24
- execute <<-SQL
25
- CREATE TRIGGER update_recorder_revisions_number
26
- BEFORE INSERT ON recorder_revisions FOR EACH ROW
27
- EXECUTE PROCEDURE get_recorder_revisions_number();
26
+ execute <<~SQL
27
+ CREATE TRIGGER update_recorder_revisions_number
28
+ BEFORE INSERT ON recorder_revisions FOR EACH ROW
29
+ EXECUTE PROCEDURE get_recorder_revisions_number();
28
30
  SQL
29
31
  end
30
32
 
@@ -1,9 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # This migration creates the `recorder_revisions` table.
2
4
  class CreateRecorderRevisions < ActiveRecord::Migration
3
5
  def change
4
6
  create_table :recorder_revisions do |t|
5
7
  t.string :item_type, null: false
6
- t.integer :item_id, null: false
8
+ t.integer :item_id
7
9
  t.string :event, null: false
8
10
  t.jsonb :data, null: false
9
11
  t.inet :ip
@@ -13,6 +15,6 @@ class CreateRecorderRevisions < ActiveRecord::Migration
13
15
  t.datetime :created_at, null: false
14
16
  end
15
17
 
16
- add_index :recorder_revisions, [:item_type, :item_id]
18
+ add_index :recorder_revisions, %i[item_type item_id]
17
19
  end
18
20
  end
@@ -1,34 +1,36 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Recorder
2
4
  class Changeset
3
5
  attr_reader :item, :changes
4
6
 
5
7
  def initialize(item, changes)
6
- @item = item;
7
- @changes = Hash[changes]
8
+ @item = item
9
+ @changes = changes.to_h
8
10
  end
9
11
 
10
12
  def keys
11
- self.changes.try(:keys) || []
13
+ changes.try(:keys) || []
12
14
  end
13
15
 
14
16
  def human_attribute_name(attribute)
15
- if defined?(Draper) && self.item.decorated?
16
- self.item.source.class.human_attribute_name(attribute.to_s)
17
+ if defined?(Draper) && item.decorated?
18
+ item.source.class.human_attribute_name(attribute.to_s)
17
19
  else
18
- self.item.class.human_attribute_name(attribute.to_s)
20
+ item.class.human_attribute_name(attribute.to_s)
19
21
  end
20
22
  end
21
23
 
22
24
  def previous(attribute)
23
- self.try("previous_#{attribute}") || self.previous_version.try(attribute)
25
+ try("previous_#{attribute}") || previous_version.try(attribute)
24
26
  end
25
27
 
26
28
  def previous_version
27
29
  return @previous_version if defined?(@previous_version)
28
30
 
29
- @previous_version = self.item.dup
31
+ @previous_version = item.dup
30
32
 
31
- self.changes.each do |key, change|
33
+ changes.each do |key, change|
32
34
  @previous_version.send("#{key}=", change[0])
33
35
  end
34
36
 
@@ -36,15 +38,15 @@ module Recorder
36
38
  end
37
39
 
38
40
  def next(attribute)
39
- self.try("next_#{attribute}") || self.next_version.try(attribute)
41
+ try("next_#{attribute}") || next_version.try(attribute)
40
42
  end
41
43
 
42
44
  def next_version
43
45
  return @next_version if defined?(@next_version)
44
46
 
45
- @next_version = self.item.dup
47
+ @next_version = item.dup
46
48
 
47
- self.changes.each do |key, change|
49
+ changes.each do |key, change|
48
50
  @next_version.send("#{key}=", change[1])
49
51
  end
50
52
 
@@ -1,10 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'singleton'
2
4
 
3
5
  module Recorder
4
6
  # Global configuration options
5
7
  class Config
6
8
  include Singleton
7
- attr_accessor :sidekiq_options, :ignore
9
+ attr_accessor :sidekiq_options
10
+ attr_reader :ignore, :async
8
11
 
9
12
  def initialize
10
13
  # Variables which affect all threads, whose access is synchronized.
@@ -12,18 +15,23 @@ module Recorder
12
15
  @enabled = true
13
16
 
14
17
  @sidekiq_options = {
15
- :queue => 'recorder',
16
- :retry => 10,
17
- :backtrace => true
18
+ queue: 'recorder',
19
+ retry: 10,
20
+ backtrace: true
18
21
  }
19
22
 
20
- @ignore = Array.new
23
+ @ignore = []
24
+ @async = false
21
25
  end
22
26
 
23
27
  def ignore=(value)
24
28
  @ignore = Array.wrap(value).map(&:to_sym)
25
29
  end
26
30
 
31
+ def async=(value)
32
+ @async = !!value
33
+ end
34
+
27
35
  # Indicates whether Recorder is on or off. Default: true.
28
36
  def enabled
29
37
  @mutex.synchronize { !!@enabled }
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Recorder
4
+ module Manager
5
+ def recorder_disabled!
6
+ Recorder.store.recorder_disabled!
7
+
8
+ if block_given?
9
+ yield
10
+ Recorder.store.recorder_enabled!
11
+ end
12
+ end
13
+
14
+ def recorder_enabled!
15
+ Recorder.store.recorder_enabled!
16
+ end
17
+ end
18
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'recorder/tape'
2
4
  require 'active_support/concern'
3
5
 
@@ -6,22 +8,19 @@ module Recorder
6
8
  extend ::ActiveSupport::Concern
7
9
 
8
10
  included do
9
- has_many :revisions, :class_name => '::Recorder::Revision', :inverse_of => :item, :as => :item do
10
- def create_async(params)
11
- Recorder::Sidekiq::RevisionsWorker.perform_async(
12
- proxy_association.owner.class.to_s,
13
- proxy_association.owner.id,
14
- params
15
- )
16
- end
17
- end
11
+ has_many :revisions, class_name: '::Recorder::Revision', inverse_of: :item, as: :item
18
12
  end
19
13
 
20
14
  def recorder_dirty?
21
15
  return @recorder_dirty if defined?(@recorder_dirty)
16
+
22
17
  true
23
18
  end
24
19
 
20
+ def recorder_record?
21
+ recorder_dirty? && Recorder.store.recorder_enabled?
22
+ end
23
+
25
24
  module ClassMethods
26
25
  def recorder(options = {})
27
26
  define_method 'recorder_options' do
@@ -29,21 +28,15 @@ module Recorder
29
28
  end
30
29
 
31
30
  after_create do
32
- if self.recorder_dirty?
33
- Recorder::Tape.new(self).record_create
34
- end
31
+ Recorder::Tape.new(self).record_create if recorder_record?
35
32
  end
36
33
 
37
34
  after_update do
38
- if self.recorder_dirty?
39
- Recorder::Tape.new(self).record_update
40
- end
35
+ Recorder::Tape.new(self).record_update if recorder_record?
41
36
  end
42
37
 
43
38
  after_destroy do
44
- if self.recorder_dirty?
45
- Recorder::Tape.new(self).record_destroy
46
- end
39
+ Recorder::Tape.new(self).record_destroy if recorder_record?
47
40
  end
48
41
  end
49
42
  end