loggable_activity 0.1.54 → 0.1.55

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.
@@ -18,117 +18,57 @@ module LoggableActivity
18
18
  validates :action, presence: true
19
19
  validate :must_have_at_least_one_payload
20
20
 
21
- # Returns a list of attributes of the activity includig the indliced relations.
22
- # The included relations are devined in the 'config/loggable_activity.yaml' file.
23
- # The attributes are packed in a way that they can be used to display the activity in the UI.
21
+ # Returns an array of hashes, each representing an activity's attributes and its associated relations. The structure and relations to include are specified in 'config/loggable_activity.yaml'. This format is designed for UI display purposes.
24
22
  #
25
- # Example:
23
+ # Each hash in the array contains:
24
+ # - :record_type: The class name of the record.
25
+ # - :payload_type: A descriptor of the payload's role (e.g., 'primary_payload' or 'current_association').
26
+ # - :attrs: A hash of the record's attributes.
27
+ #
28
+ # Example usage:
26
29
  # @activity.attrs
27
30
  #
28
- # Returns:
31
+ # Sample return value:
29
32
  # [
30
- # {
31
- # record_class: "User",
32
- # payload_type: "primary_payload",
33
- # attrs: {
34
- # "first_name" => "David",
35
- # "last_name" => "Bowie",
36
- # "age" => "69",
37
- # "email" => "david@example.com",
38
- # "user_type" => "Patient"
39
- # }
40
- # },
41
- # {
42
- # record_class: "Demo::UserProfile",
43
- # payload_type: "current_association",
44
- # attrs: {
45
- # "sex" => "Male",
46
- # "religion" => "Agnostic"
47
- # }
48
- # },
49
- # {
50
- # record_class: "Demo::Address",
51
- # payload_type: "current_association",
52
- # attrs: {
53
- # "street" => "Eiffel Tower",
54
- # "city" => "Paris",
55
- # "country" => "France",
56
- # "postal_code" => "75007"
57
- # }
58
- # },
59
- # {
60
- # record_class: "Demo::Club",
61
- # payload_type: "current_association",
62
- # attrs: {
63
- # "name" => "Mystic Fusion Lounge"
64
- # }
65
- # }
66
- # ]
67
- #
33
+ # { record_type: MODEL_NAME, payload_type: "primary_payload", attrs: { "KEY" => "VALUE", ... } },
34
+ # { record_type: MODEL_NAME, payload_type: "current_association", attrs: { "KEY" => "VALUE", ... } },
35
+ # ...
36
+ # ]
68
37
  def attrs
69
38
  ordered_payloads.map do |payload|
70
39
  {
71
- record_class: payload.record_type,
40
+ record_type: payload.record_type,
41
+ record_id: payload.record_id,
72
42
  payload_type: payload.payload_type,
73
- attrs: payload.attrs
43
+ attrs: payload.attrs,
44
+ path: payload.payload_route
74
45
  }
75
46
  end
76
47
  end
77
48
 
78
- # Returns the attributes of an upddate activity.
49
+ # Returns a hash describing the attributes of an update activity, including updated attributes for a record and any updated related attributes.
79
50
  #
80
51
  # Example:
81
52
  # @activity.update_activity_attrs
82
53
  #
83
- # Returns:
54
+ # The return value is structured as follows:
55
+ # - :update_attrs contains the attributes of the record being updated, detailing changes from previous to new values.
56
+ # - :updated_relations_attrs is an array of hashes, each representing an updated related record. Each hash details the previous and current attributes of the relation.
57
+ #
58
+ # Example Return Structure:
84
59
  # {
85
- # # Update attributes for Demo::Club
86
60
  # update_attrs: {
