🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
196 lines
7.2 KiB
Markdown
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
|