Skip to content

Instantly share code, notes, and snippets.

@dwbutler
Last active September 3, 2018 07:59
Show Gist options
  • Save dwbutler/4554269 to your computer and use it in GitHub Desktop.
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)
# 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