87
- # record_class: "Demo::Club",
88
- # attrs: [
89
- # {
90
- # "name" => {
91
- # # Previous name
92
- # from: "Electric Oasis Club",
93
- # # New name
94
- # to: "Electric Oasis Club nr 5"
95
- # }
96
- # }
97
- # ]
61
+ # record_class: "CLASS.NAME",
62
+ # attrs: [{ "KEY" => { from: "OLD_VALUE", to: "NEW_VALUE" } }]
98
63
  # },
99
- # # Updated relations attributes
100
64
  # updated_relations_attrs: [
101
65
  # {
102
- # record_class: "Demo::Address",
103
- # previous_attrs: {
104
- # # Record class
105
- # record_class: "Demo::Address",
106
- # # Previous association payload type
107
- # payload_type: "previous_association",
108
- # # Previous attributes for Demo::Address
109
- # attrs: {
110
- # "street" => "Ice Hotel, Marknadsvägen 63",
111
- # "city" => "Jukkasjärvi",
112
- # "country" => "Sweden",
113
- # "postal_code" => "981 91"
114
- # }
115
- # },
116
- # current_attrs: {
117
- # record_class: "Demo::Address",
118
- # # Current association payload type
119
- # payload_type: "current_association",
120
- # # Current attributes for Demo::Address
121
- # attrs: {
122
- # "street" => "The Palace of Versailles",
123
- # "city" => "Versailles",
124
- # "country" => "France",
125
- # "postal_code" => "78000"
126
- # }
127
- # }
66
+ # record_class: "CLASS.NAME",
67
+ # previous_attrs: { attrs: { "KEY" => "VALUE", ... } },
68
+ # current_attrs: { attrs: { "KEY" => "VALUE", ... } }
128
69
  # }
129
70
  # ]
130
71
  # }
131
- #
132
72
  def update_activity_attrs
