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
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
153153end
0 commit comments