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 93 94 |
# 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. # cache_hit defaults to false for non-cache operations so we don't send explicit nil # into a column that may carry a NOT NULL constraint from the add_diagnostic_fields migration. all_keys = rows.flat_map(&:keys).uniq rows = rows.map { |r| all_keys.each_with_object({}) { |k, h| h[k] = r.fetch(k) { k == :cache_hit ? false : nil } } } insert_all!(rows) end |
.ransackable_associations(auth_object = nil) ⇒ Object
100 101 102 |
# File 'app/models/rails_pulse/operation.rb', line 100 def self.ransackable_associations(auth_object = nil) %w[] end |
.ransackable_attributes(auth_object = nil) ⇒ Object
96 97 98 |
# File 'app/models/rails_pulse/operation.rb', line 96 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
129 130 131 |
# File 'app/models/rails_pulse/operation.rb', line 129 def label.to_s.truncate(60) end |
#to_s ⇒ Object
133 134 135 |
# File 'app/models/rails_pulse/operation.rb', line 133 def to_s id end |