From 08880f69a7912fa3f9a202caceb666582c181de4 Mon Sep 17 00:00:00 2001 From: Brian Durand Date: Mon, 16 Feb 2026 11:36:22 -0800 Subject: [PATCH 1/2] Update docs task to take an optional file path argument --- CHANGELOG.md | 8 ++ README.md | 4 +- VERSION | 2 +- .../documentation/source_file.rb | 3 + lib/tasks/support_table_data.rake | 28 +++-- lib/tasks/utils.rb | 20 +++- spec/tasks_spec.rb | 52 +++++++++ test_app/app/models/status.rb | 109 ++++++++++++++++++ 8 files changed, 207 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a3c6944..e1a65c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 1.5.1 + +### Added + +- YARD documentation tasks can now take an optional file path argument to add, verify, or remove documentation for a specific support table model instead of all models. For example, you can run `bundle exec rake support_table_data:yard_docs:add[app/models/color.rb]` to add documentation for the `Color` model. +- Added comment in generated YARD documentation indicating the command to run to update them so that it's clear to users how to keep the documentation up to date. +- Aliased `support_table_data:yard_docs` to `support_table_data:yard_docs:add` for convenience since adding the documentation is the most common action. + ## 1.5.0 ### Added diff --git a/README.md b/README.md index 384356b..70ceef3 100644 --- a/README.md +++ b/README.md @@ -187,7 +187,9 @@ completed: #### Documenting Named Instance Helpers -In a Rails application, you can add YARD documentation for the named instance helpers by running the rake task `support_table_data:yard_docs:add`. This will add YARD comments to your model classes for each of the named instance helper methods defined on the model. Adding this documentation will help IDEs provide better code completion and inline documentation for the helper methods and expose the methods to AI agents. +In a Rails application, you can add YARD documentation for the named instance helpers by running the rake task `support_table_data:yard_docs`. This will add YARD comments to your model classes for each of the named instance helper methods defined on the model. Adding this documentation will help IDEs provide better code completion and inline documentation for the helper methods and expose the methods to AI agents. + +To update a single model file, pass an optional file path argument, for example: `bundle exec rake "support_table_data:yard_docs[app/models/status.rb]"`. The default behavior is to add the documentation comments at the end of the model class by reopening the class definition. If you prefer to have the documentation comments appear elsewhere in the file, you can add the following markers to your model class and the YARD documentation will be inserted between these markers. diff --git a/VERSION b/VERSION index bc80560..26ca594 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.5.0 +1.5.1 diff --git a/lib/support_table_data/documentation/source_file.rb b/lib/support_table_data/documentation/source_file.rb index 29f27f1..e791c1b 100644 --- a/lib/support_table_data/documentation/source_file.rb +++ b/lib/support_table_data/documentation/source_file.rb @@ -9,6 +9,7 @@ class SourceFile END_YARD_COMMENT = "# End YARD docs for support_table_data" YARD_COMMENT_REGEX = /^(?[ \t]*)#{BEGIN_YARD_COMMENT}.*^[ \t]*#{END_YARD_COMMENT}$/m CLASS_DEF_REGEX = /^[ \t]*class [a-zA-Z_0-9:]+.*?$/ + UPDATE_COMMAND_COMMENT = "# To update these docs, run `bundle exec rake support_table_data:yard_docs`" # Initialize a new source file representation. # @@ -53,6 +54,7 @@ def source_with_yard_docs updated_source = source[0, existing_yard_docs.begin(0)] updated_source << "#{indent}#{BEGIN_YARD_COMMENT}\n" + updated_source << "#{indent}#{UPDATE_COMMAND_COMMENT}\n" updated_source << "#{indent}class #{klass.name}\n" if has_class_def updated_source << yard_docs updated_source << "\n#{indent}end" if has_class_def @@ -62,6 +64,7 @@ def source_with_yard_docs else yard_comments = <<~SOURCE.chomp("\n") #{BEGIN_YARD_COMMENT} + #{UPDATE_COMMAND_COMMENT} class #{klass.name} #{yard_docs.lines.map { |line| line.blank? ? "\n" : " #{line}" }.join} end diff --git a/lib/tasks/support_table_data.rake b/lib/tasks/support_table_data.rake index 3968c37..8e21bfe 100644 --- a/lib/tasks/support_table_data.rake +++ b/lib/tasks/support_table_data.rake @@ -23,14 +23,16 @@ namespace :support_table_data do end end + task yard_docs: "yard_docs:add" + namespace :yard_docs do - desc "Adds YARD documentation comments to models to document the named instance methods." - task add: :environment do + desc "Adds YARD documentation comments to models to document the named instance methods. Optional arg: file_path" + task :add, [:file_path] => :environment do |_task, args| require_relative "../support_table_data/documentation" require_relative "utils" SupportTableData::Tasks::Utils.eager_load! - SupportTableData::Tasks::Utils.support_table_sources.each do |source_file| + SupportTableData::Tasks::Utils.support_table_sources(args[:file_path]).each do |source_file| next if source_file.yard_docs_up_to_date? source_file.path.write(source_file.source_with_yard_docs) @@ -38,13 +40,13 @@ namespace :support_table_data do end end - desc "Removes YARD documentation comments added by support_table_data from models." - task remove: :environment do + desc "Removes YARD documentation comments added by support_table_data from models. Optional arg: file_path" + task :remove, [:file_path] => :environment do |_task, args| require_relative "../support_table_data/documentation" require_relative "utils" SupportTableData::Tasks::Utils.eager_load! - SupportTableData::Tasks::Utils.support_table_sources.each do |source_file| + SupportTableData::Tasks::Utils.support_table_sources(args[:file_path]).each do |source_file| next unless source_file.has_yard_docs? source_file.path.write(source_file.source_without_yard_docs) @@ -52,15 +54,15 @@ namespace :support_table_data do end end - desc "Verify that all the support table models have up to date YARD documentation for named instance methods." - task verify: :environment do + desc "Verify that support table models have up to date YARD docs for named instance methods. Optional arg: file_path" + task :verify, [:file_path] => :environment do |_task, args| require_relative "../support_table_data/documentation" require_relative "utils" SupportTableData::Tasks::Utils.eager_load! all_up_to_date = true - SupportTableData::Tasks::Utils.support_table_sources.each do |source_file| + SupportTableData::Tasks::Utils.support_table_sources(args[:file_path]).each do |source_file| unless source_file.yard_docs_up_to_date? puts "YARD documentation is not up to date for #{source_file.klass.name}." all_up_to_date = false @@ -68,9 +70,13 @@ namespace :support_table_data do end if all_up_to_date - puts "All support table models have up to date YARD documentation." + if args[:file_path] + puts "YARD documentation is up to date for #{args[:file_path]}." + else + puts "All support table models have up to date YARD documentation." + end else - raise "Run bundle exec rails support_table_data:yard_docs:add to update the documentation." + raise "Run bundle exec rake support_table_data:yard_docs to update the documentation." end end end diff --git a/lib/tasks/utils.rb b/lib/tasks/utils.rb index 108fb2a..867d953 100644 --- a/lib/tasks/utils.rb +++ b/lib/tasks/utils.rb @@ -18,10 +18,13 @@ def eager_load! end end - # Return a hash mapping all models that include SupportTableData to their source file paths. + # Return all source files for models that include SupportTableData. # + # @param file_path [String, Pathname, nil] Optional file path to filter by. # @return [Array] - def support_table_sources + def support_table_sources(file_path = nil) + require_relative file_path if file_path + sources = [] ActiveRecord::Base.descendants.each do |klass| @@ -34,13 +37,18 @@ def support_table_sources next end - file_path = SupportTableData::Tasks::Utils.model_file_path(klass) - next unless file_path&.file? && file_path.readable? + model_file_path = SupportTableData::Tasks::Utils.model_file_path(klass) + next unless model_file_path&.file? && model_file_path.readable? - sources << Documentation::SourceFile.new(klass, file_path) + sources << Documentation::SourceFile.new(klass, model_file_path) end - sources + return sources if file_path.nil? + + resolved_path = Pathname.new(file_path.to_s).expand_path + sources.select { |source| source.path.expand_path == resolved_path } + rescue ArgumentError + [] end def model_file_path(klass) diff --git a/spec/tasks_spec.rb b/spec/tasks_spec.rb index 14f7abf..59d2d37 100644 --- a/spec/tasks_spec.rb +++ b/spec/tasks_spec.rb @@ -5,6 +5,7 @@ RSpec.describe "support_table_data rake tasks" do let(:out) { StringIO.new } + let(:color_model_path) { File.join(__dir__, "models", "color.rb") } before do # Create a fresh Rake application for each test @@ -59,6 +60,23 @@ expect(color_write[:content]).to include("# Begin YARD docs for support_table_data") expect(color_write[:content]).to include("@!method self.red") end + + it "applies only to the specified file path when provided" do + require_relative "../lib/support_table_data/documentation" + require_relative "../lib/tasks/utils" + + allow($stdout).to receive(:puts) + + written_files = [] + allow_any_instance_of(Pathname).to receive(:write) do |instance, content| + written_files << {path: instance.to_s, content: content} + end + + Rake::Task["support_table_data:yard_docs:add"].invoke(color_model_path) + + expect(written_files).not_to be_empty + expect(written_files.map { |f| f[:path] }.uniq).to eq([color_model_path]) + end end describe "yard_docs:remove" do @@ -91,6 +109,26 @@ expect(color_write).not_to be_nil expect(color_write[:content]).not_to include("# Begin YARD docs for support_table_data") end + + it "applies only to the specified file path when provided" do + require_relative "../lib/support_table_data/documentation" + require_relative "../lib/tasks/utils" + + allow($stdout).to receive(:puts) + + written_files = [] + allow_any_instance_of(Pathname).to receive(:write) do |instance, content| + written_files << {path: instance.to_s, content: content} + end + + allow_any_instance_of(SupportTableData::Documentation::SourceFile) + .to receive(:has_yard_docs?).and_return(true) + + Rake::Task["support_table_data:yard_docs:remove"].invoke(color_model_path) + + expect(written_files).not_to be_empty + expect(written_files.map { |f| f[:path] }.uniq).to eq([color_model_path]) + end end describe "yard_docs:verify" do @@ -125,5 +163,19 @@ # Verify output indicates which docs are out of date expect($stdout).to have_received(:puts).at_least(:once) end + + it "verifies only the specified file path when provided" do + require_relative "../lib/support_table_data/documentation" + require_relative "../lib/tasks/utils" + + allow($stdout).to receive(:puts) + + expect_any_instance_of(SupportTableData::Documentation::SourceFile) + .to receive(:yard_docs_up_to_date?).once.and_return(true) + + Rake::Task["support_table_data:yard_docs:verify"].invoke(color_model_path) + + expect($stdout).to have_received(:puts).with("YARD documentation is up to date for #{color_model_path}.") + end end end diff --git a/test_app/app/models/status.rb b/test_app/app/models/status.rb index 631bb4f..9d298bb 100644 --- a/test_app/app/models/status.rb +++ b/test_app/app/models/status.rb @@ -9,3 +9,112 @@ class Status < ApplicationRecord validates :code, presence: true, uniqueness: true end + +# Begin YARD docs for support_table_data +# To update these docs, run `bundle exec rake support_table_data:yard_docs` +class Status + # @!group Named Instances + + # Find the named instance +active+ from the database. + # + # @!method self.active + # @return [Status] + # @raise [ActiveRecord::RecordNotFound] if the record does not exist + # @!visibility public + + # Check if this record is the named instance +active+. + # + # @!method active? + # @return [Boolean] + # @!visibility public + + # Get the name attribute from the data file + # for the named instance +active+. + # + # @!method self.active_name + # @return [Object] + # @!visibility public + + # Find the named instance +canceled+ from the database. + # + # @!method self.canceled + # @return [Status] + # @raise [ActiveRecord::RecordNotFound] if the record does not exist + # @!visibility public + + # Check if this record is the named instance +canceled+. + # + # @!method canceled? + # @return [Boolean] + # @!visibility public + + # Get the name attribute from the data file + # for the named instance +canceled+. + # + # @!method self.canceled_name + # @return [Object] + # @!visibility public + + # Find the named instance +completed+ from the database. + # + # @!method self.completed + # @return [Status] + # @raise [ActiveRecord::RecordNotFound] if the record does not exist + # @!visibility public + + # Check if this record is the named instance +completed+. + # + # @!method completed? + # @return [Boolean] + # @!visibility public + + # Get the name attribute from the data file + # for the named instance +completed+. + # + # @!method self.completed_name + # @return [Object] + # @!visibility public + + # Find the named instance +failed+ from the database. + # + # @!method self.failed + # @return [Status] + # @raise [ActiveRecord::RecordNotFound] if the record does not exist + # @!visibility public + + # Check if this record is the named instance +failed+. + # + # @!method failed? + # @return [Boolean] + # @!visibility public + + # Get the name attribute from the data file + # for the named instance +failed+. + # + # @!method self.failed_name + # @return [Object] + # @!visibility public + + # Find the named instance +pending+ from the database. + # + # @!method self.pending + # @return [Status] + # @raise [ActiveRecord::RecordNotFound] if the record does not exist + # @!visibility public + + # Check if this record is the named instance +pending+. + # + # @!method pending? + # @return [Boolean] + # @!visibility public + + # Get the name attribute from the data file + # for the named instance +pending+. + # + # @!method self.pending_name + # @return [Object] + # @!visibility public + + # @!endgroup +end +# End YARD docs for support_table_data From 484fbb5b430b6135f9764c26892301ebcfb1b71e Mon Sep 17 00:00:00 2001 From: Brian Durand Date: Mon, 16 Feb 2026 12:05:30 -0800 Subject: [PATCH 2/2] code style --- lib/tasks/utils.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tasks/utils.rb b/lib/tasks/utils.rb index 867d953..c7b6eec 100644 --- a/lib/tasks/utils.rb +++ b/lib/tasks/utils.rb @@ -28,7 +28,7 @@ def support_table_sources(file_path = nil) sources = [] ActiveRecord::Base.descendants.each do |klass| - next unless klass.included_modules.include?(SupportTableData) + next unless klass.include?(SupportTableData) begin next if klass.instance_names.empty?