Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add extensions with initial support for pre_render #358

Merged
merged 6 commits into from
Jan 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/blueprinter.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# frozen_string_literal: true

require_relative 'blueprinter/base'
require_relative 'blueprinter/extension'

module Blueprinter
end
1 change: 1 addition & 0 deletions lib/blueprinter/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,7 @@ def self.render_as_json(object, options = {})
def self.prepare(object, view_name:, local_options:, root: nil, meta: nil)
raise BlueprinterError, "View '#{view_name}' is not defined" unless view_collection.view? view_name

object = Blueprinter.configuration.extensions.pre_render(object, self, view_name, local_options)
data = prepare_data(object, view_name, local_options)
prepend_root_and_meta(data, root, meta)
end
Expand Down
10 changes: 10 additions & 0 deletions lib/blueprinter/configuration.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# frozen_string_literal: true

require_relative 'extensions'

module Blueprinter
class Configuration
attr_accessor :association_default, :datetime_format, :deprecations, :field_default, :generator, :if, :method,
Expand All @@ -22,6 +24,14 @@ def initialize
@custom_array_like_classes = []
end

def extensions
@extensions ||= Extensions.new
end

def extensions=(list)
@extensions = Extensions.new(list)
end

def array_like_classes
@array_like_classes ||= [
Array,
Expand Down
22 changes: 22 additions & 0 deletions lib/blueprinter/extension.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# frozen_string_literal: true

module Blueprinter
#
# Base class for all extensions. All extension methods are implemented as no-ops.
#
class Extension
#
# Called eary during "render", this method receives the object to be rendered and
# may return a modified (or new) object to be rendered.
#
# @param object [Object] The object to be rendered
# @param _blueprint [Class] The Blueprinter class
# @param _view [Symbol] The blueprint view
# @param _options [Hash] Options passed to "render"
# @return [Object] The object to continue rendering
#
def pre_render(object, _blueprint, _view, _options)
object
end
end
end
37 changes: 37 additions & 0 deletions lib/blueprinter/extensions.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# frozen_string_literal: true

module Blueprinter
#
# Stores and runs Blueprinter extensions. An extension is any object that implements one or more of the
# extension methods:
#
# The Render Extension intercepts an object before rendering begins. The return value from this
# method is what is ultimately rendered.
#
# def pre_render(object, blueprint, view, options)
# # returns original, modified, or new object
# end
#
class Extensions
def initialize(extensions = [])
@extensions = extensions
end

def to_a
@extensions.dup
end

# Appends an extension
def <<(ext)
@extensions << ext
self
end

# Runs the object through all Render Extensions and returns the final result
def pre_render(object, blueprint, view, options = {})
@extensions.reduce(object) do |acc, ext|
ext.pre_render(acc, blueprint, view, options)
end
end
end
end
126 changes: 126 additions & 0 deletions spec/units/extensions_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
# frozen_string_literal: true

require 'json'
require 'ostruct'

describe Blueprinter::Extensions do
let(:all_extensions) {
[
foo_extension.new,
bar_extension.new,
zar_extension.new,
]
}

let(:foo_extension) {
Class.new(Blueprinter::Extension) do
def pre_render(object, _blueprint, _view, _options)
obj = object.dup
obj.foo = "Foo"
obj
end
end
}

let(:bar_extension) {
Class.new(Blueprinter::Extension) do
def pre_render(object, _blueprint, _view, _options)
obj = object.dup
obj.bar = "Bar"
obj
end
end
}

let(:zar_extension) {
Class.new(Blueprinter::Extension) do
def self.something_else(object, _blueprint, _view, _options)
object
end
end
}

it 'should append extensions' do
extensions = Blueprinter::Extensions.new
extensions << foo_extension.new
extensions << bar_extension.new
extensions << zar_extension.new
expect(extensions.to_a.map(&:class)).to eq [
foo_extension,
bar_extension,
zar_extension,
]
end

it "should initialize with extensions, removing any that don't have recognized extension methods" do
extensions = Blueprinter::Extensions.new(all_extensions)
expect(extensions.to_a.map(&:class)).to eq [
foo_extension,
bar_extension,
zar_extension,
]
end

context '#pre_render' do
before :each do
Blueprinter.configure do |config|
config.extensions = all_extensions
end
end

after :each do
Blueprinter.configure do |config|
config.extensions = []
end
end

let(:test_blueprint) {
Class.new(Blueprinter::Base) do
field :id
field :name
field :foo

view :with_bar do
field :bar
end
end
}

it 'should run all pre_render extensions' do
extensions = Blueprinter::Extensions.new(all_extensions)
obj = OpenStruct.new(id: 42, name: 'Jack')
obj = extensions.pre_render(obj, test_blueprint, :default, {})
expect(obj.id).to be 42
expect(obj.name).to eq 'Jack'
expect(obj.foo).to eq 'Foo'
expect(obj.bar).to eq 'Bar'
end

it 'should run with Blueprinter.render using default view' do
obj = OpenStruct.new(id: 42, name: 'Jack')
res = JSON.parse(test_blueprint.render(obj))
expect(res['id']).to be 42
expect(res['name']).to eq 'Jack'
expect(res['foo']).to eq 'Foo'
expect(res['bar']).to be_nil
end

it 'should run with Blueprinter.render using with_bar view' do
obj = OpenStruct.new(id: 42, name: 'Jack')
res = JSON.parse(test_blueprint.render(obj, view: :with_bar))
expect(res['id']).to be 42
expect(res['name']).to eq 'Jack'
expect(res['foo']).to eq 'Foo'
expect(res['bar']).to eq 'Bar'
end

it 'should run with Blueprinter.render_as_hash' do
obj = OpenStruct.new(id: 42, name: 'Jack')
res = test_blueprint.render_as_hash(obj, view: :with_bar)
expect(res[:id]).to be 42
expect(res[:name]).to eq 'Jack'
expect(res[:foo]).to eq 'Foo'
expect(res[:bar]).to eq 'Bar'
end
end
end