Skip to content

Commit

Permalink
Merge pull request #372 from procore-oss/nb/nested_transformers
Browse files Browse the repository at this point in the history
Allow transformers to be included across views
  • Loading branch information
njbbaer authored Jan 12, 2024
2 parents 81d60cc + 012a8dc commit 8b715cc
Show file tree
Hide file tree
Showing 7 changed files with 199 additions and 23 deletions.
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -800,6 +800,28 @@ class UserBlueprint < Blueprinter::Base
end
```

#### Transform across views

Transformers can be included across views:

```ruby
class UserBlueprint < Blueprinter::Base
transform DefaultTransformer

view :normal do
transform ViewTransformer
end

view :extended do
include_view :normal
end
end
```

Both the `normal` and `extended` views have `DefaultTransformer` and `ViewTransformer` applied.

Transformers are executed in a top-down order, so `DefaultTransformer` will be executed first, followed by `ViewTransformer`.

#### Global Transforms

You can also specify global default transformers. Create one or more transformer classes extending from `Blueprinter::Transformer` and set the `default_transformers` configuration
Expand Down
4 changes: 0 additions & 4 deletions lib/blueprinter/view.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,6 @@ def initialize(name, fields: {}, included_view_names: [], excluded_view_names: [
@sort_by_definition = Blueprinter.configuration.sort_fields_by.eql?(:definition)
end

def transformers
view_transformers.empty? ? Blueprinter.configuration.default_transformers : view_transformers
end

def track_definition_order(method, viewable: true)
return unless @sort_by_definition

Expand Down
13 changes: 12 additions & 1 deletion lib/blueprinter/view_collection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ def fields_for(view_name)
end

def transformers(view_name)
views[view_name].transformers
included_transformers = gather_transformers_from_included_views(view_name).reverse
all_transformers = views[:default].view_transformers.concat(included_transformers).uniq
all_transformers.empty? ? Blueprinter.configuration.default_transformers : all_transformers
end

def [](view_name)
Expand Down Expand Up @@ -89,5 +91,14 @@ def add_to_ordered_fields(ordered_fields, definition, fields, view_name_filter =
ordered_fields[definition.name] = fields[definition.name]
end
end

def gather_transformers_from_included_views(view_name)
current_view = views[view_name]
current_view.included_view_names.flat_map do |included_view_name|
next [] if view_name == included_view_name

gather_transformers_from_included_views(included_view_name)
end.concat(current_view.view_transformers)
end
end
end
3 changes: 3 additions & 0 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
require 'blueprinter'
require 'json'

Dir[File.dirname(__FILE__) + '/support/**/*.rb'].each { |file| require file }

module SpecHelpers
def reset_blueprinter_config!
Expand Down
9 changes: 9 additions & 0 deletions spec/support/mock_field.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# frozen_string_literal: true

class MockField
attr_reader :name, :method
def initialize(method, name = nil)
@method = method
@name = name || method
end
end
153 changes: 153 additions & 0 deletions spec/units/view_collection_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
# frozen_string_literal: true

describe 'ViewCollection' do
subject(:view_collection) { Blueprinter::ViewCollection.new }

let!(:default_view) { view_collection[:default] }
let!(:view) { view_collection[:view] }

let(:default_field) { MockField.new(:default_field) }
let(:view_field) { MockField.new(:view_field) }
let(:new_field) { MockField.new(:new_field) }

let(:default_transformer) { Blueprinter::Transformer.new }

before do
default_view << default_field
view << view_field
end

describe '#initialize' do
it 'should create an identifier, view, and default view' do
expect(view_collection.views.keys).to eq([:identifier, :default, :view])
end
end

describe '#[]' do
it 'should return the view if it exists' do
expect(view_collection.views[:default]).to eq(default_view)
end

it 'should create the view if it does not exist' do
new_view = view_collection[:new_view]
expect(view_collection.views[:new_view]).to eq(new_view)
end
end

describe '#view?' do
it 'should return true if the view exists' do
expect(view_collection.view?(:default)).to eq(true)
end

it 'should return false if the view does not exist' do
expect(view_collection.view?(:missing_view)).to eq(false)
end
end

describe '#inherit' do
let(:parent_view_collection) { Blueprinter::ViewCollection.new }

before do
parent_view_collection[:view] << new_field
end

it 'should inherit the fields from the parent view collection' do
view_collection.inherit(parent_view_collection)
expect(view.fields).to include(parent_view_collection[:view].fields)
end
end

describe '#fields_for' do
it 'should return the fields for the view' do
expect(view_collection.fields_for(:view)).to eq([default_field, view_field])
end
end

describe '#transformers' do
let(:transformer) { Blueprinter::Transformer.new }

before do
view.add_transformer(transformer)
end

it 'should return the transformers for the view' do
expect(view_collection.transformers(:view)).to eq([transformer])
end

it 'should not return any transformers for another view' do
view_collection[:foo]
expect(view_collection.transformers(:foo)).to eq([])
end

context 'default view transformer' do
before do
default_view.add_transformer(default_transformer)
end

it 'should return the transformers for the default view' do
expect(view_collection.transformers(:default)).to eq([default_transformer])
end

it 'should return both the view transformer and default transformers for the view' do
expect(view_collection.transformers(:view)).to eq([default_transformer, transformer])
end
end

context 'include view transformer' do
let!(:includes_view) { view_collection[:includes_view] }
let!(:nested_view) { view_collection[:nested_view] }

before do
includes_view.include_view(:view)
nested_view.include_view(:includes_view)
end

it 'should return the transformers for the included view' do
expect(view_collection.transformers(:includes_view)).to include(transformer)
end

it 'should return the transformers for the nested included view' do
expect(view_collection.transformers(:nested_view)).to include(transformer)
end

it 'should only return unique transformers' do
includes_view.add_transformer(transformer)
transformers = view_collection.transformers(:nested_view)
expect(transformers.uniq.length == transformers.length).to eq(true)
end

it 'should return transformers in the correct order' do
includes_view_transformer = Blueprinter::Transformer.new
nested_view_transformer = Blueprinter::Transformer.new

default_view.add_transformer(default_transformer)
includes_view.add_transformer(includes_view_transformer)
nested_view.add_transformer(nested_view_transformer)

expect(view_collection.transformers(:nested_view)).to eq([
default_transformer, nested_view_transformer, includes_view_transformer, transformer
])
end
end

context 'global default transformers' do
before do
Blueprinter.configure { |config| config.default_transformers = [default_transformer] }
end

context 'with no view transformers' do
let!(:new_view) { view_collection[:new_view] }

it 'should return the global default transformers' do
expect(view_collection.transformers(:new_view)).to include(default_transformer)
end
end

context 'with view transformers' do
it 'should not return the global default transformers' do
expect(view_collection.transformers(:view)).to_not include(default_transformer)
end
end
end
end
end
18 changes: 0 additions & 18 deletions spec/units/view_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -98,23 +98,5 @@ class OverrideTransform < Blueprinter::Transformer; end
before do
Blueprinter.configure { |config| config.default_transformers = [default_transform] }
end

describe '#transformers' do
it 'should return the default transformers' do
expect(view_with_default_transform.transformers).to eq([default_transform])
end

it 'should allow for overriding the default transformers' do
expect(view_with_override_transform.transformers).to eq([override_transform])
end
end
end
end

class MockField
attr_reader :name, :method
def initialize(method, name = nil)
@method = method
@name = name || method
end
end

0 comments on commit 8b715cc

Please sign in to comment.