133
73
  {
134
74
  update_attrs:,
@@ -143,13 +83,7 @@ module LoggableActivity
143
83
  # @activity.primary_payload_attrs
144
84
  #
145
85
  # Returns:
146
- # {
147
- # "first_name" => "David",
148
- # "last_name" => "Bowie",
149
- # "age" => "69",
150
- # "email" => "david@example.com",
151
- # "user_type" => "Patient"
152
- # }
86
+ # { "KEY_A" => "VALUE_A", "KEY_B" => "VALUE_B", ... }
153
87
  #
154
88
  def primary_payload_attrs
155
89
  primary_payload ? primary_payload.attrs : {}
@@ -164,16 +98,10 @@ module LoggableActivity
164
98
  # Returns:
165
99
  # [
166
100
  # {
167
- # record_class: "Demo::Address",
168
- # # Current association payload type
169
- # payload_type: "current_association",
170
- # # Current attributes for Demo::Address
171
- # attrs: {
172
- # "street" => "The Palace of Versailles",
173
- # "city" => "Versailles",
174
- # "country" => "France",
175
- # "postal_code" => "78000"
176
- # }
101
+ # record_type: CLASS.NAME,
102
+ # record_id: INTEGER,
103
+ # payload_type: ENUM
104
+ # attrs: { "KEY_A" => "VALUE_A", "KEY_B" => "VALUE_B", ... }
177
105
  # }
178
106
  # ]
179
107
  def relations_attrs
@@ -195,6 +123,19 @@ module LoggableActivity
195
123
  LoggableActivity::Encryption.decrypt(encrypted_record_display_name, record_key)
196
124
  end
197
125
 
126
+ # Returns the path for the activity.
127
+ #
128
+ # Example:
129
+ #
130
+ # @activity.path
131
+ #
132
+ # Returns:
133
+ # "/path/to/activity"
134
+ #
135
+ def path
136
+ primary_payload&.route
137
+ end
138
+
198
139
  # Returns the display name for a actor. what method to use if defined in '/config/loggable_activity.yaml'
199
140
  #
200
141
  # Example:
@@ -235,6 +176,7 @@ module LoggableActivity
235
176
 
236
177
  private
237
178
 
179
+ # Returns the attributes for the update+payload.
238
180
  def update_attrs
239
181
  update_payload_attrs = attrs.find { |p| p[:payload_type] == 'update_payload' }
240
182
  return nil unless update_payload_attrs
@@ -243,42 +185,49 @@ module LoggableActivity
243
185
  update_payload_attrs
244
186
  end
245
187
 
188
+ # Returns the primary payload.
246
189
  def primary_payload
247
190
  ordered_payloads.find { |p| p.payload_type == 'primary_payload' }
248
191
  end
249
192
 
193
+ # Returns the attributes for the updated relations.
250
194
  def updated_relations_attrs
251
- grouped_associations = attrs.group_by { |p| p[:record_class] }
195
+ grouped_associations = attrs.group_by { |p| p[:record_type] }
252
196
 
253
- grouped_associations.map do |record_class, payloads|
197
+ grouped_associations.map do |record_type, payloads|
254
198
  previous_attrs = payloads.find { |p| p[:payload_type] == 'previous_association' }
255
199
  current_attrs = payloads.find { |p| p[:payload_type] == 'current_association' }
256
200
  next if previous_attrs.nil? && current_attrs.nil?
257
201
 
258
- { record_class:, previous_attrs:, current_attrs: }
202
+ { record_type:, previous_attrs:, current_attrs: }
259
203
  end.compact
260
204
  end
261
205
 
206
+ # Returns the previous association attributes.
262
207
  def previous_associations_attrs
263
208
  attrs.select { |p| p[:payload_type] == 'previous_association' }
264
209
  end
265
210
 
211
+ # Returns payloads sorted by :payload_type.
266
212
  def ordered_payloads
267
213
  payloads.order(:payload_type)
268
214
  end
269
215
 
216
+ # Returns the key for the logged record.
270
217
  def record_key
271
218
  return nil if record.nil?
272
219
 
273
220
  LoggableActivity::EncryptionKey.for_record(record)&.key
274
221
  end
275
222
 
223
+ # Returns the key for the actor.
276
224
  def actor_key
277
225
  return nil if actor.nil?
278
226
 
279
227
  LoggableActivity::EncryptionKey.for_record(actor)&.key
280
228
  end
281
229
 
230
+ # Validates that the activity has at least one payload.
282
231
  def must_have_at_least_one_payload
283
232
  errors.add(:payloads, 'must have at least one payload') if payloads.empty?
284
233
  end
@@ -3,6 +3,7 @@
3
3
  module LoggableActivity
4
4
  # This class is used to load the configuration file located at config/loggable_activity.yml
5
5
  class Configuration
6
+ # Loads the configuration file
6
7
  def self.load_config_file(config_file_path)
7
8
  @config_data = YAML.load_file(config_file_path)
8
9
  end
@@ -23,7 +23,7 @@ module LoggableActivity
23
23
  return nil if data.nil? || encoded_key.nil?
24
24
 
25
25
  encryption_key = Base64.decode64(encoded_key)
26
- raise EncryptionError, "Encryption failed: Invalid encryption key length #{encryption_key.bytesize}" unless encryption_key.bytesize == 32
26
+ raise EncryptionError, "Encryption failed: Invalid encoded_key length #{encryption_key.bytesize}" unless encryption_key.bytesize == 32
27
27
 
28
28
  cipher = OpenSSL::Cipher.new('AES-256-CBC').encrypt
29
29
  cipher.key = encryption_key
@@ -48,7 +48,7 @@ module LoggableActivity
48
48
  return '' if data.nil? || encoded_key.nil?
49
49
 
50
50
  encryption_key = Base64.decode64(encoded_key)
51
- raise EncryptionError, 'Decryption failed: Invalid encryption key length' unless encryption_key.bytesize == 32
51
+ raise EncryptionError, "Decryption failed: Invalid encoded_key length: #{encryption_key.bytesize}" unless encryption_key.bytesize == 32
52
52
 
53
53
  cipher = OpenSSL::Cipher.new('AES-256-CBC').decrypt
54
54
  cipher.key = encryption_key
@@ -60,8 +60,12 @@ module LoggableActivity
60
60
  decrypted_data.force_encoding('UTF-8')
61
61
  rescue OpenSSL::Cipher::CipherError => e
62
62
  raise EncryptionError, "Decryption failed: #{e.message}"
63
+ rescue EncryptionError => e
64
+ puts e.message
65
+ '*** DECRYPTION FAILED ***'
63
66
  end
64
67
 
68
+ # Returns true if the given value is blank
65
69
  def self.blank?(value)
66
70
  value.respond_to?(:empty?) ? value.empty? : !value
67
71
  end
@@ -65,6 +65,8 @@ module LoggableActivity
65
65
  # }
66
66
  #
67
67
  def self.for_record(record, parent_key = nil)
68
+ return nil if record.nil?
69
+
68
70
  encryption_key = find_by(record:)
69
71
  return encryption_key if encryption_key
70
72
 
@@ -26,6 +26,7 @@ module LoggableActivity
26
26
  self.auto_log = config&.fetch('auto_log', []) || []
27
27
  self.actor_display_name = config&.fetch('actor_display_name', nil)
28
28
  self.record_display_name = config&.fetch('record_display_name', nil)
29
+ self.route = config&.fetch('route', nil)
29
30
 
30
31
  after_create :log_create_activity
31
32
  after_update :log_update_activity
@@ -62,18 +63,22 @@ module LoggableActivity
62
63
 
63
64
  private
64
65
 
66
+ # Logs an activity for the current action.
65
67
  def log_activity
66
68
  create_activity(build_payloads)
67
69
  end
68
70
 
71
+ # Logs an activity for the update action.
69
72
  def log_update
70
73
  create_activity(build_update_payloads)
71
74
  end
72
75
 
76
+ # Logs an activity for the destroy action.
73
77
  def log_destroy
74
78
  create_activity(build_destroy_payload)
75
79
  end
76
80
 
81
+ # Creates an activity with the specified payloads.
77
82
  def create_activity(payloads)
78
83
  return if nothing_to_log?(payloads)
79
84
 
@@ -87,30 +92,37 @@ module LoggableActivity
87
92
  )
