Skip to content

Commit

Permalink
V2 base class
Browse files Browse the repository at this point in the history
Signed-off-by: Jordan Hollinger <[email protected]>
  • Loading branch information
jhollinger committed Jul 9, 2024
1 parent 3c4fc29 commit d4100bf
Show file tree
Hide file tree
Showing 5 changed files with 176 additions and 0 deletions.
1 change: 1 addition & 0 deletions lib/blueprinter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ module Blueprinter
autoload :BlueprinterError, 'blueprinter/blueprinter_error'
autoload :Errors, 'blueprinter/errors'
autoload :Extension, 'blueprinter/extension'
autoload :V2, 'blueprinter/v2'
end
1 change: 1 addition & 0 deletions lib/blueprinter/errors.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
module Blueprinter
module Errors
autoload :InvalidBlueprint, 'blueprinter/errors/invalid_blueprint'
autoload :UnknownView, 'blueprinter/errors/unknown_view'
end
end
7 changes: 7 additions & 0 deletions lib/blueprinter/errors/unknown_view.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# frozen_string_literal: true

module Blueprinter
module Errors
class UnknownView < Blueprinter::BlueprinterError; end
end
end
76 changes: 76 additions & 0 deletions lib/blueprinter/v2.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# frozen_string_literal: true

module Blueprinter
class V2
class << self
attr_accessor :views, :fields, :extensions, :blueprint_name
end

self.views = {}
self.fields = {}
self.extensions = []
self.blueprint_name = []

# Initialize subclass
def self.inherited(subclass)
subclass.views = {}
subclass.fields = fields.dup
subclass.extensions = extensions.dup
subclass.blueprint_name = subclass.name ? [subclass.name] : blueprint_name.dup
end

# A descriptive name for the Blueprint view, e.g. "WidgetBlueprint:extended"
def self.inspect
to_s
end

# A descriptive name for the Blueprint view, e.g. "WidgetBlueprint:extended"
def self.to_s
blueprint_name.join ':'
end

# Access a child view
def self.[](view)
views.fetch(view)
rescue KeyError
raise Blueprinter::Errors::UnknownView, "View '#{view}' could not be found in Blueprint '#{self}'"
end

# Define a new child view, which is a subclass of self
def self.view(name, &definition)
views[name] = Class.new(self)
views[name].blueprint_name << name
views[name].class_eval(&definition) if definition
views[name]
end

# Define a field
# rubocop:todo Lint/UnusedMethodArgument
def self.field(name, options = {})
fields[name] = 'TODO'
end

# Define an association
def self.association(name, blueprint, options = {})
fields[name] = 'TODO'
end

def self.render(obj, options = {})
new.render(obj, options)
end

def render(obj, options = {})
# TODO: call an external Render module/class, passing in self, obj, and options.
#
# I propose this new renderer (possibly shared with 1.x) would have an "outer" and
# "inner" API. The "inner" API would be used when rendering nested Blueprints. The
# "outer" API would only be called here.
#
# This design would allow for some render hooks to only be called ONCE per render (baring
# a field/association block calling "render" again), and others to be called on every
# nested Blueprint. This would fix some persistent issues with blueprinter-activerecord.
end

# rubocop:enable Lint/UnusedMethodArgument
end
end
91 changes: 91 additions & 0 deletions spec/v2/name_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# frozen_string_literal: true

describe "Blueprinter::V2 Names" do
context 'const named Blueprints' do
class NamedBlueprint < Blueprinter::V2
view :extended
end

it 'should have a base name' do
expect(NamedBlueprint.to_s).to eq "NamedBlueprint"
expect(NamedBlueprint.inspect).to eq "NamedBlueprint"
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"
end

it 'should raise for an invalid view name' do
expect { NamedBlueprint[:wrong_name] }.to raise_error(
Blueprinter::Errors::UnknownView,
"View 'wrong_name' could not be found in Blueprint 'NamedBlueprint'"
)
end
end

context 'manually named Blueprints' do
let(:blueprint) do
Class.new(Blueprinter::V2) do
blueprint_name << "MyBlueprint"
view :extended
end
end

it 'should have no base name' do
expect(blueprint.to_s).to eq "MyBlueprint"
expect(blueprint.inspect).to eq "MyBlueprint"
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"
end
end

context 'anonymous Blueprints' do
let(:blueprint) do
Class.new(Blueprinter::V2) do
view :extended
end
end

it 'should have no base name' do
expect(blueprint.to_s).to eq ""
expect(blueprint.inspect).to eq ""
end

it 'should find a view by name' do
expect(blueprint[:extended].to_s).to eq "extended"
expect(blueprint[:extended].inspect).to eq "extended"
end
end

context 'deeply nested Blueprints' do
let(:blueprint) do
Class.new(Blueprinter::V2) do
blueprint_name << "MyBlueprint"

view :foo do
view :bar do
view :zorp
end
end
end
end

it 'should find deeply nested names' do
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][: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"
end
end
end

0 comments on commit d4100bf

Please sign in to comment.