Skip to content

Commit cb6c77c

Browse files
authored
Merge pull request #15 from bdurand/yard-generation
Add rake task to generate YARD docs
2 parents 8d8e80d + eb7d224 commit cb6c77c

46 files changed

Lines changed: 1155 additions & 184 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/continuous_integration.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ jobs:
2828
- ruby: "ruby"
2929
standardrb: true
3030
yard: true
31+
- ruby: "4.0"
32+
appraisal: "activerecord_8.1"
3133
- ruby: "3.4"
3234
appraisal: "activerecord_8.0"
3335
- ruby: "3.2"
@@ -65,7 +67,7 @@ jobs:
6567
run: bundle exec rake
6668
- name: standardrb
6769
if: matrix.standardrb == true
68-
run: bundle exec rake standard
70+
run: bundle exec standardrb
6971
- name: yard
7072
if: matrix.yard == true
71-
run: bundle exec yard --fail-on-warning
73+
run: bundle exec yard doc --fail-on-warning

AGENTS.md

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
# Copilot Instructions for support_table_data
2+
3+
## Project Overview
4+
5+
A Ruby gem providing an ActiveRecord mixin for managing support/lookup tables with canonical data defined in YAML/JSON/CSV files. The gem dynamically generates helper methods to reference specific records naturally in code (e.g., `Status.pending` instead of `Status.find_by(name: 'Pending')`).
6+
7+
**Core concept**: Support tables blur the line between data and code—they contain small canonical datasets that must exist for the application to work.
8+
9+
## Architecture
10+
11+
### Key Components
12+
13+
- **`SupportTableData` module** ([lib/support_table_data.rb](lib/support_table_data.rb)): Main concern mixed into ActiveRecord models
14+
- **Named instance system**: Dynamically generates class methods (`.pending`), predicate methods (`.pending?`), and attribute helpers (`.pending_id`) from hash-based data files
15+
- **Data sync engine**: Compares canonical data files with database records, creating/updating as needed in atomic transactions
16+
- **File parsers**: Supports YAML, JSON, and CSV formats with unified interface
17+
18+
### Data Flow
19+
20+
1. Data files (YAML/JSON/CSV) define canonical records with unique key attributes
21+
2. `add_support_table_data` registers file paths and triggers method generation for hash-based files
22+
3. `sync_table_data!` parses files, loads matching DB records, and updates/creates within transactions
23+
4. Named instance methods are dynamically defined via `class_eval` with memoization
24+
25+
## Development Workflows
26+
27+
### Running Tests
28+
29+
```bash
30+
bundle exec rspec # Run all specs
31+
bundle exec rspec spec/support_table_data_spec.rb # Single file
32+
bundle exec rake appraisals # Test against all ActiveRecord versions
33+
```
34+
35+
Uses RSpec with in-memory SQLite database. Test models defined in [spec/models.rb](spec/models.rb), data files in `spec/data/`.
36+
37+
### Testing Against Multiple ActiveRecord Versions
38+
39+
The gem supports ActiveRecord 6.0-8.0. Uses Appraisal for multi-version testing:
40+
41+
```bash
42+
bundle exec appraisal install # Install all gemfiles
43+
bundle exec appraisal rspec # Run specs against all versions
44+
```
45+
46+
See `Appraisals` file and `gemfiles/` directory.
47+
48+
### Code Style
49+
50+
Uses Standard Ruby formatter:
51+
52+
```bash
53+
bundle exec rake standard:fix # Auto-fix style issues
54+
```
55+
56+
## Critical Patterns
57+
58+
### Named Instance Method Generation
59+
60+
**Hash-based data files** trigger dynamic method generation. Example from [spec/data/colors/named_colors.yml](spec/data/colors/named_colors.yml):
61+
62+
```yaml
63+
red:
64+
id: 1
65+
name: Red
66+
value: 16711680
67+
```
68+
69+
Generates:
70+
- `Color.red` → finds record by id
71+
- `color_instance.red?` → tests if `color_instance.id == 1`
72+
- `Color.red_id` → returns `1` (if `named_instance_attribute_helpers :id` defined)
73+
74+
**Implementation**: See `define_support_table_named_instance_methods` in [lib/support_table_data.rb](lib/support_table_data.rb#L230-L265). Methods are generated using `class_eval` with string interpolation.
75+
76+
### Custom Setters for Associations
77+
78+
Support tables often reference other support tables via named instances. Pattern from [spec/models.rb](spec/models.rb#L72-L74):
79+
80+
```ruby
81+
def group_name=(value)
82+
self.group = Group.named_instance(value)
83+
end
84+
```
85+
86+
Allows data files to reference related records by instance name instead of foreign keys.
87+
88+
### Key Attribute Configuration
89+
90+
By default, uses model's `primary_key`. Override for non-id keys:
91+
92+
```ruby
93+
self.support_table_key_attribute = :name # Use 'name' instead of 'id'
94+
```
95+
96+
Key attributes cannot be updated—changing them creates new records.
97+
98+
### Dependency Resolution
99+
100+
`sync_all!` automatically resolves dependencies via `belongs_to` associations and loads tables in correct order. For complex cases (join tables, indirect dependencies), explicitly declare:
101+
102+
```ruby
103+
support_table_dependency "OtherModel"
104+
```
105+
106+
See [lib/support_table_data.rb](lib/support_table_data.rb#L219-L222) and dependency resolution logic.
107+
108+
## Testing Conventions
109+
110+
- **Test data isolation**: Each test deletes all records in `before` block ([spec/spec_helper.rb](spec/spec_helper.rb))
111+
- **Sync before assertions**: Tests call `sync_table_data!` or `sync_all!` before verifying records exist
112+
- **Multi-file merging**: Tests verify that multiple data files for same model merge correctly (see `Color` model with 5 data files)
113+
- **STI handling**: See `Polygon`/`Triangle`/`Rectangle` tests for Single Table Inheritance patterns
114+
115+
## Common Pitfalls
116+
117+
1. **Method name conflicts**: Named instance methods raise `ArgumentError` if method already exists. Instance names must match `/\A[a-z][a-z0-9_]+\z/`
118+
2. **Array vs hash data**: Only hash-keyed data generates named instance methods. Use arrays or underscore-prefixed keys (`_others`) for records without helpers
119+
3. **Protected instances**: Records in data files cannot be deleted via `destroy` (though this gem doesn't enforce it—see companion caching gem)
120+
4. **Transaction safety**: All sync operations wrapped in transactions; changes rollback on failure
121+
122+
## Rails Integration
123+
124+
In Rails apps, the gem automatically:
125+
- Sets `SupportTableData.data_directory` to `Rails.root/db/support_tables`
126+
- Provides `rake support_table_data:sync` task ([lib/tasks/support_table_data.rake](lib/tasks/support_table_data.rake))
127+
- Handles eager loading in both classic and Zeitwerk autoloaders
128+
129+
## File References
130+
131+
- Main module: [lib/support_table_data.rb](lib/support_table_data.rb)
132+
- Test models: [spec/models.rb](spec/models.rb) - comprehensive examples of patterns
133+
- Sync task: [lib/tasks/support_table_data.rake](lib/tasks/support_table_data.rake)
134+
- Architecture docs: [ARCHITECTURE.md](ARCHITECTURE.md) - detailed diagrams and design decisions
135+
136+
## Version Compatibility
137+
138+
- Ruby ≥ 2.5
139+
- ActiveRecord ≥ 6.0
140+
- Ruby 3.4+: Requires `csv` gem in Gemfile (removed from stdlib)

Appraisals

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# frozen_string_literal: true
22

3+
appraise "activerecord_8.1" do
4+
gem "activerecord", "~> 8.1.0"
5+
gem "sqlite3", "~> 2.9.0"
6+
end
7+
38
appraise "activerecord_8.0" do
49
gem "activerecord", "~> 8.0.0"
510
gem "sqlite3", "~> 2.5.0"

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,17 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

7+
## 1.4.1
8+
9+
### Added
10+
11+
- The default data directory in a Rails application can be set with the `config.support_table_data_directory` option in the Rails application configuration.
12+
- Added rake task `support_table_data:add_yard_docs` for Rails applications that will add YARD documentation to support table models for the named instance helpers.
13+
14+
### Changed
15+
16+
- The default data directory is now set in a Railtie and can be overridden with the `config.support_table_data_directory` option in the Rails application configuration.
17+
718
## 1.4.0
819

920
### Fixed

README.md

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,9 @@ class Status < ApplicationRecord
5555

5656
You cannot update the value of the key attribute in a record in the data file. If you do, a new record will be created and the existing record will be left unchanged.
5757

58-
You can specify data files as relative paths. This can be done by setting the `SupportTableData.data_directory` value. You can override this value for a model by setting the `support_table_data_directory` attribute on its class. In a Rails application, `SupportTableData.data_directory` will be automatically set to `db/support_tables/`. Otherwise, relative file paths will be resolved from the current working directory. You must define the directory to load relative files from before loading your model classes.
58+
You can specify data files as relative paths. This can be done by setting the `SupportTableData.data_directory` value. You can override this value for a model by setting the `support_table_data_directory` attribute on its class. Otherwise, relative file paths will be resolved from the current working directory. You must define the directory to load relative files from before loading your model classes.
59+
60+
In a Rails application, `SupportTableData.data_directory` will be automatically set to `db/support_tables/`. This can be overridden by setting the `config.support_table_data_directory` option in the Rails application configuration.
5961

6062
**Note**: If you're using CSV files and Ruby 3.4 or higher, you'll need to include the `csv` gem in your Gemfile since it was removed from the standard library in Ruby 3.4.
6163

@@ -109,7 +111,6 @@ Helper methods will not override already defined methods on a model class. If a
109111

110112
You can also define helper methods for named instance attributes. These helper methods will return the hard coded values from the data file. Calling these methods does not require a database connection.
111113

112-
113114
```ruby
114115
class Status < ApplicationRecord
115116
include SupportTableData
@@ -171,6 +172,19 @@ completed:
171172
group_name: done
172173
```
173174
175+
#### Documenting Named Instance Helpers
176+
177+
In a Rails application, you can add YARD documentation for the named instance helpers by running the rake task `support_table_data:add_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.
178+
179+
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.
180+
181+
```ruby
182+
# Begin YARD docs for support_table_data
183+
# End YARD docs for support_table_data
184+
```
185+
186+
A good practice is to add a check to your CI pipeline to ensure the documentation is always up to date.
187+
174188
### Caching
175189

176190
You can use the companion [support_table_cache gem](https://github.com/bdurand/support_table_cache) to add caching support to your models. That way your application won't need to constantly query the database for records that will never change.

Rakefile

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,6 @@ rescue LoadError
44
puts "You must `gem install bundler` and `bundle install` to run rake tasks"
55
end
66

7-
require "yard"
8-
YARD::Rake::YardocTask.new(:yard)
9-
107
require "bundler/gem_tasks"
118

129
task :verify_release_branch do
@@ -23,17 +20,3 @@ require "rspec/core/rake_task"
2320
RSpec::Core::RakeTask.new(:spec)
2421

2522
task default: :spec
26-
27-
desc "run the specs using appraisal"
28-
task :appraisals do
29-
exec "bundle exec appraisal rake spec"
30-
end
31-
32-
namespace :appraisals do
33-
desc "install all the appraisal gemspecs"
34-
task :install do
35-
exec "bundle exec appraisal install"
36-
end
37-
end
38-
39-
require "standard/rake"

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.4.0
1+
1.4.1

gemfiles/activerecord_8.1.gemfile

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# This file was generated by Appraisal
2+
3+
source "https://rubygems.org"
4+
5+
gem "rspec", "~> 3.0"
6+
gem "rake"
7+
gem "sqlite3", "~> 2.9.0"
8+
gem "appraisal"
9+
gem "standard", "~>1.0"
10+
gem "pry-byebug"
11+
gem "yard"
12+
gem "csv"
13+
gem "activerecord", "~> 8.1.0"
14+
15+
gemspec path: "../"

lib/support_table_data.rb

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
module SupportTableData
99
extend ActiveSupport::Concern
1010

11+
@data_directory = nil
12+
1113
included do
1214
# Internal variables used for memoization.
1315
@mutex = Mutex.new
@@ -342,19 +344,17 @@ def support_table_record_changed?(record, seen = Set.new)
342344
end
343345

344346
class << self
345-
# Specify the default directory for data files.
346-
attr_writer :data_directory
347+
# @attribute [r]
348+
# The the default directory where data files live.
349+
# @return [String, nil]
350+
attr_reader :data_directory
347351

348-
# The directory where data files live by default. If you are running in a Rails environment,
349-
# then this will be `db/support_tables`. Otherwise, the current working directory will be used.
352+
# Set the default directory where data files live.
350353
#
351-
# @return [String]
352-
def data_directory
353-
if defined?(@data_directory)
354-
@data_directory
355-
elsif defined?(Rails.root)
356-
Rails.root.join("db", "support_tables").to_s
357-
end
354+
# @param value [String, Pathname, nil] The path to the directory.
355+
# @return [void]
356+
def data_directory=(value)
357+
@data_directory = value&.to_s
358358
end
359359

360360
# Sync all support table classes. Classes must already be loaded in order to be synced.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# frozen_string_literal: true
2+
3+
module SupportTableData
4+
module Documentation
5+
end
6+
end
7+
8+
require_relative "documentation/source_file"
9+
require_relative "documentation/yard_doc"

0 commit comments

Comments
 (0)