method_missing with unquoted string arguments in Ruby - possible? - ruby

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

Related

What is the point of using "send" instead of a normal method call?

as far as I understand 'send' method, this
some_object.some_method("im an argument")
is same as this
some_object.send :some_method, "im an argument"
So what is the point using 'send' method?
It can come in handy if you don't know in advance the name of the method, when you're doing metaprogramming for example, you can have the name of the method in a variable and pass it to the send method.
It can also be used to call private methods, although this particular usage is not considered to be a good practice by most Ruby developers.
class Test
private
def my_private_method
puts "Yay"
end
end
t = Test.new
t.my_private_method # Error
t.send :my_private_method #Ok
You can use public_send though to only be able to call public methods.
In addition to Intrepidd's use cases, it is convenient when you want to route different methods on the same receiver and/or arguments. If you have some_object, and want to do different things on it depending on what foo is, then without send, you need to write like:
case foo
when blah_blah then some_object.do_this(*some_arguments)
when whatever then some_object.do_that(*some_arguments)
...
end
but if you have send, you can write
next_method =
case foo
when blah_blah then :do_this
when whatever then :do_that
....
end
some_object.send(next_method, *some_arguments)
or
some_object.send(
case foo
when blah_blah then :do_this
when whatever then :do_that
....
end,
*some_arguments
)
or by using a hash, even this:
NextMethod = {blah_blah: :do_this, whatever: :do_that, ...}
some_object.send(NextMethod[:foo], *some_arguments)
In addition to everyone else's answers, a good use case would be for iterating through methods that contain an incrementing digit.
class Something
def attribute_0
"foo"
end
def attribute_1
"bar"
end
def attribute_2
"baz"
end
end
thing = Something.new
3.times do |x|
puts thing.send("attribute_#{x}")
end
#=> foo
# bar
# baz
This may seem trivial, but it's occasionally helped me keep my Rails code and templates DRY. It's a very specific case, but I think it's a valid one.
The summing briefly up what was already said by colleagues: send method is a syntax sugar for meta-programming. The example below demonstrates the case when native calls to methods are likely impossible:
class Validator
def name
'Mozart'
end
def location
'Salzburg'
end
end
v = Validator.new
'%name% was born in %location%'.gsub (/%(?<mthd>\w+)%/) do
# v.send :"#{Regexp.last_match[:mthd]}"
v.send Regexp.last_match[:mthd].to_sym
end
=> "Mozart was born in Salzburg"
I like this costruction
Object.get_const("Foo").send(:bar)

How do I use class_eval?

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.

Ruby - overwrite def method

How can I overwrite the def method? But it's strange, cause I don't know from where the def method is defined. It's not Module, not Object, not BasicObject (of Ruby 1.9). And def.class don't say nothing ;)
I would like to use something like:
sub_def hello
puts "Hello!"
super
end
def hello
puts "cruel world."
end
# ...and maybe it could print:
# => "Hello!"
# => "cruel world."
Many thanks, for any ideas.
Who told you def is a method? It's not. It's a keyword, like class, if, end, etc. So you cannot overwrite it, unless you want to write your own ruby interpreter.
You could use alias_method.
alias_method :orig_hello, :hello
def hello
puts "Hello!"
orig_hello
end
You can use blocks to do some similar things like this:
def hello
puts "Hello"
yield if block_given?
end
hello do
puts "cruel world"
end
As others have said, def isn't a method, it's a keyword. You can't "override" it. You can, however, define a method called "def" via Ruby metaprogramming magic:
define_method :def do
puts "this is a bad idea"
end
This still won't override the def keyword, but you can call your new method with method(:def).call.
So, there you (sort of) have it.
Note: I have no idea why you'd ever want to define a method called def. Don't do it.

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