88
93
  end
89
94
 
95
+ # Returns true if there are no payloads to log.
90
96
  def nothing_to_log?(payloads)
91
97
  payloads.empty?
92
98
  end
93
99
 
100
+ # Logs a custom activity.
94
101
  def log_custom_activity(activity); end
95
102
 
103
+ # Logs an update activity automatically if configured.
96
104
  def log_update_activity
97
105
  log(:update) if self.class.auto_log.include?('update')
98
106
  end
99
107
 
108
+ # Logs a create activity automatically if configured.
100
109
  def log_create_activity
101
110
  log(:create) if self.class.auto_log.include?('create')
102
111
  end
103
112
 
113
+ # Logs a destroy activity automatically if configured.
104
114
  def log_destroy_activity
105
115
  LoggableActivity::EncryptionKey.for_record(self)&.mark_as_deleted
106
116
  log(:destroy) if self.class.auto_log.include?('destroy')
107
117
  end
108
118
 
119
+ # Returns the encrypted name of the actor.
109
120
  def encrypted_actor_name
110
121
  actor_display_name = @actor.send(actor_display_name_field)
111
122
  LoggableActivity::Encryption.encrypt(actor_display_name, actor_encryption_key)
112
123
  end
113
124
 
125
+ # Returns the encrypted name of the record.
114
126
  def encrypted_record_name
115
127
  display_name =
116
128
  if self.class.record_display_name.nil?
@@ -121,34 +133,41 @@ module LoggableActivity
121
133
  LoggableActivity::Encryption.encrypt(display_name, primary_encryption_key)
122
134
  end
123
135
 
136
+ # Returns the action key for the current action.
124
137
  def action_key
