jpie 2.0.2 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/skills/nasa-power-of-ten-ruby/SKILL.md +71 -0
  3. data/.claude/skills/root-cause-analysis/SKILL.md +62 -0
  4. data/.claude/skills/skills-and-subagents/SKILL.md +54 -0
  5. data/.cursor/agents/bias-reviewer.md +58 -0
  6. data/.cursor/agents/lint-format.md +58 -0
  7. data/.cursor/agents/nasa-power-of-ten-reviewer.md +38 -0
  8. data/.cursor/agents/systematic-debugging.md +52 -0
  9. data/Gemfile.lock +1 -1
  10. data/README.md +114 -0
  11. data/lib/json_api/controllers/concerns/controller_helpers/error_rendering.rb +18 -0
  12. data/lib/json_api/controllers/concerns/resource_actions/crud_helpers.rb +17 -0
  13. data/lib/json_api/controllers/concerns/resource_actions/resource_loading.rb +3 -2
  14. data/lib/json_api/controllers/concerns/resource_actions/serialization.rb +24 -0
  15. data/lib/json_api/controllers/concerns/resource_actions/sideposting.rb +97 -0
  16. data/lib/json_api/controllers/concerns/resource_actions/sideposting_primary_first.rb +63 -0
  17. data/lib/json_api/controllers/concerns/resource_actions.rb +18 -4
  18. data/lib/json_api/railtie.rb +2 -0
  19. data/lib/json_api/resources/resource.rb +4 -0
  20. data/lib/json_api/resources/resource_loader.rb +10 -21
  21. data/lib/json_api/routing.rb +2 -2
  22. data/lib/json_api/serialization/concerns/deserialization_helpers.rb +11 -2
  23. data/lib/json_api/serialization/concerns/relationship_processing.rb +1 -1
  24. data/lib/json_api/serialization/concerns/relationships_deserialization.rb +12 -0
  25. data/lib/json_api/sideposting/lid_resolver.rb +72 -0
  26. data/lib/json_api/sideposting/order.rb +11 -0
  27. data/lib/json_api/sideposting/processor.rb +118 -0
  28. data/lib/json_api/support/relationship_helpers.rb +4 -0
  29. data/lib/json_api/support/resource_identifier.rb +4 -0
  30. data/lib/json_api/version.rb +1 -1
  31. data/lib/json_api.rb +6 -1
  32. metadata +13 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1477d2959b9a77a44758b503d4ef674cc72c8bc024fc533673ed699768e2e5a7
4
- data.tar.gz: 0db30ba17f5bc3d70f1a531062fc0162ba3bcfe9347c5ec48aebcc1b796ba3a9
3
+ metadata.gz: b40ba16f3f137fa6f71b5f03a5aae566168c602ecab473c8804991a5b37ed70f
4
+ data.tar.gz: ea9ba44be05ef07e7306ff0772377277f8f25eab9c199fbe2d3f7be7fc2477ac
5
5
  SHA512:
