Skip to content

Commit

Permalink
Replace sort_fields hook with blueprint_fields
Browse files Browse the repository at this point in the history
Signed-off-by: Jordan Hollinger <[email protected]>
  • Loading branch information
jhollinger committed Nov 12, 2024
1 parent 2ecc3c0 commit 76e930f
Show file tree
Hide file tree
Showing 10 changed files with 66 additions and 48 deletions.
24 changes: 13 additions & 11 deletions lib/blueprinter/extension.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ module Blueprinter
# Base class for all extensions.
#
# V2 hook call order:
# - sort_fields
# - collection? (skipped if calling render_object/render_collection)
# - input_object | input_collection
# - prepare
# - blueprint_fields
# - blueprint_input
# - exclude_field?
# - field_value
Expand All @@ -23,16 +23,6 @@ module Blueprinter
# - pre_render
#
class Extension
#
# Returns fields in the order they should appear. Default is the order in which they were defined.
#
# @param fields [Array<Blueprinter::V2::Field|Blueprinter::V2::Object|Blueprinter::V2::Collection>]
# @return [Array<Blueprinter::V2::Field|Blueprinter::V2::Object|Blueprinter::V2::Collection>]
#
def sort_fields(fields)
fields
end

#
# Returns true if the given object should be treated as a collection (i.e. supports `map { |obj| ... }`).
#
Expand All @@ -51,6 +41,18 @@ def collection?(_object)
#
def prepare(context); end

#
# Returns the fields that should be included in the correct order. Default is all fields in the order in which they were defined.
#
# NOTE Only runs once per Blueprint per render.
#
# @param context [Blueprinter::V2::Context]
# @return [Array<Blueprinter::V2::Field|Blueprinter::V2::Object|Blueprinter::V2::Collection>]
#
def blueprint_fields(ctx)
[]
end

#
# Modify or replace the object passed to render/render_object.
#
Expand Down
4 changes: 4 additions & 0 deletions lib/blueprinter/v2/extensions/collections.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ def collection?(object)
else false
end
end

def blueprint_fields(ctx)
ctx.blueprint.class.reflections[:default].ordered
end
end
end
end
Expand Down
4 changes: 2 additions & 2 deletions lib/blueprinter/v2/extensions/field_order.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ def initialize(&sorter)
@sorter = sorter
end

def sort_fields(fields)
fields.sort(&@sorter)
def blueprint_fields(ctx)
ctx.blueprint.class.reflections[:default].ordered.sort(&@sorter)
end
end
end
Expand Down
6 changes: 3 additions & 3 deletions lib/blueprinter/v2/reflection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,16 @@ class View
attr_reader :objects
# @return [Hash<Symbol, Blueprinter::V2::Collection>] Associations to collections defined on the view
attr_reader :collections
# @return [Array] All fields, objects, and collections ordered by the field_order extension hook (default is the order in which they were defined)
attr_reader :sorted
# @return [Array<Blueprinter::V2::Field|Blueprinter::V2::Object|Blueprinter::V2::Collection>] All fields, objects, and collections in the order they were defined
attr_reader :ordered


# @param blueprint [Class] A subclass of Blueprinter::V2::Base
# @param name [Symbol] Name of the view
# @api private
def initialize(blueprint, name)
@name = name
@sorted = blueprint.serializer.hooks.last(:sort_fields, blueprint.schema.values) || blueprint.schema.values
@ordered = blueprint.schema.values
@fields = blueprint.schema.select { |_, f| f.is_a? Field }
@objects = blueprint.schema.select { |_, f| f.is_a? ObjectField }
@collections = blueprint.schema.select { |_, f| f.is_a? Collection }
Expand Down
6 changes: 3 additions & 3 deletions lib/blueprinter/v2/serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ def initialize(blueprint)
end

def call(object, options, instances, data)
data[blueprint.object_id] ||= prepare! Context.new(instances[blueprint], nil, nil, nil, options, instances, data)
ctx = Context.new(instances[blueprint], nil, nil, object, options, instances, data)
ctx.data[blueprint.object_id] ||= prepare! ctx
hooks.reduce_into(:blueprint_input, ctx, :object) if @run_blueprint_input

result = blueprint.reflections[:default].sorted.each_with_object({}) do |field, acc|
result = ctx.data[blueprint.object_id][:fields].each_with_object({}) do |field, acc|
ctx.field = field
ctx.value = nil

