lapsoss 0.1.0 → 0.2.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,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "digest"
3
+ require 'digest'
4
4
 
5
5
  module Lapsoss
6
6
  module Sampling
7
7
  class Base
8
- def sample?(event, hint = {})
8
+ def sample?(_event, _hint = {})
9
9
  true
10
10
  end
11
11
  end
@@ -15,7 +15,7 @@ module Lapsoss
15
15
  @rate = rate
16
16
  end
17
17
 
18
- def sample?(event, hint = {})
18
+ def sample?(_event, _hint = {})
19
19
  rand < @rate
20
20
  end
21
21
  end
@@ -24,13 +24,13 @@ module Lapsoss
24
24
  def initialize(max_events_per_second: 10)
25
25
  @max_events_per_second = max_events_per_second
26
26
  @tokens = max_events_per_second
27
- @last_refill = Time.now
27
+ @last_refill = Time.zone.now
28
28
  @mutex = Mutex.new
29
29
  end
30
30
 
31
- def sample?(event, hint = {})
31
+ def sample?(_event, _hint = {})
32
32
  @mutex.synchronize do
33
- now = Time.now
33
+ now = Time.zone.now
34
34
  time_passed = now - @last_refill
35
35
 
36
36
  # Refill tokens based on time passed
@@ -53,7 +53,7 @@ module Lapsoss
53
53
  @default_rate = rates.fetch(:default, 1.0)
54
54
  end
55
55
 
56
- def sample?(event, hint = {})
56
+ def sample?(event, _hint = {})
57
57
  return @default_rate > rand unless event.exception
58
58
 
59
59
  exception_class = event.exception.class
@@ -92,7 +92,7 @@ module Lapsoss
92
92
  @default_rate = rates.fetch(:default, 1.0)
93
93
  end
94
94
 
95
- def sample?(event, hint = {})
95
+ def sample?(event, _hint = {})
96
96
  user = event.context[:user]
97
97
  return @default_rate > rand unless user
98
98
 
@@ -104,18 +104,18 @@ module Lapsoss
104
104
 
105
105
  def find_rate_for_user(user)
106
106
  # Check specific user ID
107
- user_id = user[:id] || user["id"]
107
+ user_id = user[:id] || user['id']
108
108
  return @rates[user_id] if user_id && @rates.key?(user_id)
109
109
 
110
110
  # Check user segments
111
111
  @rates.each do |segment, rate|
112
112
  case segment
113
- when :internal, "internal"
114
- return rate if user[:internal] || user["internal"]
115
- when :premium, "premium"
116
- return rate if user[:premium] || user["premium"]
117
- when :beta, "beta"
118
- return rate if user[:beta] || user["beta"]
113
+ when :internal, 'internal'
114
+ return rate if user[:internal] || user['internal']
115
+ when :premium, 'premium'
116
+ return rate if user[:premium] || user['premium']
117
+ when :beta, 'beta'
118
+ return rate if user[:beta] || user['beta']
119
119
  end
120
120
  end
121
121
 
@@ -140,7 +140,7 @@ module Lapsoss
140
140
 
141
141
  private
142
142
 
143
- def default_key_extractor(event, hint)
143
+ def default_key_extractor(event, _hint)
144
144
  # Use fingerprint for consistent sampling
145
145
  event.fingerprint
146
146
  end
@@ -152,8 +152,8 @@ module Lapsoss
152
152
  @default_rate = schedule.fetch(:default, 1.0)
153
153
  end
154
154
 
155
- def sample?(event, hint = {})
156
- now = Time.now
155
+ def sample?(_event, _hint = {})
156
+ now = Time.zone.now
157
157
  rate = find_rate_for_time(now)
158
158
  rate > rand
159
159
  end
@@ -165,11 +165,11 @@ module Lapsoss
165
165
  day_of_week = time.wday # 0 = Sunday
166
166
 
167
167
  # Check specific hour
168
- hour_key = "hour_#{hour}".to_sym
168
+ hour_key = :"hour_#{hour}"
169
169
  return @schedule[hour_key] if @schedule.key?(hour_key)
170
170
 
171
171
  # Check day of week
172
- day_names = [:sunday, :monday, :tuesday, :wednesday, :thursday, :friday, :saturday]
172
+ day_names = %i[sunday monday tuesday wednesday thursday friday saturday]
173
173
  day_key = day_names[day_of_week]