125
138
  @action_key ||= self.class.base_action + ".#{@action}"
126
139
  end
127
140
 
141
+ # Returns the primary encryption key for the activity
128
142
  def primary_encryption_key
129
143
  @primary_encryption_key ||=
130
144
  LoggableActivity::EncryptionKey.for_record(self)&.key
131
145
  end
132
146
 
147
+ # Returns true if the primary encryption key is deleted.
133
148
  def primary_encryption_key_deleted?
134
149
  primary_encryption_key.nil?
135
150
  end
136
151
 
152
+ # Returns the encryption key for the actor.
137
153
  def actor_encryption_key
138
154
  LoggableActivity::EncryptionKey.for_record(@actor)&.key
139
155
  end
140
156
 
157
+ # Returns the display name of the actor.
141
158
  def actor_display_name_field
142
159
  Rails.application.config.loggable_activity.actor_display_name || "id: #{@actor.id}, class: #{@actor.class.name}"
143
160
  end
144
161
 
162
+ # Returns the model name of the current user.
145
163
  def current_user_model?
146
164
  Rails.application.config.loggable_activity.current_user_model_name == self.class.name
147
165
  end
148
166
 
149
167
  class_methods do
150
- attr_accessor :loggable_attrs, :relations, :auto_log, :actor_display_name, :record_display_name
168
+ attr_accessor :loggable_attrs, :relations, :auto_log, :actor_display_name, :record_display_name, :route
151
169
 
170
+ # Convert the model name and name space in to 'base_action'.
152
171
  def base_action
153
172
  name.downcase.gsub('::', '/')
154
173
  end
@@ -50,6 +50,13 @@ module LoggableActivity
50
50
  end
51
51
  end
52
52
 
53
+ # Returns the route for the payload.
54
+ def payload_route
55
+ return nil if payload_encryption_key.nil?
56
+
57
+ route
58
+ end
59
+
53
60
  private
54
61
 
55
62
  # Retrieves the encryption key for the payload.
@@ -40,7 +40,8 @@ module LoggableActivity
40
40
  record: @record,
41
41
  payload_type: 'primary_payload',
42
42
  encrypted_attrs:,
43
- data_owner: true
43
+ data_owner: true,
44
+ route: self.class.route
44
45
  )
45
46
  end
46
47
 
@@ -52,7 +53,8 @@ module LoggableActivity
52
53
  record: @record,
53
54
  payload_type: 'primary_payload',
54
55
  encrypted_attrs:,
55
- data_owner: true
56
+ data_owner: true,
57
+ route: nil
56
58
  )
57
59
  end
58
60
 
@@ -108,7 +110,8 @@ module LoggableActivity
108
110
  record: associated_record,
109
111
  encrypted_attrs:,
110
112
  payload_type: 'current_association',
111
- data_owner: relation_config['data_owner']
113
+ data_owner: relation_config['data_owner'],
114
+ route: relation_config['route']
112
115
  )
113
116
  end
114
117
 
@@ -18,6 +18,7 @@ module LoggableActivity
18
18
 
19
19
  private
20
20
 
21
+ # Fetch the previous and current values of the primary record.
21
22
  def primary_update_attrs
22
23
  previous_values = saved_changes.transform_values(&:first)
23
24
  current_values = saved_changes.transform_values(&:last)
@@ -25,6 +26,7 @@ module LoggableActivity
25
26
  [previous_values, current_values]
26
27
  end
27
28
 
29
+ # Builds the primary update payload.
28
30
  def build_primary_update_payload(previous_values, current_values)
29
31
  return if previous_values == current_values
30
32
 
@@ -32,10 +34,12 @@ module LoggableActivity
32
34
  @update_payloads << LoggableActivity::Payload.new(
33
35
  record: @record,
34
36
  payload_type: 'update_payload',
35
- encrypted_attrs: encrypted_update_attrs
37
+ encrypted_attrs: encrypted_update_attrs,
38
+ route: self.class.route
36
39
  )
