Skip to content

Commit

Permalink
Merge pull request #233 from caws/raise-errors-for-invalid-blueprints
Browse files Browse the repository at this point in the history
Raise errors for invalid blueprints for associations
  • Loading branch information
philipqnguyen authored Aug 11, 2020
2 parents eb46aef + dab36ce commit f69faed
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 13 deletions.
7 changes: 3 additions & 4 deletions lib/blueprinter/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ def self.field(method, options = {}, &block)
#
# @return [Field] A Field object
def self.association(method, options = {}, &block)
raise BlueprinterError, 'blueprint required' unless options[:blueprint]
validate_blueprint!(options[:blueprint], method)

field(
method,
Expand Down Expand Up @@ -214,7 +214,7 @@ def self.render(object, options = {})
# # => [{id:1, title: Hello},{id:2, title: My Day}]
#
# @return [Hash]
def self.render_as_hash(object, options= {})
def self.render_as_hash(object, options = {})
prepare_for_render(object, options)
end

Expand All @@ -239,7 +239,7 @@ def self.render_as_hash(object, options= {})
# # => [{"id" => "1", "title" => "Hello"},{"id" => "2", "title" => "My Day"}]
#
# @return [Hash]
def self.render_as_json(object, options= {})
def self.render_as_json(object, options = {})
prepare_for_render(object, options).as_json
end

Expand Down Expand Up @@ -279,7 +279,6 @@ def self.fields(*field_names)
end



# Specify one transformer to be included for serialization.
# Takes a class which extends Blueprinter::Transformer
#
Expand Down
57 changes: 48 additions & 9 deletions lib/blueprinter/helpers/base_helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,31 +8,32 @@ module SingletonMethods
include TypeHelpers

private

def prepare_for_render(object, options)
view_name = options.delete(:view) || :default
root = options.delete(:root)
meta = options.delete(:meta)
validate_root_and_meta(root, meta)
validate_root_and_meta!(root, meta)
prepare(object, view_name: view_name, local_options: options, root: root, meta: meta)
end

def prepare_data(object, view_name, local_options)
if array_like?(object)
object.map do |obj|
object_to_hash(obj,
view_name: view_name,
local_options: local_options)
view_name: view_name,
local_options: local_options)
end
else
object_to_hash(object,
view_name: view_name,
local_options: local_options)
view_name: view_name,
local_options: local_options)
end
end

def prepend_root_and_meta(data, root, meta)
return data unless root
ret = { root => data }
ret = {root => data}
meta ? ret.merge!(meta: meta) : ret
end

Expand All @@ -43,15 +44,15 @@ def inherited(subclass)
def object_to_hash(object, view_name:, local_options:)
result_hash = view_collection.fields_for(view_name).each_with_object({}) do |field, hash|
next if field.skip?(field.name, object, local_options)
hash[field.name] = field.extract(object, local_options)
hash[field.name] = field.extract(object, local_options)
end
view_collection.transformers(view_name).each do |transformer|
transformer.transform(result_hash,object,local_options)
transformer.transform(result_hash, object, local_options)
end
result_hash
end

def validate_root_and_meta(root, meta)
def validate_root_and_meta!(root, meta)
case root
when String, Symbol
# no-op
Expand All @@ -62,6 +63,44 @@ def validate_root_and_meta(root, meta)
end
end

def dynamic_blueprint?(blueprint)
blueprint.is_a?(Proc)
end

def validate_blueprint!(blueprint, method)
validate_presence_of_blueprint!(blueprint)
unless dynamic_blueprint?(blueprint)
validate_blueprint_has_ancestors!(blueprint, method)
validate_blueprint_has_blueprinter_base_ancestor!(blueprint, method)
end
end

def validate_presence_of_blueprint!(blueprint)
raise BlueprinterError, 'Blueprint required' unless blueprint
end

def validate_blueprint_has_ancestors!(blueprint, association_name)
# If the class passed as a blueprint does not respond to ancestors
# it means it, at the very least, does not have Blueprinter::Base as
# one of its ancestor classes (e.g: Hash) and thus an error should
# be raised.
unless blueprint.respond_to?(:ancestors)
raise BlueprinterError, "Blueprint provided for #{association_name} "\
'association is not valid.'
end
end

def validate_blueprint_has_blueprinter_base_ancestor!(blueprint, association_name)
# Guard clause in case Blueprinter::Base is present in the ancestor list
# for the blueprint class provided.
return if blueprint.ancestors.include? Blueprinter::Base

# Raise error describing what's wrong.
raise BlueprinterError, "Class #{blueprint.name} does not inherit from "\
'Blueprinter::Base and is not a valid Blueprinter '\
"for #{association_name} association."
end

def jsonify(blob)
Blueprinter.configuration.jsonify(blob)
end
Expand Down
20 changes: 20 additions & 0 deletions spec/integrations/base_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,26 @@
end
it('returns json with association') { should eq(result) }
end
context 'Given associated blueprint does not inherit from Blueprinter::Base' do
let(:blueprint) do
vehicle_invalid_blueprint = Class.new
Class.new(Blueprinter::Base) do
identifier :id
association :vehicles, blueprint: vehicle_invalid_blueprint
end
end
it { expect { subject }.to raise_error(Blueprinter::BlueprinterError) }
end
context 'Given associated blueprint does not have any ancestors' do
let(:blueprint) do
vehicle_invalid_blueprint = {}
Class.new(Blueprinter::Base) do
identifier :id
association :vehicles, blueprint: vehicle_invalid_blueprint
end
end
it { expect { subject }.to raise_error(Blueprinter::BlueprinterError) }
end
context "Given association with dynamic blueprint" do
class UserBlueprint < Blueprinter::Base
fields :id
Expand Down

0 comments on commit f69faed

Please sign in to comment.