174
174
  return @schedule[day_key] if @schedule.key?(day_key)
175
175
 
@@ -179,9 +179,7 @@ module Lapsoss
179
179
  end
180
180
 
181
181
  # Check weekends
182
- if @schedule.key?(:weekends) && [0, 6].include?(day_of_week)
183
- return @schedule[:weekends]
184
- end
182
+ return @schedule[:weekends] if @schedule.key?(:weekends) && [0, 6].include?(day_of_week)
185
183
 
186
184
  @default_rate
187
185
  end
@@ -214,16 +212,16 @@ module Lapsoss
214
212
  @adjustment_period = adjustment_period
215
213
  @current_rate = target_rate
216
214
  @events_count = 0
217
- @last_adjustment = Time.now
215
+ @last_adjustment = Time.zone.now
218
216
  @mutex = Mutex.new
219
217
  end
220
218
 
221
- def sample?(event, hint = {})
219
+ def sample?(_event, _hint = {})
222
220
  @mutex.synchronize do
223
221
  @events_count += 1
224
222
 
225
223
  # Adjust rate periodically
226
- now = Time.now
224
+ now = Time.zone.now
227
225
  if now - @last_adjustment > @adjustment_period
228
226
  adjust_rate
229
227
  @last_adjustment = now
@@ -234,9 +232,7 @@ module Lapsoss
234
232
  @current_rate > rand
235
233
  end
236
234
 
237
- def current_rate
238
- @current_rate
239
- end
235
+ attr_reader :current_rate
240
236
 
241
237
  private
242
238
 
@@ -258,7 +254,7 @@ module Lapsoss
258
254
  @low_rate = low_rate
259
255
  end
260
256
 
261
- def sample?(event, hint = {})
257
+ def sample?(_event, _hint = {})
262
258
  healthy = @health_check.call
263
259
  rate = healthy ? @high_rate : @low_rate
264
260
  rate > rand
@@ -275,29 +271,29 @@ module Lapsoss
275
271
 
276
272
  # Different rates for different exception types
277
273
  ExceptionTypeSampler.new(rates: {
278
- # Critical errors - always sample
279
- SecurityError => 1.0,
280
- SystemStackError => 1.0,
281
- NoMemoryError => 1.0,
274
+ # Critical errors - always sample
275
+ SecurityError => 1.0,
276
+ SystemStackError => 1.0,
277
+ NoMemoryError => 1.0,
282
278
 
283
- # Common errors - sample less
284
- ArgumentError => 0.1,
285
- TypeError => 0.1,
279
+ # Common errors - sample less
280
+ ArgumentError => 0.1,
281
+ TypeError => 0.1,
286
282
 
287
- # Network errors - medium sampling
288
- /timeout/i => 0.3,
289
- /connection/i => 0.3,
283
+ # Network errors - medium sampling
284
+ /timeout/i => 0.3,
285
+ /connection/i => 0.3,
290
286
 
291
- # Default for unknown errors
292
- default: 0.5
293
- }),
287
+ # Default for unknown errors
288
+ default: 0.5
289
+ }),
294
290
 
295
291
  # Lower sampling during business hours
296
292
  TimeBasedSampler.new(schedule: {
297
- business_hours: 0.3,
298
- weekends: 0.8,
299
- default: 0.5
300
- })
293
+ business_hours: 0.3,
294
+ weekends: 0.8,
295
+ default: 0.5
296
+ })
301
297
  ],
302
298
  strategy: :all
303
299
  )
@@ -312,16 +308,16 @@ module Lapsoss
312
308
  samplers: [
313
309
  # Higher sampling for internal users
314
310
  UserBasedSampler.new(rates: {
315
- internal: 1.0,
316
- premium: 0.8,
317
- beta: 0.9,
318
- default: 0.1
319
- }),
311
+ internal: 1.0,
312
+ premium: 0.8,
313
+ beta: 0.9,
314
+ default: 0.1
315
+ }),
320
316
 
321
317
  # Consistent sampling based on user ID
322
318
  ConsistentHashSampler.new(
323
319
  rate: 0.1,
324
- key_extractor: ->(event, hint) { event.context.dig(:user, :id) }
320
+ key_extractor: ->(event, _hint) { event.context.dig(:user, :id) }
325
321
  )
