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

2.0 Base Class Proposal #437

Merged
merged 19 commits into from
Oct 31, 2024
Merged

2.0 Base Class Proposal #437

merged 19 commits into from
Oct 31, 2024

Conversation

jhollinger
Copy link
Contributor

@jhollinger jhollinger commented Jul 9, 2024

A proposed V2 base class.

Overview

  • Combines concepts of "blueprint" and "view" (from Designs for 2.0 base class #430).
    • A view is just a subclass of the Blueprint.
    • A nested view is a sublcass of its parent view.
    • The :default view is just an alias to self (i.e. the Blueprint class).
  • object = singular association, collection = multiple (names up for debate)
  • Fields, partials, extensions, and options are inherited from the parent class/view, but can then be customized.
  • Fields and associations are just structs.
  • "Partials" can be defined and included in views.

Notes

  • There's no MyBlueprint < Blueprinter[2.0] syntax in this PR, but we can add it later if we want.
  • More options for views and fields/associations will need to be added in later PRs.

Defining

# "global" config goes here
class ApplicationBlueprint < Blueprinter::V2::Base
  extensions << MyExtension.new
  options[:exclude_nil] = true

  field :id
end

class MyBlueprint < ApplicationBlueprint
  field :name
  object :category, CategoryBlueprint

  # inherits from "default" view
  view :extended do
    exclude :name
    collection :widgets, WidgetBlueprint[:extended]

    # inherits from "extended" view
    view :plus do
      # Uses a legacy blueprint
      object :foo, LegacyFooBlueprint, view: :plus
    end
  end
end

# inherits from "MyBlueprint extended plus" view
class MyOtherBlueprint < MyBlueprint["extended.plus"]
  ...
end

# Much like in Rails, you can define partials for your views
class WidgetBlueprint < ApplicationBlueprint
  field :name

  partial :parts do
    # Defining fields is the obvious use-case
    collection :parts, WidgetPartBlueprint
    # But they can also use the full V2 API, including extensions, views and including other partials
    extensions << MyExt.new
  end

  view :foo do
    # Include the partial
    use :parts
    field :foo
  end

  view :bar do
    # Include the partial
    use :parts
    field :bar
  end
end

Rendering

# Render the default view
MyBlueprint.render(obj, opts)
MyBlueprint[:default].render(obj, opts)

# Render a named view
MyBlueprint[:extended].render(obj, opts)
MyBlueprint["extended"].render(obj, opts)

# Render a nested view
MyBlueprint["extended.plus"].render(obj, opts)
# which is syntactic sugar for:
MyBlueprint[:extended][:plus].render(obj, opts)

# Universal render call
view = "default" # or :foo, "foo.bar", :"foo.bar", etc
MyBlueprint[view].render(obj, opts)

# It COULD be made backwards-compatible, although I dislike the "Hash soup" approach.
# Maybe have this in 1.x to ease adoption then remove in 2.0?
MyBlueprint.render(obj, view: "extended.plus")

Reflection

# Here, default refers to MyBlueprint
MyBlueprint.reflections.keys
=> [:default, :extended, :"extended.plus"]

# If you reflect from a child view, the keys are relative
MyBlueprint[:extended].reflections.keys
=> [:default, :plus]

@jhollinger jhollinger changed the title Jh/2.0 base 2.0 Base Class Experiments Jul 9, 2024
@jhollinger jhollinger force-pushed the jh/2.0-base branch 4 times, most recently from d9a4438 to d4100bf Compare July 9, 2024 17:28
@jhollinger
Copy link
Contributor Author

jhollinger commented Jul 9, 2024

Alt view options:

WidgetBlueprint.render(obj, view: "extended.plus")
WidgetBlueprint["extended.plus"].render(obj)

@jhollinger jhollinger force-pushed the jh/2.0-base branch 5 times, most recently from 6dd4924 to 1b9c837 Compare July 15, 2024 17:35
@jhollinger jhollinger changed the title 2.0 Base Class Experiments 2.0 Base Class Proposal Jul 15, 2024
@jhollinger jhollinger force-pushed the jh/2.0-base branch 2 times, most recently from ca8abf8 to 975c735 Compare July 15, 2024 17:47
@jhollinger jhollinger force-pushed the jh/2.0-base branch 10 times, most recently from 51781e8 to 7af7bae Compare August 6, 2024 18:58
@jhollinger jhollinger force-pushed the jh/2.0-base branch 3 times, most recently from 6ad1a78 to ef93e6f Compare August 27, 2024 20:41
@jhollinger jhollinger mentioned this pull request Sep 17, 2024
1 task
@william-stacken
Copy link

william-stacken commented Sep 26, 2024

What about extractor classes that could be provided to fields and associations? Is that something that is addressed in this PR?

@extractor = extractor

We are currently using custom extractors to cache the values of extracted fields, and to detect infinite recursion while extracting a field (field references itself). See the related issue about extractor configurability.
#404

@jhollinger
Copy link
Contributor Author

What about extractor classes that could be provided to fields and associations? Is that something that is addressed in this PR?

We're still looking at how we want to implement extractors, and a few other things, in V2. Those will be follow-up PRs.

Copy link
Contributor

@lessthanjacob lessthanjacob left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I plan on giving this another thorough review, but I'm inclined to suggest that we try breaking this apart, and introduce each component separately, as there's a lot of new concepts at play here. I know we're merging into a release branch, but I believe it'd make it easier to break down iterate on moving forward.


module Blueprinter
# Base class for V2 Blueprints
class V2
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reading through this again with fresh eyes, I'm less inclined to want to codify the version as part of the class itself, especially since it doesn't quite describe what the class is as much.

The versioning system we were originally going with does seem over-engineered based on our current expectations, but I'm curious if we could have a more expressive name, and then introduce those versioning semantics later on if needed (e.g. class Blueprint -> Blueprint[3.0]).

Otherwise, I'd lean more towards BlueprintV2 if we want to stick with the explicit version in the name.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've made V2 a module and moved this code to V2::Base for now.

self.partials = {}
self.used_partials = []
self.extensions = []
self.options = Options.new(DEFAULT_OPTIONS)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nb: I'd expect DEFAULT_OPTIONS to be encapsulated solely within Options, and that simply calling Options.new would initialize it with the defaults.

Signed-off-by: Jordan Hollinger <[email protected]>
@jhollinger
Copy link
Contributor Author

jhollinger commented Oct 15, 2024

For posterity (as discussed in the meeting): In my mind, the important part of this PR is the view/class inheritance structure and associated DSL. That is: how fields, associations, partials, options, and extensions are inherited from a parent class/view into child classes/views in a consistent, predictable manner, obviating the need for global config and unifying the "blueprint" and "view" concepts.

The included implementations of field, V2::Field, V2::Options etc are meant as starting points for follow up discussions and PRs. (Not to mention extension hooks, extractors, etc.) My suspicion is that we'll want to design the renderer/serializer sooner rather than later, and that will tell us a lot about how we'll want to handle those details.

Signed-off-by: Jordan Hollinger <[email protected]>
@jhollinger jhollinger force-pushed the jh/2.0-base branch 2 times, most recently from 16de5f5 to b6d7495 Compare October 19, 2024 17:57
Signed-off-by: Jordan Hollinger <[email protected]>
@jhollinger jhollinger merged commit 4811530 into release-2.0 Oct 31, 2024
1 check passed
@jhollinger jhollinger deleted the jh/2.0-base branch October 31, 2024 15:46
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.

4 participants