Last active
September 3, 2018 07:59
-
-
Save dwbutler/4554269 to your computer and use it in GitHub Desktop.
Demonstrates behavior of private and protected methods in child classes in Ruby (including some surprising behavior and pitfalls)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Tested in Ruby 1.8.7 | |
class ParentClass | |
def call_private_good | |
# Private methods can only be called without an explicit receiver | |
private_test | |
end | |
def call_private_bad | |
# Calling a private method with an explicit receiver (self) will fail | |
self.private_test | |
end | |
protected | |
def protected_test | |
'protected' | |
end | |
private | |
def private_test | |
'private' | |
end | |
end | |
class ChildClass < ParentClass | |
end | |
ChildClass.protected_instance_methods | |
# => ["protected_test"] | |
# The argument specifies whether or not to go up the inheritence chain | |
ChildClass.protected_instance_methods(false) | |
# => [] | |
ChildClass.protected_method_defined?('protected_test') | |
# => true | |
# Oops, this is checking the *class*, not an instance of the class | |
ChildClass.respond_to? "protected_test" | |
# => false | |
ParentClass.respond_to? "protected_test" | |
# => false | |
# Surprisingly, respond_to? returns true for protected methods. | |
# This will change in Ruby 2.0.0 (see http://tenderlovemaking.com/2012/09/07/protected-methods-and-ruby-2-0.html) | |
ChildClass.new.respond_to? :protected_test | |
# => true | |
ParentClass.new.respond_to? :protected_test | |
# => true | |
ChildClass.new.protected_test | |
# NoMethodError: protected method `protected_test' called for #<ChildClass:0x10893ac48> | |
# Surprisingly, #send bypasses method visibility completely | |
ChildClass.new.send :protected_test | |
# => "protected" | |
# #instance_eval is less surprising, because the block is executed in the context of the object | |
ChildClass.new.instance_eval {protected_test} | |
# => "protected" | |
# Oops, these are the private *class* methods | |
ChildClass.private_methods(false) | |
# => ["initialize", "initialize_copy", "inherited"] | |
ParentClass.private_methods.include? 'private_test' | |
# => false | |
ParentClass.private_instance_methods.include? 'private_test' | |
# => true | |
ChildClass.private_methods.include? 'private_test' | |
# => false | |
ChildClass.private_instance_methods.include? 'private_test' | |
# => true | |
ChildClass.private_instance_methods(false).include? 'private_test' | |
# => false | |
ChildClass.new.private_test | |
#NoMethodError: private method `private_test' called for #<ChildClass:0x10fe836d0> | |
ChildClass.new.send 'private_test' | |
# => "private" | |
ChildClass.new.instance_eval {private_test} | |
# => "private" | |
# Private methods can only be called without an explicit receiver | |
ChildClass.new.instance_eval {self.private_test} | |
# NoMethodError: private method `private_test' called for #<ChildClass:0x10fe28f00> | |
ParentClass.new.call_private_good | |
# => "private" | |
ParentClass.new.call_private_bad | |
#NoMethodError: private method `private_test' called for #<ParentClass:0x10fe1b418> | |
# from (irb):7:in `call_private_bad' | |
# Surprisingly, freshly defined classes have "fail" instance and class methods. | |
# This is because the method is defined in Kernel and included in Object. | |
ChildClass.private_methods.include? 'fail' | |
# => true | |
ChildClass.private_methods(false).include? 'fail' | |
# => false | |
ChildClass.private_instance_methods.include? 'fail' | |
# => true | |
ChildClass.private_instance_methods(false).include? 'fail' | |
# => false | |
ChildClass.respond_to? :fail | |
# => false | |
ChildClass.send :fail | |
#RuntimeError: | |
ChildClass.ancestors | |
# => [ChildClass, ParentClass, Object, Kernel] | |
ChildClass.included_modules | |
# => [Kernel] | |
Kernel.fail | |
#RuntimeError: | |
Object.fail | |
#NoMethodError: private method `fail' called for Object:Class | |
Object.send :fail | |
#RuntimeError: | |
# Surprisingly, "fail" is not a language keyword - it's a private method defined on Object! | |
fail "hello" | |
#RuntimeError: hello | |
self.fail | |
# NoMethodError: private method `fail' called for #<Object:0x10fe13290> | |
# In Ruby, everything runs in the context of some object. | |
# The top level object in Ruby is called "main". | |
# I ran these tests in the irb (Interactive Ruby) console. | |
self | |
# => #<Object:0x10fe13290 @prompt={:AUTO_INDENT=>true, :RETURN=>" => %s \n", :PROMPT_I=>"1.8.7 :%03n > ", :PROMPT_N=>"1.8.7 :%03n?> ", :PROMPT_S=>"1.8.7 :%03n%l> ", :PROMPT_C=>"1.8.7 :%03n > "}> | |
self.to_s | |
# => "main" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment