hensei-api/docs/plans/weapon-series-migration.md
Justin Edmund ef7c158736 add implementation plans for artifacts, character series, weapon series
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-13 20:54:48 -08:00

196 lines
7.2 KiB
Markdown

# Plan: Convert WeaponSeries from Enum to Database Table
## Summary
Convert the hardcoded `SERIES_SLUGS` hash in `Weapon` model to a `weapon_series` database table with full CRUD API support, enabling dynamic management of weapon series without code deploys.
## New Table: `weapon_series`
| Column | Type | Notes |
|--------|------|-------|
| id | uuid | Primary key |
| name_en | string | English name |
| name_jp | string | Japanese name |
| slug | string | Unique identifier (e.g., 'dark-opus') |
| order | integer | Sort order for display |
| extra | boolean | Allowed in extra grid positions (9, 10, 11) |
| element_changeable | boolean | Weapon element can be changed |
| has_weapon_keys | boolean | Series supports weapon keys |
| has_awakening | boolean | Series supports awakenings |
| has_ax_skills | boolean | Series supports AX skills |
## Implementation Steps
### Phase 1: Database Setup
1. **Create `weapon_series` table migration**
- `db/migrate/TIMESTAMP_create_weapon_series.rb`
- UUID primary key, unique index on slug, index on order
2. **Add `weapon_series_id` to weapons table**
- `db/migrate/TIMESTAMP_add_weapon_series_id_to_weapons.rb`
- Foreign key reference, nullable initially
3. **Create `weapon_key_series` join table**
- `db/migrate/TIMESTAMP_create_weapon_key_series.rb`
- Many-to-many between weapon_keys and weapon_series
- Replaces the integer array `weapon_keys.series` column
### Phase 2: Models
4. **Create `WeaponSeries` model**
- File: `app/models/weapon_series.rb`
- Associations: `has_many :weapons`, `has_many :weapon_key_series`, `has_many :weapon_keys, through: :weapon_key_series`
- Validations: presence for name_en, name_jp, slug; uniqueness for slug
- Scopes: `ordered`, `extra_allowed`, `element_changeable`, etc.
5. **Create `WeaponKeySeries` join model**
- File: `app/models/weapon_key_series.rb`
- Belongs to weapon_key and weapon_series
6. **Update `Weapon` model**
- File: `app/models/weapon.rb`
- Add `belongs_to :weapon_series, optional: true`
- Update `opus_or_draconic?` to use `weapon_series.slug`
- Update `draconic_or_providence?` to use `weapon_series.slug`
- Update `element_changeable?` to use `weapon_series.element_changeable`
- Update `compatible_with_key?` to use new relationship
- Keep `SERIES_SLUGS` temporarily for backwards compatibility
7. **Update `WeaponKey` model**
- File: `app/models/weapon_key.rb`
- Add `has_many :weapon_key_series` and `has_many :weapon_series, through: :weapon_key_series`
8. **Update `GridWeapon` model**
- File: `app/models/grid_weapon.rb`
- Replace `ALLOWED_EXTRA_SERIES` constant with `weapon.weapon_series&.extra` check
### Phase 3: Data Migration
9. **Populate weapon_series table**
- `db/data/TIMESTAMP_populate_weapon_series.rb`
- Create 45 series records from existing `SERIES_SLUGS`
- Set boolean flags appropriately for each series
10. **Migrate weapons to use weapon_series_id**
- `db/data/TIMESTAMP_migrate_weapons_to_weapon_series.rb`
- Map legacy integer `series` to new `weapon_series_id`
11. **Migrate weapon_key series to join table**
- `db/data/TIMESTAMP_migrate_weapon_key_series.rb`
- Convert integer arrays to `weapon_key_series` records
### Phase 4: API
12. **Create `WeaponSeriesBlueprint`**
- File: `app/blueprints/api/v1/weapon_series_blueprint.rb`
- Fields: name (en/ja), slug, order
- Full view: include boolean flags
13. **Create `WeaponSeriesController`**
- File: `app/controllers/api/v1/weapon_series_controller.rb`
- Actions: index, show, create (admin), update (admin), destroy (admin)
- Lookup by slug or id
14. **Add routes**
- File: `config/routes.rb`
- `resources :weapon_series, only: [:index, :show, :create, :update, :destroy]`
15. **Update `WeaponBlueprint`**
- File: `app/blueprints/api/v1/weapon_blueprint.rb`
- Change `series` field to return object: `{ id, slug, name: { en, ja } }`
- Keep legacy `series` integer for backwards compatibility during transition
16. **Update `GridWeaponBlueprint`**
- File: `app/blueprints/api/v1/grid_weapon_blueprint.rb`
- Update weapon_keys conditional to use `weapon.weapon_series&.has_weapon_keys`
### Phase 5: Service Updates
17. **Update `WeaponProcessor`**
- File: `app/services/processors/weapon_processor.rb`
- Map incoming `series_id` integers to `weapon_series_id` UUIDs
- Update `element_changeable?` calls
18. **Update `CollectionWeapon` model**
- File: `app/models/collection_weapon.rb`
- Update `by_series` scope to use `weapon_series_id`
- Update validations to use new associations
### Phase 6: Testing
19. **Create `WeaponSeries` factory**
- File: `spec/factories/weapon_series.rb`
- Traits for common series types (opus, draconic, etc.)
20. **Update existing specs**
- Update weapon factory to use weapon_series association
- Add tests for new controller endpoints
- Add tests for model methods
## Files to Modify
| File | Changes |
|------|---------|
| `app/models/weapon.rb` | Add association, update methods |
| `app/models/weapon_key.rb` | Add associations |
| `app/models/grid_weapon.rb` | Replace ALLOWED_EXTRA_SERIES |
| `app/models/collection_weapon.rb` | Update scope and validations |
| `app/blueprints/api/v1/weapon_blueprint.rb` | Update series field |
| `app/blueprints/api/v1/grid_weapon_blueprint.rb` | Update conditional |
| `app/services/processors/weapon_processor.rb` | Update series lookups |
| `config/routes.rb` | Add weapon_series routes |
## New Files to Create
| File | Purpose |
|------|---------|
| `app/models/weapon_series.rb` | WeaponSeries model |
| `app/models/weapon_key_series.rb` | Join table model |
| `app/controllers/api/v1/weapon_series_controller.rb` | CRUD controller |
| `app/blueprints/api/v1/weapon_series_blueprint.rb` | API serializer |
| `db/migrate/*_create_weapon_series.rb` | Table migration |
| `db/migrate/*_add_weapon_series_id_to_weapons.rb` | FK migration |
| `db/migrate/*_create_weapon_key_series.rb` | Join table migration |
| `db/data/*_populate_weapon_series.rb` | Seed data |
| `db/data/*_migrate_weapons_to_weapon_series.rb` | Data migration |
| `db/data/*_migrate_weapon_key_series.rb` | WeaponKey data migration |
| `spec/factories/weapon_series.rb` | Test factory |
## Boolean Flags by Series
Key series with special flags:
- **element_changeable**: revenant, ultima, superlative, class-champion
- **extra**: xeno, cosmos, superlative, eternal-splendor, ancestral, militis, menace
- **has_weapon_keys**: grand, dark-opus, superlative, vyrmament, menace
- **has_awakening**: (to be determined based on current weapon data)
- **has_ax_skills**: (to be determined based on current weapon data)
## API Response Format
```json
// GET /weapon_series
[
{
"id": "uuid",
"name": { "en": "Dark Opus", "ja": "オプス" },
"slug": "dark-opus",
"order": 3
}
]
// GET /weapons/:id (updated series field)
{
"id": "uuid",
"series": {
"id": "series-uuid",
"slug": "dark-opus",
"name": { "en": "Dark Opus", "ja": "オプス" }
}
}
```
## Backwards Compatibility
- Keep legacy `series` integer column on weapons until frontend is updated
- API can return both formats during transition period
- `SERIES_SLUGS` constant can be removed after migration is complete