|
| 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) |
0 commit comments