Skip to content
Open
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
26 changes: 25 additions & 1 deletion lib/blueprinter/extractors/block_extractor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,31 @@ module Blueprinter
# @api private
class BlockExtractor < Extractor
def extract(_field_name, object, local_options, options = {})
options[:block].call(object, local_options)
block = options[:block]

# Symbol#to_proc creates procs with signature [[:req], [:rest]]
# These procs forward ALL arguments to the method, which causes
# issues when we call block.call(object, local_options) because
# it becomes object.method_name(local_options), and most methods
# don't accept extra arguments.
#
# For Symbol#to_proc, we only pass the object.
# For regular blocks, we pass both object and local_options.
if symbol_to_proc?(block)
block.call(object)
else
block.call(object, local_options)
end
end

private

def symbol_to_proc?(block)
# Symbol#to_proc has a characteristic signature:
# - Parameters: [[:req], [:rest]] (one required + rest args)
# - This is different from regular blocks which typically have
# optional parameters like [[:opt, :obj], [:opt, :options]]
block.parameters == [[:req], [:rest]]
end
end
end
57 changes: 57 additions & 0 deletions spec/integrations/base_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,63 @@
end
end

context 'when using Symbol#to_proc syntax' do
let(:obj) { object_with_attributes }

context 'with field using &:method_name' do
let(:blueprint) do
Class.new(Blueprinter::Base) do
identifier :id
field :name, &:first_name
field :job_position, &:position
end
end

it 'renders the field by calling the method on the object' do
result = JSON.parse(blueprint.render(obj))
expect(result['id']).to eq(1)
expect(result['name']).to eq('Meg')
expect(result['job_position']).to eq('Manager')
end
end

context 'with identifier using &:method_name' do
let(:blueprint) do
Class.new(Blueprinter::Base) do
identifier :user_id, &:id
field :first_name
end
end

it 'renders the identifier using Symbol#to_proc' do
expect(blueprint.render(obj)).to eq('{"user_id":1,"first_name":"Meg"}')
end
end

context 'when mixing Symbol#to_proc and regular blocks' do
let(:blueprint) do
Class.new(Blueprinter::Base) do
identifier :id
field :name, &:first_name
field :full_name do |obj|
"#{obj.first_name} #{obj.last_name}"
end
field :info do |obj, options|
"#{obj.position} (options: #{options.inspect})"
end
end
end

it 'handles both block types correctly' do
result = JSON.parse(blueprint.render(obj))
expect(result['id']).to eq(1)
expect(result['name']).to eq('Meg')
expect(result['full_name']).to eq('Meg Ryan')
expect(result['info']).to include('Manager')
end
end
end

context 'Outside Rails project' do
context 'Given passed object has dot notation accessible attributes' do
let(:obj) { object_with_attributes }
Expand Down