Is `respond_to_missing?`'s second argument useful for anything?
I think that respond_to_missing?
has a second argument for the same reason that respond_to?
does. In both cases, it allows code to ask an object what methods it responds to in a way that respects method privacy. If used properly, it can help you encapsulate your objects better.
You have pointed out a missing feature in method_missing
, namely that it should have an argument that says whether the method was called in a public or private context. Maybe method_missing
will have that feature some day. Until then, all functionality of the object that is implemented through method_missing
will effectively be public, but you can still discourage people from using it in your documentation and via respond_to_missing?
.
respond_to? and protected methods
It is under debate if respond_to?
should look for protected methods or not (check this issue)
Matz has stated that it will probably change in Ruby 2.0.
Note some classes might use #method_missing
and specialize #respond_to?
(or better by specify a #respond_to_missing?
in Ruby 1.9.2+), in which case your obj.methods.include?
will not be reliable.
`respond_to?` vs. `respond_to_missing?`
Without respond_to_missing?
defined, trying to get the method via method
will fail:
class Foo
def method_missing name, *args
p args
end
def respond_to? name, include_private = false
true
end
end
f = Foo.new
f.bar #=> []
f.respond_to? :bar #=> true
f.method :bar # NameError: undefined method `bar' for class `Foo'
class Foo
def respond_to? *args; super; end # “Reverting” previous redefinition
def respond_to_missing? *args
true
end
end
f.method :bar #=> #<Method: Foo#bar>
Marc-André (a Ruby core committer) has a good blog post on respond_to_missing?
. 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
How to solve Rubocop respond_to_missing? offence
Rubocop expects super
to be called without arguments. As the arguments you are passing to super
are the same as those you received, you can simply remove the arguments:
def method_missing(name, *args, &block)
if name =~ /(.+)\=/
self[$1.to_sym] = args[0]
elsif has_index?(name)
self[name]
else
super
end
end
Ruby - actually get ALL methods for an instance
Maybe you missed the last paragraph in the accepted answer of linked post How to list all methods for an object in Ruby?:
To make it clearer what happens a code example:Added Note that :has_many does not add methods directly. Instead, the ActiveRecord machinery uses the Ruby method_missing and responds_to techniques to handle method calls on the fly. As a result, the methods are not listed in the methods method result.
class Foo
def hello
puts 'Hello'
end
def method_missing(name, *args, &block)
case name
when :unknown_method
puts "handle unknown method %s" % name # name is a symbol
else
super #raises NoMethodError unless there is something else defined
end
end
end
foo = Foo.new
p foo.respond_to?(:hello) #-> true
p foo.respond_to?(:unknown_method) #-> false
foo.unknown_method #-> 'handle unknown method unknown_method'
foo.another_unknown_method #-> Exception
The method unknown_method
is never defined, but there is a method to handle unknown methods. So the class makes the impression of an existing method, but there is none.Maybe How can I get source code of a method dynamically and also which file is this method locate in helps the get information about the content:
Foo.instance_method(:method_missing).source_location
Addition
When you define your own method_missing
, then you should also change the behaviour of respond_to?
with respond_to_missing?
def respond_to_missing?(method, *)
return method == :unknown_method || super
#or if you have a list of methods:
#~ return %i{unknown_method}.include?(method) || super
#or with a regex for
#~ method =~ /another_(\w+)/ || super
end
end
See also `respond_to?` vs. `respond_to_missing?` for details. argument in system call
It counts as two. Since there's a space in between the two, the shell sees it and splits it as -P
and /public/google
(if you've worked with arguments, even in ruby, the shell would pass those in as separate arguments to the script).
Rails/Rspec respond_to a method_missing lookup
Use respond_to_missing
. More infos here.
Now, with all this being said. Your pattern will still look hackish if you ask me.
Refactors
Ruby has tons of way to clean this.
Use a delegation pattern
delegate :method_name, :to => :request_params
(check other options in doc). This should solve your problems by having a method in your object so
respond_to?
will work and you will avoid overridingmethod_missing
.Generate your access methods when setting
request_params
(meta-programming your accessors).Use OpenStruct since these can be initialized with a
Hash
such as yourrequest_params
. If you add delegation on top, you should be cool.
Related Topics
How to Tell a Ruby Method to Expect a Specific Parameter Type
Undefined Method 'Merge' for '####':String <%= Form_For %> Helper
Ruby on Rails Tutorial Section 3.2 Rspec Testing Error
Outputting Stdout to a File and Back Again
Securely Display an Image Uploaded with Paperclip Gem
Dynamic Role Attributes in Chef
Automatically Run Rspec When Plain-Old Ruby (Not Rails) Files Change
Indent Multiline String in Erb
Need Help Installing Ruby 2.7.2 on Mac
Reading Files in a Zip Archive, Without Unzipping The Archive
How to Print a Multi-Dimensional Array in Ruby
Pg.Rb Segmentation Fault [Mojave Upgrade]
How to Specify Regexp Options Using Regexp.Union