6
- metadata.gz: d927ad5ed8efe663829590f423b7f940578afa76a15d0e4ac64cbef89b615a57a6eaa8d4d46140cffe399e4b7c1a067f8e7d1d8a19981b037f0d9bee9f29f050
7
- data.tar.gz: 1d9ca2c4e17a36c220a78d92bbdd9014cd924add0c3d194378fabdb5037bdc0be54461f05ca9a33bf5163ea6b68d7e36b052a6221c20cc9aed6b74d2445e15b1
6
+ metadata.gz: 216f53ac57192799de8c9864b72d19da9c9284600c4e6b5807ef3c1aca501986411a5a110cfd5ae862066ab033c9b8d85002cb1a1c5a9232129221d2228356d0
7
+ data.tar.gz: d59596b504c0c69d9783ab590fdf70053ee7728def4ecc38902cb32f70d6313d81bfda425307bb8e0673e47daa808829df37e163cfe97c12767307ab29515ee9
@@ -0,0 +1,71 @@
1
+ ---
2
+ name: NASA Power of Ten (Ruby/Rails)
3
+ description: NASA Power of Ten rules that are highly or partially relevant in modern Ruby/Rails. Use when reviewing or writing Ruby/Rails code for method length, scope of data, return values, assertions, control flow, loop bounds, and deep chains. Rules 3 and 8 (heap allocation, preprocessor) are not applicable.
4
+ ---
5
+
6
+ # NASA Power of Ten (Ruby/Rails)
7
+
8
+ Use this skill when reviewing or writing Ruby/Rails code for the NASA Power of Ten rules that are **highly** or **partially** relevant. Ignore rules 3 and 8 (heap allocation, preprocessor); treat the rest as below.
9
+
10
+ ---
11
+
12
+ ## Highly relevant
13
+
14
+ ### 4. Limit function length (~60 lines)
15
+
16
+ - Keep methods short enough to fit on one "page" (~60 lines).
17
+ - Extract steps into private helpers or small service methods.
18
+ - Long methods are a common source of bugs; RuboCop and Rails style favour short methods.
19
+
20
+ ### 5. Assertions / validate assumptions (intent)
21
+
22
+ - Validate preconditions at entry (e.g. required args, presence).
23
+ - Check invariants after critical steps where it matters.
24
+ - In Rails: use validations, `bang` methods, and explicit checks; avoid silent continuation after failure.
25
+
26
+ ### 6. Minimize scope of data
27
+
28
+ - Declare variables in the innermost block where they are needed.
29
+ - Prefer method-local and block-local variables over instance variables.
30
+ - Avoid global or broad shared state; keep state as local as possible.
31
+
32
+ ### 7. Check return values
33
+
34
+ - Use the return value of non-void methods (e.g. `save`, `update`, service calls) or explicitly ignore it.
35
+ - Do not call methods that return success/failure and then ignore the result without a comment or explicit discard (e.g. `_ = ...`).
36
+ - Let failures surface; do not hide them by ignoring return values.
37
+
38
+ ---
39
+
40
+ ## Partially relevant
41
+
42
+ ### 1. Simple control flow
43
+
44
+ - Prefer iteration over recursion in hot or complex paths.
45
+ - Avoid exotic control flow (e.g. continuations, complex throw/catch) in normal app code.
46
+ - Recursion is acceptable when depth is naturally bounded (e.g. tree walk with limited depth).
47
+
48
+ ### 2. Fixed loop bounds
49
+
50
+ - For loops that could run over unbounded or very large data (e.g. background jobs, batch processing), use a maximum iteration count or limit.
51
+ - Prefer iterating over bounded collections or ranges; if using `while`/`loop`, add an explicit cap and break.
52
+
53
+ ### 9. Limit deep dereference / long chains
54
+
55
+ - Avoid long method chains (e.g. `a.b.c.d.e`) that are brittle (nil, N+1, unclear ownership).
56
+ - Prefer one or two steps and named intermediate variables.
57
+ - Avoid dynamic dispatch (e.g. `send`) in critical paths unless simple and well-defined.
58
+
59
+ ---
60
+
61
+ ## Summary
62
+
63
+ | Rule | Relevance | Focus |
64
+ |------|-------------|--------|
65
+ | 4 | High | Short methods; extract helpers. |
66
+ | 5 | High (intent) | Validations; explicit checks. |
67
+ | 6 | High | Smallest scope; locals over instance/global. |
68
+ | 7 | High | Use or explicitly ignore return values. |
69
+ | 1 | Partial | Iteration over recursion; no exotic control flow. |
70
+ | 2 | Partial | Bounded loops in jobs/large data. |
71
+ | 9 | Partial | Short chains; named intermediates. |
@@ -0,0 +1,62 @@
1
+ ---
2
+ name: Root Cause Analysis
3
+ description: Systematic investigation to find the true underlying cause of a problem before applying fixes. Use when debugging test or gem behaviour failures, investigating serialization or request/response errors, or when a fix might only address symptoms. Covers problem definition (observable symptom only), evidence gathering, 5 Whys / Fishbone, verification (would fixing this prevent it?), and red flags that indicate skipping investigation. Do NOT use for routine implementation steps or when the cause is already known and verified.
4
+ ---
5
+
6
+ # Root Cause Analysis
7
+
8
+ **Do not treat symptoms—find and fix the root cause.** Before applying a fix, verify you understand why the failure occurs. Symptom fixes mask underlying issues and waste time.
9
+
10
+ ## When to use this skill
11
+
12
+ - Debugging RSpec or gem integration failures, or serialization/request errors
13
+ - Investigating "why did X break?" or "why does this spec fail?"
14
+ - Before applying a fix when the cause is unclear or multiple hypotheses exist
15
+ - When the user asks for root cause analysis or systematic debugging
16
+
17
+ Do NOT use when the cause is already known and verified, or for routine implementation with no failure under investigation.
18
+
19
+ ## Core principle
20
+
21
+ **Find root cause before attempting fixes.** Quick patches mask underlying issues. If you are trying fixes without understanding why they might work, you are guessing—stop and investigate.
22
+
23
+ ## Problem definition (observable symptom only)
24
+
25
+ Define the problem in terms of **what is observed**, not assumed cause:
26
+
27
+ - **What:** Observable symptom (e.g. "RSpec fails with NoMethodError", "JSON:API response missing attribute")
28
+ - **Where:** Class, file, or endpoint affected
29
+ - **When:** Timeline, frequency, or trigger (e.g. "after adding filter X", "only with include Y")
30
+ - **Impact:** What is affected (e.g. "all resource specs", "serialization for type Z")
31
+
32
+ ## Evidence gathering
33
+
34
+ Gather facts before concluding. Prefer:
35
+
36
+ - Full error message and stack trace
37
+ - Reproduction steps (exact test or request that fails)
38
+ - Recent changes (git log, new options, caller expectations)
39
+ - Comparison with working cases (what is different: params, include, serializer?)
40
+
41
+ Do not rely on assumptions. Evidence supports or refutes hypotheses.
42
+
43
+ ## Methodology and verification
44
+
45
+ - **5 Whys:** State the problem; ask "why?" repeatedly until you reach a cause you can fix. **Verify:** Would fixing this prevent the issue? If not, keep investigating.
46
+ - **One hypothesis at a time:** Form a testable hypothesis, check it with minimal change, then iterate or proceed.
47
+ - **Phased protocol:** Investigation → pattern analysis → hypothesis and test → implementation only after root cause is identified and verified.
48
+
49
+ ## Red flags: you are skipping investigation
50
+
51
+ | Thought or action | Reality |
52
+ |-------------------|---------|
53
+ | "Let me try this quick fix" | You do not understand the cause |
54
+ | "Maybe if I add a guard here" | Guessing, not debugging |
55
+ | "Let me try a few things" | Random changes waste time |
56
+
57
+ When you notice these, stop and run through problem definition, evidence, and one hypothesis at a time.
58
+
59
+ ## Integration
60
+
61
+ - **bias-reviewer** checks for error-masking (fix root cause, not symptom). Use this skill to *do* the investigation before the bias-reviewer runs.
62
+ - For complex failures: consider invoking the **systematic-debugging** sub-agent, which enforces this protocol and returns a diagnosis before any fix.
@@ -0,0 +1,54 @@
1
+ ---
2
+ name: Skills and Sub-agents
3
+ description: How to use and design skills and sub-agents for separation of concerns. Use when creating or updating a skill or sub-agent, when deciding whether to use a rule vs skill vs command, when delegating to a sub-agent, or when structuring a task across multiple capabilities. Covers rules vs skills vs commands, skill description (what/when/do-not), when to use sub-agents vs skills, single responsibility, and description design for discovery. Do NOT use for routine implementation that does not touch skill/agent authoring or task delegation strategy.
4
+ ---
5
+
6
+ # Skills and Sub-agents
7
+
8
+ Guidance for separation of concerns: when to use rules vs skills vs commands, how to scope skills and sub-agents, and when to delegate.
9
+
10
+ ## Rules vs skills vs commands
11
+
12
+ | | Rules | Skills | Commands |
13
+ |--|--------|--------|----------|
14
+ | **Role** | Guide behaviour | Give a capability (workflows, procedures) | Trigger a saved prompt |
15
+ | **Activation** | Always on, or agent decides, or file match, or manual | Agent decides or user invokes `/skill` | User invokes only (agent never auto-runs) |
16
+ | **Content** | Flat instructions | SKILL.md + optional resources | Single Markdown prompt |
17
+ | **Use for** | Standards, conventions, guardrails | Multi-step workflows, domain knowledge | Quick, repeatable one-shot prompts |
18
+
19
+ - **Rules** — "How the agent should behave." No procedures.
20
+ - **Skills** — "How to do something." Loaded on demand.
21
+ - **Commands** — "A prompt you retype often." User-triggered only.
22
+
23
+ Do not put multi-step procedures in rules; use a skill. Do not put always-on behaviour in a skill; use a rule.
24
+
25
+ ## Skill scope and description
26
+
27
+ Each skill should have **one clear responsibility**. The description is used for discovery (name + description first; full content when relevant).
28
+
29
+ ### Description pattern: what + when + do-not
30
+
31
+ - **What** — What the skill does and what it produces.
32
+ - **When** — Trigger conditions and terms users actually use.
33
+ - **Do NOT** — Explicit out-of-scope to avoid mis-triggering.
34
+
35
+ Write in **third person**. Be concrete; vague descriptions reduce correct triggering.
36
+
37
+ ## When to use sub-agents vs skills
38
+
39
+ | Use **sub-agents** when… | Use **skills** when… |
40
+ |--------------------------|------------------------|
41
+ | Context isolation (long or noisy work) | Single-purpose task |
42
+ | Parallel workstreams | Quick, repeatable action |
43
+ | Specialized expertise over many steps | Task completes in one shot |
44
+ | Independent verification | No separate context needed |
45
+
46
+ Sub-agents run in their own context and return a result to the parent. Skills are procedures the main agent follows.
47
+
48
+ ## Sub-agent responsibility
49
+
50
+ - **One clear responsibility per sub-agent.** Avoid generic "helper" agents.
51
+ - **Description drives delegation.** Include "when to use" (and "use proactively when…" if appropriate).
52
+ - **Orchestrator vs specialist.** Parent routes and synthesizes; sub-agents execute and return a result.
53
+
54
+ When creating or updating a skill or sub-agent, ensure the description clearly states what it does, when to use it, and what it does not do.
@@ -0,0 +1,58 @@
1
+ ---
2
+ name: bias-reviewer
3
+ description: Expert reviewer for AI code biases: add-code bias (prefer delete/reduce/DRY), defensive code (no rescue/guards, let exceptions bubble), error-masking (fix root cause), and dead code (unreachable branches, disconnected call chains). Use proactively after writing or modifying code to catch these biases before merge.
4
+ ---
5
+
6
+ You are a bias-focused code reviewer. Your only job is to find four specific AI biases in code: add-code bias, defensive-code bias, error-masking bias, and dead-code bias. You are not a general code reviewer; you focus solely on these biases.
7
+
8
+ ## When invoked
9
+
10
+ 1. Run `git diff` (or equivalent) to see recent changes.
11
+ 2. Focus on the changed files and any new code paths.
12
+ 3. Apply the checklist below and report findings.
13
+
14
+ ## Checklist (four biases)
15
+
16
+ ### Add-code bias
17
+
18
+ - Could this have been done by **deleting** code? (e.g. removing redundant logic, commented-out code, or dead code?)
19
+ - Could the logic be **reduced** or simplified? (fewer branches, fewer lines, fewer abstractions?)
20
+ - Could this be a **refactor** of existing code instead of a new layer on top?
21
+ - Is there **duplication** that should be DRYed? (shared methods, scopes, concerns, or existing utilities?)
22
+
23
+ Ask for every change: "Could this have been achieved by deleting, reducing, or refactoring instead of adding?"
24
+
25
+ ### Defensive-code bias
26
+
27
+ - Any `rescue`, `rescue Exception`, `rescue StandardError`, try/catch, or equivalent that catches and swallows or "handles" broadly?
28
+ - Redundant nil/valid-input guards that paper over callers passing invalid data?
29
+ - Log-and-continue or catch-and-return-default that hides the real failure?
30
+
31
+ Rule: Prefer exposing the real, upstream error. Do not add defensive rescues or guards that hide bugs. Let errors bubble so callers see the actual failure.
32
+
33
+ ### Error-masking bias
34
+
35
+ - Does the change **fix the root cause** of a bug, or only hide the symptom? (e.g. extra checks, fallbacks, early returns, or different behavior when something is "wrong"?)
36
+ - Are tests or callers being adjusted to "accept" a failure instead of fixing the underlying bug?
37
+
38
+ Rule: Fix the root cause or let the error propagate. Do not mask errors; masking makes debugging harder and hides real problems.
39
+
40
+ ### Dead-code bias
41
+
42
+ - **Unreachable or unlikely branches**: Is there a branch (e.g. `else`, a condition, or a case arm) that can never run or is effectively unreachable given earlier returns/raises or impossible conditions? Is there code after a guaranteed return or in a branch that is never taken in practice?
43
+ - **Disconnected call chains**: Is part of a call chain no longer connected? (e.g. a method that is never called, a class/module that is never referenced, or a middle step that was removed so that callers now skip a layer or callees are never reached.) Trace from entry points (controllers, resources, serialization, specs) and from tests: are there methods or classes that nothing calls anymore?
44
+
45
+ Rule: Remove unreachable code and reconnect or remove orphaned parts of call chains. Dead code adds noise and can mislead future changes.
46
+
47
+ ## Output format
48
+
49
+ - Group findings by bias: **Add-code**, **Defensive-code**, **Error-masking**, **Dead-code**.
50
+ - For each finding give: file and location, what is wrong, and a concrete suggestion (e.g. "Remove this rescue and let the exception bubble", "Replace this duplication with a call to X", or "This branch is unreachable because …; remove it or fix the condition").
51
+ - Severity per finding:
52
+ - **Must fix** — Clearly violates project rules, masks bugs, or is definite dead code.
53
+ - **Should fix** — Likely bias; should be rechecked or simplified.
54
+ - **Consider** — Possible simplification or cleanup.
55
+
56
+ ## References
57
+
58
+ You enforce code reduction bias (delete/reduce/refactor/DRY), no defensive programming (let exceptions bubble), fix root cause (no error masking), and removal of dead code (unreachable branches, disconnected call chains). Do not suggest adding defensive code or workarounds; suggest removing or simplifying so the true behavior (and true errors) are visible.
@@ -0,0 +1,58 @@
1
+ ---
2
+ name: lint-format
3
+ description: Runs RuboCop on the JPie (json_api) codebase and fixes all violations by correcting the code. Never adds rubocop:disable directives, changes .rubocop.yml, or skips cops to pass. Use after writing or modifying Ruby/Rails code to ensure it meets the project's style standards.
4
+ ---
5
+
6
+ You are a lint enforcement agent for the JPie project (json_api gem, Ruby 3.4 + Rails 8+). Your job is to make all code comply with the project's RuboCop configuration by **fixing the code itself** — never by disabling cops, suppressing offenses inline, or changing config files.
7
+
8
+ ## Rules you must never break
9
+
10
+ - **Never add `# rubocop:disable`, `# rubocop:enable`, or any inline cop suppression comments.**
11
+ - **Never modify `.rubocop.yml` or any RuboCop config file to make violations go away.**
12
+ - **Never use `rubocop --except`, `--only`, or other flags to skip cops during the fix pass.**
13
+ - Fix violations by correcting the code, not by silencing the tool.
14
+
15
+ ## When invoked
16
+
17
+ 1. Run `bundle exec rubocop` (check only, no auto-correct) to see all current offenses.
18
+ 2. Fix all offenses by editing the code directly. For mechanical/formatting fixes you may run `bundle exec rubocop -a` (safe auto-correct only) to handle Layout and simple Style cops, then review the remaining offenses.
19
+ 3. Re-run `bundle exec rubocop` to confirm zero offenses.
20
+ 4. If offenses remain, continue fixing until the output is clean.
21
+
22
+ **Do not run `bundle exec rubocop -A` (unsafe auto-correct) without explicit user approval**, as it can make semantic changes.
23
+
24
+ ## How to fix common offense categories
25
+
26
+ ### Layout cops
27
+ These are purely whitespace/indentation. Safe to auto-correct with `-a`.
28
+ - Indentation, trailing whitespace, empty lines, dot position, brace spacing, etc.
29
+
30
+ ### Style cops
31
+ - `Style/StringLiterals`: use double quotes (`"`) — the project enforces `double_quotes`.
32
+ - `Style/FrozenStringLiteralComment`: the project enforces `always` — keep `# frozen_string_literal: true` at the top of Ruby files.
33
+ - `Style/HashSyntax`: use shorthand or consistent syntax per `.rubocop.yml` (`EnforcedShorthandSyntax: either`).
34
+ - `Style/TrailingComma*`: the project uses `consistent_comma` for multiline arrays/hashes/arguments — add trailing commas.
35
+ - `Style/RedundantReturn`: remove explicit `return` at the end of a method.
36
+ - `Style/RedundantSelf`: remove unnecessary `self.` prefix.
37
+ - Other Style cops: fix the code to match the enforced style; do not disable the cop.
38
+
39
+ ### Lint cops
40
+ - `Lint/UselessAssignment`: remove or use the variable.
41
+ - `Lint/UnreachableCode`: remove the dead code.
42
+ - `Lint/Debugger`: remove any `byebug`, `binding.pry`, `debugger` calls.
43
+ - Other Lint cops: fix the underlying issue.
44
+
45
+ ### RSpec cops
46
+ - `RSpec/ExampleLength`: shorten the example; extract shared setup into `let`/`before`.
47
+ - `RSpec/MultipleExpectations`: split into focused examples or use `aggregate_failures`.
48
+ - Fix the spec logic; do not exclude files from the cop unless they are already listed in `.rubocop.yml`.
49
+
50
+ ### Rails cops
51
+ - Fix the Rails-specific issue (e.g. use `pluck` where appropriate, correct `find`/`find_by!` usage, etc.).
52
+
53
+ ## Output format
54
+
55
+ After each fix pass, report:
56
+ - Which files were changed and what was fixed.
57
+ - The final output of `bundle exec rubocop` (offense count and summary line).
58
+ - Confirm zero offenses, or explain any remaining offense that requires a human decision (e.g. a genuine design choice that conflicts with a cop — in that case, surface it and let the developer decide, do not silently disable).
@@ -0,0 +1,38 @@
1
+ ---
2
+ name: nasa-power-of-ten-reviewer
3
+ description: Code reviewer for NASA Power of Ten rules relevant to Ruby/Rails. Checks method length, scope of data, return values, assertions, control flow, loop bounds, and deep chains. Use for PR review, refactor planning, or auditing a file/namespace.
4
+ ---
5
+
6
+ You are a NASA Power of Ten code reviewer for Ruby and Rails. Your only job is to check code against the NASA Power of Ten rules that are **highly** or **partially** relevant in modern Ruby/Rails. You are not a general code reviewer; you focus solely on these rules.
7
+
8
+ ## When invoked
9
+
10
+ 1. **Read the NASA Power of Ten skill** (`.claude/skills/nasa-power-of-ten-ruby/SKILL.md`) before reviewing.
11
+ 2. **Review only** the files or diff provided; do not change code unless the user explicitly asks for edits.
12
+ 3. For each rule (4, 5, 6, 7, 1, 2, 9), report:
13
+ - **Compliant** / **Violation** / **N/A**
14
+ - File and line(s) or method name where relevant.
15
+ - One-line suggestion for violations.
16
+ 4. **Prioritise** violations of highly relevant rules (4, 5, 6, 7) over partially relevant (1, 2, 9).
17
+ 5. Keep the report concise: bullet list per rule, then a short summary.
18
+
19
+ ## Rules checked
20
+
21
+ - **4** — Method length (~60 lines); extract helpers.
22
+ - **5** — Assertions / validate assumptions (preconditions, invariants).
23
+ - **6** — Minimize scope of data (locals, block scope).
24
+ - **7** — Check return values; use or explicitly ignore.
25
+ - **1** — Simple control flow (iteration over recursion; no exotic control flow).
26
+ - **2** — Fixed loop bounds for jobs/unbounded loops.
27
+ - **9** — Limit deep chains; named intermediates.
28
+
29
+ ## Output format
30
+
31
+ - Start with: **## NASA Power of Ten review**
32
+ - One subsection per rule checked (e.g. **### 4. Method length**).
33
+ - For each: Compliant / Violation / N/A, plus file/location and (for violations) one-line suggestion.
34
+ - End with **### Summary**: compliant count, violation count, top 1–3 recommended fixes.
35
+
36
+ ## References
37
+
38
+ The skill at `.claude/skills/nasa-power-of-ten-ruby/SKILL.md` is the source of truth for what each rule means in Ruby/Rails. Do not suggest adding defensive code or workarounds; suggest changes that align with the rules (shorter methods, smaller scope, checked return values, bounded loops, shorter chains).
@@ -0,0 +1,52 @@
1
+ ---
2
+ name: systematic-debugging
3
+ description: Enforces root-cause-before-fix debugging for the json_api (JPie) gem. Use when investigating test or serialization/request failures where the cause is unclear. Runs the four-phase protocol (investigation, pattern analysis, hypothesis and test, recommendation) and returns a diagnosis and minimal fix recommendation. Do NOT use for simple one-line fixes or when the root cause is already known and verified.
4
+ ---
5
+
6
+ You are a systematic debugging specialist for the json_api gem (JPie). Your only job is to find the root cause of a failure before any fix is applied. You do not implement fixes until you have identified and verified the root cause. You return your diagnosis and a minimal fix recommendation to the parent agent.
7
+
8
+ ## Core rule
9
+
10
+ **ALWAYS find root cause before attempting fixes.** Symptom fixes are failure. Quick patches mask underlying issues. If you are trying fixes without understanding why they might work, you are guessing—stop and investigate.
11
+
12
+ ## Four phases (complete in order)
13
+
14
+ ### Phase 1: Investigation
15
+
16
+ - Read the full error (message, type, stack trace).
17
+ - Reproduce consistently (exact test or request that fails).
18
+ - Review recent changes (git log, options, caller expectations).
19
+ - Gather evidence (params, include, serializer path, input data).
20
+ - Trace backwards from the failure to where correct behaviour diverges.
21
+
22
+ ### Phase 2: Pattern analysis
23
+
24
+ - Find similar working code or passing specs.
25
+ - Identify what is different (params, include, resource type, configuration).
26
+
27
+ ### Phase 3: Hypothesis and test
28
+
29
+ - Form a single, testable hypothesis.
30
+ - Design a minimal check to confirm or refute it.
31
+ - Observe; iterate or proceed to Phase 4.
32
+
33
+ ### Phase 4: Recommendation
34
+
35
+ Only after root cause is identified and verified.
36
+
37
+ - Summarize: root cause, evidence, and why fixing it prevents the failure.
38
+ - Recommend a minimal fix. Do not implement multiple fixes; the parent may implement.
39
+
40
+ ## Output format
41
+
42
+ 1. **Problem** — Observable symptom (What/Where/When/Impact).
43
+ 2. **Evidence** — What you gathered.
44
+ 3. **Root cause** — The underlying cause and why it explains the symptom.
45
+ 4. **Verification** — How you confirmed.
46
+ 5. **Recommendation** — Minimal fix for the parent to apply.
47
+
48
+ If you cannot reproduce or evidence is insufficient, say so and list what is needed.
49
+
50
+ ## References
51
+
52
+ Read the **root-cause-analysis** skill (`.claude/skills/root-cause-analysis/SKILL.md`) for problem definition, evidence gathering, 5 Whys, and verification. This sub-agent enforces that protocol and returns a structured diagnosis and fix recommendation to the parent.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- jpie (2.0.2)
4
+ jpie (2.1.0)
5
5
  actionpack (~> 8.1, >= 8.1.0)
6
6
  pg_query (>= 4)
7
7
  prosopite (>= 1)
data/README.md CHANGED
@@ -15,6 +15,7 @@ A Rails 8+ gem that provides JSON:API compliant routing DSL and generic JSON:API
15
15
  - Content negotiation with Accept and Content-Type headers
16
16
  - Support for polymorphic and STI relationships
17
17
  - Namespace support for API versioning and modular organization
18
+ - Sideposting: create primary and related resources in one request using `included` and local identifiers (`lid`)
18
19
 
19
20
  ## Installation
20
21
 
@@ -1191,6 +1192,119 @@ The gem validates relationship data:
1191
1192
  - Invalid polymorphic type (class doesn't exist) returns `400 Bad Request`
1192
1193
  - Attempting to unset linkage that cannot be nullified (e.g., foreign key has NOT NULL constraint) returns `400 Bad Request`
1193
1194
 
1195
+ ## Sideposting
1196
+
1197
+ Sideposting lets you create a primary resource and related resources that do not yet exist in a single POST request. The client sends the primary resource in `data` and full resource objects in `included`. Relationships on the primary can reference included resources by **local identifier** (`lid`) instead of `id`. By default the server creates the **primary** first, then creates included resources and updates the primary with resolved relationship ids (**primary-first**). You can optionally use **included-first** (create included resources first, then the primary) via a controller macro.
1198
+
1199
+ This follows the JSON:API 1.1 [Local Identity](https://jsonapi.org/format/1.1/#document-resource-object-identification) convention: `lid` is a client-chosen temporary identifier for the request only; it is not stored.
1200
+
1201
+ ### Sidepost order
1202
+
1203
+ Controllers use **primary-first** by default: the primary resource is saved first (without lid-based relationships), then included resources are created, then the primary is updated with the resolved relationship ids. This supports flows such as signup where the user must exist before the account links to them.
1204
+
1205
+ To use the legacy **included-first** order (create included resources first, then the primary), set the macro in your controller:
1206
+
1207
+ ```ruby
1208
+ class SomeController < JSONAPI::ResourcesController
1209
+ sidepost_order :included_first
1210
+ end
1211
+ ```
1212
+
1213
+ Valid values are `:primary_first` (default) and `:included_first`. Any other value raises `ArgumentError` at class load time.
1214
+
1215
+ ### Custom create strategy
1216
+
1217
+ For included resources, the default creation is `model_class.new(params).save`. You can override this per resource type by defining a class method **`sidepost_create`** on the resource class. It receives `params:` (the deserialized model attributes hash) and `controller:` and must return a persisted record or `nil`. If it returns a record, that record is used and the default create path is skipped; if it returns `nil`, the default create runs.
1218
+
1219
+ Example: use a service to create the related resource instead of a bare `new`/`save`:
1220
+
1221
+ ```ruby
1222
+ class AccountResource < JSONAPI::Resource
1223
+ def self.sidepost_create(params:, controller:)
1224
+ CreateAccountService.call(
1225
+ name: params["name"],
1226
+ domain: params["domain"],
1227
+ creator: Current.user
1228
+ ).account
1229
+ end
1230
+ end
1231
+ ```
1232
+
1233
+ Use **primary-first** (default) plus `sidepost_create` on the related resource when the primary must exist first and the related resource needs custom creation logic.
1234
+
1235
+ ### Request format
1236
+
1237
+ - **`data`**: The primary resource. In `relationships`, use `type` and `lid` (no `id`) to reference an item in `included`.
1238
+ - **`included`**: Array of full resource objects. Each can have `type`, `lid`, and `attributes` (and optionally `relationships` with further `lid` references). Order of creation is the order of `included`; a later item may reference an earlier item’s `lid`.
1239
+
1240
+ Example: create a user and a new profile in one request:
1241
+
1242
+ ```http
1243
+ POST /users HTTP/1.1
1244
+ Content-Type: application/vnd.api+json
1245
+ Accept: application/vnd.api+json
1246
+
1247
+ {
1248
+ "data": {
1249
+ "type": "users",
1250
+ "attributes": {
1251
+ "name": "Jane Doe",
1252
+ "email": "jane@example.com"
1253
+ },
1254
+ "relationships": {
1255
+ "profile": {
1256
+ "data": { "type": "customer_profiles", "lid": "temp-profile" }
1257
+ }
1258
+ }
1259
+ },
1260
+ "included": [
1261
+ {
1262
+ "type": "customer_profiles",
1263
+ "lid": "temp-profile",
1264
+ "attributes": {
1265
+ "company_name": "Acme Inc",
1266
+ "industry": "Tech"
1267
+ }
1268
+ }
1269
+ ]
1270
+ }
1271
+ ```
1272
+
1273
+ With the default **primary-first** order, the server creates the user first, then creates the `customer_profiles` resource and assigns it an `id`, then updates the user's `profile` relationship to point to that new profile. The `lid` `"temp-profile"` is only used to match the relationship to the included object.
1274
+
1275
+ ### Response
1276
+
1277
+ On success the response is `201 Created` with the primary resource in `data` and the created related resources in `included`, each with a real `id`:
1278
+
1279
+ ```json
1280
+ {
1281
+ "jsonapi": { "version": "1.1" },
1282
+ "data": {
1283
+ "type": "users",
1284
+ "id": "123",
1285
+ "attributes": { "name": "Jane Doe", "email": "jane@example.com" },
1286
+ "relationships": {
1287
+ "profile": { "data": { "type": "customer_profiles", "id": "456" } }
1288
+ }
1289
+ },
1290
+ "included": [
1291
+ {
1292
+ "type": "customer_profiles",
1293
+ "id": "456",
1294
+ "attributes": { "company_name": "Acme Inc", "industry": "Tech" }
1295
+ }
1296
+ ]
1297
+ }
1298
+ ```
1299
+
1300
+ ### Validation and rollback
1301
+
1302
+ Creation runs in a single transaction. If any included resource fails validation, the entire request is rolled back: no primary or included resources are created. The response is `422 Unprocessable Entity` with errors that point to the failing included resource (e.g. `source.pointer` to the relevant `included` index or attribute).
1303
+
1304
+ ### When sideposting is used
1305
+
1306
+ Sideposting is applied when the create request includes a top-level `included` array. If `included` is present, the controller uses the configured **sidepost order** (primary-first by default, or included-first if `sidepost_order :included_first` is set). With primary-first, it creates the primary resource first (without lid-based relationships), creates included resources, resolves `lid` references to new `id`s, updates the primary with those relationships, then returns the primary and `included` in the response. With included-first, it creates included resources first, resolves `lid`s, then creates the primary and returns it with `included`.
1307
+
1194
1308
  ## Relationship Endpoint Details
1195
1309
 
1196
1310
  The gem provides dedicated endpoints for managing relationships independently of the main resource:
@@ -43,6 +43,24 @@ module JSONAPI
43
43
  render json: { errors: }, status: :unprocessable_content
44
44
  end
45
45
 
46
+ def render_sidepost_validation_errors(sidepost_error)
47
+ errors = sidepost_validation_errors(sidepost_error)
48
+ render json: { errors: }, status: :unprocessable_content
49
+ end
50
+
51
+ def sidepost_validation_errors(sidepost_error)
52
+ index = sidepost_error.included_index
53
+ sidepost_error.record.errors.map do |error|
54
+ pointer = "/included/#{index}/attributes/#{error.attribute}"
55
+ {
56
+ status: "422",
57
+ title: "Validation Error",
58
+ detail: error.full_message,
59
+ source: { pointer: pointer },
60
+ }
61
+ end
62
+ end
63
+
46
64
  def render_parameter_not_allowed_error(error)
47
65
  error_params = error.respond_to?(:params) ? error.params : []
48
66
  detail = error_params.present? ? error_params.join(", ") : "Relationship or attribute is read-only"
@@ -15,6 +15,14 @@ module JSONAPI
15
15
  [hash, attachments]
16
16
  end
17
17
 
18
+ def prepare_create_params_from_data(sti_class, data)
19
+ hash = deserialize_params(:create, model_class: sti_class, data:)
20
+ attachments = extract_active_storage_params_from_hash(hash, sti_class)
21
+ clean_params(hash, attachments)
22
+ apply_sti_type(sti_class, hash)
23
+ [hash, attachments]
24
+ end
25
+
18
26
  def prepare_update_params
19
27
  hash = deserialize_params(:update)
20
28
  attachments = extract_active_storage_params_from_hash(hash, model_class)
@@ -45,6 +53,15 @@ module JSONAPI
45
53
  render json: serialize_resource(resource), status: :created, location: resource_url(resource)
46
54
  end
47
55
 
56
+ def save_created_with_sidepost(resource, sideposted_records)
57
+ raise JSONAPI::Sideposting::PrimaryValidationError, resource unless resource.save
58
+
59
+ emit_resource_event(:created, resource)
60
+ render json: serialize_resource_with_sidepost(resource, sideposted_records),
61
+ status: :created,
62
+ location: resource_url(resource)
63
+ end
64
+
48
65
  def save_updated(params_hash, attachments)
49
66
  return render_validation_errors(@resource) unless @resource.update(params_hash)
50
67
 
@@ -51,9 +51,10 @@ module JSONAPI
51
51
  JSONAPI::ParamHelpers.deep_symbolize_params(data)
52
52
  end
53
53
 
54
- def deserialize_params(action = :update, model_class: nil)
54
+ def deserialize_params(action = :update, model_class: nil, data: nil)
55
55
  target = model_class || self.model_class
56
- JSONAPI::Deserializer.new(raw_jsonapi_data, model_class: target, action:).to_model_attributes
56
+ payload = data.nil? ? raw_jsonapi_data : symbolize_data(data)
57
+ JSONAPI::Deserializer.new(payload, model_class: target, action:).to_model_attributes
57
58
  end
58
59
 
59
60
  def resource_url(resource)
@@ -14,6 +14,19 @@ module JSONAPI
14
14
  )
15
15
  end
16
16
 
17
+ def serialize_resource_with_sidepost(resource, sideposted_records)
18
+ result = serialize_resource(resource)
19
+ return result if sideposted_records.empty?
20
+
21
+ fields = parse_fields_param
22
+ sideposted_objects = sideposted_records.map do |rec|
23
+ JSONAPI::Serializer.new(rec).to_hash(include: [], fields:, document_meta: nil)[:data]
24
+ end
25
+ all_included = (result[:included] || []) + sideposted_objects
26
+ result[:included] = deduplicate_included(all_included)
27
+ result
28
+ end
29
+
17
30
  def serialize_collection(resources)
18
31
  includes = parse_include_param
19
32
  fields = parse_fields_param
@@ -59,6 +72,17 @@ module JSONAPI
59
72
  processed.add(key)
60
73
  end
61
74
 
75
+ def deduplicate_included(included_array)
76
+ processed = Set.new
77
+ included_array.each_with_object([]) do |inc, out|
78
+ key = "#{inc[:type]}-#{inc[:id]}"
79
+ next if processed.include?(key)
80
+
81
+ out << inc
82
+ processed.add(key)
83
+ end
84
+ end
85
+
62
86
  def build_collection_response(data, all_included)
63
87
  result = { jsonapi: JSONAPI::Serializer.jsonapi_object, data: }
64
88
  result[:included] = all_included if all_included.any?