@Instance_Variable Not Available Inside a Ruby Block

@instance_variable not available inside a ruby block?

It depends on how the block is being called. If it is called using the yield keyword or the Proc#call method, then you'll be able to use your instance variables in the block. If it's called using Object#instance_eval or Module#class_eval then the context of the block will be changed and you won't be able to access your instance variables.

@x = "Outside the class"

class Test
def initialize
@x = "Inside the class"
end

def a(&block)
block.call
end

def b(&block)
self.instance_eval(&block)
end
end

Test.new.a { @x } #=> "Outside the class"
Test.new.b { @x } #=> "Inside the class"

In your case, it looks like Sunspot.search is calling your block in a different context using instance_eval, because the block needs easy access to that keywords method.

How can I access an instance variable inside of a Ruby block?

The interesting question isn't so much why the local variable is accessible inside the block (of course it is, blocks are closures, having access to local variables from the surrounding scope is kind of the point), but rather why the instance variable isn't. Because, actually, it should be accessible.

However, if the block is being instance_evaled or instance_execed, then the value of self will be changed to the receiver of the instance_eval or instance_exec message, and instance variables are always looked up in self, so since self is now a different object, it probably won't have an @line_hash instance variable. You would have to look at the implementation of Xml::Parser::new to find out.

See also How does instance_eval work and why does DHH hate it?

How to access instance variable from a block

Variables starting with '@' aren't global; they are instance variables. This means that they belong to a particular object, being (mostly) inaccessible from others.

What seems to be happening is that the search method is changing the context of execution (probably via instance_eval/instance_exec), which means that, inside the block, your self isn't the same, and you won't have access to the same instance variables.

A simple workaround is to use a local variable instead:

node           = Node.find(params[:id])
@similar_nodes = Tire.search 'nodes', load: true do
query do
fuzzy_like_this node.title
end
end

Then, if you really need node to be an instance variable, you can assign it later:

@node = node

Why do instance variables seemingly disappear when inside a block?

First off, @user is not a "private variable" in Ruby; it is an instance variable. Instance variables are available within the the scope of the current object (what self refers to). I have edited the title of your question to more accurately reflect your question.

A block is like a function, a set of code to be executed at a later date. Often that block will be executed in the scope where the block was defined, but it is also possible to evaluate the block in another context:

class Foo
def initialize( bar )
# Save the value as an instance variable
@bar = bar
end
def unchanged1
yield if block_given? # call the block with its original scope
end
def unchanged2( &block )
block.call # another way to do it
end
def changeself( &block )
# run the block in the scope of self
self.instance_eval &block
end
end

@bar = 17
f = Foo.new( 42 )
f.unchanged1{ p @bar } #=> 17
f.unchanged2{ p @bar } #=> 17
f.changeself{ p @bar } #=> 42

So either you are defining the block outside the scope where @user is set, or else the implementation of client.request causes the block to be evaluated in another scope later on. You could find out by writing:

client.request("createSession"){ p [self.class,self] }

to gain some insight into what sort of object is the current self in your block.

The reason they "disappear" in your case—instead of throwing an error—is that Ruby permissively allows you to ask for the value of any instance variable, even if the value has never been set for the current object. If the variable has never been set, you'll just get back nil (and a warning, if you have them enabled):

$ ruby -e "p @foo"
nil

$ ruby -we "p @foo"
-e:1: warning: instance variable @foo not initialized
nil

As you found, blocks are also closures. This means that when they run they have access to local variables defined in the same scope as the block is defined. This is why your second set of code worked as desired. Closures are one excellent way to latch onto a value for use later on, for example in a callback.

Continuing the code example above, you can see that the local variable is available regardless of the scope in which the block is evaluated, and takes precedence over same-named methods in that scope (unless you provide an explicit receiver):

class Foo
def x
123
end
end
x = 99
f.changeself{ p x } #=> 99
f.unchanged1{ p x } #=> 99
f.changeself{ p self.x } #=> 123
f.unchanged1{ p self.x } #=> Error: undefined method `x' for main:Object

Save instance variable in a block in Ruby

  1. Since you are saving the proc instance as @description, it is better to access that via a variable rather than using yield.

    def description &pr
    @description = pr
    end
  2. You need to evaluate @description in the appropriate environment, i.e. within the instance of Car.

    def show_description
    instance_exec(&@description)
    end

With the fixes as above, it will work as you intended.

c = Car.new("Ford")
c.description{"#{@model} is very good."}
puts c.show_description
# => Ford is very good.

By the way, you can simplify "#{@model} is very good." to "#@model is very good.".

Access variable inside block

My guess is that async runs the block with instance_eval, so your instance variable is binding to some other object when used inside the block. If you only need to read the variable, just use a local copy inside the block

@my_var = true
my_var = @my_var
Dispatch::Queue.concurrent.async do
my_var
end

or if you have an accessor method

@my_var = true
this = self
Dispatch::Queue.concurrent.async do
this.my_var
end

Can someone please explain this block of Ruby code and why the variable is null inside the block?

The block passed to .search may be evaluated in a different context with the help of BasicObject#instance_eval like (instance_exec, class_eval, class_exec) method, so that the method fulltext (and other DSL methods) defined for the receiver of instance_eval(maybe Refinery::Books::Book?) can be seen from the block. The magic here is Ruby changes the self binding inside the block. The receiver of instance_eval acts as the self there.

Instance variable resolution also depends on self. When you refer to @post, you are actually fetching instance variable @post of self. Since the context of self has been modified in the block, and the actual self has no instance variable @post, nil is returned as the default value.

The fix is assign @post.title to a local variable and use that variable in the .search block.

title = @post.title
solr = Refinery::Books::Book.search { fulltext title.split(' ').join(' OR ') }

The block will capture the local binding at the place of its definition. In side the block, Ruby will first look up names in the local binding, and if missing, it will look up methods defined on self.

can't access ActiveRecord mutators in a block

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



Related Topics



Leave a reply



Submit