How Does Instance_Eval Work and Why Does Dhh Hate It

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_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?

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



Leave a reply



Submit