From a8f53af2ef9cffa7ce580876dcedb11f468edb2d Mon Sep 17 00:00:00 2001 From: Jordan Hollinger Date: Wed, 10 Jul 2024 13:25:02 -0400 Subject: [PATCH] Allow 'dot syntax' for accessing nested views. Add :default view as alias to self Signed-off-by: Jordan Hollinger --- lib/blueprinter/v2.rb | 16 +++++++++++----- spec/v2/name_spec.rb | 34 ++++++++++++++++++++++++---------- 2 files changed, 35 insertions(+), 15 deletions(-) diff --git a/lib/blueprinter/v2.rb b/lib/blueprinter/v2.rb index 78be5282..7f809efa 100644 --- a/lib/blueprinter/v2.rb +++ b/lib/blueprinter/v2.rb @@ -13,7 +13,7 @@ class << self # Initialize subclass def self.inherited(subclass) - subclass.views = {} + subclass.views = { default: subclass } subclass.fields = fields.dup subclass.extensions = extensions.dup subclass.blueprint_name = subclass.name ? [subclass.name] : blueprint_name.dup @@ -26,18 +26,24 @@ def self.inspect # A descriptive name for the Blueprint view, e.g. "WidgetBlueprint:extended" def self.to_s - blueprint_name.join ':' + blueprint_name.join '.' end # Access a child view + # MyBlueprint[:extended] + # MyBlueprint[:extended][:plus] + # MyBlueprint["extended.plus"] def self.[](view) - views.fetch(view) - rescue KeyError - raise Blueprinter::Errors::UnknownView, "View '#{view}' could not be found in Blueprint '#{self}'" + view.to_s.split('.').reduce(self) do |blueprint, child| + blueprint.views[child.to_sym] || + raise(Errors::UnknownView, "View '#{child}' could not be found in Blueprint '#{blueprint}'") + end end # Define a new child view, which is a subclass of self def self.view(name, &definition) + raise Errors::InvalidBlueprint, "View name may not contain '.'" if name.to_s =~ /\./ + views[name] = Class.new(self) views[name].blueprint_name << name views[name].class_eval(&definition) if definition diff --git a/spec/v2/name_spec.rb b/spec/v2/name_spec.rb index e4220170..5f53e5b4 100644 --- a/spec/v2/name_spec.rb +++ b/spec/v2/name_spec.rb @@ -12,8 +12,8 @@ class NamedBlueprint < Blueprinter::V2 end it 'should find a view by name' do - expect(NamedBlueprint[:extended].to_s).to eq "NamedBlueprint:extended" - expect(NamedBlueprint[:extended].inspect).to eq "NamedBlueprint:extended" + expect(NamedBlueprint[:extended].to_s).to eq "NamedBlueprint.extended" + expect(NamedBlueprint[:extended].inspect).to eq "NamedBlueprint.extended" end it 'should raise for an invalid view name' do @@ -38,8 +38,8 @@ class NamedBlueprint < Blueprinter::V2 end it 'should find a view by name' do - expect(blueprint[:extended].to_s).to eq "MyBlueprint:extended" - expect(blueprint[:extended].inspect).to eq "MyBlueprint:extended" + expect(blueprint[:extended].to_s).to eq "MyBlueprint.extended" + expect(blueprint[:extended].inspect).to eq "MyBlueprint.extended" end end @@ -78,14 +78,28 @@ class NamedBlueprint < Blueprinter::V2 expect(blueprint.to_s).to eq "MyBlueprint" expect(blueprint.inspect).to eq "MyBlueprint" - expect(blueprint[:foo].to_s).to eq "MyBlueprint:foo" - expect(blueprint[:foo].inspect).to eq "MyBlueprint:foo" + expect(blueprint[:foo].to_s).to eq "MyBlueprint.foo" + expect(blueprint[:foo].inspect).to eq "MyBlueprint.foo" - expect(blueprint[:foo][:bar].to_s).to eq "MyBlueprint:foo:bar" - expect(blueprint[:foo][:bar].inspect).to eq "MyBlueprint:foo:bar" + expect(blueprint[:foo][:bar].to_s).to eq "MyBlueprint.foo.bar" + expect(blueprint[:foo][:bar].inspect).to eq "MyBlueprint.foo.bar" - expect(blueprint[:foo][:bar][:zorp].to_s).to eq "MyBlueprint:foo:bar:zorp" - expect(blueprint[:foo][:bar][:zorp].inspect).to eq "MyBlueprint:foo:bar:zorp" + expect(blueprint[:foo][:bar][:zorp].to_s).to eq "MyBlueprint.foo.bar.zorp" + expect(blueprint[:foo][:bar][:zorp].inspect).to eq "MyBlueprint.foo.bar.zorp" end + + it 'should find deeply nested names using dot syntax' do + expect(blueprint["foo"].to_s).to eq "MyBlueprint.foo" + expect(blueprint["foo.bar"].to_s).to eq "MyBlueprint.foo.bar" + expect(blueprint["foo.bar.zorp"].to_s).to eq "MyBlueprint.foo.bar.zorp" + end + end + + it "should not contain periods" do + blueprint = Class.new(Blueprinter::V2) + expect { blueprint.view :"foo.bar" }.to raise_error( + Blueprinter::Errors::InvalidBlueprint, + /name may not contain/ + ) end end