Expand Down Expand Up @@ -66,7 +66,7 @@ def prepare!(ctx)
values.prepare ctx
exclusions.prepare ctx
hooks.each(:prepare, ctx) if @run_prepare
true
{ fields: hooks.last(:blueprint_fields, ctx).freeze }.freeze
end

def block_unused_hooks!
Expand Down
6 changes: 3 additions & 3 deletions spec/extensions/extension_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
let(:object) { { foo: 'Foo' } }
let(:context) { Blueprinter::V2::Context }

it 'should default sort fields to the given order' do
fields = [Blueprinter::V2::Field.new(name: :foo), Blueprinter::V2::Field.new(name: :bar)]
expect(subject.new.sort_fields(fields)).to eq fields
it 'should default to no fields' do
ctx = context.new(blueprint.new, nil, nil, object, {})
expect(subject.new.blueprint_fields(ctx)).to eq []
end

it 'should default collections? to false' do
Expand Down
11 changes: 11 additions & 0 deletions spec/v2/extensions/collections_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,15 @@
x = OpenStruct.new
expect(subject.collection? x).to be false
end

it 'should return all fields in the order they were defined' do
blueprint = Class.new(Blueprinter::V2::Base) do
field :name
object :category, self
collection :parts, self
end
ctx = Blueprinter::V2::Context.new(blueprint.new, nil, nil, nil, {}, {}, {})

expect(subject.blueprint_fields(ctx).map(&:name)).to eq %i(name category parts)
end
end
5 changes: 3 additions & 2 deletions spec/v2/extensions/field_order_spec.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# frozen_string_literal: true

describe Blueprinter::V2::Extensions::FieldOrder do
let(:context) { Blueprinter::V2::Context.new(blueprint.new, nil, nil, nil, {}, {}, {}) }
let(:blueprint) do
Class.new(Blueprinter::V2::Base) do
field :foo
Expand All @@ -11,7 +12,7 @@

it 'should sort fields alphabetically' do
ext = described_class.new { |a, b| a.name <=> b.name }
result = ext.sort_fields(blueprint.schema.values)
result = ext.blueprint_fields(context)
expect(result.map(&:name)).to eq %i(bar foo id)
end

Expand All @@ -25,7 +26,7 @@
a.name <=> b.name
end
end
result = ext.sort_fields(blueprint.schema.values)
result = ext.blueprint_fields(context)
expect(result.map(&:name)).to eq %i(id bar foo)
end
end
27 changes: 3 additions & 24 deletions spec/v2/reflection_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -90,33 +90,12 @@
expect(blueprint.reflections[:extended].collections.keys).to eq %i(widgets)
end

it 'should be in the default order' do
names = blueprint.reflections[:default].sorted.map(&:name)
it 'should be in the definition order' do
names = blueprint.reflections[:default].ordered.map(&:name)
expect(names).to eq %i(name category)

names = blueprint.reflections[:extended].sorted.map(&:name)
names = blueprint.reflections[:extended].ordered.map(&:name)
expect(names).to eq %i(name category widgets description)
end

it 'should be in a custom order' do
ext = Class.new(Blueprinter::Extension) do
def initialize(&sorter)
@sorter = sorter
end

def sort_fields(fields)
fields.sort(&@sorter)
end
end

blueprint.extensions << ext.new { |a, b| b.name <=> a.name }
blueprint.extensions << ext.new { |a, b| a.name <=> b.name }

names = blueprint.reflections[:default].sorted.map(&:name)
expect(names).to eq %i(category name)

names = blueprint.reflections[:extended].sorted.map(&:name)
expect(names).to eq %i(category description name widgets)
end
end
end
21 changes: 21 additions & 0 deletions spec/v2/serializer_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,27 @@
})
end

it 'should respect the blueprint_fields hook' do
ext = Class.new(Blueprinter::Extension) do
def blueprint_fields(ctx)
ctx.blueprint.class.reflections[:default].ordered.sort_by(&:name)
end
end
widget_blueprint.extensions << ext.new
widget = {
name: 'Foo',
category: { name: 'Bar' },
parts: [{ num: 42 }, { num: 43 }]
}

result = described_class.new(widget_blueprint).call(widget, {}, instance_cache, {})
expect(result.to_json).to eq({
category: { name: 'Bar' },
name: 'Foo',
parts: [{ num: 42 }, { num: 43 }]
}.to_json)
end

it 'should enable the if conditionals extension' do
widget_blueprint = Class.new(Blueprinter::V2::Base) do
field :name
Expand Down

0 comments on commit 76e930f

Please sign in to comment.