1- class AuditLog < ApplicationRecord
2- # Associations
3- belongs_to :organization
4- belongs_to :user , optional : true
5-
6- # Validations
7- validates :action , presence : true
8- validates :entity_type , presence : true
9-
10- # Scopes
11- scope :by_action , -> ( action ) { where ( action : action ) }
12- scope :by_entity_type , -> ( type ) { where ( entity_type : type ) }
13- scope :by_user , -> ( user_id ) { where ( user_id : user_id ) }
14- scope :recent , -> ( days = 30 ) { where ( created_at : days . days . ago ..Time . current ) }
15- scope :for_entity , -> ( type , id ) { where ( entity_type : type , entity_id : id ) }
16-
17- # Instance methods
18- def action_display
19- action . humanize
20- end
21-
22- def user_display
23- user &.full_name || user &.email || 'System'
24- end
25-
26- def entity_display
27- "#{ entity_type } ##{ entity_id } "
28- end
29-
30- def changes_summary
31- return 'Created' if action == 'create'
32- return 'Deleted' if action == 'delete'
33-
34- return 'No changes recorded' if old_values . blank? && new_values . blank?
35-
36- changes = [ ]
37-
38- if old_values . present? && new_values . present?
39- new_values . each do |key , new_val |
40- old_val = old_values [ key ]
41- next if old_val == new_val
42-
43- changes << "#{ key . humanize } : #{ format_value ( old_val ) } → #{ format_value ( new_val ) } "
44- end
45- end
46-
47- changes . empty? ? 'No changes recorded' : changes . join ( ', ' )
48- end
49-
50- def ip_location
51- # This could be enhanced with a GeoIP service
52- return 'Unknown' if ip_address . blank?
53-
54- if ip_address . to_s . start_with? ( '127.0.0.1' , '::1' )
55- 'Local'
56- elsif ip_address . to_s . start_with? ( '192.168.' , '10.' , '172.' )
57- 'Private Network'
58- else
59- 'External'
60- end
61- end
62-
63- def browser_info
64- return 'Unknown' if user_agent . blank?
65-
66- # Simple browser detection
67- case user_agent
68- when /Chrome/i then 'Chrome'
69- when /Firefox/i then 'Firefox'
70- when /Safari/i then 'Safari'
71- when /Edge/i then 'Edge'
72- when /Opera/i then 'Opera'
73- else 'Unknown Browser'
74- end
75- end
76-
77- def time_ago
78- time_diff = Time . current - created_at
79-
80- case time_diff
81- when 0 ...60
82- "#{ time_diff . to_i } seconds ago"
83- when 60 ...3600
84- "#{ ( time_diff / 60 ) . to_i } minutes ago"
85- when 3600 ...86400
86- "#{ ( time_diff / 3600 ) . to_i } hours ago"
87- when 86400 ...2592000
88- "#{ ( time_diff / 86400 ) . to_i } days ago"
89- else
90- created_at . strftime ( '%B %d, %Y' )
91- end
92- end
93-
94- def risk_level
95- case action
96- when 'delete' then 'high'
97- when 'update' then 'medium'
98- when 'create' then 'low'
99- when 'login' , 'logout' then 'info'
100- else 'medium'
101- end
102- end
103-
104- def risk_color
105- case risk_level
106- when 'high' then 'red'
107- when 'medium' then 'orange'
108- when 'low' then 'green'
109- when 'info' then 'blue'
110- else 'gray'
111- end
112- end
113-
114- def self . log_action ( organization :, user : nil , action :, entity_type :, entity_id : nil , old_values : { } , new_values : { } , ip : nil , user_agent : nil )
115- create! (
116- organization : organization ,
117- user : user ,
118- action : action ,
119- entity_type : entity_type ,
120- entity_id : entity_id ,
121- old_values : old_values ,
122- new_values : new_values ,
123- ip_address : ip ,
124- user_agent : user_agent
125- )
126- end
127-
128- def self . security_events
129- where ( action : %w[ login logout failed_login password_reset ] )
130- end
131-
132- def self . data_changes
133- where ( action : %w[ create update delete ] )
134- end
135-
136- def self . high_risk_actions
137- where ( action : %w[ delete user_role_change organization_settings_change ] )
138- end
139-
140- private
141-
142- def format_value ( value )
143- case value
144- when nil then 'nil'
145- when true then 'true'
146- when false then 'false'
147- when String
148- value . length > 50 ? "#{ value [ 0 ..47 ] } ..." : value
149- else
150- value . to_s
151- end
152- end
153- end
1+ # frozen_string_literal: true
2+
3+ class AuditLog < ApplicationRecord
4+ # Associations
5+ belongs_to :organization
6+ belongs_to :user , optional : true
7+
8+ # Validations
9+ validates :action , presence : true
10+ validates :entity_type , presence : true
11+
12+ # Scopes
13+ scope :by_action , -> ( action ) { where ( action : action ) }
14+ scope :by_entity_type , -> ( type ) { where ( entity_type : type ) }
15+ scope :by_user , -> ( user_id ) { where ( user_id : user_id ) }
16+ scope :recent , -> ( days = 30 ) { where ( created_at : days . days . ago ..Time . current ) }
17+ scope :for_entity , -> ( type , id ) { where ( entity_type : type , entity_id : id ) }
18+
19+ # Instance methods
20+ def action_display
21+ action . humanize
22+ end
23+
24+ def user_display
25+ user &.full_name || user &.email || 'System'
26+ end
27+
28+ def entity_display
29+ "#{ entity_type } ##{ entity_id } "
30+ end
31+
32+ def changes_summary
33+ return 'Created' if action == 'create'
34+ return 'Deleted' if action == 'delete'
35+
36+ return 'No changes recorded' if old_values . blank? && new_values . blank?
37+
38+ changes = [ ]
39+
40+ if old_values . present? && new_values . present?
41+ new_values . each do |key , new_val |
42+ old_val = old_values [ key ]
43+ next if old_val == new_val
44+
45+ changes << "#{ key . humanize } : #{ format_value ( old_val ) } → #{ format_value ( new_val ) } "
46+ end
47+ end
48+
49+ changes . empty? ? 'No changes recorded' : changes . join ( ', ' )
50+ end
51+
52+ def ip_location
53+ # This could be enhanced with a GeoIP service
54+ return 'Unknown' if ip_address . blank?
55+
56+ if ip_address . to_s . start_with? ( '127.0.0.1' , '::1' )
57+ 'Local'
58+ elsif ip_address . to_s . start_with? ( '192.168.' , '10.' , '172.' )
59+ 'Private Network'
60+ else
61+ 'External'
62+ end
63+ end
64+
65+ def browser_info
66+ return 'Unknown' if user_agent . blank?
67+
68+ # Simple browser detection
69+ case user_agent
70+ when /Chrome/i then 'Chrome'
71+ when /Firefox/i then 'Firefox'
72+ when /Safari/i then 'Safari'
73+ when /Edge/i then 'Edge'
74+ when /Opera/i then 'Opera'
75+ else 'Unknown Browser'
76+ end
77+ end
78+
79+ def time_ago
80+ time_diff = Time . current - created_at
81+
82+ case time_diff
83+ when 0 ...60
84+ "#{ time_diff . to_i } seconds ago"
85+ when 60 ...3600
86+ "#{ ( time_diff / 60 ) . to_i } minutes ago"
87+ when 3600 ...86_400
88+ "#{ ( time_diff / 3600 ) . to_i } hours ago"
89+ when 86_400 ...2_592_000
90+ "#{ ( time_diff / 86_400 ) . to_i } days ago"
91+ else
92+ created_at . strftime ( '%B %d, %Y' )
93+ end
94+ end
95+
96+ def risk_level
97+ case action
98+ when 'delete' then 'high'
99+ when 'update' then 'medium'
100+ when 'create' then 'low'
101+ when 'login' , 'logout' then 'info'
102+ else 'medium'
103+ end
104+ end
105+
106+ def risk_color
107+ case risk_level
108+ when 'high' then 'red'
109+ when 'medium' then 'orange'
110+ when 'low' then 'green'
111+ when 'info' then 'blue'
112+ else 'gray'
113+ end
114+ end
115+
116+ def self . log_action ( organization :, action :, entity_type :, user : nil , entity_id : nil , old_values : { } , new_values : { } ,
117+ ip : nil , user_agent : nil )
118+ create! (
119+ organization : organization ,
120+ user : user ,
121+ action : action ,
122+ entity_type : entity_type ,
123+ entity_id : entity_id ,
124+ old_values : old_values ,
125+ new_values : new_values ,
126+ ip_address : ip ,
127+ user_agent : user_agent
128+ )
129+ end
130+
131+ def self . security_events
132+ where ( action : %w[ login logout failed_login password_reset ] )
133+ end
134+
135+ def self . data_changes
136+ where ( action : %w[ create update delete ] )
137+ end
138+
139+ def self . high_risk_actions
140+ where ( action : %w[ delete user_role_change organization_settings_change ] )
141+ end
142+
143+ private
144+
145+ def format_value ( value )
146+ case value
147+ when nil then 'nil'
148+ when true then 'true'
149+ when false then 'false'
150+ when String
151+ value . length > 50 ? "#{ value [ 0 ..47 ] } ..." : value
152+ else
153+ value . to_s
154+ end
155+ end
156+ end
0 commit comments