326
322
  ],
327
323
  strategy: :any
data/lib/lapsoss/scope.rb CHANGED
@@ -29,13 +29,11 @@ module Lapsoss
29
29
  @extra.merge!(context[:extra] || {})
30
30
 
31
31
  # Handle breadcrumbs if provided
32
- if context[:breadcrumbs]
33
- @breadcrumbs.concat(context[:breadcrumbs])
34
- # Keep breadcrumbs to a reasonable limit
35
- while @breadcrumbs.length > 20
36
- @breadcrumbs.shift
37
- end
38
- end
32
+ return unless context[:breadcrumbs]
33
+
34
+ @breadcrumbs.concat(context[:breadcrumbs])
35
+ # Keep breadcrumbs to a reasonable limit
36
+ @breadcrumbs.shift while @breadcrumbs.length > 20
39
37
  end
40
38
 
41
39
  def clear
@@ -97,9 +95,7 @@ module Lapsoss
97
95
  def merge_breadcrumbs
98
96
  result = @base_scope.breadcrumbs.dup
99
97
  @scope_stack.each do |context|
100
- if context[:breadcrumbs]
101
- result.concat(context[:breadcrumbs])
102
- end
98
+ result.concat(context[:breadcrumbs]) if context[:breadcrumbs]
103
99
  end
104
100
  # Add our own breadcrumbs
105
101
  result.concat(@own_breadcrumbs)
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/parameter_filter"
3
+ require 'active_support/parameter_filter'
4
4
 
5
5
  module Lapsoss
6
6
  class Scrubber
@@ -25,22 +25,20 @@ module Lapsoss
25
25
  @rails_parameter_filter = rails_parameter_filter
26
26
 
27
27
  # Only use custom scrubbing if Rails parameter filter is not available
28
- unless @rails_parameter_filter
29
- @scrub_fields = Array(config[:scrub_fields] || DEFAULT_SCRUB_FIELDS)
30
- @scrub_all = config[:scrub_all] || false
31
- @whitelist_fields = Array(config[:whitelist_fields] || [])
32
- @randomize_scrub_length = config[:randomize_scrub_length] || false
33
- @scrub_value = config[:scrub_value] || "**SCRUBBED**"
34
- end
28
+ return if @rails_parameter_filter
29
+
30
+ @scrub_fields = Array(config[:scrub_fields] || DEFAULT_SCRUB_FIELDS)
31
+ @scrub_all = config[:scrub_all] || false
32
+ @whitelist_fields = Array(config[:whitelist_fields] || [])
33
+ @randomize_scrub_length = config[:randomize_scrub_length] || false
34
+ @scrub_value = config[:scrub_value] || '**SCRUBBED**'
35
35
  end
36
36
 
37
37
  def scrub(data)
38
38
  return data if data.nil?
39
39
 
40
40
  # If Rails parameter filter is available, use it exclusively
41
- if @rails_parameter_filter
42
- return @rails_parameter_filter.filter(data)
43
- end
41
+ return @rails_parameter_filter.filter(data) if @rails_parameter_filter
44
42
 
45
43
  # Fallback to custom scrubbing logic only if Rails filter is not available
46
44
  @scrubbed_objects = {}.compare_by_identity
@@ -68,15 +66,15 @@ module Lapsoss
68
66
  hash.each_with_object({}) do |(key, value), result|
69
67
  key_string = key.to_s.downcase
70
68
 
71
- if should_scrub_field?(key_string)
72
- result[key] = generate_scrub_value(value)
73
- elsif value.is_a?(Hash)
74
- result[key] = scrub_recursive(value)
75
- elsif value.is_a?(Array)
76
- result[key] = scrub_array(value)
77
- else
78
- result[key] = scrub_value(value)
79
- end
69
+ result[key] = if should_scrub_field?(key_string)
70
+ generate_scrub_value(value)
71
+ elsif value.is_a?(Hash)
72
+ scrub_recursive(value)
73
+ elsif value.is_a?(Array)
74
+ scrub_array(value)
75
+ else
76
+ scrub_value(value)
77
+ end
80
78
  end
81
79
  end
82
80
 
@@ -118,7 +116,7 @@ module Lapsoss
118
116
  PROTECTED_EVENT_FIELDS.include?(field_name.to_s)
