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.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Recorder
2
4
  module Rails
3
5
  # Extensions to rails controllers. Provides convenient ways to pass certain
@@ -8,7 +10,7 @@ module Recorder
8
10
  base.before_action :set_recorder_meta
9
11
  end
10
12
 
11
- protected
13
+ protected
12
14
 
13
15
  # Returns the user who is responsible for any changes that occur.
14
16
  # By default this calls `current_user` and returns the result.
@@ -17,6 +19,7 @@ module Recorder
17
19
  # method, e.g. `current_person`, or anything you like.
18
20
  def recorder_user_id
19
21
  return unless defined?(current_user)
22
+
20
23
  current_user.try!(:id)
21
24
  rescue NoMethodError
22
25
  current_user
@@ -24,10 +27,10 @@ module Recorder
24
27
 
25
28
  def recorder_info
26
29
  {
27
- :user_id => self.recorder_user_id,
28
- :ip => request.remote_ip,
29
- :action_date => Date.current,
30
- :meta => self.recorder_meta
30
+ user_id: recorder_user_id,
31
+ ip: request.remote_ip,
32
+ action_date: Date.current,
33
+ meta: recorder_meta
31
34
  }
32
35
  end
33
36
 
@@ -40,11 +43,11 @@ module Recorder
40
43
  # Tells Recorder any information from the controller you want to store
41
44
  # alongside any changes that occur.
42
45
  def set_recorder_info
43
- ::Recorder.info = self.recorder_info
46
+ ::Recorder.info = recorder_info
44
47
  end
45
48
 
46
49
  def set_recorder_meta
47
- ::Recorder.meta = self.recorder_meta
50
+ ::Recorder.meta = recorder_meta
48
51
  end
49
52
  end
50
53
  end
@@ -1,7 +1,11 @@
1
- class Recorder::Rails::Railtie < Rails::Railtie
2
- initializer "recorder.configure" do |app|
3
- if defined?(Sidekiq)
4
- require 'recorder/sidekiq/revisions_worker'
1
+ # frozen_string_literal: true
2
+
3
+ module Recorder
4
+ module Rails
5
+ class Railtie < Rails::Railtie
6
+ initializer 'recorder.configure' do |_app|
7
+ require 'recorder/sidekiq/revisions_worker' if defined?(Sidekiq)
8
+ end
5
9
  end
6
10
  end
7
11
  end
@@ -1,72 +1,93 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_record'
2
4
 
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
5
+ module Recorder
6
+ class Revision < ActiveRecord::Base
7
+ self.table_name = 'recorder_revisions'
17
8
 
18
- belongs_to :item, :polymorphic => true, :inverse_of => :revisions
19
- belongs_to :user
9
+ if ::Recorder.active_record_protected_attributes?
10
+ attr_accessible(
11
+ :event,
12
+ :user_id,
13
+ :ip,
14
+ :user_agent,
15
+ :action_date,
16
+ :data,
17
+ :meta
18
+ )
19
+ end
20
20
 
21
- validates :item, :presence => true
22
- validates :event, :presence => true
23
- validates :action_date, :presence => true
24
- validates :data, :presence => true
21
+ belongs_to :user
25
22
 
26
- scope :ordered_by_created_at, -> { order(:created_at => :desc) }
23
+ validates :item_type, presence: true
24
+ validates :event, presence: true
25
+ validates :action_date, presence: true
26
+ validates :data, presence: true
27
27
 
28
- # Get changeset for an item
29
- # @return [Recorder::Changeset]
30
- def item_changeset
31
- return @item_changeset if defined?(@item_changeset)
32
- return nil if self.data['changes'].nil?
28
+ scope :ordered_by_created_at, -> { order(created_at: :desc) }
33
29
 
34
- @item_changeset ||= self.changeset_class(self.item).new(self.item, self.data['changes'])
35
- end
30
+ def item
31
+ return @item if defined?(@item)
32
+ return if item_id.nil?
36
33
 
