support_table_data 1.4.0 → 1.5.1
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 +4 -4
- data/ARCHITECTURE.md +538 -0
- data/CHANGELOG.md +21 -0
- data/README.md +52 -5
- data/VERSION +1 -1
- data/lib/support_table_data/documentation/source_file.rb +98 -0
- data/lib/support_table_data/documentation/yard_doc.rb +91 -0
- data/lib/support_table_data/documentation.rb +9 -0
- data/lib/support_table_data/railtie.rb +22 -1
- data/lib/support_table_data/validation_error.rb +16 -0
- data/lib/support_table_data.rb +71 -53
- data/lib/tasks/support_table_data.rake +61 -12
- data/lib/tasks/utils.rb +71 -0
- data/support_table_data.gemspec +2 -1
- data/test_app/.gitignore +4 -0
- data/test_app/Gemfile +7 -0
- data/test_app/Rakefile +6 -0
- data/test_app/app/models/application_record.rb +5 -0
- data/test_app/app/models/secondary_application_record.rb +7 -0
- data/test_app/app/models/status.rb +120 -0
- data/test_app/app/models/thing.rb +10 -0
- data/test_app/bin/rails +4 -0
- data/test_app/config/application.rb +42 -0
- data/test_app/config/boot.rb +3 -0
- data/test_app/config/database.yml +17 -0
- data/test_app/config/environment.rb +5 -0
- data/test_app/config/environments/development.rb +11 -0
- data/test_app/config/environments/test.rb +11 -0
- data/test_app/config.ru +6 -0
- data/test_app/db/migrate/20260103060951_create_status.rb +8 -0
- data/test_app/db/schema.rb +20 -0
- data/test_app/db/secondary_migrate/20260104000001_create_things.rb +7 -0
- data/test_app/db/secondary_schema.rb +25 -0
- data/test_app/db/support_tables/statuses.yml +19 -0
- data/test_app/db/support_tables/things.yml +5 -0
- data/test_app/lib/tasks/database.rake +11 -0
- data/test_app/log/.keep +0 -0
- metadata +33 -8
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SupportTableData
|
|
4
|
+
module Documentation
|
|
5
|
+
class SourceFile
|
|
6
|
+
attr_reader :klass, :path
|
|
7
|
+
|
|
8
|
+
BEGIN_YARD_COMMENT = "# Begin YARD docs for support_table_data"
|
|
9
|
+
END_YARD_COMMENT = "# End YARD docs for support_table_data"
|
|
10
|
+
YARD_COMMENT_REGEX = /^(?<indent>[ \t]*)#{BEGIN_YARD_COMMENT}.*^[ \t]*#{END_YARD_COMMENT}$/m
|
|
11
|
+
CLASS_DEF_REGEX = /^[ \t]*class [a-zA-Z_0-9:]+.*?$/
|
|
12
|
+
UPDATE_COMMAND_COMMENT = "# To update these docs, run `bundle exec rake support_table_data:yard_docs`"
|
|
13
|
+
|
|
14
|
+
# Initialize a new source file representation.
|
|
15
|
+
#
|
|
16
|
+
# @param klass [Class] The model class
|
|
17
|
+
# @param path [Pathname] The path to the source file
|
|
18
|
+
def initialize(klass, path)
|
|
19
|
+
@klass = klass
|
|
20
|
+
@path = path
|
|
21
|
+
@source = nil
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Return the source code of the file.
|
|
25
|
+
#
|
|
26
|
+
# @return [String]
|
|
27
|
+
def source
|
|
28
|
+
@source ||= @path.read
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Return the source code without any generated YARD documentation.
|
|
32
|
+
#
|
|
33
|
+
# @return [String]
|
|
34
|
+
def source_without_yard_docs
|
|
35
|
+
"#{source.sub(YARD_COMMENT_REGEX, "").rstrip}#{trailing_newline}"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Return the source code with the generated YARD documentation added.
|
|
39
|
+
# The YARD docs are identified by a begin and end comment block. By default
|
|
40
|
+
# the generated docs are added to the end of the file by reopening the class
|
|
41
|
+
# definition. You can move the comment block inside the original class
|
|
42
|
+
# if desired.
|
|
43
|
+
#
|
|
44
|
+
# @return [String]
|
|
45
|
+
def source_with_yard_docs
|
|
46
|
+
yard_docs = YardDoc.new(klass).named_instance_yard_docs
|
|
47
|
+
return source if yard_docs.nil?
|
|
48
|
+
|
|
49
|
+
existing_yard_docs = source.match(YARD_COMMENT_REGEX)
|
|
50
|
+
if existing_yard_docs
|
|
51
|
+
indent = existing_yard_docs[:indent]
|
|
52
|
+
has_class_def = existing_yard_docs.to_s.match?(CLASS_DEF_REGEX)
|
|
53
|
+
yard_docs = yard_docs.lines.map { |line| line.blank? ? "\n" : "#{indent}#{" " if has_class_def}#{line}" }.join
|
|
54
|
+
|
|
55
|
+
updated_source = source[0, existing_yard_docs.begin(0)]
|
|
56
|
+
updated_source << "#{indent}#{BEGIN_YARD_COMMENT}\n"
|
|
57
|
+
updated_source << "#{indent}#{UPDATE_COMMAND_COMMENT}\n"
|
|
58
|
+
updated_source << "#{indent}class #{klass.name}\n" if has_class_def
|
|
59
|
+
updated_source << yard_docs
|
|
60
|
+
updated_source << "\n#{indent}end" if has_class_def
|
|
61
|
+
updated_source << "\n#{indent}#{END_YARD_COMMENT}"
|
|
62
|
+
updated_source << source[existing_yard_docs.end(0)..-1]
|
|
63
|
+
updated_source
|
|
64
|
+
else
|
|
65
|
+
yard_comments = <<~SOURCE.chomp("\n")
|
|
66
|
+
#{BEGIN_YARD_COMMENT}
|
|
67
|
+
#{UPDATE_COMMAND_COMMENT}
|
|
68
|
+
class #{klass.name}
|
|
69
|
+
#{yard_docs.lines.map { |line| line.blank? ? "\n" : " #{line}" }.join}
|
|
70
|
+
end
|
|
71
|
+
#{END_YARD_COMMENT}
|
|
72
|
+
SOURCE
|
|
73
|
+
"#{source.rstrip}\n\n#{yard_comments}#{trailing_newline}"
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Check if the YARD documentation in the source file is up to date.
|
|
78
|
+
#
|
|
79
|
+
# @return [Boolean]
|
|
80
|
+
def yard_docs_up_to_date?
|
|
81
|
+
source == source_with_yard_docs
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Check if the source file has any YARD documentation added by support_table_data.
|
|
85
|
+
#
|
|
86
|
+
# @return [Boolean]
|
|
87
|
+
def has_yard_docs?
|
|
88
|
+
source.match?(YARD_COMMENT_REGEX)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
private
|
|
92
|
+
|
|
93
|
+
def trailing_newline
|
|
94
|
+
source.end_with?("\n") ? "\n" : ""
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SupportTableData
|
|
4
|
+
module Documentation
|
|
5
|
+
class YardDoc
|
|
6
|
+
# @param klass [Class] The model class to generate documentation for
|
|
7
|
+
def initialize(klass)
|
|
8
|
+
@klass = klass
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Generate YARD documentation class definition for the model's helper methods.
|
|
12
|
+
#
|
|
13
|
+
# @return [String, nil] The YARD documentation class definition, or nil if no named instances
|
|
14
|
+
def named_instance_yard_docs
|
|
15
|
+
instance_names = klass.instance_names
|
|
16
|
+
generate_yard_docs(instance_names)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Generate YARD documentation comment for named instance singleton method.
|
|
20
|
+
#
|
|
21
|
+
# @param name [String] The name of the instance method.
|
|
22
|
+
# @return [String] The YARD comment text
|
|
23
|
+
def instance_helper_yard_doc(name)
|
|
24
|
+
<<~YARD.chomp("\n")
|
|
25
|
+
# Find the named instance +#{name}+ from the database.
|
|
26
|
+
#
|
|
27
|
+
# @!method self.#{name}
|
|
28
|
+
# @return [#{klass.name}]
|
|
29
|
+
# @raise [ActiveRecord::RecordNotFound] if the record does not exist
|
|
30
|
+
# @!visibility public
|
|
31
|
+
YARD
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Generate YARD documentation comment for the predicate method for the named instance.
|
|
35
|
+
#
|
|
36
|
+
# @param name [String] The name of the instance method.
|
|
37
|
+
# @return [String] The YARD comment text
|
|
38
|
+
def predicate_helper_yard_doc(name)
|
|
39
|
+
<<~YARD.chomp("\n")
|
|
40
|
+
# Check if this record is the named instance +#{name}+.
|
|
41
|
+
#
|
|
42
|
+
# @!method #{name}?
|
|
43
|
+
# @return [Boolean]
|
|
44
|
+
# @!visibility public
|
|
45
|
+
YARD
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Generate YARD documentation comment for the attribute method helper for the named instance.
|
|
49
|
+
#
|
|
50
|
+
# @param name [String] The name of the instance method.
|
|
51
|
+
# @return [String] The YARD comment text
|
|
52
|
+
def attribute_helper_yard_doc(name, attribute_name)
|
|
53
|
+
<<~YARD.chomp("\n")
|
|
54
|
+
# Get the #{attribute_name} attribute from the data file
|
|
55
|
+
# for the named instance +#{name}+.
|
|
56
|
+
#
|
|
57
|
+
# @!method self.#{name}_#{attribute_name}
|
|
58
|
+
# @return [Object]
|
|
59
|
+
# @!visibility public
|
|
60
|
+
YARD
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
attr_reader :klass
|
|
66
|
+
|
|
67
|
+
def generate_yard_docs(instance_names)
|
|
68
|
+
return nil if instance_names.empty?
|
|
69
|
+
|
|
70
|
+
yard_lines = ["# @!group Named Instances"]
|
|
71
|
+
|
|
72
|
+
# Generate docs for each named instance
|
|
73
|
+
instance_names.sort.each do |name|
|
|
74
|
+
yard_lines << ""
|
|
75
|
+
yard_lines << instance_helper_yard_doc(name)
|
|
76
|
+
yard_lines << ""
|
|
77
|
+
yard_lines << predicate_helper_yard_doc(name)
|
|
78
|
+
klass.support_table_attribute_helpers.each do |attribute_name|
|
|
79
|
+
yard_lines << ""
|
|
80
|
+
yard_lines << attribute_helper_yard_doc(name, attribute_name)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
yard_lines << ""
|
|
85
|
+
yard_lines << "# @!endgroup"
|
|
86
|
+
|
|
87
|
+
yard_lines.join("\n")
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -2,8 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
module SupportTableData
|
|
4
4
|
class Railtie < Rails::Railtie
|
|
5
|
-
|
|
5
|
+
unless config.respond_to?(:support_table) && config.support_table
|
|
6
|
+
config.support_table = ActiveSupport::OrderedOptions.new
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
config.support_table.data_directory ||= "db/support_tables"
|
|
10
|
+
config.support_table.auto_sync ||= true
|
|
11
|
+
|
|
12
|
+
initializer "support_table_data" do |app|
|
|
13
|
+
SupportTableData.data_directory ||= app.root.join(app.config.support_table&.data_directory).to_s
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
rake_tasks do |app|
|
|
6
17
|
load File.expand_path("../tasks/support_table_data.rake", __dir__)
|
|
18
|
+
|
|
19
|
+
if app.config.support_table.auto_sync
|
|
20
|
+
["db:seed", "db:seed:replant", "db:prepare", "db:test:prepare", "db:fixtures:load"].each do |task_name|
|
|
21
|
+
next unless Rake::Task.task_defined?(task_name)
|
|
22
|
+
|
|
23
|
+
Rake::Task[task_name].enhance do
|
|
24
|
+
Rake::Task["support_table_data:sync"].invoke
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
7
28
|
end
|
|
8
29
|
end
|
|
9
30
|
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SupportTableData
|
|
4
|
+
# Error class that is raised when validation fails when loading support table data.
|
|
5
|
+
# It provides more context than the standard ActiveRecord::RecordInvalid to help identify
|
|
6
|
+
# which record caused the validation failure.
|
|
7
|
+
class ValidationError < StandardError
|
|
8
|
+
def initialize(invalid_record)
|
|
9
|
+
key_attribute = invalid_record.class.support_table_key_attribute
|
|
10
|
+
key_value = invalid_record[key_attribute]
|
|
11
|
+
message = "Validation failed for #{invalid_record.class} with #{key_attribute}: #{key_value.inspect} - " \
|
|
12
|
+
"#{invalid_record.errors.full_messages.join(", ")}"
|
|
13
|
+
super(message)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
data/lib/support_table_data.rb
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
module SupportTableData
|
|
9
9
|
extend ActiveSupport::Concern
|
|
10
10
|
|
|
11
|
+
@data_directory = nil
|
|
12
|
+
|
|
11
13
|
included do
|
|
12
14
|
# Internal variables used for memoization.
|
|
13
15
|
@mutex = Mutex.new
|
|
@@ -17,9 +19,13 @@ module SupportTableData
|
|
|
17
19
|
@support_table_instance_keys = nil
|
|
18
20
|
@support_table_dependencies = []
|
|
19
21
|
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
class_attribute :
|
|
22
|
+
# Private class attribute to hold the key attribute name. Use `support_table_key_attribute` instead.
|
|
23
|
+
# @private
|
|
24
|
+
class_attribute :_support_table_key_attribute, instance_accessor: false
|
|
25
|
+
class << self
|
|
26
|
+
private :_support_table_key_attribute=
|
|
27
|
+
private :_support_table_key_attribute
|
|
28
|
+
end
|
|
23
29
|
|
|
24
30
|
# Define the directory where data files should be loaded from. This value will override the global
|
|
25
31
|
# value set by SupportTableData.data_directory. This is only used if relative paths are passed
|
|
@@ -28,6 +34,20 @@ module SupportTableData
|
|
|
28
34
|
end
|
|
29
35
|
|
|
30
36
|
class_methods do
|
|
37
|
+
# Define the attribute used as the key of the hash in the data files.
|
|
38
|
+
# This should be an attribute with values that never change.
|
|
39
|
+
# By default the key attribute will be the table's primary key.
|
|
40
|
+
def support_table_key_attribute=(attribute_name)
|
|
41
|
+
self._support_table_key_attribute = attribute_name&.to_s
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Get the attribute used as the unique to identify records in the data files.
|
|
45
|
+
#
|
|
46
|
+
# @return [String] The name of the key attribute.
|
|
47
|
+
def support_table_key_attribute
|
|
48
|
+
_support_table_key_attribute || "id"
|
|
49
|
+
end
|
|
50
|
+
|
|
31
51
|
# Synchronize the rows in the table with the values defined in the data files added with
|
|
32
52
|
# `add_support_table_data`. Note that rows will not be deleted if they are no longer in
|
|
33
53
|
# the data files.
|
|
@@ -36,36 +56,41 @@ module SupportTableData
|
|
|
36
56
|
def sync_table_data!
|
|
37
57
|
return unless table_exists?
|
|
38
58
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
59
|
+
canonical_data = support_table_data.each_with_object({}) do |attributes, hash|
|
|
60
|
+
hash[attributes[support_table_key_attribute].to_s] = attributes
|
|
61
|
+
end
|
|
62
|
+
records = where(support_table_key_attribute => canonical_data.keys)
|
|
42
63
|
changes = []
|
|
43
64
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
65
|
+
begin
|
|
66
|
+
ActiveSupport::Notifications.instrument("support_table_data.sync", class: self) do
|
|
67
|
+
transaction do
|
|
68
|
+
records.each do |record|
|
|
69
|
+
key = record[support_table_key_attribute].to_s
|
|
70
|
+
attributes = canonical_data.delete(key)
|
|
71
|
+
attributes&.each do |name, value|
|
|
72
|
+
record.send(:"#{name}=", value) if record.respond_to?(:"#{name}=", true)
|
|
73
|
+
end
|
|
74
|
+
if support_table_record_changed?(record)
|
|
75
|
+
changes << record.changes
|
|
76
|
+
record.save!
|
|
77
|
+
end
|
|
51
78
|
end
|
|
52
|
-
|
|
79
|
+
|
|
80
|
+
canonical_data.each_value do |attributes|
|
|
81
|
+
class_name = attributes[inheritance_column]
|
|
82
|
+
klass = class_name ? sti_class_for(class_name) : self
|
|
83
|
+
record = klass.new
|
|
84
|
+
attributes.each do |name, value|
|
|
85
|
+
record.send(:"#{name}=", value) if record.respond_to?(:"#{name}=", true)
|
|
86
|
+
end
|
|
53
87
|
changes << record.changes
|
|
54
88
|
record.save!
|
|
55
89
|
end
|
|
56
90
|
end
|
|
57
|
-
|
|
58
|
-
canonical_data.each_value do |attributes|
|
|
59
|
-
class_name = attributes[inheritance_column]
|
|
60
|
-
klass = class_name ? sti_class_for(class_name) : self
|
|
61
|
-
record = klass.new
|
|
62
|
-
attributes.each do |name, value|
|
|
63
|
-
record.send(:"#{name}=", value) if record.respond_to?(:"#{name}=", true)
|
|
64
|
-
end
|
|
65
|
-
changes << record.changes
|
|
66
|
-
record.save!
|
|
67
|
-
end
|
|
68
91
|
end
|
|
92
|
+
rescue ActiveRecord::RecordInvalid => e
|
|
93
|
+
raise SupportTableData::ValidationError.new(e.record)
|
|
69
94
|
end
|
|
70
95
|
|
|
71
96
|
changes
|
|
@@ -116,14 +141,12 @@ module SupportTableData
|
|
|
116
141
|
# @return [Array<Hash>] List of attributes for all records in the data files.
|
|
117
142
|
def support_table_data
|
|
118
143
|
data = {}
|
|
119
|
-
key_attribute = (support_table_key_attribute || primary_key).to_s
|
|
120
|
-
|
|
121
144
|
@support_table_data_files.each do |data_file_path|
|
|
122
145
|
file_data = support_table_parse_data_file(data_file_path)
|
|
123
146
|
file_data = file_data.values if file_data.is_a?(Hash)
|
|
124
147
|
file_data = Array(file_data).flatten
|
|
125
148
|
file_data.each do |attributes|
|
|
126
|
-
key_value = attributes[
|
|
149
|
+
key_value = attributes[support_table_key_attribute].to_s
|
|
127
150
|
existing = data[key_value]
|
|
128
151
|
if existing
|
|
129
152
|
existing.merge!(attributes)
|
|
@@ -171,9 +194,8 @@ module SupportTableData
|
|
|
171
194
|
# @return [ActiveRecord::Base] The instance loaded from the database.
|
|
172
195
|
# @raise [ActiveRecord::RecordNotFound] If the instance does not exist.
|
|
173
196
|
def named_instance(instance_name)
|
|
174
|
-
key_attribute = (support_table_key_attribute || primary_key).to_s
|
|
175
197
|
instance_name = instance_name.to_s
|
|
176
|
-
find_by!(
|
|
198
|
+
find_by!(support_table_key_attribute => @support_table_instance_names[instance_name])
|
|
177
199
|
end
|
|
178
200
|
|
|
179
201
|
# Get the key values for all instances loaded from the data files.
|
|
@@ -181,13 +203,12 @@ module SupportTableData
|
|
|
181
203
|
# @return [Array] List of all the key attribute values.
|
|
182
204
|
def instance_keys
|
|
183
205
|
if @support_table_instance_keys.nil?
|
|
184
|
-
key_attribute = (support_table_key_attribute || primary_key).to_s
|
|
185
206
|
values = []
|
|
186
207
|
support_table_data.each do |attributes|
|
|
187
|
-
key_value = attributes[
|
|
208
|
+
key_value = attributes[support_table_key_attribute]
|
|
188
209
|
instance = new
|
|
189
|
-
instance.send(:"#{
|
|
190
|
-
values << instance.send(
|
|
210
|
+
instance.send(:"#{support_table_key_attribute}=", key_value)
|
|
211
|
+
values << instance.send(support_table_key_attribute)
|
|
191
212
|
end
|
|
192
213
|
@support_table_instance_keys = values.uniq
|
|
193
214
|
end
|
|
@@ -198,14 +219,12 @@ module SupportTableData
|
|
|
198
219
|
#
|
|
199
220
|
# @return [Boolean]
|
|
200
221
|
def protected_instance?(instance)
|
|
201
|
-
key_attribute = (support_table_key_attribute || primary_key).to_s
|
|
202
|
-
|
|
203
222
|
unless defined?(@protected_keys)
|
|
204
|
-
keys = support_table_data.collect { |attributes| attributes[
|
|
223
|
+
keys = support_table_data.collect { |attributes| attributes[support_table_key_attribute].to_s }
|
|
205
224
|
@protected_keys = keys
|
|
206
225
|
end
|
|
207
226
|
|
|
208
|
-
@protected_keys.include?(instance[
|
|
227
|
+
@protected_keys.include?(instance[support_table_key_attribute].to_s)
|
|
209
228
|
end
|
|
210
229
|
|
|
211
230
|
# Explicitly define other support tables that this model depends on. A support table depends
|
|
@@ -249,12 +268,11 @@ module SupportTableData
|
|
|
249
268
|
raise ArgumentError.new("Cannot define named instance #{method_name} on #{name}; name contains illegal characters")
|
|
250
269
|
end
|
|
251
270
|
|
|
252
|
-
|
|
253
|
-
key_value = attributes[key_attribute]
|
|
271
|
+
key_value = attributes[support_table_key_attribute]
|
|
254
272
|
|
|
255
273
|
unless @support_table_instance_names.include?(method_name)
|
|
256
|
-
define_support_table_instance_helper(method_name,
|
|
257
|
-
define_support_table_predicates_helper("#{method_name}?",
|
|
274
|
+
define_support_table_instance_helper(method_name, support_table_key_attribute, key_value)
|
|
275
|
+
define_support_table_predicates_helper("#{method_name}?", support_table_key_attribute, key_value)
|
|
258
276
|
@support_table_instance_names = @support_table_instance_names.merge(method_name => key_value)
|
|
259
277
|
end
|
|
260
278
|
|
|
@@ -342,19 +360,17 @@ module SupportTableData
|
|
|
342
360
|
end
|
|
343
361
|
|
|
344
362
|
class << self
|
|
345
|
-
#
|
|
346
|
-
|
|
363
|
+
# @attribute [r]
|
|
364
|
+
# The the default directory where data files live.
|
|
365
|
+
# @return [String, nil]
|
|
366
|
+
attr_reader :data_directory
|
|
347
367
|
|
|
348
|
-
#
|
|
349
|
-
# then this will be `db/support_tables`. Otherwise, the current working directory will be used.
|
|
368
|
+
# Set the default directory where data files live.
|
|
350
369
|
#
|
|
351
|
-
# @
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
elsif defined?(Rails.root)
|
|
356
|
-
Rails.root.join("db", "support_tables").to_s
|
|
357
|
-
end
|
|
370
|
+
# @param value [String, Pathname, nil] The path to the directory.
|
|
371
|
+
# @return [void]
|
|
372
|
+
def data_directory=(value)
|
|
373
|
+
@data_directory = value&.to_s
|
|
358
374
|
end
|
|
359
375
|
|
|
360
376
|
# Sync all support table classes. Classes must already be loaded in order to be synced.
|
|
@@ -467,6 +483,8 @@ module SupportTableData
|
|
|
467
483
|
end
|
|
468
484
|
end
|
|
469
485
|
|
|
486
|
+
require_relative "support_table_data/validation_error"
|
|
487
|
+
|
|
470
488
|
if defined?(Rails::Railtie)
|
|
471
489
|
require_relative "support_table_data/railtie"
|
|
472
490
|
end
|
|
@@ -3,18 +3,9 @@
|
|
|
3
3
|
namespace :support_table_data do
|
|
4
4
|
desc "Syncronize data for all models that include SupportTableData."
|
|
5
5
|
task sync: :environment do
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
if defined?(Rails.application.eager_load!)
|
|
10
|
-
Rails.application.eager_load!
|
|
11
|
-
elsif defined?(Rails.autoloaders.zeitwerk_enabled?) && Rails.autoloaders.zeitwerk_enabled?
|
|
12
|
-
Rails.autoloaders.each(&:eager_load)
|
|
13
|
-
else
|
|
14
|
-
warn "Could not eager load models; some support table data may not load"
|
|
15
|
-
end
|
|
16
|
-
end
|
|
17
|
-
end
|
|
6
|
+
require_relative "utils"
|
|
7
|
+
|
|
8
|
+
SupportTableData::Tasks::Utils.eager_load!
|
|
18
9
|
|
|
19
10
|
logger_callback = lambda do |name, started, finished, unique_id, payload|
|
|
20
11
|
klass = payload[:class]
|
|
@@ -31,4 +22,62 @@ namespace :support_table_data do
|
|
|
31
22
|
SupportTableData.sync_all!
|
|
32
23
|
end
|
|
33
24
|
end
|
|
25
|
+
|
|
26
|
+
task yard_docs: "yard_docs:add"
|
|
27
|
+
|
|
28
|
+
namespace :yard_docs do
|
|
29
|
+
desc "Adds YARD documentation comments to models to document the named instance methods. Optional arg: file_path"
|
|
30
|
+
task :add, [:file_path] => :environment do |_task, args|
|
|
31
|
+
require_relative "../support_table_data/documentation"
|
|
32
|
+
require_relative "utils"
|
|
33
|
+
|
|
34
|
+
SupportTableData::Tasks::Utils.eager_load!
|
|
35
|
+
SupportTableData::Tasks::Utils.support_table_sources(args[:file_path]).each do |source_file|
|
|
36
|
+
next if source_file.yard_docs_up_to_date?
|
|
37
|
+
|
|
38
|
+
source_file.path.write(source_file.source_with_yard_docs)
|
|
39
|
+
puts "Added YARD documentation to #{source_file.klass.name}."
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
desc "Removes YARD documentation comments added by support_table_data from models. Optional arg: file_path"
|
|
44
|
+
task :remove, [:file_path] => :environment do |_task, args|
|
|
45
|
+
require_relative "../support_table_data/documentation"
|
|
46
|
+
require_relative "utils"
|
|
47
|
+
|
|
48
|
+
SupportTableData::Tasks::Utils.eager_load!
|
|
49
|
+
SupportTableData::Tasks::Utils.support_table_sources(args[:file_path]).each do |source_file|
|
|
50
|
+
next unless source_file.has_yard_docs?
|
|
51
|
+
|
|
52
|
+
source_file.path.write(source_file.source_without_yard_docs)
|
|
53
|
+
puts "Removed YARD documentation from #{source_file.klass.name}."
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
desc "Verify that support table models have up to date YARD docs for named instance methods. Optional arg: file_path"
|
|
58
|
+
task :verify, [:file_path] => :environment do |_task, args|
|
|
59
|
+
require_relative "../support_table_data/documentation"
|
|
60
|
+
require_relative "utils"
|
|
61
|
+
|
|
62
|
+
SupportTableData::Tasks::Utils.eager_load!
|
|
63
|
+
|
|
64
|
+
all_up_to_date = true
|
|
65
|
+
SupportTableData::Tasks::Utils.support_table_sources(args[:file_path]).each do |source_file|
|
|
66
|
+
unless source_file.yard_docs_up_to_date?
|
|
67
|
+
puts "YARD documentation is not up to date for #{source_file.klass.name}."
|
|
68
|
+
all_up_to_date = false
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
if all_up_to_date
|
|
73
|
+
if args[:file_path]
|
|
74
|
+
puts "YARD documentation is up to date for #{args[:file_path]}."
|
|
75
|
+
else
|
|
76
|
+
puts "All support table models have up to date YARD documentation."
|
|
77
|
+
end
|
|
78
|
+
else
|
|
79
|
+
raise "Run bundle exec rake support_table_data:yard_docs to update the documentation."
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
34
83
|
end
|
data/lib/tasks/utils.rb
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SupportTableData
|
|
4
|
+
module Tasks
|
|
5
|
+
module Utils
|
|
6
|
+
class << self
|
|
7
|
+
# Helper for eager loading a Rails application.
|
|
8
|
+
def eager_load!
|
|
9
|
+
return unless defined?(Rails.application.config.eager_load)
|
|
10
|
+
return if Rails.application.config.eager_load
|
|
11
|
+
|
|
12
|
+
if defined?(Rails.application.eager_load!)
|
|
13
|
+
Rails.application.eager_load!
|
|
14
|
+
elsif defined?(Rails.autoloaders.zeitwerk_enabled?) && Rails.autoloaders.zeitwerk_enabled?
|
|
15
|
+
Rails.autoloaders.each(&:eager_load)
|
|
16
|
+
else
|
|
17
|
+
raise "Failed to eager load application."
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Return all source files for models that include SupportTableData.
|
|
22
|
+
#
|
|
23
|
+
# @param file_path [String, Pathname, nil] Optional file path to filter by.
|
|
24
|
+
# @return [Array<SupportTableData::Documentation::SourceFile>]
|
|
25
|
+
def support_table_sources(file_path = nil)
|
|
26
|
+
require_relative file_path if file_path
|
|
27
|
+
|
|
28
|
+
sources = []
|
|
29
|
+
|
|
30
|
+
ActiveRecord::Base.descendants.each do |klass|
|
|
31
|
+
next unless klass.include?(SupportTableData)
|
|
32
|
+
|
|
33
|
+
begin
|
|
34
|
+
next if klass.instance_names.empty?
|
|
35
|
+
rescue NoMethodError
|
|
36
|
+
# Skip models where instance_names is not properly initialized
|
|
37
|
+
next
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
model_file_path = SupportTableData::Tasks::Utils.model_file_path(klass)
|
|
41
|
+
next unless model_file_path&.file? && model_file_path.readable?
|
|
42
|
+
|
|
43
|
+
sources << Documentation::SourceFile.new(klass, model_file_path)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
return sources if file_path.nil?
|
|
47
|
+
|
|
48
|
+
resolved_path = Pathname.new(file_path.to_s).expand_path
|
|
49
|
+
sources.select { |source| source.path.expand_path == resolved_path }
|
|
50
|
+
rescue ArgumentError
|
|
51
|
+
[]
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def model_file_path(klass)
|
|
55
|
+
file_path = "#{klass.name.underscore}.rb"
|
|
56
|
+
model_path = nil
|
|
57
|
+
|
|
58
|
+
Rails.application.config.paths["app/models"].each do |path_prefix|
|
|
59
|
+
path = Pathname.new(path_prefix.to_s).join(file_path)
|
|
60
|
+
if path&.file? && path.readable?
|
|
61
|
+
model_path = path
|
|
62
|
+
break
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
model_path
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
data/support_table_data.gemspec
CHANGED
|
@@ -12,13 +12,14 @@ Gem::Specification.new do |spec|
|
|
|
12
12
|
spec.metadata = {
|
|
13
13
|
"homepage_uri" => spec.homepage,
|
|
14
14
|
"source_code_uri" => spec.homepage,
|
|
15
|
-
"changelog_uri" => "#{spec.homepage}/blob/
|
|
15
|
+
"changelog_uri" => "#{spec.homepage}/blob/main/CHANGELOG.md"
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
# Specify which files should be added to the gem when it is released.
|
|
19
19
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
|
20
20
|
ignore_files = %w[
|
|
21
21
|
.
|
|
22
|
+
AGENTS.md
|
|
22
23
|
Appraisals
|
|
23
24
|
Gemfile
|
|
24
25
|
Gemfile.lock
|
data/test_app/.gitignore
ADDED
data/test_app/Gemfile
ADDED
data/test_app/Rakefile
ADDED