119
117
  end
120
118
 
121
- def whitelisted_value?(value)
119
+ def whitelisted_value?(_value)
122
120
  # Basic implementation - could be extended
123
121
  false
124
122
  end
@@ -136,7 +134,7 @@ module Lapsoss
136
134
  original_filename: safe_call(attachment, :original_filename),
137
135
  size: safe_call(attachment, :size) || safe_call(attachment, :tempfile, :size)
138
136
  }
139
- rescue => e
137
+ rescue StandardError => e
140
138
  { __attachment__: true, error: "Failed to extract attachment info: #{e.message}" }
141
139
  end
142
140
 
@@ -146,9 +144,9 @@ module Lapsoss
146
144
  end
147
145
  end
148
146
 
149
- def generate_scrub_value(original_value)
147
+ def generate_scrub_value(_original_value)
150
148
  if @randomize_scrub_length
151
- "*" * rand(6..12)
149
+ '*' * rand(6..12)
152
150
  else
153
151
  @scrub_value
154
152
  end
@@ -30,16 +30,14 @@ module Lapsoss
30
30
  key_sym = key.to_sym
31
31
 
32
32
  # Skip if not in allowed fields list (when specified)
33
- if @allowed_fields && !@allowed_fields.include?(key_sym)
34
- next
35
- end
33
+ next if @allowed_fields&.exclude?(key_sym)
36
34
 
37
35
  # Apply privacy filtering
38
- if @privacy_mode && sensitive_field?(key_sym)
39
- processed[key] = apply_privacy_filter(key_sym, value)
40
- else
41
- processed[key] = transform_field(key_sym, value)
42
- end
36
+ processed[key] = if @privacy_mode && sensitive_field?(key_sym)
37
+ apply_privacy_filter(key_sym, value)
38
+ else
39
+ transform_field(key_sym, value)
40
+ end
43
41
  end
44
42
 
45
43
  processed
@@ -70,18 +68,18 @@ module Lapsoss
70
68
  segments = {}
71
69
 
72
70
  # Check for common user segments
73
- segments[:internal] = !!(user_data[:internal] || user_data["internal"])
74
- segments[:premium] = !!(user_data[:premium] || user_data["premium"])
75
- segments[:beta] = !!(user_data[:beta] || user_data["beta"])
76
- segments[:admin] = !!(user_data[:admin] || user_data["admin"])
71
+ segments[:internal] = !(user_data[:internal] || user_data['internal']).nil?
72
+ segments[:premium] = !(user_data[:premium] || user_data['premium']).nil?
73
+ segments[:beta] = !(user_data[:beta] || user_data['beta']).nil?
74
+ segments[:admin] = !(user_data[:admin] || user_data['admin']).nil?
77
75
 
78
76
  # Check role-based segments
79
- if role = (user_data[:role] || user_data["role"])
77
+ if role = user_data[:role] || user_data['role']
80
78
  segments[:role] = role.to_s.downcase
81
79
  end
82
80
 
83
81
  # Check plan-based segments
84
- if plan = (user_data[:plan] || user_data["plan"])
82
+ if plan = user_data[:plan] || user_data['plan']
85
83
  segments[:plan] = plan.to_s.downcase
86
84
  end
87
85
 
@@ -96,15 +94,15 @@ module Lapsoss
96
94
  user_data.each do |key, value|
97
95
  key_sym = key.to_sym
98
96
 
99
- if sensitive_field?(key_sym)
100
- sanitized[key] = "[REDACTED]"
101
- elsif value.is_a?(Hash)
102
- sanitized[key] = sanitize_for_logging(value)
103
- elsif value.is_a?(Array)
104
- sanitized[key] = value.map { |v| v.is_a?(Hash) ? sanitize_for_logging(v) : v }
105
- else
106
- sanitized[key] = value
107
- end
97
+ sanitized[key] = if sensitive_field?(key_sym)
98
+ '[REDACTED]'
99
+ elsif value.is_a?(Hash)
100
+ sanitize_for_logging(value)
101
+ elsif value.is_a?(Array)
102
+ value.map { |v| v.is_a?(Hash) ? sanitize_for_logging(v) : v }
103
+ else
104
+ value
105
+ end
108
106
  end
109
107
 
110
108
  sanitized
@@ -125,7 +123,7 @@ module Lapsoss
125
123
  when :ip_address, :last_login_ip
