|
20 | 20 | //! *zoo*. [`recipe_concept_from_surface`] is the **lift-time resolver** |
21 | 21 | //! that maps a surface string to the shared canonical id, keeping the |
22 | 22 | //! string as the per-language label skin. Rails `belongs_to` and Odoo |
23 | | -//! `Many2one` resolve to the **same** [`recipe_ids::REL_MANY_TO_ONE`] — |
24 | | -//! the verb-side twin of `WorkPackage ≡ Issue ≡ 0x0102`. |
| 23 | +//! `many2one` (the lowercased `relation_kind` token ruff emits) resolve to |
| 24 | +//! the **same** [`recipe_ids::REL_MANY_TO_ONE`] — the verb-side twin of |
| 25 | +//! `WorkPackage ≡ Issue ≡ 0x0102`. |
25 | 26 | //! |
26 | 27 | //! Per RAILS-COVERAGE-KIT §5 the recipe vocabulary MUST converge the same |
27 | 28 | //! way class concepts do, or the behavioural arm fragments back into the |
|
37 | 38 | //! |---|---|---| |
38 | 39 | //! | `0x01` | [`RecipeFamily::Lifecycle`] | `before_save` / `after_commit` ‖ `@api.constrains` / `@depends` | |
39 | 40 | //! | `0x02` | [`RecipeFamily::Guard`] | `validates presence:/uniqueness:` ‖ `_check_*` | |
40 | | -//! | `0x03` | [`RecipeFamily::Relation`] | `belongs_to`/`has_many` ‖ `Many2one`/`One2many` | |
| 41 | +//! | `0x03` | [`RecipeFamily::Relation`] | `belongs_to`/`has_many` ‖ `many2one`/`one2many` | |
41 | 42 | //! | `0x04` | [`RecipeFamily::Action`] | controller `HandlerKind` ‖ `action_*` | |
42 | 43 | //! |
43 | 44 | //! Ids are stable forever: they only ARRIVE (append a new low byte / a new |
@@ -276,7 +277,7 @@ pub const RECIPE_CODEBOOK: &[(&str, RecipeConceptId)] = &[ |
276 | 277 | /// Per-language **label DTO** table — `(id, lang, surface)`. The surface |
277 | 278 | /// string is render-only and per-language; the id is the truth. Multiple |
278 | 279 | /// surfaces (across languages) map to one id — that IS the cross-consumer |
279 | | -/// convergence (Rails `belongs_to` ≡ Odoo `Many2one` → `rel_many_to_one`). |
| 280 | +/// convergence (Rails `belongs_to` ≡ Odoo `many2one` → `rel_many_to_one`). |
280 | 281 | /// |
281 | 282 | /// Append only; a new surface for an existing concept adds a row, mints |
282 | 283 | /// nothing (`RAILS-COVERAGE-KIT` §5 rule 2). Fuzzy Odoo body-pattern |
@@ -347,13 +348,18 @@ const RECIPE_LABELS: &[(RecipeConceptId, RecipeLang, &str)] = &[ |
347 | 348 | ), |
348 | 349 | (recipe_ids::REL_ONE_TO_ONE, RecipeLang::Ruby, "has_one"), |
349 | 350 | (recipe_ids::REL_THROUGH, RecipeLang::Ruby, "through"), |
350 | | - // Relation — Python/Odoo field kinds (the convergence pins). |
351 | | - (recipe_ids::REL_MANY_TO_ONE, RecipeLang::Python, "Many2one"), |
352 | | - (recipe_ids::REL_ONE_TO_MANY, RecipeLang::Python, "One2many"), |
| 351 | + // Relation — Python/Odoo field kinds (the convergence pins). The |
| 352 | + // surface is the LOWERCASED `relation_kind` token ruff emits (Odoo's |
| 353 | + // `fields.Many2one(...)` constructor lowercased at harvest — |
| 354 | + // `ruff_python_spo::walk` `kind.to_lowercase()`, `ruff_spo_triplet` |
| 355 | + // `ir.rs` Field::relation_kind, `ogar_from_ruff::odoo_relation_kind`), |
| 356 | + // NOT the capitalized source constructor. Match the emitted token. |
| 357 | + (recipe_ids::REL_MANY_TO_ONE, RecipeLang::Python, "many2one"), |
| 358 | + (recipe_ids::REL_ONE_TO_MANY, RecipeLang::Python, "one2many"), |
353 | 359 | ( |
354 | 360 | recipe_ids::REL_MANY_TO_MANY, |
355 | 361 | RecipeLang::Python, |
356 | | - "Many2many", |
| 362 | + "many2many", |
357 | 363 | ), |
358 | 364 | // Action — controller HandlerKind surfaces (framework-neutral ids; |
359 | 365 | // tagged Ruby as the harvest frontend that produces them today). |
@@ -426,7 +432,7 @@ pub fn recipe_concept_name(id: RecipeConceptId) -> Option<&'static str> { |
426 | 432 | /// // resolve to ONE canonical concept. |
427 | 433 | /// assert_eq!( |
428 | 434 | /// recipe_concept_from_surface("belongs_to", RecipeLang::Ruby), |
429 | | -/// recipe_concept_from_surface("Many2one", RecipeLang::Python), |
| 435 | +/// recipe_concept_from_surface("many2one", RecipeLang::Python), |
430 | 436 | /// ); |
431 | 437 | /// assert_eq!( |
432 | 438 | /// recipe_concept_from_surface("belongs_to", RecipeLang::Ruby), |
@@ -568,12 +574,15 @@ mod tests { |
568 | 574 | /// relation surfaces resolve to ONE canonical recipe concept. |
569 | 575 | #[test] |
570 | 576 | fn relation_verbs_converge_across_ruby_and_python() { |
| 577 | + // The Python surface is the lowercased `relation_kind` token ruff |
| 578 | + // emits (`ruff_python_spo` lowercases the `fields.Many2one(...)` |
| 579 | + // constructor at harvest), NOT the capitalized source name. |
571 | 580 | let pairs = [ |
572 | | - ("belongs_to", "Many2one", recipe_ids::REL_MANY_TO_ONE), |
573 | | - ("has_many", "One2many", recipe_ids::REL_ONE_TO_MANY), |
| 581 | + ("belongs_to", "many2one", recipe_ids::REL_MANY_TO_ONE), |
| 582 | + ("has_many", "one2many", recipe_ids::REL_ONE_TO_MANY), |
574 | 583 | ( |
575 | 584 | "has_and_belongs_to_many", |
576 | | - "Many2many", |
| 585 | + "many2many", |
577 | 586 | recipe_ids::REL_MANY_TO_MANY, |
578 | 587 | ), |
579 | 588 | ]; |
|
0 commit comments