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 reflection on views, fields, and associations #357

Merged
merged 9 commits into from
Jan 9, 2024
2 changes: 2 additions & 0 deletions lib/blueprinter/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@
require_relative 'view'
require_relative 'view_collection'
require_relative 'transformer'
require_relative 'reflection'

module Blueprinter
class Base
include BaseHelpers
extend Reflection

# Specify a field or method name used as an identifier. Usually, this is
# something like :id
Expand Down
71 changes: 71 additions & 0 deletions lib/blueprinter/reflection.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# frozen_string_literal: true

module Blueprinter
#
# Public methods for reflecting on a Blueprint.
#
module Reflection
Field = Struct.new(:name, :display_name, :options)
Association = Struct.new(:name, :display_name, :blueprint, :view, :options)

#
# Returns a Hash of views keyed by name.
#
# Example:
#
# widget_view = WidgetBlueprint.reflections[:default]
# category = widget_view.associations[:category]
# category.blueprint
# => CategoryBlueprint
# category.view
# => :default
#
# @return [Hash<Symbol, Blueprinter::Reflection::View>]
#
def reflections
@reflections ||= view_collection.views.transform_values do |view|
View.new(view.name, view_collection)
end
end

#
# Represents a view within a Blueprint.
#
class View
attr_reader :name

def initialize(name, view_collection)
@name = name
@view_collection = view_collection
end

#
# Returns a Hash of fields in this view (recursive) keyed by method name.
#
# @return [Hash<Symbol, Blueprinter::Reflection::Field>]
#
def fields
@fields ||= @view_collection.fields_for(name).each_with_object({}) do |field, obj|
next if field.options[:association]

obj[field.method] = Field.new(field.method, field.name, field.options)
end
end

#
# Returns a Hash of associations in this view (recursive) keyed by method name.
jhollinger marked this conversation as resolved.
Show resolved Hide resolved
#
# @return [Hash<Symbol, Blueprinter::Reflection::Association>]
#
def associations
@associations ||= @view_collection.fields_for(name).each_with_object({}) do |field, obj|
next unless field.options[:association]

blueprint = field.options.fetch(:blueprint)
view = field.options[:view] || :default
obj[field.method] = Association.new(field.method, field.name, blueprint, view, field.options)
end
end
end
end
end
102 changes: 102 additions & 0 deletions spec/units/reflection_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# frozen_string_literal: true

require 'json'

describe Blueprinter::Reflection do
let(:category_blueprint) {
Class.new(Blueprinter::Base) do
fields :id, :name
end
}

let(:part_blueprint) {
Class.new(Blueprinter::Base) do
fields :id, :name

view :extended do
field :description
end
end
}

let(:widget_blueprint) {
cat_bp = category_blueprint
part_bp = part_blueprint
Class.new(Blueprinter::Base) do
fields :id, :name
association :category, blueprint: cat_bp

view :extended do
association :parts, blueprint: part_bp, view: :extended
end

view :extended_plus do
include_view :extended
field :foo
association :foos, blueprint: part_bp
end

view :extended_plus_plus do
include_view :extended_plus
field :bar
association :bars, blueprint: part_bp
end

view :legacy do
association :parts, blueprint: part_bp, name: :pieces
end
end
}

it 'should list views' do
expect(widget_blueprint.reflections.keys.sort).to eq [
:identifier,
:default,
:extended,
:extended_plus,
:extended_plus_plus,
:legacy,
].sort
end

it 'should list fields' do
expect(part_blueprint.reflections.fetch(:extended).fields.keys.sort).to eq [
:id,
:name,
:description,
].sort
end

it 'should list fields from included views' do
expect(widget_blueprint.reflections.fetch(:extended_plus_plus).fields.keys.sort).to eq [
:id,
:name,
:foo,
:bar,
].sort
end

it 'should list associations' do
associations = widget_blueprint.reflections.fetch(:default).associations
expect(associations.keys).to eq [:category]
end

it 'should list associations from included views' do
associations = widget_blueprint.reflections.fetch(:extended_plus_plus).associations
expect(associations.keys.sort).to eq [:category, :parts, :foos, :bars].sort
end

it 'should list associations using custom names' do
associations = widget_blueprint.reflections.fetch(:legacy).associations
expect(associations.keys).to eq [:category, :parts]
expect(associations[:parts].display_name).to eq :pieces
end

it 'should get a blueprint and view from an association' do
assoc = widget_blueprint.reflections[:extended].associations[:parts]
expect(assoc.name).to eq :parts
expect(assoc.display_name).to eq :parts
expect(assoc.blueprint).to eq part_blueprint
expect(assoc.view).to eq :extended
end
end