best way to 'hide' a method when method_missing is in town

K

Kevin Barnes

I am trying to hide a method in a subclass whose base class has
method_missing defined.

class Base
def method_missing(method, *args)
puts "#{method} caught in base"
end
end

class Sub < Base
def see_me
puts 'hi'
end

def no_see
puts 'hidden'
end
private :no_see
end

s = Sub.new
s.see_me
s.no_see

Of course, the method isn't really hidden/private anymore. I know one
way to solve this with method_missing in Sub and querying
private_methods.include? Is there a better way?

Cheers,
Kevin
 
P

Paul Brannan

Of course, the method isn't really hidden/private anymore. I know one
way to solve this with method_missing in Sub and querying
private_methods.include? Is there a better way?

The method is still private, and can't be called via Sub's public
interface.

There probably is a better solution to your problem, but it depends on
*why* you desire this behavior.

Paul
 
V

vinbarnes

There probably is a better solution to your problem, but it depends on
*why* you desire this behavior.

I'm trying to hide attributes in a Rails model. ActiveRecord::Base has
method_missing defined to build getter/setter methods for attributes
on the fly. But the problem is more general in nature as I described.

Kevin
 
Y

Yossef Mendelssohn

I'm trying to hide attributes in a Rails model. ActiveRecord::Base has
method_missing defined to build getter/setter methods for attributes
on the fly. But the problem is more general in nature as I described.

Kevin

As you said, this problem can be solved with method_missing in Sub.

class Sub
def method_missing(method, *args)
if private_methods.include?(method.to_s)
send(method, *args)
else
puts "#{method} caught in sub"
end
end
end


This isn't a solution for your problem, but a comment on the
(possible?) root of it: IMO, method_missing shouldn't fire on private/
protected method calls. My opinion goes further, that calling a
private or protected method shouldn't raise the same exception as
calling a method that doesn't exist. If for some reason one wants to
track attempted access to private methods, rescuing NoMethodError and
checking text is somewhat unsavory. It would be nicer to rescue
MethodAccessLevelError (or something with an even better name), which
could be related to NoMethodError based on use cases (right now, I see
them as children of the same parent).
 
Y

Yossef Mendelssohn

As you said, this problem can be solved with method_missing in Sub.

class Sub
def method_missing(method, *args)
if private_methods.include?(method.to_s)
send(method, *args)
else
puts "#{method} caught in sub"
end
end
end

This isn't a solution for your problem, but a comment on the
(possible?) root of it: IMO, method_missing shouldn't fire on private/
protected method calls. My opinion goes further, that calling a
private or protected method shouldn't raise the same exception as
calling a method that doesn't exist. If for some reason one wants to
track attempted access to private methods, rescuing NoMethodError and
checking text is somewhat unsavory. It would be nicer to rescue
MethodAccessLevelError (or something with an even better name), which
could be related to NoMethodError based on use cases (right now, I see
them as children of the same parent).

Wow, excellent double-sig there. To balance it all out, I won't put a
sig on this.
 
G

Gaspard Bucher

Shouldn't you just use attr_protected ?
class Node < ActiveRecord::Base
attr_protected :no_see
end
 
V

vinbarnes

Shouldn't you just use attr_protected ?
class Node < ActiveRecord::Base
attr_protected :no_see
end

It does not prevent the attribute from being used from an instance of
the object.

Kevin
 
G

Gaspard Bucher

What about this solution ?
class HidenMethod < Exception; end
module Hide
def self.included(klass)
klass.class_eval <<-END_TXT
@@hidden_methods = []
def self.hide(method)
private method
@@hidden_methods << method.to_sym
end

def method_missing(method, *args)
raise Hide::HidenMethod.new("Hidden \#{method} called.") if
@@hidden_methods.include?(method)
super
end
END_TXT
end
end

class Base
def method_missing(method, *args)
puts "#{method} caught in base"
end

end

class Sub < Base
include Hide
def see_me
puts 'hi'
end

def no_see
puts 'hidden'
end
hide :no_see
end

s = Sub.new
s.see_me
s.no_see
 
T

Tom M

This isn't a solution for your problem, but a comment on the
(possible?) root of it: IMO, method_missing shouldn't fire on private/
protected method calls. My opinion goes further, that calling a
private or protected method shouldn't raise the same exception as
calling a method that doesn't exist. If for some reason one wants to
track attempted access to private methods, rescuing NoMethodError and
checking text is somewhat unsavory. It would be nicer to rescue
MethodAccessLevelError (or something with an even better name), which
could be related to NoMethodError based on use cases (right now, I see
them as children of the same parent).


Your suggestion seems to violate the notion of encapsulation. If an
change were to be made, I'd think it would be better to make the error
the same for calling a parent's private method as it is for calling a
non-existant method.
 
Y

Yossef Mendelssohn

Your suggestion seems to violate the notion of encapsulation. If an
change were to be made, I'd think it would be better to make the error
the same for calling a parent's private method as it is for calling a
non-existant method.

A parent's private method? I'm not sure I follow. Do you mean
something like the following?


class A
private

def priv_method
'private'
end
end

class B < A
end

b = B.new
b.priv_method


Because that's not calling a parent private method. The private method
is inherited from A and part of B now.

My suggestion might violate encapsulation. It's not necessary to
change which exceptions are raised, but I am convinced that
method_missing shouldn't come into the picture when attempting to call
a private or protected method. I took a look at the method_missing
code and it specifically checks for different reasons why the desired
method couldn't be called, including access level. And it uses the
NoMethodError exception.

It seems to me that there are two viewpoints to consider. From outside
the class, there's no difference between calling a private or
protected method and a method that doesn't exist: it simply doesn't
work. But a private method actually exists, so it is not exactly the
same as an undefined method. With that in mind, I go with my feeling
that they should be treated differently.
 
G

Giles Bowkett

There probably is a better solution to your problem, but it depends on
I'm trying to hide attributes in a Rails model. ActiveRecord::Base has
method_missing defined to build getter/setter methods for attributes
on the fly. But the problem is more general in nature as I described.

In keeping with the idea that you should do the simplest thing that
can possibly work, I would just remove_method the getters and setters.
=> nil
=> "YES!!1"
=> Penguin
NoMethodError: undefined method `yes' for #<Penguin:0x109562c>
from (irb):30

However, I would seriously advocate caution here. Stripping methods
from ActiveRecord models manually is kind of running up the down
staircase.

--
Giles Bowkett

Blog: http://gilesbowkett.blogspot.com
Portfolio: http://www.gilesgoatboy.org
Tumblelog: http://giles.tumblr.com/
 
Y

Yossef Mendelssohn

In keeping with the idea that you should do the simplest thing that
can possibly work, I would just remove_method the getters and setters.


=> nil




=> "YES!!1"


=> Penguin


NoMethodError: undefined method `yes' for #<Penguin:0x109562c>
from (irb):30

However, I would seriously advocate caution here. Stripping methods
from ActiveRecord models manually is kind of running up the down
staircase.

--
Giles Bowkett

Blog:http://gilesbowkett.blogspot.com
Portfolio:http://www.gilesgoatboy.org
Tumblelog:http://giles.tumblr.com/

But removing the method doesn't work when method_missing is in play:

Cassady:~ yossef$ irb
irb(main):001:0> class Penguin
irb(main):002:1> def yes
irb(main):003:2> "YES!!1"
irb(main):004:2> end
irb(main):005:1>
irb(main):006:1* def method_missing(meth, *args, &block)
irb(main):007:2> "caught attempt at #{meth}"
irb(main):008:2> end
irb(main):009:1> end
=> nil
irb(main):010:0>
irb(main):011:0* pokey = Penguin.new
=> #<Penguin:0x732a8>
irb(main):012:0> pokey.yes
=> "YES!!1"
irb(main):013:0> Penguin.class_eval {remove_method :yes}
=> Penguin
irb(main):014:0> pokey.yes
=> "caught attempt at yes"
 
T

Tom M

A parent's private method? I'm not sure I follow. Do you mean
something like the following?

class A
private

def priv_method
'private'
end
end

class B < A
end

b = B.new
b.priv_method
Yes... that is the scenario I was referring to.
Because that's not calling a parent private method. The private method
is inherited from A and part of B now.
That's how I understand it to work.

Your analysis seems sound enough. When using a class or module, I
consider it's interface to be its public methods. Unless you override
the default behavior of Object#respond_to?, it responds false for
private methods. Also, whether you are inside our outside a class,
you can't call a private method w/ an explicit receiver, so
foo.priv_method is _never_ valid; regardless of whether or not you are
in the class. Thus, to me, priave methods are "fair game" to be
appropriated by method_missing. (Note: I am not weighing in here on
what I think *good* usage of method_missing is!)

At the end of the day though, I think this point can be argued both
ways ad infinitum.

Cheers,

Tom
 
G

Giles Bowkett

Penguin.class_eval {remove_method :yes}
But removing the method doesn't work when method_missing is in play:

Doh. Good point. I think the masking method_missing in the subclass is
the best way to do it. You can probably also just define empty
methods, in which case the method won't be missing, it'll be empty.
This really seems like a bad idea, though. I ran into a situation on a
Rails app where I thought I needed something like this and then I
realized just changing the name of a particular table would involve
infinitely fewer headaches.

--
Giles Bowkett

Blog: http://gilesbowkett.blogspot.com
Portfolio: http://www.gilesgoatboy.org
Tumblelog: http://giles.tumblr.com/
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,767
Messages
2,569,570
Members
45,045
Latest member
DRCM

Latest Threads

Top