126
124
  mask_ip(value)
127
125
  else
128
- "[FILTERED]"
126
+ '[FILTERED]'
129
127
  end
130
128
  end
131
129
 
@@ -138,38 +136,39 @@ module Lapsoss
138
136
  end
139
137
 
140
138
  def mask_email(email)
141
- return "[INVALID_EMAIL]" unless email.is_a?(String) && email.include?("@")
139
+ return '[INVALID_EMAIL]' unless email.is_a?(String) && email.include?('@')
142
140
 
143
- local, domain = email.split("@", 2)
144
- masked_local = local.length > 2 ? "#{local[0]}***#{local[-1]}" : "***"
141
+ local, domain = email.split('@', 2)
142
+ masked_local = local.length > 2 ? "#{local[0]}***#{local[-1]}" : '***'
145
143
  "#{masked_local}@#{domain}"
146
144
  end
147
145
 
148
146
  def mask_phone(phone)
149
- return "[INVALID_PHONE]" unless phone.is_a?(String)
147
+ return '[INVALID_PHONE]' unless phone.is_a?(String)
150
148
 
151
149
  # Remove all non-digits
152
- digits = phone.gsub(/\D/, "")
153
- return "[INVALID_PHONE]" if digits.length < 4
150
+ digits = phone.gsub(/\D/, '')
151
+ return '[INVALID_PHONE]' if digits.length < 4
154
152
 
155
153
  # Show last 4 digits
156
- "*" * (digits.length - 4) + digits[-4..-1]
154
+ ('*' * (digits.length - 4)) + digits[-4..]
157
155
  end
158
156
 
159
157
  def mask_ip(ip)
160
- return "[INVALID_IP]" unless ip.is_a?(String)
158
+ return '[INVALID_IP]' unless ip.is_a?(String)
161
159
 
162
- if ip.include?(":")
160
+ if ip.include?(':')
163
161
  # IPv6 - mask last 4 groups
164
- parts = ip.split(":")
165
- parts[-4..-1] = ["****"] * 4 if parts.length >= 4
166
- parts.join(":")
162
+ parts = ip.split(':')
163
+ parts[-4..-1] = ['****'] * 4 if parts.length >= 4
164
+ parts.join(':')
167
165
  else
168
166
  # IPv4 - mask last octet
169
- parts = ip.split(".")
170
- return "[INVALID_IP]" if parts.length != 4
171
- parts[-1] = "***"
172
- parts.join(".")
167
+ parts = ip.split('.')
168
+ return '[INVALID_IP]' if parts.length != 4
169
+
170
+ parts[-1] = '***'
171
+ parts.join('.')
173
172
  end
174
173
  end
175
174
  end
@@ -185,14 +184,12 @@ module Lapsoss
185
184
 
186
185
  # Try each provider in order
187
186
  @providers.each do |name, provider|
188
- begin
189
- if provider_context = provider.call(event, hint)
190
- context.merge!(provider_context)
191
- end
192
- rescue StandardError => e
193
- # Log provider error but don't fail
194
- warn "User context provider #{name} failed: #{e.message}"
187
+ if provider_context = provider.call(event, hint)
188
+ context.merge!(provider_context)
195
189
  end
190
+ rescue StandardError => e
191
+ # Log provider error but don't fail
192
+ warn "User context provider #{name} failed: #{e.message}"
196
193
  end
197
194
 
198
195
  context
@@ -200,12 +197,12 @@ module Lapsoss
200
197
 
201
198
  # Built-in providers for common authentication systems
202
199
  def self.devise_provider
203
- lambda do |event, hint|
200
+ lambda do |_event, hint|
204
201
  return {} unless defined?(Devise) && defined?(Warden)
205
202
 
206
203
  # Try to get user from Warden (used by Devise)
207
204
  if request = hint[:request]
208
- user = request.env["warden"]&.user
205
+ user = request.env['warden']&.user
209
206
  return {} unless user
210
207
 
