Skip to content

Commit 32852d0

Browse files
committed
Add blueprint extension hook and move deprecated 'view' option to it
Signed-off-by: Jordan Hollinger <[email protected]>
1 parent cb92e2e commit 32852d0

File tree

8 files changed

+92
-27
lines changed

8 files changed

+92
-27
lines changed

lib/blueprinter/extension.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ module Blueprinter
77
# V2 hook call order:
88
#
99
# - around_hook (called around any other extension hook)
10+
# - blueprint
1011
# - blueprint_fields
1112
# - blueprint_setup
1213
# - around_serialize_object | around_serialize_collection
@@ -27,6 +28,7 @@ module Blueprinter
2728
class Extension
2829
HOOKS = %i[
2930
around_hook
31+
blueprint
3032
blueprint_fields
3133
blueprint_setup
3234
around_serialize_object
@@ -67,6 +69,9 @@ def hidden? = false
6769
# `blueprint_output` hooks. MUST yield!
6870
# @param context [Blueprinter::V2::Context::Object]
6971

72+
# blueprint: Returns the blueprint class to render with. The context's "fields" field will be empty.
73+
# @param context [Blueprinter::V2::Context::Render]
74+
7075
# blueprint_fields: Returns the fields that should be included in the correct order. Default is all fields in the order
7176
# in which they were defined.
7277
# NOTE If there are multiple blueprint_fields hooks, only the last one is called.

lib/blueprinter/extensions.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,6 @@ module Extensions
66
autoload :FieldOrder, 'blueprinter/extensions/field_order'
77
autoload :MultiJson, 'blueprinter/extensions/multi_json'
88
autoload :OpenTelemetry, 'blueprinter/extensions/open_telemetry'
9+
autoload :ViewOption, 'blueprinter/extensions/view_option'
910
end
1011
end
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# frozen_string_literal: true
2+
3+
module Blueprinter
4+
module Extensions
5+
#
6+
# An optional, built-in extension for a ":view" option on render.
7+
#
8+
class ViewOption < Extension
9+
# @param ctx [Blueprinter::V2::Context::Render]
10+
def blueprint(ctx)
11+
if (view = ctx.options[:view])
12+
ctx.blueprint.class[view]
13+
else
14+
ctx.blueprint.class
15+
end
16+
end
17+
18+
def hidden? = true
19+
end
20+
end
21+
end

lib/blueprinter/v2/base.rb

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# frozen_string_literal: true
22

3+
require 'blueprinter/v2/instance_cache'
34
require 'blueprinter/v2/render'
45

56
module Blueprinter
@@ -87,21 +88,26 @@ def self.render(obj, options = {})
8788
end
8889

8990
def self.render_object(obj, options = {})
90-
# DEPRECATED
91-
if options[:view]
92-
options = options.dup
93-
return self[options.delete(:view)].render_object(obj, options)
94-
end
95-
Render.new(obj, options, blueprint: self, collection: false)
91+
instances = InstanceCache.new
92+
blueprint = get_blueprint_class(instances, options)
93+
Render.new(obj, options, blueprint:, instances:, collection: false)
9694
end
9795

9896
def self.render_collection(objs, options = {})
99-
# DEPRECATED
100-
if options[:view]
101-
options = options.dup
102-
return self[options.delete(:view)].render_collection(objs, options)
97+
instances = InstanceCache.new
98+
blueprint = get_blueprint_class(instances, options)
99+
Render.new(objs, options, blueprint:, instances:, collection: true)
100+
end
101+
102+
# @api private
103+
def self.get_blueprint_class(instances, options)
104+
hooks = Hooks.new(extensions.map { |ext| instances.extension ext })
105+
if hooks.registered? :blueprint
106+
ctx = Context::Render.new(instances.blueprint(self), [], options, 1)
107+
hooks.last(:blueprint, ctx) || self
108+
else
109+
self
103110
end
104-
Render.new(objs, options, blueprint: self, collection: true)
105111
end
106112

107113
# Apply partials and field exclusions

lib/blueprinter/v2/render.rb

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,29 @@
11
# frozen_string_literal: true
22

33
require 'blueprinter/v2/serializer'
4-
require 'blueprinter/v2/instance_cache'
54

65
module Blueprinter
76
module V2
87
class Render
9-
def initialize(object, options, blueprint:, collection:)
8+
def initialize(object, options, blueprint:, collection:, instances:)
109
@object = object
1110
@options = options.dup.freeze
1211
@blueprint = blueprint
12+
@instances = instances
1313
@collection = collection
1414
end
1515

1616
# Serialize the object to a Hash or array of Hashes
1717
# @return [Hash|Array<Hash>]
1818
def to_hash
19-
instances = InstanceCache.new
20-
serializer = instances.serializer(@blueprint, @options, 1)
19+
serializer = @instances.serializer(@blueprint, @options, 1)
2120
serialize serializer
2221
end
2322

2423
# Serialize the object to a JSON string
2524
# @return [String]
2625
def to_json(_arg = nil)
27-
instances = InstanceCache.new
28-
serializer = instances.serializer(@blueprint, @options, 1)
26+
serializer = @instances.serializer(@blueprint, @options, 1)
2927
result = serialize serializer
3028
ctx = Context::Result.new(serializer.blueprint, serializer.fields, @options, @object, result, 1)
3129
serializer.hooks.last(:json, ctx)
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# frozen_string_literal: true
2+
3+
describe Blueprinter::Extensions::ViewOption do
4+
let(:instances) { Blueprinter::V2::InstanceCache.new }
5+
let(:serializer) { Blueprinter::V2::Serializer.new(blueprint, {}, instances, initial_depth: 1) }
6+
let(:context) { Blueprinter::V2::Context::Render }
7+
let(:object) { { id: 42, foo: 'Foo', bar: 'Bar' } }
8+
let(:blueprint) do
9+
Class.new(Blueprinter::V2::Base) do
10+
view :foo do
11+
field :foo
12+
view :bar do
13+
field :bar
14+
end
15+
end
16+
end
17+
end
18+
19+
it 'does nothing by default' do
20+
ctx = context.new(serializer.blueprint, [], {}, 1)
21+
klass = described_class.new.blueprint ctx
22+
expect(klass).to be blueprint
23+
end
24+
25+
it 'finds a nested view' do
26+
ctx = context.new(serializer.blueprint, [], { view: 'foo.bar' }, 1)
27+
klass = described_class.new.blueprint ctx
28+
expect(klass).to be blueprint['foo.bar']
29+
end
30+
end

spec/v2/render_spec.rb

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@
1616
end
1717
end
1818

19+
let(:instances) { Blueprinter::V2::InstanceCache.new }
20+
1921
it 'renders an object to a hash' do
2022
widget = { name: 'Foo', description: 'About', category: { n: 'Bar' } }
21-
render = described_class.new(widget, {}, blueprint: widget_blueprint, collection: false)
23+
render = described_class.new(widget, {}, blueprint: widget_blueprint, collection: false, instances:)
2224

2325
expect(render.to_hash).to eq({
2426
name: 'Foo',
@@ -32,7 +34,7 @@
3234
{ name: 'Foo', description: 'About', category: { n: 'Bar' } },
3335
{ name: 'Foo 2', description: 'About 2', category: { n: 'Bar 2' } },
3436
]
35-
render = described_class.new(widgets, {}, blueprint: widget_blueprint, collection: true)
37+
render = described_class.new(widgets, {}, blueprint: widget_blueprint, collection: true, instances:)
3638

3739
expect(render.to_hash).to eq([
3840
{
@@ -50,7 +52,7 @@
5052

5153
it 'renders an object to JSON' do
5254
widget = { name: 'Foo', description: 'About', category: { n: 'Bar' } }
53-
render = described_class.new(widget, {}, blueprint: widget_blueprint, collection: false)
55+
render = described_class.new(widget, {}, blueprint: widget_blueprint, collection: false, instances:)
5456

5557
expect(render.to_json).to eq({
5658
name: 'Foo',
@@ -61,7 +63,7 @@
6163

6264
it 'renders a collection to JSON' do
6365
widget = { name: 'Foo', description: 'About', category: { n: 'Bar' } }
64-
render = described_class.new([widget], {}, blueprint: widget_blueprint, collection: true)
66+
render = described_class.new([widget], {}, blueprint: widget_blueprint, collection: true, instances:)
6567

6668
expect(render.to_json).to eq([{
6769
name: 'Foo',
@@ -86,15 +88,15 @@ def json(ctx)
8688
widget_blueprint.extensions << json_ext.new('A', log)
8789
widget_blueprint.extensions << json_ext.new('B', log)
8890

89-
render = described_class.new({ name: 'Foo' }, {}, blueprint: widget_blueprint, collection: false)
91+
render = described_class.new({ name: 'Foo' }, {}, blueprint: widget_blueprint, collection: false, instances:)
9092

9193
expect(render.to_json).to eq '{"name":"Foo"}'
9294
expect(log).to eq ['B: custom json!']
9395
end
9496

9597
it 'renders to JSON and ignores the arg (for Rails `render json:`)' do
9698
widget = { name: 'Foo', description: 'About', category: { n: 'Bar' } }
97-
render = described_class.new([widget], {}, blueprint: widget_blueprint, collection: true)
99+
render = described_class.new([widget], {}, blueprint: widget_blueprint, collection: true, instances:)
98100

99101
expect(render.to_json({ junk: 'junk' })).to eq([{
100102
name: 'Foo',
@@ -105,7 +107,7 @@ def json(ctx)
105107

106108
it 'responds to to_str with json' do
107109
widget = { name: 'Foo', description: 'About', category: { n: 'Bar' } }
108-
render = described_class.new(widget, {}, blueprint: widget_blueprint, collection: false)
110+
render = described_class.new(widget, {}, blueprint: widget_blueprint, collection: false, instances:)
109111

110112
expect(render.to_str).to eq({
111113
name: 'Foo',
@@ -122,7 +124,7 @@ def json(ctx)
122124
end
123125
widget_blueprint.extensions << ext.new
124126
widget = { name: 'Foo', description: 'About', category: { n: 'Bar' } }
125-
render = described_class.new(widget, {}, blueprint: widget_blueprint, collection: false)
127+
render = described_class.new(widget, {}, blueprint: widget_blueprint, collection: false, instances:)
126128

127129
expect(render.to_json).to eq({
128130
name: 'Foo',

spec/v2/rendering_spec.rb

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,8 @@
9090
})
9191
end
9292

93-
it 'renders an object with the deprecated view option' do
93+
it 'renders an object using the blueprint hook' do
94+
widget_blueprint.extensions << Blueprinter::Extensions::ViewOption.new
9495
result = widget_blueprint.render_object(widget, { view: :extended }).to_hash
9596
expect(result).to eq({
9697
name: 'Foo',
@@ -100,7 +101,8 @@
100101
})
101102
end
102103

103-
it 'renders a collection with the deprecated view option' do
104+
it 'renders a collection using the blueprint hook' do
105+
widget_blueprint.extensions << Blueprinter::Extensions::ViewOption.new
104106
result = widget_blueprint.render_collection([widget], { view: :extended }).to_hash
105107
expect(result).to eq([{
106108
name: 'Foo',

0 commit comments

Comments
 (0)