diff --git a/lib/blueprinter/extractors/block_extractor.rb b/lib/blueprinter/extractors/block_extractor.rb index 44210262..a6048e4e 100644 --- a/lib/blueprinter/extractors/block_extractor.rb +++ b/lib/blueprinter/extractors/block_extractor.rb @@ -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 diff --git a/spec/integrations/base_spec.rb b/spec/integrations/base_spec.rb index e1e2e78c..cbbb7577 100644 --- a/spec/integrations/base_spec.rb +++ b/spec/integrations/base_spec.rb @@ -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 }