37
- # Get names of item associations that has been changed
38
- # @return [Array]
39
- def changed_associations
40
- self.data['associations'].try(:keys) || []
41
- end
34
+ @item = item_type.classify.constantize.new(data['attributes'])
42
35
 
43
- # Get changeset for an association
44
- # @param name [String] name of association to return changeset
45
- # @return [Recorder::Changeset]
46
- def association_changeset(name)
47
- association = self.item.send(name)
48
- # association = association.source if association.decorated?
36
+ if data['associations'].present?
37
+ data['associations'].each do |name, association|
38
+ @item.send("build_#{name}", association['attributes'])
39
+ end
40
+ end
49
41
 
50
- self.changeset_class(association).new(association, self.data['associations'].fetch(name.to_s).try(:fetch, 'changes'))
51
- end
42
+ @item
43
+ end
52
44
 
53
- protected
45
+ # Get changeset for an item
46
+ # @return [Recorder::Changeset]
47
+ def item_changeset
48
+ return @item_changeset if defined?(@item_changeset)
49
+ return nil if item.nil?
50
+ return nil if data['changes'].nil?
54
51
 
55
- # Returns changeset class for passed object.
56
- # Changeset class name can be overriden with `#recorder_changeset_class` method.
57
- # If `#recorder_changeset_class` method is not defined, then class name is generated as "#{class}Changeset"
58
- # @api private
59
- def changeset_class(object)
60
- klass = (defined?(Draper) && object.decorated?) ? object.source.class : object.class
61
- klass = klass.base_class
52
+ @item_changeset ||= changeset_class(item).new(item, data['changes'])
53
+ end
62
54
 
63
- if klass.respond_to?(:recorder_changeset_class)
64
- return klass.send(:recorder_changeset_class)
55
+ # Get names of item associations that has been changed
56
+ # @return [Array]
57
+ def changed_associations
58
+ data['associations'].try(:keys) || []
65
59
  end
66
60
 
67
- klass = "#{klass}Changeset"
61
+ # Get changeset for an association
62
+ # @param name [String] name of association to return changeset
63
+ # @return [Recorder::Changeset]
64
+ def association_changeset(name)
65
+ association = item.send(name)
66
+ # association = association.source if association.decorated?
68
67
 
69
- klass = klass.constantize rescue nil
70
- klass.present? ? klass : Recorder::Changeset
68
+ changeset_class(association).new(association, data['associations'].fetch(name.to_s).try(:fetch, 'changes'))
69
+ end
70
+
71
+ protected
72
+
73
+ # Returns changeset class for passed object.
74
+ # Changeset class name can be overriden with `#recorder_changeset_class` method.
75
+ # If `#recorder_changeset_class` method is not defined, then class name is generated as "#{class}Changeset"
76
+ # @api private
77
+ def changeset_class(object)
78
+ klass = defined?(Draper) && object.decorated? ? object.source.class : object.class
79
+ klass = klass.base_class
80
+
81
+ return klass.send(:recorder_changeset_class) if klass.respond_to?(:recorder_changeset_class)
82
+
83
+ klass = "#{klass}Changeset"
84
+
85
+ klass = begin
86
+ klass.constantize
87
+ rescue
88
+ nil
89
+ end
90
+ klass.present? ? klass : Recorder::Changeset
91
+ end
71
92
  end
72
93
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Recorder
2
4
  module Sidekiq
3
5
  class RevisionsWorker
@@ -5,9 +7,8 @@ module Recorder
5
7
 
6
8
  sidekiq_options Recorder.config.sidekiq_options
7
9
 
8
- def perform(klass, id, params)
9
- object = klass.constantize.find_by(:id => id)
10
- object.revisions.create(params) if object.present?
10
+ def perform(params)
11
+ Recorder::Revision.create(params)
11
12
  end
12
13
  end
13
14
  end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'request_store'
