How does instance_eval work and why does DHH hate it?
The thing that instance_eval
does is that it runs the block in the context of a different instance. In other words, it changes the meaning of self
which means it changes the meaning of instance methods and instance variables.
This creates a cognitive disconnect: the context in which the block runs is not the context in which it appears on the screen.
Let me demonstrate that with a slight variation of @Matt Briggs's example. Let's say we're building an email instead of a form:
def mail
builder = MailBuilder.new
yield builder
# executed after the block
# do stuff with builder
end
mail do |f|
f.subject @subject
f.name name
end
In this case, @subject
is an instance variable of your object and name
is a method of your class. You can use nice object-oriented decomposition and store your subject in a variable.
def mail &block
builder = MailBuilder.new
builder.instance_eval &block
# do stuff with builder
end
mail do
subject @subject
name name # Huh?!?
end
In this case, @subject
is an instance variable of the mail builder object! It might not even exist! (Or even worse, it might exist and contain some completely stupid value.) There is no way for you to get access to your object's instance variables. And how do you even call the name
method of your object? Everytime you try to call it, you get the mail builder's method.
Basically, instance_eval
makes it hard to use your own code inside the DSL code. So, it should really only be used in cases where there is very little chance that this might be needed.
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
.
@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.
Why is my define_method not working?
"#{attr}_list"
by itself is just the string "specialty_list"
or "qualification_list"
, and strings don't have an add method. I think you want to the send
the specialty_list
method e.g.
%w{ specialty qualification }.each do |attr|
define_method("add_#{attr}") do |arg|
send("#{attr}_list").add(arg)
save
end
end
Ruby Scoping Can't pass class variable
Probably, as Marek Lipka says, the context is modified by instance_eval
. If that is the case, you cannot access instance variables of the Scraper
instance, but can access local variables. Do this:
def scrape
options = @options
Wombat.crawl do
base_url options[:base_url]
path options[:path]
some_data css: options[:css]
end
end
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?
Can't access instance var from do block in Ruby
The Mail.defaults
method is defined as follows:
def self.defaults(&block)
Configuration.instance.instance_eval(&block)
end
Which means that it evaluates the block within the singleton instance (Configuration
includes the Singleton
module) of Configuration
which is not the context of the class you are working with and where @mail_settings
resides.
To overcome this you can define a local variable to hold @mail_settings
so that you can access it within the block:
m = @mail_settings
Mail.defaults do
retriever_method :imap, :address => m[:address],
:port => m[:port],
:user_name => m[:username],
:password => m[:password],
:enable_ssl => m[:enable_ssl]
end
How does title method inside TkRoot block work in Ruby?
The key to this black magic is instance_eval
, which will evaluate the block in the context of its receiver. Normally self
inside a block will be determined by scope; instance_eval
will change the self
inside its block to be whatever received the instance_eval
message.
class Dog
def bark
puts "Woof"
end
def initialize(&block)
instance_eval(&block)
end
end
Dog.new { bark }
# => "Woof"
In this case, instance_eval(&block)
is the same as self.instance_eval(&block)
, which means self
inside the block will be the Dog
instance; then bark
is of course self.bark
, where self
is the Dog
.
There is a school of thought that instance_eval
is harmful. I agree that it is best to limit its use to clearly delineated DSLs. I.e. it may work for TK, RSpec or Sinatra, but if you think it's cool and want to use it, it's good to think twice.
How to call Class methods in block?
If you want to call block
in the context of the items
message recipient, you can use instance_eval
:
def items(&block)
instance_eval(&block)
end
Related Topics
What Does the (Unary) * Operator Do in This Ruby Code
Difference Between Class Variables and Class Instance Variables
How to Match All Occurrences of a Regex
How to Install Sqlite3 For Ruby on Windows
How to Specify a Local Gem in My Gemfile
Rhc Setup Gives Error 'No Such File Dl/Import'
What Does ≪≪-Constant Do
Rvm and Thin, Root Vs. Local User
Is Ruby Pass by Reference or by Value
How to Change Default Timezone For Active Record in Rails
Difference or Value of These Block Coding Styles in Ruby
How to Modify Path For Homebrew
How to Create a Deep Copy of an Object in Ruby