Respond_To' VS. 'Respond_To_Missing'

`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?.

Rspec - Argument error after overwriting method_missing and respond_to_missing

Ruby Delegator#respond_to_missing? is method take responsible for returning whether a missing method be able to handled by the object or not, it takes 2 parameters: the missing method name and the option include_private.

The best practice is: always define respond_to_missing? when overriding method_missing.

However i do not prefer the way you applied, the reason behind that is The Rule of Least Surprise, take a look:

class DataHelper
def method_missing(method_name, *args, &block)
if method_name.to_s.start_with?('delegate')
puts "a delegate method"
else
super
end
end

def respond_to_missing?(method_name, include_private = false)
true
end
end

d = DataHelper.new
d.respond_to?(:answer) # true
d.answer # `method_missing': undefined method `answer' ... SURPRISE

as you can see, d response that he can responsible for the answer method but when call that method, a method_missing error be raised.

So, you need to make both method_missing and respond_to_missing? match together:

class DataHelper
def method_missing(method_name, *args, &block)
if can_handle?(method_name)
puts "a delegate method"
else
super
end
end

def respond_to_missing?(method_name, include_private = false)
return true if can_handle?(method_name)
super
end

private

def can_handle?(method_name)
method_name.to_s.start_with?('delegate')
end
end

d = D.new
d.respond_to?(:delegate_answer) # true
d.delegate_answer # delegate method
d.respond_to?(:answer) # false
d.answer # error

Ruby respond_to_missing? Call super or not?

It depends on the implementation of the class and the behavior you want out of #respond_to_missing?. Looking at ActiveSupport::TimeWithZone, it is a proxy wrapper for Time. It tries to mimic it, fooling you into thinking it is an instance of Time. TimeWithZone#is_a? would respond true when passed Time, for example.

# Say we're a Time to thwart type checking.
def is_a?(klass)
klass == ::Time || super
end
alias_method :kind_of?, :is_a?

respond_to_missing? should catch cases that would be caught by method_missing, so you have to look at both methods. TimeWithZone#method_missing delegates missing methods to Time instead of super.

def method_missing(sym, *args, &block)
wrap_with_time_zone time.__send__(sym, *args, &block)
rescue NoMethodError => e
raise e, e.message.sub(time.inspect, inspect), e.backtrace
end

So it makes sense that it would delegate respond_to_missing? to Time as well.

# Ensure proxy class responds to all methods that underlying time instance
# responds to.
def respond_to_missing?(sym, include_priv)
return false if sym.to_sym == :acts_like_date?
time.respond_to?(sym, include_priv)
end

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.

  1. 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 overriding method_missing.

  2. Generate your access methods when setting request_params (meta-programming your accessors).

  3. Use OpenStruct since these can be initialized with a Hash such as your request_params. If you add delegation on top, you should be cool.

Hope this helps.

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

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

Rare error about missing view welcome/index (:formats=[image/*])

You can add respond_with in your action to avoid this error.

For example, in users_contoller:

  respond_to :html

def index
@users = User.all
respond_with(@users)
end

If there is a request of /users.jpg, it will return 406 Not Acceptable.



Related Topics



Leave a reply



Submit