Class: RailsPulse::Operation
- Inherits:
-
ApplicationRecord
- Object
- ActiveRecord::Base
- ApplicationRecord
- RailsPulse::Operation
- Defined in:
- app/models/rails_pulse/operation.rb
Constant Summary collapse
- OPERATION_TYPES =
%w[ sql controller template partial layout collection cache_read cache_write http job mailer storage ].freeze
Class Method Summary collapse
-
.persist_bulk(ops, context) ⇒ Object
Bulk-insert a batch of operation hashes captured during a request or job run.
- .ransackable_associations(auth_object = nil) ⇒ Object
- .ransackable_attributes(auth_object = nil) ⇒ Object
Instance Method Summary collapse
Class Method Details
.persist_bulk(ops, context) ⇒ Object
Bulk-insert a batch of operation hashes captured during a request or job run. ‘context` is merged into every row to bind it to its parent — either `{ request_id: id }` or `{ job_run_id: id, request_id: nil }`.
SQL operations are handled in two phases to minimise DB round-trips:
1. Normalise each unique SQL source once and collect hashed→normalised pairs.
2. Resolve (or create) the corresponding Query records in bulk rather than
one create_or_find_by per operation, which matters for N+1-heavy requests.
insert_all! is used instead of individual creates to bypass ActiveRecord callbacks and validations — normalisation and truncation are done inline above. All rows must have the same key set, so keys are union-normalised before insert.
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
# File 'app/models/rails_pulse/operation.rb', line 52 def self.persist_bulk(ops, context) return if ops.empty? # Phase 1: normalise each unique SQL source once. # norm_cache avoids re-normalising repeated SQL (e.g. N+1 queries). # norm_map feeds the bulk Query resolver: { hashed_sql => normalized_sql }. norm_cache = {} norm_map = {} ops.each do |op| next unless op[:operation_type] == "sql" sql_source = op[:actual_sql].presence || op[:label].presence next unless sql_source unless norm_cache.key?(sql_source) normalized = RailsPulse::SqlQueryNormalizer.normalize(sql_source) hashed = Digest::MD5.hexdigest(normalized) norm_cache[sql_source] = [ hashed, normalized ] norm_map[hashed] ||= normalized end end # Phase 2: resolve Query IDs in bulk (1 SELECT in steady state). query_id_map = RailsPulse::Query.bulk_find_or_create(norm_map) now = Time.current rows = ops.map do |op| op = op.merge(context).merge(created_at: now, updated_at: now) if op[:operation_type] == "sql" sql_source = op[:actual_sql].presence || op[:label].presence if sql_source && ( = norm_cache[sql_source]) hashed, normalized = op = op.merge(label: normalized.truncate(255), query_id: query_id_map[hashed]) end end op[:label] = op[:label]&.truncate(255) op end # insert_all! requires every row to have identical keys. all_keys = rows.flat_map(&:keys).uniq rows = rows.map { |r| all_keys.each_with_object({}) { |k, h| h[k] = r[k] } } insert_all!(rows) end |
.ransackable_associations(auth_object = nil) ⇒ Object
98 99 100 |
# File 'app/models/rails_pulse/operation.rb', line 98 def self.ransackable_associations(auth_object = nil) %w[] end |
.ransackable_attributes(auth_object = nil) ⇒ Object
94 95 96 |
# File 'app/models/rails_pulse/operation.rb', line 94 def self.ransackable_attributes(auth_object = nil) %w[id occurred_at label duration start_time average_query_time_ms query_count operation_type query_id] end |
Instance Method Details
#to_breadcrumb ⇒ Object
127 128 129 |
# File 'app/models/rails_pulse/operation.rb', line 127 def label.to_s.truncate(60) end |
#to_s ⇒ Object
131 132 133 |
# File 'app/models/rails_pulse/operation.rb', line 131 def to_s id end |