recorder 0.1.23 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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