Skip to content

Commit 51581e5

Browse files
authored
Merge pull request #539 from procore-oss/jh/rack-inspired-hooks
V2 Extensions as Middleware
2 parents fef3af4 + 993bdbe commit 51581e5

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+2059
-1799
lines changed

lib/blueprinter/errors/extension_hook.rb

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,10 @@
33
module Blueprinter
44
module Errors
55
class ExtensionHook < StandardError
6-
attr_reader :extension, :hook, :message
6+
attr_reader :message
77

88
def initialize(extension, hook, message)
9-
@extension = extension
10-
@hook = hook
11-
@message = message
9+
@message = "Extension hook error in #{extension.class.name}##{hook}: #{message}"
1210
end
1311
end
1412
end

lib/blueprinter/extension.rb

Lines changed: 35 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -7,49 +7,29 @@ module Blueprinter
77
# V2 hook call order:
88
#
99
# - around_hook (called around any other extension hook)
10-
# - blueprint
11-
# - blueprint_fields
12-
# - blueprint_setup
13-
# - around_serialize_object | around_serialize_collection
14-
# - object_input | collection_input
15-
# - blueprint_input
16-
# - extract_value
17-
# - field_value | object_field_value | collection_field_value
18-
# - exclude_field? | exclude_object_field? | exclude_collection_field?
19-
# - blueprint_fields …
20-
# - field_result | object_field_result | collection_field_result
21-
# - blueprint_output
22-
# - object_output | collection_output
23-
# - json
10+
# - around_result
11+
# - around_blueprint_init
12+
# - around_serialize_object | around_serialize_collection
13+
# - around_blueprint
14+
# - around_field_value | around_object_value | around_collection_value
15+
# - around_blueprint_init …
2416
#
2517
# V1 hook call order:
2618
# - pre_render
2719
#
2820
class Extension
21+
include V2::Helpers
22+
2923
HOOKS = %i[
3024
around_hook
31-
blueprint
32-
blueprint_fields
33-
blueprint_setup
25+
around_result
26+
around_blueprint_init
3427
around_serialize_object
3528
around_serialize_collection
36-
object_input
37-
collection_input
38-
blueprint_input
39-
extract_value
40-
field_value
41-
exclude_field?
42-
field_result
43-
object_field_value
44-
exclude_object_field?
45-
object_field_result
46-
collection_field_value
47-
exclude_collection_field?
48-
collection_field_result
49-
blueprint_output
50-
object_output
51-
collection_output
52-
json
29+
around_blueprint
30+
around_field_value
31+
around_object_value
32+
around_collection_value
5333
pre_render
5434
].freeze
5535

@@ -61,16 +41,23 @@ def self.hooks
6141
# If this returns true, around_hook will not be called when this extension's hooks are run. Used by core extensions.
6242
def hidden? = false
6343

64-
# around_serialize_object: Runs around serialization of a Blueprint object. Surrounds the `prepare` through
65-
# `blueprint_output` hooks. MUST yield!
44+
# around_result TODO
45+
# @param context [Blueprinter::V2::Context::Result]
46+
47+
# around_serialize_object: Runs around serialization of a Blueprint object.
6648
# @param context [Blueprinter::V2::Context::Object]
6749

68-
# around_collection: Runs around serialization of a Blueprint collection. Surrounds the `prepare` through
69-
# `blueprint_output` hooks. MUST yield!
50+
# around_serialize_collection: Runs around serialization of a Blueprint collection.
7051
# @param context [Blueprinter::V2::Context::Object]
7152

72-
# blueprint: Returns the blueprint class to render with. The context's "fields" field will be empty.
73-
# @param context [Blueprinter::V2::Context::Render]
53+
# around_blueprint: Runs around serialization of every Blueprint.
54+
# @param context [Blueprinter::V2::Context::Object]
55+
56+
# around_field_value TODO
57+
58+
# around_object_value TODO
59+
60+
# around_collection_value TODO
7461

7562
# blueprint_fields: Returns the fields that should be included in the correct order. Default is all fields in the order
7663
# in which they were defined.
@@ -83,52 +70,6 @@ def hidden? = false
8370
# and cache them in context.data, so we don't have to recalculate them for every field.
8471
# @param context [Blueprinter::V2::Context::Render]
8572