211
208
  {
@@ -222,19 +219,17 @@ module Lapsoss
222
219
  end
223
220
 
224
221
  def self.omniauth_provider
225
- lambda do |event, hint|
222
+ lambda do |_event, hint|
226
223
  return {} unless defined?(OmniAuth)
227
224
 
228
- if request = hint[:request]
229
- if auth_info = request.env["omniauth.auth"]
230
- {
231
- provider: auth_info["provider"],
232
- uid: auth_info["uid"],
233
- name: auth_info.dig("info", "name"),
234
- email: auth_info.dig("info", "email"),
235
- username: auth_info.dig("info", "nickname")
236
- }.compact
237
- end
225
+ if (request = hint[:request]) && (auth_info = request.env['omniauth.auth'])
226
+ {
227
+ provider: auth_info['provider'],
228
+ uid: auth_info['uid'],
229
+ name: auth_info.dig('info', 'name'),
230
+ email: auth_info.dig('info', 'email'),
231
+ username: auth_info.dig('info', 'nickname')
232
+ }.compact
238
233
  end
239
234
 
240
235
  {}
@@ -242,22 +237,26 @@ module Lapsoss
242
237
  end
243
238
 
244
239
  def self.session_provider
245
- lambda do |event, hint|
240
+ lambda do |_event, hint|
246
241
  return {} unless hint[:request]
247
242
 
248
243
  request = hint[:request]
249
- session = request.session rescue {}
244
+ session = begin
245
+ request.session
246
+ rescue StandardError
247
+ {}
248
+ end
250
249
 
251
250
  {
252
- session_id: session[:session_id] || session["session_id"],
253
- user_id: session[:user_id] || session["user_id"],
254
- csrf_token: session[:_csrf_token] || session["_csrf_token"]
251
+ session_id: session[:session_id] || session['session_id'],
252
+ user_id: session[:user_id] || session['user_id'],
253
+ csrf_token: session[:_csrf_token] || session['_csrf_token']
255
254
  }.compact
256
255
  end
257
256
  end
258
257
 
259
258
  def self.thread_local_provider
260
- lambda do |event, hint|
259
+ lambda do |_event, _hint|
261
260
  # Get user from thread-local storage
262
261
  Thread.current[:current_user] || {}
263
262
  end
@@ -270,20 +269,18 @@ module Lapsoss
270
269
  return unless defined?(Devise)
271
270
 
272
271
  # Add middleware to capture user context
273
- if defined?(Rails)
274
- Rails.application.config.middleware.use(UserContextMiddleware)
275
- end
272
+ Rails.application.config.middleware.use(UserContextMiddleware) if defined?(Rails)
276
273
  end
277
274
 
278
275
  def self.setup_clearance_integration
279
276
  return unless defined?(Clearance)
280
277
 
281
278
  # Clearance integration
282
- if defined?(Rails)
283
- Rails.application.config.middleware.use(UserContextMiddleware) do |middleware|
284
- middleware.user_provider = lambda do |request|
285
- request.env[:clearance].current_user if request.env[:clearance]
286
- end
279
+ return unless defined?(Rails)
280
+
281
+ Rails.application.config.middleware.use(UserContextMiddleware) do |middleware|
282
+ middleware.user_provider = lambda do |request|
283
+ request.env[:clearance].current_user if request.env[:clearance]
287
284
  end
288
285
  end
289
286
  end
@@ -292,11 +289,11 @@ module Lapsoss
292
289
  return unless defined?(Authlogic)
293
290
 
294
291
  # Authlogic integration
295
- if defined?(Rails)
296
- Rails.application.config.middleware.use(UserContextMiddleware) do |middleware|
297
- middleware.user_provider = lambda do |request|
298
- UserSession.find&.user if defined?(UserSession)
299
- end
292
+ return unless defined?(Rails)
293
+
294
+ Rails.application.config.middleware.use(UserContextMiddleware) do |middleware|
295
+ middleware.user_provider = lambda do |_request|
296
+ UserSession.find&.user if defined?(UserSession)
300
297
  end
301
298
  end
302
299
  end
@@ -337,14 +334,10 @@ module Lapsoss
337
334
  }
338
335
 
339
336
  # Add role information if available
340
- if user.respond_to?(:role)
341
- context[:role] = user.role
342
- end
337
+ context[:role] = user.role if user.respond_to?(:role)
343
338
 
344
339
  # Add plan information if available
345
- if user.respond_to?(:plan)
346
- context[:plan] = user.plan
347
- end
340
+ context[:plan] = user.plan if user.respond_to?(:plan)
348
341
 
349
342
  context.compact
350
343
  else