can't access ActiveRecord mutators in a block

2019-09-07 05:37发布

问题:

I am inside a Rails controller and I am trying to access my instance variable in a block: This gives an error saying that "no method field1 for Nil":

Prawn::Document.generate("hello.pdf") do
  @model.field1
end

However, if I do this, then it works:

my_model = @model
Prawn::Document.generate("hello.pdf") do
  my_model.field1
end

Could this have something to do with ActiveRecord accessors or instance variables in a block?

回答1:

That's happening because code inside block is executed in context of Prawn::Document object. Let's go inside this code:

module Prawn
  class Document
    def self.generate(filename,options={},&block)
      pdf = new(options,&block)
      pdf.render_file(filename)
    end

    def initialize(options={},&block)
      if block
        block.arity < 1 ? instance_eval(&block) : block[self]
      end
    end
  end
end

As you can see, block is executed with Document object as self. It try to find @model as instance variable of self, can't do this and return nil. If you use local variable model, you get help of closures and your code is working properly



回答2:

This kind of problem appears when a block is being executed in a different context, usually through a instance_eval. So let's check the code:

#lib/prawn/document.rb: Document#initialize    
if block
  block.arity < 1 ? instance_eval(&block) : block[self]
end

There you have your instance_eval, and you can also see the solution: pass a block that accepts the document as argument and you'll now be able to access the instance variables as usual:

Prawn::Document.generate("hello.pdf") do |doc|
  @my_model.field1
end