Class: RSpecTracer::Rails::Notifications Private

Inherits:
Object
  • Object
show all
Defined in:
lib/rspec_tracer/rails/notifications.rb

Overview

This class is part of a private API. You should avoid using this class if possible, as it may be removed or be changed in the future.

ActiveSupport::Notifications observer for Rails-side inputs that Coverage and IOHooks can’t see directly. Subscribes to:

- render_template.action_view   -> :template
- render_partial.action_view    -> :template
- render_collection.action_view -> :template (Rails 7.1+)
- sql.active_record             -> :notification
    (on the first query per example, emits db/schema.rb +
     db/structure.sql as inputs)

Lifecycle mirrors IOHooks: Engine.setup installs, example_started opens a thread-local bucket, subscribers append, example_finished harvests and clears the bucket, Engine.finalize unsubscribes.

Payload access is defensively permissive - Rails minors differ on symbol vs string keys; missing payload or nil identifier is swallowed. Errors inside subscribers never propagate (CLAUDE.md “graceful degradation”).

Precedence: an observed template path already covered by a user declared glob (e.g. the Preset :views glob) skips notification emission via the injected ‘filter:` callable. Matches IOHooks’ declared-glob-precedence rule from ARCHITECTURE.md.

The sql.active_record subscriber is only attached when the caller passes a non-empty ‘ar_schema_paths:` list. Engine resolves the list from the `track_ar_schema_notifications` opt-in DSL; absent that, the AR subscriber is never installed and the sql.active_record event stream is ignored.

Constant Summary collapse

BUCKET_KEY =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Internal constant.

:rspec_tracer_rails_bucket
AR_FLAG_KEY =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Internal constant.

:rspec_tracer_rails_ar_emitted

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.rootObject (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Internal attribute.



50
51
52
# File 'lib/rspec_tracer/rails/notifications.rb', line 50

def root
  @root
end

Class Method Details

.clear_bucketObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

rubocop:enable Naming/AccessorMethodName



94
95
96
97
# File 'lib/rspec_tracer/rails/notifications.rb', line 94

def clear_bucket
  Thread.current[BUCKET_KEY] = nil
  Thread.current[AR_FLAG_KEY] = nil
end

.current_bucketObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Internal method on the tracer pipeline.



101
102
103
# File 'lib/rspec_tracer/rails/notifications.rb', line 101

def current_bucket
  Thread.current[BUCKET_KEY]
end

.handle_render_event(payload) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Pure-logic entry point for the render_*.action_view subscribers. Extracted so mutation smoke can exercise payload parsing without driving AS::Notifications.



108
109
110
111
112
113
114
115
# File 'lib/rspec_tracer/rails/notifications.rb', line 108

def handle_render_event(payload)
  return nil unless payload.is_a?(Hash)

  identifier = payload[:identifier] || payload['identifier']
  return nil if identifier.nil?

  record_template(identifier)
end

.handle_sql_event(_payload) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Pure-logic entry point for the sql.active_record subscriber. Payload contents are ignored - the event itself is the “this example touched AR” signal.



120
121
122
# File 'lib/rspec_tracer/rails/notifications.rb', line 120

def handle_sql_event(_payload)
  record_ar_schema
end

.install(root:, filter: ->(_path) { true }, ar_schema_paths: []) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Internal method on the tracer pipeline.



54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/rspec_tracer/rails/notifications.rb', line 54

def install(root:, filter: ->(_path) { true }, ar_schema_paths: [])
  @root = File.expand_path(root)
  @root_prefix = "#{@root}/"
  @filter = filter
  @ar_schema_inputs = build_schema_inputs(ar_schema_paths)
  @handles = []

  subscribe_render_template
  subscribe_render_partial
  subscribe_render_collection if render_collection_supported?
  subscribe_sql_active_record if ar_enabled?

  self
end

.installed?Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Internal method on the tracer pipeline.

Returns:

  • (Boolean)


83
84
85
# File 'lib/rspec_tracer/rails/notifications.rb', line 83

def installed?
  !@root_prefix.nil?
end

.record_ar_schemaObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Emit the pre-digested schema Inputs into the bucket on the first sql.active_record event per example. Subsequent events short-circuit via AR_FLAG_KEY - O(1) after the first query.



158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
# File 'lib/rspec_tracer/rails/notifications.rb', line 158

def record_ar_schema
  return nil if @ar_schema_inputs.nil? || @ar_schema_inputs.empty?

  bucket = Thread.current[BUCKET_KEY]
  return nil if bucket.nil?
  return nil if Thread.current[AR_FLAG_KEY]

  Thread.current[AR_FLAG_KEY] = true
  @ar_schema_inputs.each do |input|
    bucket[input.identity] ||= input
  end
  nil
rescue StandardError
  nil
end

.record_template(path) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Record a :template Input for the observed render identifier. Guarded by the same fast-reject ladder as IOHooks: install state, bucket presence, path-under-root, filter callable, identity dedup. The early-return ladder looks complex to rubocop’s metric but is the simplest shape for a hot path. rubocop:disable Metrics/PerceivedComplexity



130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/rspec_tracer/rails/notifications.rb', line 130

def record_template(path)
  return nil if @root_prefix.nil?

  bucket = Thread.current[BUCKET_KEY]
  return nil if bucket.nil?
  return nil unless path.is_a?(String) || path.respond_to?(:to_s)

  path_str = path.to_s
  return nil unless path_str.start_with?(@root_prefix)
  return nil unless @filter.call(path_str)

  identity = "template:#{path_str[@root_prefix.length..]}"
  return nil if bucket.key?(identity)

  digest = Tracker::FileDigest.compute(path_str)
  return nil if digest.nil?

  bucket[identity] = Tracker::Input.for_file(
    path: path_str, kind: :template, digest: digest, root: @root
  )
rescue StandardError
  nil
end

.set_bucket(bucket) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

rubocop:disable Naming/AccessorMethodName



88
89
90
91
# File 'lib/rspec_tracer/rails/notifications.rb', line 88

def set_bucket(bucket)
  Thread.current[BUCKET_KEY] = bucket
  Thread.current[AR_FLAG_KEY] = false
end

.uninstallObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Internal method on the tracer pipeline.



71
72
73
74
75
76
77
78
79
# File 'lib/rspec_tracer/rails/notifications.rb', line 71

def uninstall
  (@handles || []).each { |handle| safely_unsubscribe(handle) }
  @handles = nil
  @root = nil
  @root_prefix = nil
  @filter = nil
  @ar_schema_inputs = nil
  self
end