rails_action_tracker 0.1.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.
- checksums.yaml +7 -0
- data/.rubocop.yml +61 -0
- data/Appraisals +16 -0
- data/CHANGELOG.md +41 -0
- data/CONTRIBUTING.md +172 -0
- data/DEVELOPMENT.md +191 -0
- data/LICENSE.txt +21 -0
- data/README.md +312 -0
- data/Rakefile +12 -0
- data/lib/generators/rails_action_tracker/install_generator.rb +23 -0
- data/lib/generators/rails_action_tracker/templates/initializer.rb +67 -0
- data/lib/rails_action_tracker/middleware.rb +36 -0
- data/lib/rails_action_tracker/railtie.rb +15 -0
- data/lib/rails_action_tracker/tracker.rb +318 -0
- data/lib/rails_action_tracker/version.rb +5 -0
- data/lib/rails_action_tracker.rb +11 -0
- data/script/test-all +65 -0
- data/sig/rails_action_tracker.rbs +4 -0
- metadata +105 -0
@@ -0,0 +1,318 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'set'
|
4
|
+
require 'logger'
|
5
|
+
require 'fileutils'
|
6
|
+
|
7
|
+
module RailsActionTracker
|
8
|
+
class Tracker
|
9
|
+
THREAD_KEY = :rails_action_tracker_logs
|
10
|
+
|
11
|
+
class << self
|
12
|
+
attr_accessor :config, :custom_logger
|
13
|
+
|
14
|
+
def configure(options = {})
|
15
|
+
@config = {
|
16
|
+
print_to_rails_log: true,
|
17
|
+
write_to_file: false,
|
18
|
+
log_file_path: nil,
|
19
|
+
services: [],
|
20
|
+
ignored_tables: %w[pg_attribute pg_index pg_class pg_namespace pg_type ar_internal_metadata
|
21
|
+
schema_migrations],
|
22
|
+
ignored_controllers: [],
|
23
|
+
ignored_actions: {}
|
24
|
+
}.merge(options)
|
25
|
+
|
26
|
+
setup_custom_logger if @config[:write_to_file] && @config[:log_file_path]
|
27
|
+
end
|
28
|
+
|
29
|
+
def start_tracking
|
30
|
+
Thread.current[THREAD_KEY] = {
|
31
|
+
read: Set.new,
|
32
|
+
write: Set.new,
|
33
|
+
captured_logs: [],
|
34
|
+
controller: nil,
|
35
|
+
action: nil
|
36
|
+
}
|
37
|
+
subscribe_to_sql_notifications
|
38
|
+
subscribe_to_logger
|
39
|
+
end
|
40
|
+
|
41
|
+
def stop_tracking
|
42
|
+
unsubscribe_from_sql_notifications
|
43
|
+
unsubscribe_from_logger
|
44
|
+
logs = Thread.current[THREAD_KEY] || { read: Set.new, write: Set.new, captured_logs: [], controller: nil,
|
45
|
+
action: nil }
|
46
|
+
Thread.current[THREAD_KEY] = nil
|
47
|
+
logs
|
48
|
+
end
|
49
|
+
|
50
|
+
def print_summary
|
51
|
+
logs = Thread.current[THREAD_KEY]
|
52
|
+
return unless logs
|
53
|
+
|
54
|
+
# Check if this controller/action should be ignored
|
55
|
+
return if should_ignore_controller_action?(logs[:controller], logs[:action])
|
56
|
+
|
57
|
+
services_accessed = detect_services(logs[:captured_logs])
|
58
|
+
read_models = logs[:read].to_a.uniq.sort
|
59
|
+
write_models = logs[:write].to_a.uniq.sort
|
60
|
+
|
61
|
+
controller_action = "#{logs[:controller]}##{logs[:action]}" if logs[:controller] && logs[:action]
|
62
|
+
|
63
|
+
# Generate outputs with and without colors
|
64
|
+
colored_output = format_summary(read_models, write_models, services_accessed, controller_action, true)
|
65
|
+
plain_output = format_summary(read_models, write_models, services_accessed, controller_action, false)
|
66
|
+
|
67
|
+
log_output(colored_output, plain_output)
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def should_ignore_controller_action?(controller, action)
|
73
|
+
return false unless config && controller && action
|
74
|
+
|
75
|
+
# Check if entire controller is ignored
|
76
|
+
ignored_controllers = config[:ignored_controllers] || []
|
77
|
+
return true if ignored_controllers.include?(controller)
|
78
|
+
|
79
|
+
# Check controller-specific action ignores
|
80
|
+
ignored_actions = config[:ignored_actions] || {}
|
81
|
+
|
82
|
+
# Handle flexible controller/action filtering
|
83
|
+
ignored_actions.each do |pattern_controller, actions|
|
84
|
+
# Match controller name (exact match or empty string for all controllers)
|
85
|
+
next unless pattern_controller.empty? || pattern_controller == controller
|
86
|
+
|
87
|
+
# If actions is empty array or nil, ignore entire controller
|
88
|
+
return true if actions.nil? || (actions.is_a?(Array) && actions.empty?)
|
89
|
+
|
90
|
+
# Check specific actions
|
91
|
+
return true if actions.is_a?(Array) && actions.include?(action)
|
92
|
+
end
|
93
|
+
|
94
|
+
false
|
95
|
+
end
|
96
|
+
|
97
|
+
def setup_custom_logger
|
98
|
+
return unless config[:log_file_path]
|
99
|
+
|
100
|
+
log_dir = File.dirname(config[:log_file_path])
|
101
|
+
FileUtils.mkdir_p(log_dir) unless Dir.exist?(log_dir)
|
102
|
+
|
103
|
+
@custom_logger = Logger.new(config[:log_file_path])
|
104
|
+
@custom_logger.formatter = proc do |severity, datetime, _progname, msg|
|
105
|
+
"[#{datetime.strftime('%Y-%m-%d %H:%M:%S')}] #{severity}: #{msg}\n"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def log_query(sql)
|
110
|
+
logs = Thread.current[THREAD_KEY]
|
111
|
+
return unless logs
|
112
|
+
|
113
|
+
return unless (match = sql.match(/(FROM|INTO|UPDATE|INSERT INTO)\s+["']?(\w+)["']?/i))
|
114
|
+
|
115
|
+
table = match[2]
|
116
|
+
|
117
|
+
# Skip ignored tables (case insensitive)
|
118
|
+
ignored_tables = config&.dig(:ignored_tables) || %w[pg_attribute pg_index pg_class pg_namespace
|
119
|
+
pg_type ar_internal_metadata schema_migrations]
|
120
|
+
return if ignored_tables.map(&:downcase).include?(table.downcase)
|
121
|
+
|
122
|
+
if sql =~ /\A\s*SELECT/i
|
123
|
+
logs[:read] << table
|
124
|
+
else
|
125
|
+
logs[:write] << table
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def log_message(message)
|
130
|
+
logs = Thread.current[THREAD_KEY]
|
131
|
+
return unless logs
|
132
|
+
|
133
|
+
logs[:captured_logs] << message
|
134
|
+
end
|
135
|
+
|
136
|
+
def detect_services(captured_logs)
|
137
|
+
services_accessed = []
|
138
|
+
default_service_patterns = [
|
139
|
+
{ name: 'Pusher', pattern: /pusher/i },
|
140
|
+
{ name: 'Honeybadger', pattern: /honeybadger/i },
|
141
|
+
{ name: 'Redis', pattern: /redis/i },
|
142
|
+
{ name: 'Sidekiq', pattern: /sidekiq/i },
|
143
|
+
{ name: 'ActionMailer', pattern: /mail|email/i },
|
144
|
+
{ name: 'HTTP', pattern: /http|api/i }
|
145
|
+
]
|
146
|
+
|
147
|
+
service_patterns = config&.dig(:services) || default_service_patterns
|
148
|
+
|
149
|
+
captured_logs.each do |line|
|
150
|
+
service_patterns.each do |service_config|
|
151
|
+
if service_config.is_a?(Hash)
|
152
|
+
services_accessed << service_config[:name] if line.match?(service_config[:pattern])
|
153
|
+
elsif service_config.is_a?(String)
|
154
|
+
services_accessed << service_config if line.downcase.include?(service_config.downcase)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
services_accessed.uniq
|
160
|
+
end
|
161
|
+
|
162
|
+
# rubocop:disable Style/OptionalBooleanParameter
|
163
|
+
def format_summary(read_models, write_models, services, controller_action = nil, colorize = true)
|
164
|
+
colors = setup_colors(colorize)
|
165
|
+
max_rows = [read_models.size, write_models.size, services.size].max
|
166
|
+
|
167
|
+
return format_empty_summary(controller_action, colors) if max_rows.zero?
|
168
|
+
|
169
|
+
padded_arrays = pad_arrays_to_max_length(read_models, write_models, services, max_rows)
|
170
|
+
column_widths = calculate_column_widths(padded_arrays[:read], padded_arrays[:write], padded_arrays[:services])
|
171
|
+
|
172
|
+
build_table(padded_arrays, column_widths, controller_action, colors, max_rows)
|
173
|
+
end
|
174
|
+
# rubocop:enable Style/OptionalBooleanParameter
|
175
|
+
|
176
|
+
def setup_colors(colorize)
|
177
|
+
return { green: '', red: '', blue: '', yellow: '', reset: '' } unless colorize
|
178
|
+
return default_colors unless rails_colorized?
|
179
|
+
|
180
|
+
{
|
181
|
+
green: fetch_rails_color('GREEN', "\e[32m"),
|
182
|
+
red: fetch_rails_color('RED', "\e[31m"),
|
183
|
+
blue: fetch_rails_color('BLUE', "\e[34m"),
|
184
|
+
yellow: fetch_rails_color('YELLOW', "\e[33m"),
|
185
|
+
reset: fetch_rails_color('CLEAR', "\e[0m")
|
186
|
+
}
|
187
|
+
end
|
188
|
+
|
189
|
+
def default_colors
|
190
|
+
{ green: '', red: '', blue: '', yellow: '', reset: '' }
|
191
|
+
end
|
192
|
+
|
193
|
+
def rails_colorized?
|
194
|
+
defined?(Rails) && Rails.logger.respond_to?(:colorize_logging) && Rails.logger.colorize_logging
|
195
|
+
end
|
196
|
+
|
197
|
+
def fetch_rails_color(color_name, fallback)
|
198
|
+
if defined?(ActiveSupport::LogSubscriber.const_get(color_name))
|
199
|
+
ActiveSupport::LogSubscriber.const_get(color_name)
|
200
|
+
else
|
201
|
+
fallback
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
def format_empty_summary(controller_action, colors)
|
206
|
+
header = controller_action ? "#{colors[:yellow]}#{controller_action}#{colors[:reset]}: " : ''
|
207
|
+
"#{header}No models or services accessed during this request.\n"
|
208
|
+
end
|
209
|
+
|
210
|
+
def pad_arrays_to_max_length(read_models, write_models, services, max_rows)
|
211
|
+
{
|
212
|
+
read: read_models + [''] * (max_rows - read_models.size),
|
213
|
+
write: write_models + [''] * (max_rows - write_models.size),
|
214
|
+
services: services + [''] * (max_rows - services.size)
|
215
|
+
}
|
216
|
+
end
|
217
|
+
|
218
|
+
def calculate_column_widths(read_models, write_models, services)
|
219
|
+
{
|
220
|
+
read: [read_models.map(&:length).max || 0, 'Models Read'.length].max,
|
221
|
+
write: [write_models.map(&:length).max || 0, 'Models Written'.length].max,
|
222
|
+
services: [services.map(&:length).max || 0, 'Services Accessed'.length].max
|
223
|
+
}
|
224
|
+
end
|
225
|
+
|
226
|
+
def build_table(arrays, widths, controller_action, colors, max_rows)
|
227
|
+
separator = build_separator(widths)
|
228
|
+
header = controller_action ? "#{colors[:yellow]}#{controller_action}#{colors[:reset]} - " : ''
|
229
|
+
|
230
|
+
table = "#{header}Models and Services accessed during request:\n"
|
231
|
+
table += "#{separator}\n"
|
232
|
+
table += build_header_row(widths, colors)
|
233
|
+
table += "#{separator}\n"
|
234
|
+
table += build_data_rows(arrays, widths, max_rows)
|
235
|
+
table + "#{separator}\n"
|
236
|
+
end
|
237
|
+
|
238
|
+
def build_separator(widths)
|
239
|
+
"+#{'-' * (widths[:read] + 2)}+#{'-' * (widths[:write] + 2)}+#{'-' * (widths[:services] + 2)}+"
|
240
|
+
end
|
241
|
+
|
242
|
+
def build_header_row(widths, colors)
|
243
|
+
read_header = "#{colors[:green]}#{'Models Read'.ljust(widths[:read])}#{colors[:reset]}"
|
244
|
+
write_header = "#{colors[:red]}#{'Models Written'.ljust(widths[:write])}#{colors[:reset]}"
|
245
|
+
services_header = "#{colors[:blue]}#{'Services Accessed'.ljust(widths[:services])}#{colors[:reset]}"
|
246
|
+
|
247
|
+
"| #{read_header} | #{write_header} | #{services_header} |\n"
|
248
|
+
end
|
249
|
+
|
250
|
+
def build_data_rows(arrays, widths, max_rows)
|
251
|
+
table = ''
|
252
|
+
max_rows.times do |i|
|
253
|
+
read_cell = arrays[:read][i].ljust(widths[:read])
|
254
|
+
write_cell = arrays[:write][i].ljust(widths[:write])
|
255
|
+
services_cell = arrays[:services][i].ljust(widths[:services])
|
256
|
+
table += "| #{read_cell} | #{write_cell} | #{services_cell} |\n"
|
257
|
+
end
|
258
|
+
table
|
259
|
+
end
|
260
|
+
|
261
|
+
def log_output(colored_output, plain_output)
|
262
|
+
if config.nil?
|
263
|
+
Rails.logger.info "\n#{colored_output}" if defined?(Rails)
|
264
|
+
else
|
265
|
+
Rails.logger.info "\n#{colored_output}" if config[:print_to_rails_log] && defined?(Rails)
|
266
|
+
|
267
|
+
custom_logger.info "\n#{plain_output}" if config[:write_to_file] && custom_logger
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
def subscribe_to_sql_notifications
|
272
|
+
@subscribe_to_sql_notifications ||= ActiveSupport::Notifications
|
273
|
+
.subscribe('sql.active_record') do |_name, _start, _finish, _id, payload|
|
274
|
+
sql = payload[:sql]
|
275
|
+
log_query(sql) unless sql.include?('SCHEMA')
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
def unsubscribe_from_sql_notifications
|
280
|
+
ActiveSupport::Notifications.unsubscribe(@sql_subscriber) if @sql_subscriber
|
281
|
+
@sql_subscriber = nil
|
282
|
+
end
|
283
|
+
|
284
|
+
def subscribe_to_logger
|
285
|
+
return unless defined?(Rails)
|
286
|
+
|
287
|
+
@subscribe_to_logger ||= ActiveSupport::Notifications.subscribe(/.*/) do |name, _start, _finish, _id, payload|
|
288
|
+
next if name.include?('sql.active_record')
|
289
|
+
|
290
|
+
# Capture controller and action information
|
291
|
+
if name == 'process_action.action_controller'
|
292
|
+
logs = Thread.current[THREAD_KEY]
|
293
|
+
if logs
|
294
|
+
logs[:controller] = payload[:controller]
|
295
|
+
logs[:action] = payload[:action]
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
message = case name
|
300
|
+
when 'process_action.action_controller'
|
301
|
+
"Controller: #{payload[:controller]}##{payload[:action]}"
|
302
|
+
when 'render_template.action_view'
|
303
|
+
"Template: #{payload[:identifier]}"
|
304
|
+
else
|
305
|
+
payload.to_s
|
306
|
+
end
|
307
|
+
|
308
|
+
log_message(message) if message && !message.empty?
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
def unsubscribe_from_logger
|
313
|
+
ActiveSupport::Notifications.unsubscribe(@logger_subscriber) if @logger_subscriber
|
314
|
+
@logger_subscriber = nil
|
315
|
+
end
|
316
|
+
end
|
317
|
+
end
|
318
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'rails_action_tracker/version'
|
4
|
+
require_relative 'rails_action_tracker/tracker'
|
5
|
+
require_relative 'rails_action_tracker/middleware'
|
6
|
+
|
7
|
+
module RailsActionTracker
|
8
|
+
class Error < StandardError; end
|
9
|
+
end
|
10
|
+
|
11
|
+
require_relative 'rails_action_tracker/railtie' if defined?(Rails) && defined?(Rails::Railtie)
|
data/script/test-all
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
set -e
|
4
|
+
|
5
|
+
echo "🚀 Testing Rails Action Tracker across all Rails versions..."
|
6
|
+
echo
|
7
|
+
|
8
|
+
# Colors for output
|
9
|
+
GREEN='\033[0;32m'
|
10
|
+
RED='\033[0;31m'
|
11
|
+
YELLOW='\033[1;33m'
|
12
|
+
NC='\033[0m' # No Color
|
13
|
+
|
14
|
+
# Track results
|
15
|
+
PASSED=()
|
16
|
+
FAILED=()
|
17
|
+
|
18
|
+
# Rails versions to test
|
19
|
+
RAILS_VERSIONS=(
|
20
|
+
"rails-5.0"
|
21
|
+
"rails-5.1"
|
22
|
+
"rails-5.2"
|
23
|
+
"rails-6.0"
|
24
|
+
"rails-6.1"
|
25
|
+
"rails-7.0"
|
26
|
+
"rails-7.1"
|
27
|
+
"rails-8.0"
|
28
|
+
)
|
29
|
+
|
30
|
+
echo -e "${YELLOW}Installing Appraisal gemfiles...${NC}"
|
31
|
+
bundle exec appraisal install
|
32
|
+
echo
|
33
|
+
|
34
|
+
for rails_version in "${RAILS_VERSIONS[@]}"; do
|
35
|
+
echo -e "${YELLOW}Testing $rails_version...${NC}"
|
36
|
+
|
37
|
+
if bundle exec appraisal $rails_version rake test; then
|
38
|
+
echo -e "${GREEN}✅ $rails_version PASSED${NC}"
|
39
|
+
PASSED+=($rails_version)
|
40
|
+
else
|
41
|
+
echo -e "${RED}❌ $rails_version FAILED${NC}"
|
42
|
+
FAILED+=($rails_version)
|
43
|
+
fi
|
44
|
+
echo
|
45
|
+
done
|
46
|
+
|
47
|
+
echo "========================================="
|
48
|
+
echo "📊 SUMMARY:"
|
49
|
+
echo "========================================="
|
50
|
+
echo -e "${GREEN}✅ PASSED (${#PASSED[@]}):${NC}"
|
51
|
+
for version in "${PASSED[@]}"; do
|
52
|
+
echo " - $version"
|
53
|
+
done
|
54
|
+
|
55
|
+
if [ ${#FAILED[@]} -ne 0 ]; then
|
56
|
+
echo -e "${RED}❌ FAILED (${#FAILED[@]}):${NC}"
|
57
|
+
for version in "${FAILED[@]}"; do
|
58
|
+
echo " - $version"
|
59
|
+
done
|
60
|
+
echo
|
61
|
+
exit 1
|
62
|
+
else
|
63
|
+
echo -e "${GREEN}🎉 All Rails versions passed!${NC}"
|
64
|
+
echo
|
65
|
+
fi
|
metadata
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rails_action_tracker
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Deepak Mahakale
|
8
|
+
bindir: exe
|
9
|
+
cert_chain: []
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
11
|
+
dependencies:
|
12
|
+
- !ruby/object:Gem::Dependency
|
13
|
+
name: actionpack
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - ">="
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: '5.0'
|
19
|
+
type: :runtime
|
20
|
+
prerelease: false
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
requirements:
|
23
|
+
- - ">="
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: '5.0'
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: activesupport
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
29
|
+
requirements:
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '5.0'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '5.0'
|
40
|
+
- !ruby/object:Gem::Dependency
|
41
|
+
name: railties
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '5.0'
|
47
|
+
type: :runtime
|
48
|
+
prerelease: false
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '5.0'
|
54
|
+
description: A Rails gem that provides detailed tracking of model read/write operations
|
55
|
+
and service usage during controller action execution, with configurable logging
|
56
|
+
options.
|
57
|
+
email:
|
58
|
+
- deepakmahakale@gmail.com
|
59
|
+
executables: []
|
60
|
+
extensions: []
|
61
|
+
extra_rdoc_files: []
|
62
|
+
files:
|
63
|
+
- ".rubocop.yml"
|
64
|
+
- Appraisals
|
65
|
+
- CHANGELOG.md
|
66
|
+
- CONTRIBUTING.md
|
67
|
+
- DEVELOPMENT.md
|
68
|
+
- LICENSE.txt
|
69
|
+
- README.md
|
70
|
+
- Rakefile
|
71
|
+
- lib/generators/rails_action_tracker/install_generator.rb
|
72
|
+
- lib/generators/rails_action_tracker/templates/initializer.rb
|
73
|
+
- lib/rails_action_tracker.rb
|
74
|
+
- lib/rails_action_tracker/middleware.rb
|
75
|
+
- lib/rails_action_tracker/railtie.rb
|
76
|
+
- lib/rails_action_tracker/tracker.rb
|
77
|
+
- lib/rails_action_tracker/version.rb
|
78
|
+
- script/test-all
|
79
|
+
- sig/rails_action_tracker.rbs
|
80
|
+
homepage: https://github.com/deepakmahakale/rails_action_tracker
|
81
|
+
licenses:
|
82
|
+
- MIT
|
83
|
+
metadata:
|
84
|
+
homepage_uri: https://github.com/deepakmahakale/rails_action_tracker
|
85
|
+
source_code_uri: https://github.com/deepakmahakale/rails_action_tracker
|
86
|
+
changelog_uri: https://github.com/deepakmahakale/rails_action_tracker/blob/master/CHANGELOG.md
|
87
|
+
rdoc_options: []
|
88
|
+
require_paths:
|
89
|
+
- lib
|
90
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
91
|
+
requirements:
|
92
|
+
- - ">="
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: 2.7.0
|
95
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
96
|
+
requirements:
|
97
|
+
- - ">="
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: '0'
|
100
|
+
requirements: []
|
101
|
+
rubygems_version: 3.6.7
|
102
|
+
specification_version: 4
|
103
|
+
summary: Track ActiveRecord model operations and service usage during Rails action
|
104
|
+
calls
|
105
|
+
test_files: []
|