86-
# blueprint_input: Modify or replace an object right before it's serialized by a Blueprint. The returned object will be
87-
# used as the input to the Blueprint.
88-
# @param context [Blueprinter::V2::Context::Object]
89-
# @return [Object]
90-
91-
# blueprint_output: Modify or replace the serialized output from any Blueprint. The returned object will be used as the
92-
# output of the Blueprint.
93-
# @param context [Blueprinter::V2::Context::Result]
94-
# @return [Object]
95-
96-
# extract_value: Extract a field, objecet, or collection value from an object. The returned value will be run through the
97-
# NOTE If there are multiple extract_value hooks, only the last one is called.
98-
# field_value, object_fled_value, or collection_fled_value hooks.
99-
100-
# field_value: Modify or replace the value used for the field. The returned value will be run through any formatters and
101-
# used as the field's value.
102-
# @param context [Blueprinter::V2::Context::Field]
103-
# @return [Object]
104-
105-
# object_field_value: Modify or replace the value used for the object. The returned value will be used as the input for
106-
# the object's Blueprint.
107-
# @param context [Blueprinter::V2::Context::Field]
108-
# @return [Object]
109-
110-
# collection_field_value: Modify or replace the value used for the collection. The returned value will be used as the
111-
# input for the collection's Blueprint.
112-
# @param context [Blueprinter::V2::Context::Field]
113-
# @return [Enumerable]
114-
115-
# exclude_field?: Return true to exclude this field from the result.
116-
# @param context [Blueprinter::V2::Context::Field]
117-
# @return [Boolean]
118-
119-
# exclude_object_field?: Return true to exclude this object from the result.
120-
# @param context [Blueprinter::V2::Context::Field]
121-
# @return [Boolean]
122-
123-
# exclude_collection_field?: Return true to exclude this collection from the result.
124-
# @param context [Blueprinter::V2::Context::Field]
125-
# @return [Boolean]
126-
127-
# json: Override the default JSON encoder. The returned string will be the JSON output.
128-
# NOTE If there are multiple json hooks, only the final one is called.
129-
# @param context [Blueprinter::V2::Context::Result]
130-
# @return [String]
131-
13273
# around_hook: Instrument extension hook calls. MUST yield!
13374
# @param extension [Blueprinter::Extension] Instance of the extension
13475
# @param hook [Symbol] Name of hook being called
@@ -140,5 +81,13 @@ def hidden? = false
14081
# @param view [Symbol] The blueprint view
14182
# @param options [Hash] Options passed to "render"
14283
# @return [Object] The object to continue rendering
84+
85+
private
86+
87+
# Helper for around_result hooks to declare that a result is "final"
88+
def final(val) = V2::Context::Final.new(val)
89+
90+
# Helper for around_result hooks to check if a previous hook has declared a result "final"
91+
def final?(val) = val.is_a? V2::Context::Final
14392
end
14493
end

lib/blueprinter/extensions/field_order.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ def initialize(&sorter)
1717
end
1818

1919
# @param ctx [Blueprinter::V2::Context::Render]
20-
# @return [Array<Blueprinter::V2::Fields::*>]
21-
def blueprint_fields(ctx)
22-
ctx.fields.sort(&@sorter)
20+
def around_blueprint_init(ctx)
21+
ctx.fields = ctx.fields.sort(&@sorter)
22+
yield ctx
2323
end
2424
end
2525
end

lib/blueprinter/extensions/multi_json.rb

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,16 @@ def initialize(options = {})
1616
end
1717

1818
# @param ctx [Blueprinter::V2::Context::Result]
19-
def json(ctx)
20-
opts = ctx.options[:multi_json] ? @options.merge(ctx.options[:multi_json]) : @options
21-
::MultiJson.dump(ctx.result, opts)
19+
def around_result(ctx)
20+
case ctx.format
21+
when :json
22+
ctx.format = :hash
23+
result = yield ctx
24+
opts = ctx.options[:multi_json] ? @options.merge(ctx.options[:multi_json]) : @options
25+
final ::MultiJson.dump(result, opts)
26+
else
27+
yield ctx
28+
end
2229
end
2330
end
2431
end

lib/blueprinter/extensions/open_telemetry.rb

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,24 @@ def initialize(tracer_name)
1313
end
1414

1515
# @param ctx [Blueprinter::V2::Context::Object]
16-
def around_serialize_object(ctx, &)
17-
tracer.in_span('blueprinter.object', attributes: attributes(ctx), &)
16+
def around_serialize_object(ctx)
17+
tracer.in_span('blueprinter.object', attributes: attributes(ctx)) do
18+
yield ctx
19+
end
1820
end
1921

2022
# @param ctx [Blueprinter::V2::Context::Object]
21-
def around_serialize_collection(ctx, &)
22-
tracer.in_span('blueprinter.collection', attributes: attributes(ctx), &)
23+
def around_serialize_collection(ctx)
24+
tracer.in_span('blueprinter.collection', attributes: attributes(ctx)) do
25+
yield ctx
26+
end
2327
end
2428

2529
# @param ctx [Blueprinter::V2::Context::Hook]
26-
def around_hook(ctx, &)
30+
def around_hook(ctx)
2731
extension = ctx.extension.class.name
2832
attributes = { extension:, hook: ctx.hook, 'library.name' => 'Blueprinter', 'library.version' => VERSION }
29-
tracer.in_span('blueprinter.extension', attributes:, &)
33+
tracer.in_span('blueprinter.extension', attributes:) { |_| yield }
3034
end
3135

3236
def hidden? = true

lib/blueprinter/extensions/view_option.rb

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@ module Extensions
66
# An optional, built-in extension for a ":view" option on render.
77
#
88
class ViewOption < Extension
9-
# @param ctx [Blueprinter::V2::Context::Render]
10-
def blueprint(ctx)
9+
# @param ctx [Blueprinter::V2::Context::Result]
10+
def around_result(ctx)
1111
if (view = ctx.options[:view])
12-
ctx.blueprint.class[view]
13-
else
14-
ctx.blueprint.class
12+
ctx.blueprint = ctx.blueprint.class[view].new
13+
ctx.options = ctx.options.except(:view).freeze
1514
end
15+
yield ctx
1616
end
1717

1818
def hidden? = true

lib/blueprinter/extractors/association_extractor.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,10 @@ def extract(association_name, object, local_options, options = {})
3232

3333
def extract_v2(value, blueprint, local_options, options)
3434
view = options[:view] || :default
35+
store = local_options[:v2_store] || {}
3536
depth = local_options[:v2_depth] || 1
3637
instances = local_options[:v2_instances] || V2::InstanceCache.new
37-
serializer = instances.serializer(blueprint[view], local_options.except(:v2_instances), depth + 1)
38+
serializer = instances.serializer(blueprint[view], local_options.except(:v2_instances), store, depth + 1)
3839
if value.is_a?(Enumerable) && !value.is_a?(Hash)
3940
serializer.collection(value, depth: depth + 1)
4041
else

0 commit comments

Comments
 (0)