Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.5.0
1.5.1
3 changes: 3 additions & 0 deletions lib/support_table_data/documentation/source_file.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ class SourceFile
END_YARD_COMMENT = "# End YARD docs for support_table_data"
YARD_COMMENT_REGEX = /^(?<indent>[ \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.
#
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
28 changes: 17 additions & 11 deletions lib/tasks/support_table_data.rake
Original file line number Diff line number Diff line change
Expand Up @@ -23,54 +23,60 @@ 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)
puts "Added YARD documentation to #{source_file.klass.name}."
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)
puts "Removed YARD documentation from #{source_file.klass.name}."
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
end
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
Expand Down
22 changes: 15 additions & 7 deletions lib/tasks/utils.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,17 @@ 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<SupportTableData::Documentation::SourceFile>]
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|
next unless klass.included_modules.include?(SupportTableData)
next unless klass.include?(SupportTableData)

begin
next if klass.instance_names.empty?
Expand All @@ -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)
Expand Down
52 changes: 52 additions & 0 deletions spec/tasks_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
109 changes: 109 additions & 0 deletions test_app/app/models/status.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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