37
40
  end
38
41
 
42
+ # Returns the encrypted attributes for the update payload.
39
43
  def encrypted_update_attrs(previous_values, current_values)
40
44
  changes = []
41
45
  changed_attrs = previous_values.slice(*self.class.loggable_attrs)
@@ -48,6 +52,7 @@ module LoggableActivity
48
52
  { changes: }
49
53
  end
50
54
 
55
+ # Builds update payloads for relations.
51
56
  def build_update_relation_payloads(relation_config)
52
57
  relation_config.each_key do |key|
53
58
  case key
@@ -61,6 +66,7 @@ module LoggableActivity
61
66
  end
62
67
  end
63
68
 
69
+ # Builds the update payload for a has_many relation.
64
70
  def build_relation_update_for_has_many(relation_config)
65
71
  # NOTE: This method is not implemented yet.
66
72
  # It requires that there is a form where it is possible to change
@@ -68,13 +74,15 @@ module LoggableActivity
68
74
  # puts relation_config['has_many']
69
75
  end
70
76
 
77
+ # Builds the update payload for a has_one relation.
71
78
  def build_relation_update_for_has_one(relation_config)
72
79
  # NOTE: This method is not implemented yet.
73
80
  # It requires that there is a form where it is possible to change
74
81
  # the related records. This is not implemented yet. in the Demo app
75
- # puts relation_config['has_many']
82
+ # puts relation_config['has_one']
76
83
  end
77
84
 
85
+ # Builds the update payload for a belongs_to relation.
78
86
  def build_relation_update_for_belongs_to(relation_config)
79
87
  relation_id = "#{relation_config['belongs_to']}_id"
80
88
  model_class_name = relation_config['model']
@@ -91,25 +99,27 @@ module LoggableActivity
91
99
 
92
100
  payload_type = index.zero? ? 'previous_association' : 'current_association'
93
101
  build_relation_update_payload(
94
- relation_record.attributes,
95
- relation_config['loggable_attrs'],
102
+ relation_config,
96
103
  relation_record,
97
104
  payload_type
98
105
  )
99
106
  end
100
107
  end
101
108
 
102
- def build_relation_update_payload(_attrs, loggable_attrs, record, payload_type)
109
+ # Builds the update payload for a relation.
110
+ def build_relation_update_payload(relation_config, record, payload_type)
103
111
  encryption_key = LoggableActivity::EncryptionKey.for_record(record)&.key
104
- encrypted_attrs = relation_encrypted_attrs(record.attributes, loggable_attrs, encryption_key)
112
+ encrypted_attrs = relation_encrypted_attrs(record.attributes, relation_config['loggable_attrs'], encryption_key)
105
113
 
106
114
  @update_payloads << LoggableActivity::Payload.new(
107
115
  record:,
108
116
  encrypted_attrs:,
109
- payload_type:
117
+ payload_type:,
118
+ route: relation_config['route']
110
119
  )
111
120
  end
112
121
 
122
+ # Returns the encrypted attributes for a relation.
113
123
  def relation_encrypted_attrs(attrs, loggable_attrs, encryption_key)
114
124
  encrypt_attrs(attrs, loggable_attrs, encryption_key)
115
125
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # Version of the gem
3
4
  module LoggableActivity
4
- VERSION = '0.1.54'
5
+ # Version
6
+ VERSION = '0.1.55'
5
7
  end
@@ -12,5 +12,4 @@ require_relative 'loggable_activity/update_payloads_builder'
12
12
 
13
13
  module LoggableActivity
14
14
  class Error < StandardError; end
15
- # Your code goes here...
16
15
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: loggable_activity
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.54
4
+ version: 0.1.55
5
5
  platform: ruby
6
6
  authors:
7
7
  - "Max \nGroenlund"
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-02-24 00:00:00.000000000 Z
11
+ date: 2024-03-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord