Skip to content

Duck-type fields and add serializer context (#23, #21) - #32

Merged
Nitemaeric merged 1 commit into
mainfrom
claude/duck-typed-fields-context
Jul 3, 2026
Merged

Duck-type fields and add serializer context (#23, #21)#32
Nitemaeric merged 1 commit into
mainfrom
claude/duck-typed-fields-context

Conversation

@Nitemaeric

Copy link
Copy Markdown
Contributor

Context

Closes #23 and #21, building on the conditional-rendering work from #31.

The two compose: context is exactly what a field's render? condition needs, so they land together.

Changes

#23 — duck-typed fields

  • New Transmutation::Field base with Attribute and Association subclasses. Each answers render?(serializer) and value(serializer, options); Association#render? also enforces max_depth, and Association#value serializes one level deeper.
  • Serializer#as_json collapses to the shape the issue described:
    def as_json(options = {})
      fields.each_with_object({}) do |field, hash|
        hash[field.key] = field.value(self, options) if field.render?(self)
      end
    end
  • The if:/unless: handling, the attribute/association branch, and the block-vs-send branch all move onto the field objects; field_config/render_field?/field_value/evaluate_condition are gone from Serializer.
  • Each field precomputes its frozen JSON key once at declaration, removing the per-serialize name.to_s allocation, and as_json iterates field objects directly (fewer per-call allocations than the old pair destructuring).

#21 — serializer context

  • Serializers accept context:, exposed as #context. It defaults to the caller of serialize (the controller, since Serialization is mixed into controllers), so context.current_user works out of the box.
  • Context threads through serialize, serialize_collection, associations (children inherit the original caller's context), and render (overridable with render json:, context:).

Housekeeping

  • Bumped the Metrics/MethodLength budget in .rubocop_todo.yml from 12 → 13: the old limit was pinned to as_json (now tiny after the refactor); render is the current longest method.

Testing

  • Full suite green (54 examples), RuboCop clean.
  • Added specs: context passing/gating on attributes, context defaulting to the caller, explicit override, and context propagation into nested associations. Existing Add conditional rendering of attributes and associations #22 conditional-rendering and max_depth/collection specs still pass unchanged.
  • The benchmark job will compare this branch against main (head-vs-main table) to confirm the frozen-key/field-object changes don't regress and trim allocations.

🤖 Generated with Claude Code


Generated by Claude Code

#23: Replace the attributes_config hash-of-options with Field objects —
Attribute and Association — that each answer `render?` and `value`. This
collapses the two conditionals in Serializer#as_json:

    def as_json(options = {})
      fields.each_with_object({}) do |field, hash|
        hash[field.key] = field.value(self, options) if field.render?(self)
      end
    end

The if/unless logic and the attribute-vs-association branch move onto the
field objects. Each field precomputes its frozen JSON key once, removing
the per-serialize `name.to_s` allocation.

#21: Add `context` to serializers, defaulting to the caller of
`serialize` (e.g. the controller), so serializers can reach
`context.current_user`. Context threads through serialize,
serialize_collection, associations (children inherit the caller's
context), and render (overridable via `render json:, context:`).
@Nitemaeric
Nitemaeric merged commit 6ac69f6 into main Jul 3, 2026
8 checks passed
@Nitemaeric
Nitemaeric deleted the claude/duck-typed-fields-context branch July 3, 2026 08:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Use duck-typing to avoid conditional between attributes and associations

2 participants