annotate_callbacks 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/LICENSE.txt +21 -0
- data/README.md +81 -0
- data/lib/annotate_callbacks/annotator.rb +139 -0
- data/lib/annotate_callbacks/inspector.rb +122 -0
- data/lib/annotate_callbacks/railtie.rb +37 -0
- data/lib/annotate_callbacks/version.rb +5 -0
- data/lib/annotate_callbacks.rb +10 -0
- metadata +109 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 972243c4b3851cdbe9e9856027d52e0525be83d010d5c7d8ccb9a84acf647afe
|
|
4
|
+
data.tar.gz: '08e595601483bc43aa3b2c177dfbe1873dbdce029593048d3d270362dfe1726c'
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: e7bb6ae953a4458147133311a2ba92290f541e089e55decbd51fe1ca531dd9a157594045efd711e0d68a54e509444a3edbdfa869cb86d23508b4d526c205c3db
|
|
7
|
+
data.tar.gz: bce0d79758a6487d7507663bce8b398f5540d2fb7197af1127ee71d9520a75dd5ca6bb40e8b72309cbfcb984d76a808fe8f81315cde2fc47ec21e474519498c3
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 sloppybook
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# AnnotateCallbacks
|
|
2
|
+
|
|
3
|
+
Automatically annotate Rails model files with a comment block summarizing their ActiveRecord callbacks, including those inherited from concerns and parent classes.
|
|
4
|
+
|
|
5
|
+
## Example Output
|
|
6
|
+
|
|
7
|
+
```ruby
|
|
8
|
+
# == Callbacks ==
|
|
9
|
+
#
|
|
10
|
+
# before_validation :normalize_email
|
|
11
|
+
# before_save :encrypt_password
|
|
12
|
+
# before_save :track_changes if: :changed? [Trackable]
|
|
13
|
+
# after_create :send_welcome_email
|
|
14
|
+
# after_create (block: app/models/user.rb:18)
|
|
15
|
+
# after_save :update_cache if: :name_changed?
|
|
16
|
+
# before_destroy :check_admin
|
|
17
|
+
#
|
|
18
|
+
# == End Callbacks ==
|
|
19
|
+
|
|
20
|
+
class User < ApplicationRecord
|
|
21
|
+
include Trackable
|
|
22
|
+
|
|
23
|
+
before_validation :normalize_email
|
|
24
|
+
before_save :encrypt_password
|
|
25
|
+
after_create -> { NotificationService.ping(self) }
|
|
26
|
+
# ...
|
|
27
|
+
end
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
- Callbacks from concerns are shown with `[ConcernName]`
|
|
31
|
+
- Block/lambda callbacks show source location: `(block: path:line)`
|
|
32
|
+
- Internal framework callbacks (`autosave_associated_records_for_*`, `dependent: :destroy` etc.) are automatically filtered out
|
|
33
|
+
|
|
34
|
+
## Installation
|
|
35
|
+
|
|
36
|
+
Add to your Gemfile:
|
|
37
|
+
|
|
38
|
+
```ruby
|
|
39
|
+
group :development do
|
|
40
|
+
gem "annotate_callbacks"
|
|
41
|
+
end
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
bundle install
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Usage
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
# Add callback annotations
|
|
52
|
+
bundle exec rake callbacks:annotate
|
|
53
|
+
|
|
54
|
+
# Remove callback annotations
|
|
55
|
+
bundle exec rake callbacks:remove
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## How It Works
|
|
59
|
+
|
|
60
|
+
Uses `ApplicationRecord.descendants` and Rails runtime reflection (`_save_callbacks`, `_create_callbacks`, etc.) to detect all registered callbacks on each model class.
|
|
61
|
+
|
|
62
|
+
- Callbacks defined in concerns are included, with source module shown as `[ModuleName]`
|
|
63
|
+
- Block/lambda callbacks show relative source location: `(block: app/models/user.rb:10)`
|
|
64
|
+
- Symbol conditions (`if: :active?`) are shown; Proc conditions are omitted
|
|
65
|
+
- Internal framework callbacks are filtered out by name pattern and source module
|
|
66
|
+
- Abstract classes are skipped
|
|
67
|
+
- Files are written atomically (Tempfile + mv)
|
|
68
|
+
- `rake callbacks:annotate` requires Rails environment; `rake callbacks:remove` does not
|
|
69
|
+
|
|
70
|
+
## Development
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
git clone https://github.com/sloppybook/annotate_callbacks.git
|
|
74
|
+
cd annotate_callbacks
|
|
75
|
+
bundle install
|
|
76
|
+
bundle exec rspec
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## License
|
|
80
|
+
|
|
81
|
+
[MIT License](LICENSE.txt)
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "tempfile"
|
|
4
|
+
require "fileutils"
|
|
5
|
+
|
|
6
|
+
module AnnotateCallbacks
|
|
7
|
+
class Annotator
|
|
8
|
+
ANNOTATION_START = "# == Callbacks =="
|
|
9
|
+
ANNOTATION_END = "# == End Callbacks =="
|
|
10
|
+
ANNOTATION_REGEX = /^#{Regexp.escape(ANNOTATION_START)}\n(.*?)^#{Regexp.escape(ANNOTATION_END)}\n\n?/m
|
|
11
|
+
MODEL_DIR = "app/models"
|
|
12
|
+
|
|
13
|
+
def annotate(model_class, file_path)
|
|
14
|
+
callbacks = Inspector.new(model_class).callbacks
|
|
15
|
+
return :skipped if callbacks.empty?
|
|
16
|
+
|
|
17
|
+
content = File.read(file_path, encoding: "UTF-8")
|
|
18
|
+
clean_content = content.sub(ANNOTATION_REGEX, "")
|
|
19
|
+
|
|
20
|
+
insert_pos = class_definition_line(clean_content)
|
|
21
|
+
return :skipped unless insert_pos
|
|
22
|
+
|
|
23
|
+
annotation = build_annotation(callbacks)
|
|
24
|
+
lines = clean_content.lines
|
|
25
|
+
insert_pos = skip_preceding_comments(lines, insert_pos)
|
|
26
|
+
lines.insert(insert_pos, annotation)
|
|
27
|
+
new_content = lines.join
|
|
28
|
+
|
|
29
|
+
return :unchanged if new_content == content
|
|
30
|
+
|
|
31
|
+
atomic_write(file_path, new_content)
|
|
32
|
+
:annotated
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def remove_annotation(file_path)
|
|
36
|
+
content = File.read(file_path, encoding: "UTF-8")
|
|
37
|
+
return :skipped unless content.include?(ANNOTATION_START)
|
|
38
|
+
|
|
39
|
+
new_content = content.sub(ANNOTATION_REGEX, "")
|
|
40
|
+
return :unchanged if new_content == content
|
|
41
|
+
|
|
42
|
+
atomic_write(file_path, new_content)
|
|
43
|
+
:removed
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def annotate_all
|
|
47
|
+
results = Hash.new { |h, k| h[k] = [] }
|
|
48
|
+
|
|
49
|
+
each_model do |model_class, file_path|
|
|
50
|
+
status = annotate(model_class, file_path)
|
|
51
|
+
results[status] << file_path
|
|
52
|
+
rescue StandardError => e
|
|
53
|
+
results[:errors] << { file: file_path, error: e.message }
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
results
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def remove_all
|
|
60
|
+
results = Hash.new { |h, k| h[k] = [] }
|
|
61
|
+
|
|
62
|
+
Dir.glob(File.join(MODEL_DIR, "**", "*.rb")).sort.each do |file|
|
|
63
|
+
status = remove_annotation(file)
|
|
64
|
+
results[status] << file
|
|
65
|
+
rescue StandardError => e
|
|
66
|
+
results[:errors] << { file: file, error: e.message }
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
results
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
def each_model
|
|
75
|
+
ApplicationRecord.descendants.each do |klass|
|
|
76
|
+
next if klass.abstract_class?
|
|
77
|
+
|
|
78
|
+
file = source_file_for(klass)
|
|
79
|
+
next unless file
|
|
80
|
+
|
|
81
|
+
yield klass, file
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def source_file_for(klass)
|
|
86
|
+
file = Object.const_source_location(klass.name)&.first if klass.name
|
|
87
|
+
return nil unless file
|
|
88
|
+
|
|
89
|
+
file.start_with?(project_model_dir) ? file : nil
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def project_model_dir
|
|
93
|
+
@project_model_dir ||= File.expand_path(MODEL_DIR)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def class_definition_line(content)
|
|
97
|
+
content.lines.index { |line| line.match?(/\A\s*(class|module)\s+/) }
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def skip_preceding_comments(lines, pos)
|
|
101
|
+
while pos > 0
|
|
102
|
+
prev = lines[pos - 1].strip
|
|
103
|
+
break unless prev.start_with?("#")
|
|
104
|
+
break if prev.match?(/^#.*(?:frozen_string_literal|encoding|warn_indent):/)
|
|
105
|
+
pos -= 1
|
|
106
|
+
end
|
|
107
|
+
pos
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def atomic_write(file_path, content)
|
|
111
|
+
dir = File.dirname(file_path)
|
|
112
|
+
Tempfile.create(["annotate_callbacks", ".rb"], dir) do |tmp|
|
|
113
|
+
tmp.write(content)
|
|
114
|
+
tmp.flush
|
|
115
|
+
FileUtils.mv(tmp.path, file_path)
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def build_annotation(callbacks)
|
|
120
|
+
max_type_len = callbacks.map { |c| c.type.length }.max
|
|
121
|
+
max_name_len = callbacks.map { |c| format_name(c).length }.max
|
|
122
|
+
|
|
123
|
+
lines = [ANNOTATION_START, "#"]
|
|
124
|
+
callbacks.each do |cb|
|
|
125
|
+
entry = "# %-#{max_type_len}s %-#{max_name_len}s" % [cb.type, format_name(cb)]
|
|
126
|
+
entry += " #{cb.options}" if cb.options
|
|
127
|
+
entry += " [#{cb.source}]" if cb.source
|
|
128
|
+
lines << entry.rstrip
|
|
129
|
+
end
|
|
130
|
+
lines << "#"
|
|
131
|
+
lines << ANNOTATION_END
|
|
132
|
+
lines.map { |l| "#{l}\n" }.join + "\n"
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def format_name(cb)
|
|
136
|
+
cb.method_name.start_with?("(") ? cb.method_name : ":#{cb.method_name}"
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AnnotateCallbacks
|
|
4
|
+
class Inspector
|
|
5
|
+
CallbackEntry = Data.define(:type, :method_name, :options, :source)
|
|
6
|
+
|
|
7
|
+
CALLBACK_TYPES = %w[
|
|
8
|
+
initialize find touch validation
|
|
9
|
+
save create update destroy
|
|
10
|
+
commit rollback
|
|
11
|
+
].freeze
|
|
12
|
+
|
|
13
|
+
INTERNAL_FILTER_PATTERNS = [
|
|
14
|
+
/\Aautosave_associated_records_for_/,
|
|
15
|
+
].freeze
|
|
16
|
+
|
|
17
|
+
INTERNAL_BLOCK_PATHS = %w[
|
|
18
|
+
active_record/associations/builder/
|
|
19
|
+
active_record/timestamp
|
|
20
|
+
active_record/transactions
|
|
21
|
+
active_record/locking
|
|
22
|
+
].freeze
|
|
23
|
+
|
|
24
|
+
INTERNAL_SOURCES = %w[
|
|
25
|
+
ActiveRecord::AutosaveAssociation
|
|
26
|
+
ActiveRecord::Timestamp
|
|
27
|
+
ActiveRecord::Transactions
|
|
28
|
+
ActiveRecord::Persistence
|
|
29
|
+
ActiveRecord::AttributeMethods::Dirty
|
|
30
|
+
ActiveRecord::Locking::Optimistic
|
|
31
|
+
ActiveRecord::CounterCache
|
|
32
|
+
ActiveRecord::Normalization
|
|
33
|
+
ActiveModel::Attributes::Normalization
|
|
34
|
+
].freeze
|
|
35
|
+
|
|
36
|
+
def initialize(model_class)
|
|
37
|
+
@target = model_class
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def callbacks
|
|
41
|
+
CALLBACK_TYPES
|
|
42
|
+
.flat_map { |type| extract_callbacks_for(type) }
|
|
43
|
+
.reject { |cb| internal?(cb) }
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def extract_callbacks_for(type)
|
|
49
|
+
method_name = "_#{type}_callbacks"
|
|
50
|
+
return [] unless @target.respond_to?(method_name)
|
|
51
|
+
|
|
52
|
+
@target.send(method_name).filter_map { |cb| build_callback_info(cb, type) }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def build_callback_info(cb, type)
|
|
56
|
+
name = case cb.filter
|
|
57
|
+
when Symbol then cb.filter.to_s
|
|
58
|
+
when Proc then format_block(cb.filter)
|
|
59
|
+
else return nil
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
CallbackEntry.new(
|
|
63
|
+
type: "#{cb.kind}_#{type}",
|
|
64
|
+
method_name: name,
|
|
65
|
+
options: extract_options(cb),
|
|
66
|
+
source: detect_source(cb.filter)
|
|
67
|
+
)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def internal?(cb)
|
|
71
|
+
internal_by_name?(cb) || internal_by_block_path?(cb) || internal_by_source?(cb)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def internal_by_name?(cb)
|
|
75
|
+
INTERNAL_FILTER_PATTERNS.any? { |p| p.match?(cb.method_name) }
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def internal_by_block_path?(cb)
|
|
79
|
+
return false unless cb.method_name.start_with?("(block:")
|
|
80
|
+
|
|
81
|
+
INTERNAL_BLOCK_PATHS.any? { |path| cb.method_name.include?(path) }
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def internal_by_source?(cb)
|
|
85
|
+
cb.source && INTERNAL_SOURCES.any? { |s| cb.source.start_with?(s) }
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def detect_source(filter)
|
|
89
|
+
return nil unless filter.is_a?(Symbol)
|
|
90
|
+
return nil unless @target.method_defined?(filter) || @target.private_method_defined?(filter)
|
|
91
|
+
|
|
92
|
+
owner = @target.instance_method(filter).owner
|
|
93
|
+
owner == @target ? nil : owner.name
|
|
94
|
+
rescue NameError
|
|
95
|
+
nil
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def extract_options(cb)
|
|
99
|
+
if_conditions = extract_symbol_conditions(cb, :@if)
|
|
100
|
+
unless_conditions = extract_symbol_conditions(cb, :@unless)
|
|
101
|
+
|
|
102
|
+
parts = []
|
|
103
|
+
parts << "if: #{if_conditions.join(", ")}" if if_conditions.any?
|
|
104
|
+
parts << "unless: #{unless_conditions.join(", ")}" if unless_conditions.any?
|
|
105
|
+
parts.empty? ? nil : parts.join(", ")
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def extract_symbol_conditions(cb, ivar)
|
|
109
|
+
(cb.instance_variable_get(ivar) || [])
|
|
110
|
+
.select { |c| c.is_a?(Symbol) }
|
|
111
|
+
.map { |c| ":#{c}" }
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def format_block(proc_obj)
|
|
115
|
+
loc = proc_obj.source_location
|
|
116
|
+
return "(block)" unless loc
|
|
117
|
+
|
|
118
|
+
path = defined?(Rails) ? loc[0].sub("#{Rails.root}/", "") : loc[0]
|
|
119
|
+
"(block: #{path}:#{loc[1]})"
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AnnotateCallbacks
|
|
4
|
+
class Railtie < Rails::Railtie
|
|
5
|
+
rake_tasks do
|
|
6
|
+
namespace :callbacks do
|
|
7
|
+
desc "Annotate callbacks in model files"
|
|
8
|
+
task annotate: :environment do
|
|
9
|
+
Rails.application.eager_load!
|
|
10
|
+
|
|
11
|
+
results = Annotator.new.annotate_all
|
|
12
|
+
|
|
13
|
+
puts "Annotated: #{results[:annotated].length} file(s)"
|
|
14
|
+
results[:annotated].each { |f| puts " #{f}" }
|
|
15
|
+
|
|
16
|
+
if results[:errors].any?
|
|
17
|
+
puts "\nErrors: #{results[:errors].length}"
|
|
18
|
+
results[:errors].each { |e| puts " #{e[:file]}: #{e[:error]}" }
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
desc "Remove callback annotations from model files"
|
|
23
|
+
task :remove do
|
|
24
|
+
results = Annotator.new.remove_all
|
|
25
|
+
|
|
26
|
+
puts "Removed: #{results[:removed].length} file(s)"
|
|
27
|
+
results[:removed].each { |f| puts " #{f}" }
|
|
28
|
+
|
|
29
|
+
if results[:errors].any?
|
|
30
|
+
puts "\nErrors: #{results[:errors].length}"
|
|
31
|
+
results[:errors].each { |e| puts " #{e[:file]}: #{e[:error]}" }
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "annotate_callbacks/version"
|
|
4
|
+
require_relative "annotate_callbacks/inspector"
|
|
5
|
+
require_relative "annotate_callbacks/annotator"
|
|
6
|
+
|
|
7
|
+
module AnnotateCallbacks
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
require_relative "annotate_callbacks/railtie" if defined?(Rails::Railtie)
|
metadata
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: annotate_callbacks
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- sloppybook
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-02-23 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: activerecord
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '7.0'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '7.0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: activesupport
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">="
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '7.0'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ">="
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '7.0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: rake
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '13.0'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '13.0'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: rspec
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - "~>"
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '3.0'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - "~>"
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '3.0'
|
|
69
|
+
description: Adds a comment block summarizing all ActiveRecord callbacks (including
|
|
70
|
+
those from concerns and parent classes) at the top of each model file using runtime
|
|
71
|
+
reflection.
|
|
72
|
+
email:
|
|
73
|
+
executables: []
|
|
74
|
+
extensions: []
|
|
75
|
+
extra_rdoc_files: []
|
|
76
|
+
files:
|
|
77
|
+
- LICENSE.txt
|
|
78
|
+
- README.md
|
|
79
|
+
- lib/annotate_callbacks.rb
|
|
80
|
+
- lib/annotate_callbacks/annotator.rb
|
|
81
|
+
- lib/annotate_callbacks/inspector.rb
|
|
82
|
+
- lib/annotate_callbacks/railtie.rb
|
|
83
|
+
- lib/annotate_callbacks/version.rb
|
|
84
|
+
homepage: https://github.com/sloppybook/annotate_callbacks
|
|
85
|
+
licenses:
|
|
86
|
+
- MIT
|
|
87
|
+
metadata:
|
|
88
|
+
homepage_uri: https://github.com/sloppybook/annotate_callbacks
|
|
89
|
+
source_code_uri: https://github.com/sloppybook/annotate_callbacks
|
|
90
|
+
post_install_message:
|
|
91
|
+
rdoc_options: []
|
|
92
|
+
require_paths:
|
|
93
|
+
- lib
|
|
94
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
95
|
+
requirements:
|
|
96
|
+
- - ">="
|
|
97
|
+
- !ruby/object:Gem::Version
|
|
98
|
+
version: 3.2.0
|
|
99
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
100
|
+
requirements:
|
|
101
|
+
- - ">="
|
|
102
|
+
- !ruby/object:Gem::Version
|
|
103
|
+
version: '0'
|
|
104
|
+
requirements: []
|
|
105
|
+
rubygems_version: 3.4.10
|
|
106
|
+
signing_key:
|
|
107
|
+
specification_version: 4
|
|
108
|
+
summary: Annotate ActiveRecord callbacks as comments in model files
|
|
109
|
+
test_files: []
|