diff --git a/README.md b/README.md index 68c4271b..dde177aa 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/lib/blueprinter/view.rb b/lib/blueprinter/view.rb index 4db27600..d38d4870 100644 --- a/lib/blueprinter/view.rb +++ b/lib/blueprinter/view.rb @@ -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 diff --git a/lib/blueprinter/view_collection.rb b/lib/blueprinter/view_collection.rb index 0f4f9103..952aa5fd 100644 --- a/lib/blueprinter/view_collection.rb +++ b/lib/blueprinter/view_collection.rb @@ -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) @@ -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 diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 8a0215cb..56915a8d 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -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! diff --git a/spec/support/mock_field.rb b/spec/support/mock_field.rb new file mode 100644 index 00000000..c81eff38 --- /dev/null +++ b/spec/support/mock_field.rb @@ -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 diff --git a/spec/units/view_collection_spec.rb b/spec/units/view_collection_spec.rb new file mode 100644 index 00000000..8ad5561c --- /dev/null +++ b/spec/units/view_collection_spec.rb @@ -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 diff --git a/spec/units/view_spec.rb b/spec/units/view_spec.rb index 22466aec..f299a388 100644 --- a/spec/units/view_spec.rb +++ b/spec/units/view_spec.rb @@ -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