4
+
5
+ module Recorder
6
+ class Store
7
+ def params
8
+ store[:params]
9
+ end
10
+
11
+ def recorder_enabled!
12
+ store[:enabled] = true
13
+ end
14
+
15
+ def recorder_disabled!
16
+ store[:enabled] = false
17
+ end
18
+
19
+ def recorder_enabled?
20
+ store[:enabled]
21
+ end
22
+
23
+ def recorder_disabled?
24
+ !store[:enabled]
25
+ end
26
+
27
+ private
28
+
29
+ def store
30
+ RequestStore.store[:recorder] ||= {
31
+ enabled: true,
32
+ params: {}
33
+ }
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Recorder
4
+ class Tape
5
+ class Data
6
+ attr_reader :item
7
+
8
+ def initialize(item)
9
+ @item = item
10
+ end
11
+
12
+ def data_for(event, options = {})
13
+ {
14
+ **attributes_for(event, options),
15
+ **changes_for(event, options),
16
+ **associations_for(event, options)
17
+ }
18
+ end
19
+
20
+ def attributes_for(_event, options)
21
+ {attributes: sanitize_attributes(item.attributes, options)}
22
+ end
23
+
24
+ def changes_for(event, options)
25
+ changes =
26
+ case event.to_sym
27
+ when :update
28
+ sanitize_attributes(item.saved_changes, options)
29
+ end
30
+
31
+ changes.present? ? {changes: changes} : {}
32
+ end
33
+
34
+ def associations_for(event, options)
35
+ associations = parse_associations_attributes(event, options)
36
+
37
+ associations.present? ? {associations: associations} : {}
38
+ end
39
+
40
+ private
41
+
42
+ def sanitize_attributes(attributes, options)
43
+ if options[:only].present?
44
+ only = wrap_options(options[:only])
45
+ attributes.symbolize_keys.slice(*only)
46
+ elsif options[:ignore].present?
47
+ ignore = wrap_options(options[:ignore])
48
+ attributes.symbolize_keys.except(*ignore)
49
+ else
50
+ attributes.symbolize_keys.except(*Recorder.config.ignore)
51
+ end
52
+ end
53
+
54
+ def wrap_options(values)
55
+ Array.wrap(values).map(&:to_sym)
56
+ end
57
+
58
+ def parse_associations_attributes(event, options)
59
+ return unless options[:associations]
60
+
61
+ options[:associations].each_with_object({}) do |(association, options), hash|
62
+ name, data = parse_association(event, association, options)
63
+
64
+ hash[name] = data if data.any?
65
+ end
66
+ end
67
+
68
+ def parse_association(event, association, options)
69
+ reflection = item.class.reflect_on_association(association)
70
+
71
+ if reflection.present?
72
+ if reflection.collection?
73
+
74
+ elsif (object = item.send(association))
75
+ data = Recorder::Tape::Data.new(object).data_for(event, options || {})
76
+
77
+ [reflection.name, data]
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Recorder
4
+ class Tape
5
+ module Record
6
+ def record(params, options = {})
7
+ return if Recorder.store.recorder_disabled?
8
+
9
+ params = params_for(params)
10
+
11
+ if async?(options)
12
+ record_async(params, options)
13
+ else
14
+ Recorder::Revision.create(params)
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def params_for(params)
21
+ Recorder.store.params.merge({
22
+ action_date: Date.today,
23
+ **params
24
+ })
25
+ end
26
+
27
+ def async?(options)
28
+ options[:async].nil? ? Recorder.config.async : options[:async]
29
+ end
30
+
31
+ def record_async(params, options)
32
+ Recorder::Sidekiq::RevisionsWorker.perform_in(
33
+ options[:delay] || 2.seconds,
34
+ params
35
+ )
36
+ end
37
+ end
38
+ end
39
+ end
data/lib/recorder/tape.rb CHANGED
@@ -1,105 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'recorder/tape/data'
4
+ require 'recorder/tape/record'
5
+
1
6
  module Recorder
2
7
  class Tape
3
- attr_reader :item
8
+ extend Record
4
9
 
