@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_eval
ed or instance_exec
ed, 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
Since you are saving the proc instance as
@description
, it is better to access that via a variable rather than usingyield
.def description &pr
@description = pr
endYou need to evaluate
@description
in the appropriate environment, i.e. within the instance ofCar
.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
Rendering File with Mime Type in Rails
Rails 4.1 Can't Deploy via Capistrano 3
Natural Language Date Parser for Ruby/Rails
Unable to Load Gem Cocoa Pods While Creating Repo
Understanding Ruby Symbol as Method Call
How to Load Some Activerecord Models from a Yaml File and Save Them to the Db
Ruby on Rails: Yielding Specific Views in a Specific Places in the Layout
Polymorphic Association with Multiple Associations on the Same Model
Using Multiple Controllers in One View in Rails
Paperclip Amazon S3 Setup with Heroku
What Does the Underscore Mean in Literal Numbers
If Else Statements in .Html.Erb in Views
Creating a Hash with Values as Arrays and Default Value as Empty Array
Detect Rspec Test Failure on After Each Method
How to Find Where a Ruby Method Is Declared
How to Configure Mongomapper and Activerecord in Same Ruby Rails Project