method_missing gotchas in Ruby
A somewhat obvious one: always redefine respond_to?
if you redefine method_missing
. If method_missing(:sym)
works, respond_to?(:sym)
should always return true. There are many libraries that rely on this.
Later:
An example:
# Wrap a Foo; don't expose the internal guts.
# Pass any method that starts with 'a' on to the
# Foo.
class FooWrapper
def initialize(foo)
@foo = foo
end
def some_method_that_doesnt_start_with_a
'bar'
end
def a_method_that_does_start_with_a
'baz'
end
def respond_to?(sym, include_private = false)
pass_sym_to_foo?(sym) || super(sym, include_private)
end
def method_missing(sym, *args, &block)
return foo.call(sym, *args, &block) if pass_sym_to_foo?(sym)
super(sym, *args, &block)
end
private
def pass_sym_to_foo?(sym)
sym.to_s =~ /^a/ && @foo.respond_to?(sym)
end
end
class Foo
def argh
'argh'
end
def blech
'blech'
end
end
w = FooWrapper.new(Foo.new)
w.respond_to?(:some_method_that_doesnt_start_with_a)
# => true
w.some_method_that_doesnt_start_with_a
# => 'bar'
w.respond_to?(:a_method_that_does_start_with_a)
# => true
w.a_method_that_does_start_with_a
# => 'baz'
w.respond_to?(:argh)
# => true
w.argh
# => 'argh'
w.respond_to?(:blech)
# => false
w.blech
# NoMethodError
w.respond_to?(:glem!)
# => false
w.glem!
# NoMethodError
w.respond_to?(:apples?)
w.apples?
# NoMethodError
What are the Ruby Gotchas a newbie should be warned about?
Wikipedia Ruby gotchas
From the article:
- Names which begin with a capital letter are treated as constants, so local variables should begin with a lowercase letter.
- The characters
$
and@
do not indicate variable data type as in Perl, but rather function as scope resolution operators. - To denote floating point numbers, one must follow with a zero digit (
99.0
) or an explicit conversion (99.to_f
). It is insufficient to append a dot (99.
), because numbers are susceptible to method syntax. - Boolean evaluation of non-boolean data is strict:
0
,""
and[]
are all evaluated totrue
. In C, the expression0 ? 1 : 0
evaluates to0
(i.e. false). In Ruby, however, it yields1
, as all numbers evaluate totrue
; onlynil
andfalse
evaluate tofalse
. A corollary to this rule is that Ruby methods by convention — for example, regular-expression searches — return numbers, strings, lists, or other non-false values on success, butnil
on failure (e.g., mismatch). This convention is also used in Smalltalk, where only the special objectstrue
andfalse
can be used in a boolean expression. - Versions prior to 1.9 lack a character data type (compare to C, which provides type
char
for characters). This may cause surprises when slicing strings:"abc"[0]
yields97
(an integer, representing the ASCII code of the first character in the string); to obtain"a"
use"abc"[0,1]
(a substring of length 1) or"abc"[0].chr
. The notation
statement until expression
, unlike other languages' equivalent statements (e.g.do { statement } while (not(expression));
in C/C++/...), actually never runs the statement if the expression is alreadytrue
. This is becausestatement until expression
is actually syntactic sugar overuntil expression
statement
end, the equivalent of which in C/C++ is
while (not(expression)) statement;
just likestatement if expression
is an equivalent toif expression
statement
endHowever, the notation
begin
statement
end until expressionin Ruby will in fact run the statement once even if the expression is already true.
- Because constants are references to objects, changing what a constant refers to generates a warning, but modifying the object itself does not. For example,
Greeting << " world!" if Greeting == "Hello"
does not generate an error or warning. This is similar tofinal
variables in Java, but Ruby does also have the functionality to "freeze" an object, unlike Java.
Some features which differ notably from other languages:
The usual operators for conditional expressions,
and
andor
, do not follow the normal rules of precedence:and
does not bind tighter thanor
. Ruby also has expression operators||
and&&
which work as expected.def
insidedef
doesn't do what a Python programmer might expect:def a_method
x = 7
def print_x; puts x end
print_x
endThis gives an error about
x
not being defined. You need to use aProc
.
Language features
- Omission of parentheses around method arguments may lead to unexpected results if the methods take multiple parameters. The Ruby developers have stated that omission of parentheses on multi-parameter methods may be disallowed in future Ruby versions; the current (November 2007) Ruby interpreter throws a warning which encourages the writer not to omit
()
, to avoid ambiguous meaning of code. Not using()
is still common practice, and can be especially nice to use Ruby as a human readable domain-specific programming language itself, along with the method calledmethod_missing()
.
Ruby any way to catch messages before method_missing?
Not that I know of.
The most performant bet is usually to use method_missing
to dynamically add the method being to a called to the class so that the overhead is only ever incurred once. From then on it calls the method like any other method.
Such as:
class Foo
def method_missing(name, str)
# log something out when we call method_missing so we know it only happens once
puts "Defining method named: #{name}"
# Define the new instance method
self.class.class_eval <<-CODE
def #{name}(arg1)
puts 'you passed in: ' + arg1.to_s
end
CODE
# Run the instance method we just created to return the value on this first run
send name, str
end
end
# See if it works
f = Foo.new
f.echo_string 'wtf'
f.echo_string 'hello'
f.echo_string 'yay!'
Which spits out this when run:
Defining method named: echo_string
you passed in: wtf
you passed in: hello
you passed in: yay!
method_missing Within instance_eval
The expression foo = ""
will never be a method call. It is a local variable assignment. This is a fact of Ruby's syntax. In order to call a setter, you have to specify a receiver explicitly. This is why most Ruby pseudo-DSLs use the Dwemthy-style:
class Dragon < Creature
life 1340 # tough scales
strength 451 # bristling veins
charisma 1020 # toothy smile
weapon 939 # fire breath
end
That avoids the equals sign problem.
Ruby: Catching All Methods Sent to an Object
As noted in the comments and the answer from @DanCheail, if you're using ruby 1.9+ you should use a BasicObject
rather than undefining methods of Object
. At the time of this posting usage of 1.8.x was still quite prevalent, even though 1.9 had been released for some time.
Rather than attempting to "redefine" anything, you could wrap your object in a blanked (for the most part) proxy object, something like:
class MyProxy
instance_methods.each do |meth|
# skipping undef of methods that "may cause serious problems"
undef_method(meth) if meth !~ /^(__|object_id)/
end
def initialize(object)
@object = object
end
def method_missing(*args)
# do stuff
p args
# ...
@object.send(*args)
end
end
MyProxy.new(MyClass.new).whatever
Method_missing not running when it should
There are a number of problems with your code. Let's go through one by one.
From the documentation,
method_missing(*args) private
Invoked by Ruby when obj is sent a message it cannot handle.
Here message
refers to the method
. In ruby, whenever you're calling a method on an object, you're actually send
ing a message
to the object
To better understand this, try this in the irb shell.
1+2
=> 3
1.send(:+,2)
=> 3
Here 1 and 2 are objects of Fixnum
class. You can confirm that by using 1.class
. Ok, back to your question. So, a method_missing
method should be called on an instance.
team = Team.new
team.hawks
If you try the above piece of code, you'll get an error saying 'fetch': key not found: :roster (KeyError)
You can get around this by passing a default value
as the second parameter to fetch
method. Replace your initialize
method with
def initialize(stats = {})
@cust_roster = stats.fetch(:roster, [])
@cust_total_per = stats.fetch(:per, 0)
@cust_name = stats.fetch(:name, "anon")
@cust_best_player = stats.fetch(:best, "anon")
@@teams << self
end
If you execute the script, you'll get a stack level too deep (SystemStackError)
because of a small typo in this line.
str = methID.id2name
In the method definition, you're receiving an argument with the name of methId
but inside you're trying to call methID
. Fix it with
str = methId.id2name
If you execute your script, you'll again get an error saying undefined method uppercase for "hawks":String (NoMethodError)
This is because there is no uppercase
method on strings. You should instead use the upcase
method.
Team.new(roster:[], per: 0, name: str.upcase, best: 0)
and you should be good to go.
For more, see http://apidock.com/ruby/BasicObject/method_missing
Hope this helps!
Ruby: why does puts call to_ary?
puts
is a synonym for $stdout.puts
. $stdout is an IO
class, so look at the documentation for IO.puts:
Writes the given objects to ios as with IO#print. Writes a record
separator (typically a newline) after any that do not already end with
a newline sequence. If called with an array argument, writes each
element on a new line.
This mean that puts
method is intended to write several lines of output. Thus it tries to call to_ary
method on an object and if to_ary
is defined, then prints each element of the returned Array
on a new line, else puts
calls to_s
method.
to_ary
internal usage is really not well documented in the Ruby documentation (Matz points this out in his The Ruby Programming Language book).
Methods print
and p
on the other hand don't call to_ary
, only to_s
.
Sidenote: Interesting, that to_ary
must return real Array
object, not an object defining each
method or something else:
class Test
def to_ary
10.downto(1)
end
end
puts Test.new
#TypeError: can't convert Test to Array (Test#to_ary gives Enumerator)
# from (irb):28:in `puts'
# from (irb):28:in `puts'
# from (irb):28
When might a dispatch table be as good as method_missing in Ruby?
If you are frequently calling something caught by method_missing
, the act of searching for a nonexistent method may end up being a performance issue in your code. So a dispatch table might be one way of working around that.
Here's a good article describing the performance issues of method_missing
and attempts at optimizing them: http://www.alef1.org/ruby/method_missing/
How to find out what is intercepting 'method_missing'
I think @Sebi's answer is helpful, but I'd like to improve it like this:
set_trace_func proc { |event, file, line, id, binding, classname|
printf "%8s %s:%-2d %10s %8s\n", event, file, line, id, classname if id.to_s == 'method_missing'
}
The result is like this:
ruby-1.8.7-p334 :036 > SomeModel.some_missing_method
call /Users/.../.rvm/gems/ruby-1.8.7-p334/gems/activerecord-2.3.12/lib/active_record/base.rb:1873 method_missing ActiveRecord::Base
line /Users/.../.rvm/gems/ruby-1.8.7-p334/gems/activerecord-2.3.12/lib/active_record/base.rb:1874 method_missing ActiveRecord::Base
line /Users/.../.rvm/gems/ruby-1.8.7-p334/gems/activerecord-2.3.12/lib/active_record/base.rb:1874 method_missing ActiveRecord::Base
line /Users/.../.rvm/gems/ruby-1.8.7-p334/gems/activerecord-2.3.12/lib/active_record/base.rb:1981 method_missing ActiveRecord::Base
line /Users/.../.rvm/gems/ruby-1.8.7-p334/gems/activerecord-2.3.12/lib/active_record/base.rb:1998 method_missing ActiveRecord::Base
c-call /Users/.../.rvm/gems/ruby-1.8.7-p334/gems/activerecord-2.3.12/lib/active_record/base.rb:1998 method_missing Kernel
raise /Users/.../.rvm/gems/ruby-1.8.7-p334/gems/activerecord-2.3.12/lib/active_record/base.rb:1998 method_missing ActiveRecord::Base
c-return /Users/.../.rvm/gems/ruby-1.8.7-p334/gems/activerecord-2.3.12/lib/active_record/base.rb:1998 method_missing Kernel
return /Users/.../.rvm/gems/ruby-1.8.7-p334/gems/activerecord-2.3.12/lib/active_record/base.rb:1998 method_missing ActiveRecord::Base
Ruby metaprogramming with method_missing to make a HTML DSL
Your problem is not that @result
is reset, only that you add into the @result
the return value of instance_eval(&block)
, which is the last line in the block, and not the aggregated block. This should work better (although not perfectly):
class HtmlDsl
attr_reader :result
def initialize(&block)
instance_eval(&block)
end
private
def method_missing(name, *args, &block)
tag = name.to_s
content = args.first
(@result ||= '') << "<#{tag}>"
if block_given?
instance_eval(&block)
else
@result << content
end
@result << "</#{tag}>"
end
end
So now:
html = HtmlDsl.new do
html do
head do
title 'yoyo'
end
body do
h1 'hey'
end
end
end
p html.result
#=> "<html><head><title>yoyo</title></head><body><h1>hey</h1></body></html>"
What I've done is that each call actually renders a fragment to the @result
, so inner calls render inner fragments, each wrapping its own inner fragments with tags.
Related Topics
How to Use Ruby for Shell Scripting
How to Upgrade to the Current Version of Ruby (2.2.3) on Os X V10.6.8
Change Rails Version Used by Rvm
Does Ruby Have a String.Startswith("Abc") Built in Method
Why Should I Care About Rvm's Gemset Feature When I Use Bundler
Rubygems + Cygwin: Posix Path Not Found by Ruby.Exe
Why Does Ruby "Script/Generate" Return "No Such File or Directory"
Cannot Start Rails 4 Console on Production Server
How to Find Where Gem Files Are Installed
Upgrading from Rails 3 to Rails 3.1
What's the Opposite of Chr() in Ruby
Can Someone Explain Ruby's Use of Pipe Characters in a Block