Skip to content

Commit 1df3087

Browse files
committed
Include information about objects' and collections' parents
1 parent 38c05a7 commit 1df3087

File tree

5 files changed

+67
-48
lines changed

5 files changed

+67
-48
lines changed

lib/blueprinter/v2/context.rb

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,16 +55,25 @@ module Context
5555
# @return [Hash] Options passed to `render`
5656
# @!attribute [r] object
5757
# @return [Object] The object or collection that's currently being rendered
58+
# @!attribute [r] parent
59+
# @return [Blueprinter::V2::Context::Parent] Information about the parent, if any
5860
# @!attribute [r] depth
5961
# @return [Integer] Blueprint depth (1-indexed)
6062
#
61-
Object = Struct.new(:blueprint, :fields, :options, :object, :depth) do
63+
Object = Struct.new(:blueprint, :fields, :options, :object, :parent, :depth) do
6264
(members - %i[object]).each do |attr|
6365
remove_method("#{attr}=")
6466
define_method("#{attr}=") { |_| raise BlueprinterError, "Context field `#{attr}` is immutable" }
6567
end
6668
end
6769

70+
Parent = Struct.new(:blueprint, :field, :object) do
71+
members.each do |attr|
72+
remove_method("#{attr}=")
73+
define_method("#{attr}=") { |_| raise BlueprinterError, "Parent field `#{attr}` is immutable" }
74+
end
75+
end
76+
6877
#
6978
# The current field.
7079
#

lib/blueprinter/v2/field_serializers/collection.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,9 @@ def serialize(ctx, result)
3131
def blueprint_value(value, ctx)
3232
field_blueprint = ctx.field.blueprint
3333
if @instances.blueprint(field_blueprint).is_a? V2::Base
34+
parent = Context::Parent.new(ctx.blueprint.class, ctx.field, ctx.object)
3435
child_serializer = @instances.serializer(field_blueprint, ctx.options, ctx.depth + 1)
35-
child_serializer.collection(value, depth: ctx.depth + 1)
36+
child_serializer.collection(value, parent:, depth: ctx.depth + 1)
3637
else
3738
opts = { v2_instances: @instances, v2_depth: ctx.depth }
3839
field_blueprint.hashify(value, view_name: :default, local_options: ctx.options.dup.merge(opts))

lib/blueprinter/v2/field_serializers/object.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,9 @@ def serialize(ctx, result)
3131
def blueprint_value(value, ctx)
3232
field_blueprint = ctx.field.blueprint
3333
if @instances.blueprint(field_blueprint).is_a? V2::Base
34+
parent = Context::Parent.new(ctx.blueprint.class, ctx.field, ctx.object)
3435
child_serializer = @instances.serializer(field_blueprint, ctx.options, ctx.depth + 1)
35-
child_serializer.object(value, depth: ctx.depth + 1)
36+
child_serializer.object(value, parent:, depth: ctx.depth + 1)
3637
else
3738
opts = { v2_instances: @instances, v2_depth: ctx.depth }
3839
field_blueprint.hashify(value, view_name: :default, local_options: ctx.options.dup.merge(opts))

lib/blueprinter/v2/serializer.rb

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -42,41 +42,45 @@ def initialize(blueprint_class, options, instances, initial_depth:)
4242
# Serialize a single object to a Hash.
4343
#
4444
# @param object [Object] The object to serialize
45+
# @param depth [Object] Depth of this object in the serialization tree
46+
# @param parent [Blueprinter::V2::Context::Parent] Information about the container object (if any)
4547
# @return [Hash] The serialized object
4648
#
47-
def object(object, depth:)
49+
def object(object, depth:, parent: nil)
4850
if @hook_around_serialize_object
49-
ctx = Context::Object.new(@blueprint, @fields, @options, object, depth)
51+
ctx = Context::Object.new(@blueprint, @fields, @options, object, parent, depth)
5052
@hooks.around(:around_serialize_object, ctx) do |ctx|
51-
serialize_object(ctx.object, depth:)
53+
serialize_object(ctx.object, depth:, parent:)
5254
end
5355
else
54-
serialize_object(object, depth:)
56+
serialize_object(object, depth:, parent:)
5557
end
5658
end
5759

5860
#
5961
# Serialize a collection of objects to a Hash.
6062
#
6163
# @param collection [Enumerable] The collection to serialize
64+
# @param depth [Object] Depth of this object in the serialization tree
65+
# @param parent [Blueprinter::V2::Context::Parent] Information about the container object (if any)
6266
# @return [Enumerable] The serialized hashes
6367
#
64-
def collection(collection, depth:)
68+
def collection(collection, depth:, parent: nil)
6569
if @hook_around_serialize_collection
66-
ctx = Context::Object.new(@blueprint, @fields, @options, collection, depth)
70+
ctx = Context::Object.new(@blueprint, @fields, @options, collection, parent, depth)
6771
@hooks.around(:around_serialize_collection, ctx) do |ctx|
68-
ctx.object.map { |object| serialize_object(object, depth:) }.to_a
72+
ctx.object.map { |object| serialize_object(object, depth:, parent:) }.to_a
6973
end
7074
else
71-
collection.map { |object| serialize_object(object, depth:) }.to_a
75+
collection.map { |object| serialize_object(object, depth:, parent:) }.to_a
7276
end
7377
end
7478

