Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Design New Extension Hooks #441

Open
jhollinger opened this issue Jul 15, 2024 · 2 comments
Open

Design New Extension Hooks #441

jhollinger opened this issue Jul 15, 2024 · 2 comments
Assignees

Comments

@jhollinger
Copy link
Contributor

jhollinger commented Jul 15, 2024

As pointed out in #417 and elsewhere, we have many configuration options restricted to certain API levels (global config, Blueprint, view, and field). A frequent request is "make config X available at API level Y". Each request starts familiar discussions about overrides and inheritance, and there are often several reasonable but mutually-exclusive paths forward.

While it may not be appropriate for every option to migrate to an extension hook, it certainly makes sense for extractors, transformers, and [date] formatters in 2.0. The goal of this issue to is to design these config-as-extension-hooks, as well as call out config options that might not make sense as hooks (e.g. maybe boolean options?).

NOTE: This issue is not to implement every hook or option, but to document what new hooks we need (and potentially their call signatures).

In 2.0, whether hooks or options, they'll all follow the same inheritance and override rules as fields/associations: Class/view > class/view > class/view, etc. fields/associations would have the final override (when applicable).

@jhollinger jhollinger changed the title Config as Extension Hooks Design New Extension Hooks Jul 15, 2024
Copy link

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@jhollinger
Copy link
Contributor Author

jhollinger commented Oct 31, 2024

Extracted from #479:

Formatters

Adding a formatter should be easy. Forcing people to learn "extensions" seems a little heavy. What if formatters were part of the Blueprint DSL?

class MyBlueprint < ApplicationBlueprint
  format(Date) { |date| date.iso8601 }
  format Time, :time_fmt

  def time_fmt(t)
    t.iso8601
  end
end

Hooks

  • collection? (returns true if something is a collection, e.g ActiveRecord::Relation)
  • around (must yield - runs around all other hooks)
  • input_object | input_collection (the object that was passed to the outer render call - our ActiveRecord extension will preload with this)
  • around_blueprint (must yield - finishes after blueprint_output)
  • prepare (for caching data in context.data to speed up later hooks)
  • blueprint_fields (returns fields in the order they should be serialized)
  • blueprint_input (any object that's about to be serialized by a Blueprint)
  • field_value (modify or replace a field value)
  • exclude_field? (returning true blocks this field and value)
  • object_value (modify or replace an object value)
  • exclude_object? (returning true blocks this object field and value)
  • collection_value (modify or replace a collection value)
  • exclude_collection? (returning true blocks this collection field and value)
  • blueprint_output (modify or replace an entire serialization output)
  • output_object | output_collection (modify or replace the final output, e.g. wrap in a root element)

Extractors

While we could make it work, I don't think extractors are a great case for extension hooks. You can have N extensions, but we only ever need one extractor for a given field.

The idea is provide a base extractor that behaves like our current collection of them. People can inherit from it and call super as needed.

class MyExtractor < Blueprinter::V2::Extractor
  def field(blueprint, field, object, options)
    if object.is_a? Something
      # special behavior
    else
      super
    end
  end

  def object(blueprint, field, object, options)
    # ...
  end

  def collection(blueprint, field, object, options)
    # ...
  end
end

class MyBlueprint < ApplicationBlueprint
  options[:extractor] = MyExtractor

  field :name
  field :description, extractor: OtherExtractor
end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants