How do I use class_eval? - ruby

I don't understand class_eval.
class Module
def attr_ (*syms)
syms.each do |sym|
class_eval %{def #{sym}= (val)
##{sym} = val
end}
end
end
end
What does the % mean?
What does class_eval do?
And where is (val) coming from?

The short answer is: you probably want to avoid using class_eval like this.
Here's an explanation of your code:
The %{hello} is just another way to write a string literal in Ruby, without having to worry about escaping double or single quotes within the string:
%{hello "world"} == "hello \"world\"" # => true
The val in your code is an argument of the method being defined.
The class_eval is used to define some methods by computing the text one would write to do the definition and then evaluating it. It is not necessary here, BTW. An equivalent code would be:
class Module
def attr_ (*syms)
syms.each do |sym|
define_method "#{sym}=" do |val|
instance_variable_set "##{sym}", val
end
end
end
end
This is just equivalent to the builtin attr_writer.
Update: There can actually be a significant difference between the two...
The class_eval version is vulnerable if you can't trust the argument syms. For example:
class Foo
attr_ "x; end; puts 'I can execute anything here!'; val=42; begin; val"
end
The class_eval version will print "I can execute anything here" twice, proving it can execute anything. The define_method version won't print anything.
This type of code was pivotal to create major vulnerability for all installed Rails apps.

Related

Pass block into define_method

The Problem
There is a pattern that I find myself to be frequently using, so I'd like to dry it up. I have stuff like this:
class InfoGatherer
def foo
true
end
def people
unless #people
#people = # Long and complex calculation (using foo)
end
#people
end
end
I'd like to dry this up to look like this:
class InfoGatherer
extend AttrCalculator
def foo
true
end
attr_calculator(:people) { # Long and complex calculation (using foo) }
end
To accomplish this, I defined a module AttrCalculator to extend into InfoGatherer. Here's what I tried:
module AttrCalculator
def attr_calculator(variable_name_symbol)
variable_name = "##{variable_name_symbol}"
define_method variable_name_symbol do
unless instance_variable_defined?(variable_name)
instance_variable_set(variable_name, block.call)
end
instance_variable_get(variable_name)
end
end
end
Unfortunately, when I try something as simple as InfoGatherer.new.people, I get:
NameError: undefined local variable or method `foo' for InfoGatherer:Class
Well, that's odd. Why is block running in the scope of InfoGatherer:Class, rather than its instance InfoGatherer.new?
The Research
I know I can't use yield, because that would try to catch the wrong block, as seen here.
I attempted to use self.instance_exec(block) in the place of block.call above, but then I received a new error:
LocalJumpError: no block given
Huh? I see the same error in this SO question, but I'm already using bracket notation, so the answers there don't seem to apply.
I also tried to use class_eval, but I'm not sure how to call block inside of a string. This certainly doesn't work:
class_eval("
def #{variable_name_symbol}
unless #{variable_name}
#{variable_name} = #{block.call}
end
#{variable_name}
end
")
That use case is called memoization. It can be done easily like:
def people
#people ||= # Long and complex calculation (using foo)
end
You shouldn't go into the mess like you are.
The problem was that, inside the define_method, self was surprisingly InfoGatherer, rather than an instance of InfoGatherer. So I was on the right track with self.instance_exec(block).
The working solution is self.instance_exec(&block) (note the ampersand). I guess the interpreter doesn't recognize that block is a block unless you label it as such? If anyone can explain this better than me, please do.
As a side note, this is not the best way to solve this particular problem. See #sawa's answer for a clean way to memoize complicated calculations.
To expand on the last persons
def people(varariable = nil)
#people ||= ComplexCalculation.new(variable).evaluate
end
class ComplexCalculation
def initialize(variable)
#variable = variable
end
def evaluate(variable)
#stuff
end
end
By extracting this class you are isolating that complexity and will have a much better experience.

method_missing with unquoted string arguments in Ruby - possible?

I'm learning Ruby and want to be able to do this:
Printer.hi there
and have Ruby output
"hi there"
So far I have the following implementation
class Printer
def method_missing(name, *args)
puts "#{name} #{args.join(',')}"
end
end
But this only lets me do
Printer.hi "there"
If I attempt
Printer.hi there
I get a
NameError: undefined local variable or method `there' for main:Object
which makes sense as I haven't ever defined 'there'. Is there a way to make this work though?
No, this is not possible in the form given (as far as I know).
You aren't looking for method missing, you are looking for the equivalent in the Ruby interpreter to capture when it cannot find a given symbol. So while you cannot intercept it there, you can do it inside of a block:
def hi(&block)
begin
yield
rescue NameError => e
e.message =~ /variable or method `(.+)'/
puts "hi #{$1}"
end
end
hi { there } # => hi there
Please note that I feel like a terrible world citizen for showing you this. Please don't use it anywhere, ever.
Yes, there is a way. When you write there without an explicit receiver, the receiver is the self object of that scope. In this case, it is main. Define methods_missing in the main context.
def method_missing(name, *args)
puts "#{name} was called with arguments: #{args.join(',')}"
end
But if you do so, that would mess up the rest of your code, perhaps. I see not point in doing this.
Since the return value of puts is nil, if you do Printer.hi there, it will evaluate to Printer.hi(nil). So in order for it to output "hi there", you need to define:
class Printer
def self.hi _; puts "hi there" end
end
No because strings need to be quoted, so they are not seen as variables.
Otherwise variables such as there would need some special sort of character to indicate that it is a string. However this still wouldn't work well as spaces would then need to be dealt with.
Use single or double quotes.
It's how the language works. accept this and move on to the next challenge :)
Interestingly you can do this in ruby 1.8.7 with just this code:
def method_missing(*args)
puts args.join ' '
end
I learned about this from Gary Bernhardt's talk, Wat. In 1.9 this gives you a stack level too deep error unless you do it inside a class. Google lead me to this post on Aurthur's tech blog thing, which claims you can do something similar in JRuby 1.9 mode:
def method_missing(*args)
puts [method.to_s, args].flatten.join ' '
end
However when I tried this on MRI 1.9.3 it did not work either. So in 1.9 you can't quite do what you want. Here is the closest I could come:
class Printer
def self.hi(message)
puts "hi #{message}"
end
def self.method_missing(m, *args)
[m.to_s, args].flatten.join ' '
end
def self.bare
hi there
end
end
Printer.bare

How to provide class/object methods in a block in Ruby?

Sometimes you can see:
do_this do
available_method1 "arg1"
available_method2 "arg1"
end
When I use the block from do_this method then I get some methods I could use inside that block.
I wonder how this is accomplished? How does the code look like behind the scenes?
I want to be able to provide some methods through a block.
It's called a Domain-Specific Language (DSL). Here's (Last archived version) some great info on various forms of Ruby DSL blocks.
There are really two ways to go about doing this, with different syntaxes:
do_thing do |thing| # with a block parameter
thing.foo :bar
thing.baz :wibble
end
# versus
do_thing do # with block-specific methods
foo :bar
baz :wibble
end
The first is more explicit and less likely to fail, while the second is more concise.
The first can be implemented like so, by simply passing a new instance as the block parameter with yield:
class MyThing
def self.create
yield new
end
def foo(stuff)
puts "doing foo with #{stuff}"
end
end
MyThing.create do |thing|
thing.foo :bar
end
And the second, which runs the block in the context of the new object, giving it access to self, instance variables, and methods:
class MyThing
def self.create(&block)
new.instance_eval &block
end
def foo(stuff)
puts "doing foo with #{stuff}"
end
end
MyThing.create do
foo :bar
end
And if you really want to do it without calling MyThing.create, just:
def create_thing(&block)
MyThing.create &block
end
This is usually done using instance_eval to change the value of self inside the block to be some different object, which then handles those method calls.
As a quick example:
class ExampleReceiver
def available_method1 arg ; p [:available_method1, arg] ; end
def available_method2 arg ; p [:available_method2, arg] ; end
end
def do_this(&blk) ; ExampleReceiver.new.instance_eval(&blk) ; end
do_this do
available_method1 "arg1" #=> [:available_method1, "arg1"]
available_method2 "arg1" #=> [:available_method2, "arg1"]
end
Though this is a powerful language feature, and has been used before to great effect, there is still some debate on whether it's a good idea or not. If you don't know what's going on, you might be surprised that the value of #some_instance_variable changes inside and outside the block, since it's relative to the current value of self.
See Daniel Azuma's excellent article for more discussion and details.

Ruby switch like idiom

I have recently started a project in Ruby on Rails. I used to do all my projects before in Python but decided to give Ruby a shot.
In the projects I wrote in Python I used a nice little technique explained by the correct answer in this post:
Dictionary or If statements, Jython
I use this technique due to Python not having a native switch function and it also get rid of big if else blocks
I have been trying to do recreate the above method in Ruby but can't seem to quite get it.
Could anyone help me out?
If you only need to call a method by its name stored in a string, standard Ruby way of doing it is using method Object#send:
def extractTitle dom
puts "title from #{dom}"
end
def extractMetaTags dom
puts "metatags from #{dom}"
end
dom = 'foo'
type = 'extractTitle'
send type, dom
#=> title from foo
type = 'extractMetaTags'
send type, dom
#=> metatags from foo
Otherwise, you can use Ruby's case statement, as already suggested.
While nothing prevents you from using the class-based approach, why avoid rubys case statement?
case thing
when 'something'
do_something
when 'nothing'
do_nothing
else
do_fail
end
As others have said, there are alternative ways of doing this in Ruby, but if you are just curious then an equivalent to that Python approach in Ruby (making use of Object#send once you have determined the method name) would be:
class MyHandler
def handle_test(arg)
puts "handle_test called with #{arg}"
end
def handle_other(arg)
puts "handle_other called with #{arg}"
end
def handle(type, *args)
method_name = "handle_#{type}"
if respond_to? method_name
send(method_name, args)
else
raise "No handler method for #{type}"
end
end
end
You can then do:
h = MyHandler.new
h.handle 'test', 'example'
h.handle 'other', 'example'
h.handle 'missing', 'example'
and the output would be:
handle_test called with example
handle_other called with example
handle.rb:15:in `handle': No handler method for missing (RuntimeError)
from handle.rb:23

Can Ruby operators be aliased?

I'm interested in how one would go in getting this to work :
me = "this is a string"
class << me
alias :old<< :<<
def <<(text)
old<<(text)
puts "appended #{text}"
end
end
I'd like that when something gets appended to the me variable, the object will use the redefined method.
If I try to run this, I get syntax error, unexpected ':', expecting kEND at :<<.
Only certain characters are allowed in symbol literals. You are looking for:
alias :"old<<" :"<<"
:old<< looks like ":old <<". Try just :old, or if you really want, :"old<<" (but have fun calling it through that name).
As others have already explained, the problem is simply that old<< is not a legal Ruby identifier. You can, with tricks, create a method with that name, but you can't call it in the normal ways, and it certainly won't be recognized as an operator.
However, all the answers so far, while they have certainly answered your question, have completely ignored the underlying problem: that method shouldn't even have a name in the first place! And if it doesn't have a name, then the problem of the name being illegal simply doesn't even arise.
#!/usr/bin/env ruby
require 'test/unit'
require 'stringio'
class TestOperatorDecorator < Test::Unit::TestCase
def setup; #old_stdout, $> = $>, (#fake_stdout = StringIO.new) end
def teardown; $> = #old_stdout end
def test_that_me_dot_append_writes_to_stdio
me = 'this is a string'
class << me
old_method = instance_method :<<
define_method :<< do |text|
old_method.bind(self).(text)
puts "appended #{text}"
end
end
me << 'Test'
assert_equal "appended Test\n", #fake_stdout.string
end
end
In this case, the method never gets named, which not only means that we don't have to invent a name for it, it also means that it doesn't pollute the namespace.
The problem is with :old<<. It gets interpreted as :old <<, i.e. a symbol :old followed by the << operator, so it is a syntax error. Maybe you can try :"old<<"?
While I agree with thenduks and ephemient, You can alias the operator that way then use send to call it, you can also still use class inheritance. e.g.:
me = "is a string"
class << me
def <<(text)
super
puts "appended #{text}"
end
end
me << " bob"
puts me #=> is a string appended bob

Resources