7579
private
7680

77-
def serialize_object(object, depth:)
81+
def serialize_object(object, depth:, parent: nil)
7882
if @hook_around_blueprint
79-
ctx = Context::Object.new(@blueprint, @fields, @options, object, depth)
83+
ctx = Context::Object.new(@blueprint, @fields, @options, object, parent, depth)
8084
@hooks.around(:around_blueprint, ctx) do |ctx|
8185
serialize(ctx.object, depth:)
8286
end

spec/v2/serializer_spec.rb

Lines changed: 39 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -111,41 +111,6 @@ def around_collection_value(ctx)
111111
})
112112
end
113113

114-
# it 'uses field-level extractor (class)' do
115-
# test = self
116-
# blueprint = Class.new(application_blueprint) do
117-
# self.blueprint_name = "BlockBlueprint"
118-
# field :name, extractor: test.name_of_extractor
119-
# object :category, test.category_blueprint, extractor: test.name_of_extractor
120-
# collection :parts, test.part_blueprint, extractor: test.name_of_extractor
121-
# end
122-
123-
# result = described_class.new(blueprint, {}, instances, initial_depth: 1).object(widget, depth: 1)
124-
# expect(result).to eq({
125-
# name: 'Name of Foo',
126-
# category: { name: 'Name of Bar' },
127-
# parts: [{ num: 1 }, { num: 2 }]
128-
# })
129-
# end
130-
131-
# it 'uses field-level extractor (instance)' do
132-
# test = self
133-
# blueprint = Class.new(application_blueprint) do
134-
# self.blueprint_name = "BlockBlueprint"
135-
# extensions << test.name_of_extractor.new
136-
# field :name, extractor: test.name_of_extractor.new(prefix: 'X')
137-
# object :category, test.category_blueprint, extractor: test.name_of_extractor.new(prefix: 'Y')
138-
# collection :parts, test.part_blueprint, extractor: test.name_of_extractor.new
139-
# end
140-
141-
# result = described_class.new(blueprint, {}, instances, initial_depth: 1).object(widget, depth: 1)
142-
# expect(result).to eq({
143-
# name: 'X of Foo',
144-
# category: { name: 'Y of Bar' },
145-
# parts: [{ num: 1 }, { num: 2 }]
146-
# })
147-
# end
148-
149114
it 'uses blueprint extractor extension' do
150115
test = self
151116
blueprint = Class.new(application_blueprint) do
@@ -504,4 +469,43 @@ def instances.blueprints = @blueprints
504469
blueprint_instances = instances.blueprints.count
505470
expect(blueprint_instances).to eq 1
506471
end
472+
473+
it 'passes objects information about the parent' do
474+
ext = Class.new(Blueprinter::Extension) do
475+
def initialize(log) = @log = log
476+
477+
def around_serialize_object(ctx)
478+
if ctx.parent
479+
@log << "Object Parent Blueprint: #{ctx.parent.blueprint}"
480+
@log << "Object Parent field: #{ctx.parent.field.name}"
481+
@log << "Object Parent object: #{ctx.parent.object[:name]}"
482+
end
483+
yield ctx
484+
end
485+
486+
def around_serialize_collection(ctx)
487+
if ctx.parent
488+
@log << "Collection Parent Blueprint: #{ctx.parent.blueprint}"
489+
@log << "Collection Parent field: #{ctx.parent.field.name}"
490+
@log << "Collection Parent object: #{ctx.parent.object[:name]}"
491+
end
492+
yield ctx
493+
end
494+
end
495+
log = []
496+
category_blueprint.extensions << ext.new(log)
497+
part_blueprint.extensions << ext.new(log)
498+
serializer = described_class.new(widget_blueprint, {}, instances, initial_depth: 1)
499+
500+
widget = { name: 'Foo', category: { name: 'Bar' }, parts: [{ num: 42 }] }
501+
result = serializer.object(widget, depth: 1)
502+
expect(log).to eq [
503+
"Object Parent Blueprint: WidgetBlueprint",
504+
"Object Parent field: category",
505+
"Object Parent object: Foo",
506+
"Collection Parent Blueprint: WidgetBlueprint",
507+
"Collection Parent field: parts",
508+
"Collection Parent object: Foo"
509+
]
510+
end
507511
end

0 commit comments

Comments
 (0)