5
- def initialize(item)
6
- @item = item;
10
+ attr_reader :item, :data
7
11
 
8
- self.item.instance_variable_set(:@recorder_dirty, true)
9
- end
10
-
11
- def changes_for(event)
12
- changes = case event.to_sym
13
- when :create
14
- self.sanitize_attributes(self.item.attributes)
15
- when :update
16
- self.sanitize_attributes(self.item.changes)
17
- when :destroy
18
- self.sanitize_attributes(self.item.changes)
19
- else
20
- raise ArgumentError
21
- end
12
+ def initialize(item)
13
+ @item = item
14
+ @data = Data.new(item)
22
15
 
23
- changes.any? ? { :changes => changes } : {}
16
+ item.instance_variable_set(:@recorder_dirty, true)
24
17
  end
25
18
 
26
19
  def record_create
27
- data = self.changes_for(:create)
28
-
29
- associations_attributes = self.parse_associations_attributes(:create)
30
- data.merge!(:associations => associations_attributes) if associations_attributes.present?
20
+ data = data_for(:create, recorder_options)
31
21
 
32
- if data.any?
33
- self.record(
34
- Recorder.store.merge({
35
- :event => :create,
36
- :data => data
37
- })
38
- )
39
- end
22
+ record(event: :create, data: data) if data.any?
40
23
  end
41
24
 
42
25
  def record_update
43
- data = self.changes_for(:update)
26
+ data = data_for(:update, recorder_options)
44
27
 
45
- associations_attributes = self.parse_associations_attributes(:update)
46
- data.merge!(:associations => associations_attributes) if associations_attributes.present?
47
-
48
- if data.any?
49
- self.record(
50
- Recorder.store.merge({
51
- :event => :update,
52
- :data => data
53
- })
54
- )
55
- end
28
+ record(event: :update, data: data) if data.any?
56
29
  end
57
30
 
58
31
  def record_destroy
59
- end
32
+ data = data_for(:destroy, recorder_options)
60
33
 
61
- protected
34
+ record(event: :destroy, data: data) if data.any?
35
+ end
62
36
 
63
- def record(params)
64
- params.merge!({
65
- :action_date => Date.today
66
- })
37
+ protected
67
38
 
68
- if self.item.recorder_options[:async]
69
- self.item.revisions.create_async(params)
70
- else
71
- self.item.revisions.create(params)
72
- end
39
+ def recorder_options
40
+ item.respond_to?(:recorder_options) ? item.recorder_options : {}
73
41
  end
74
42
 
75
- def sanitize_attributes(attributes = {})
76
- if self.item.respond_to?(:recorder_options) && self.item.recorder_options[:ignore].present?
77
- ignore = Array.wrap(self.item.recorder_options[:ignore]).map(&:to_sym)
78
- attributes.symbolize_keys.except(*ignore)
79
- else
80
- attributes.symbolize_keys.except(*Recorder.config.ignore)
81
- end
43
+ def data_for(event, options)
44
+ data.data_for(event, options)
82
45
  end
83
46
 
84
- def parse_associations_attributes(event)
85
- if self.item.respond_to?(:recorder_options) && self.item.recorder_options[:associations].present?
86
- self.item.recorder_options[:associations].inject({}) do |hash, association|
87
- reflection = self.item.class.reflect_on_association(association)
88
- if reflection.present?
89
- if reflection.collection?
90
-
91
- else
92
- if object = self.item.send(association)
93
- changes = Recorder::Tape.new(object).changes_for(event)
94
- hash[reflection.name] = changes if changes.any?
95
- object.instance_variable_set(:@recorder_dirty, false)
96
- end
97
- end
98
- end
99
-
100
- hash
101
- end
102
- end
47
+ def record(params)
48
+ Recorder::Tape.record(
49
+ {
50
+ item_type: item.class.to_s,
51
+ item_id: item.id,
52
+ **params
53
+ },
54
+ item.recorder_options
55
+ )
103
56
  end
104
57
  end
105
58
  end