Ruby’s Object
has a method, methods
. You can use it to see the methods which an object has. Sort of. In this post I’m examining methods
, public_methods
, and private_methods
as well as some of their implications.
Let’s take this simple class and look at it:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
class MethodsTest attr_reader :foo def initialize(foo=nil) @foo = foo end private def fix_it() something end def foo=(val) @foo = val end public def something() @foo + 5 end end |
One thing to remember is that once the private
keyword is seen within a source file, any subsequent methods are private, unless they follow either public
or protected
. The above class illustrates this. However, in practice it is not a good idea to mix public and private methods like this.
Ok, let’s take a look at using this class:
1 2 3 4 5 6 7 8 9 10 |
bash-3.00$ irb irb(main):001:0> require 'methods_test' => true irb(main):002:0> m = MethodsTest.new(1) => #<MethodsTest:0x2a9567e480 @foo=1> irb(main):003:0> m.methods => ["inspect", "my_methods", "tap", "clone", "public_methods", "__send__", "object_id", "instance_variable_defined?", "equal?", "freeze", "extend", "send", "methods", "where_is_this_defined", "hash", "dup", "to_enum", "instance_variables", "eql?", "instance_eval", "id", "singleton_methods", "taint", "something", "enum_for", "frozen?", "instance_variable_get", "foo", "instance_of?", "display", "to_a", "method", "type", "instance_exec", "protected_methods", "==", "===", "instance_variable_set", "kind_of?", "respond_to?", "to_s", "class", "__id__", "tainted?", "=~", "private_methods", "untaint", "nil?", "is_a?"] irb(main):004:0> m.methods - m.public_methods => [] irb(main):005:0> |
You might have noticed above the my_methods
method. This is a not a part of the ruby language, but a helper method which I use in my .irbrc and looks like this:
1 2 3 4 5 |
class Object def my_methods (self.methods - Object.methods).sort end end |
I think it’s useful for querying an object for it’s public methods.
The interesting bit here is that the methods
method returns the same as the public_methods
method. There’s a lot of methods here beyond what we’ve defined — this is because of the methods defined by Object
:
1 2 |
irb(main):005:0> m.my_methods => ["foo", "something"] |
Ok, that makes more sense. Here we see our two methods. However, the initialize
method is missing — this is because it’s not a public method of the instance, but rather a private method of the class(Note: see Class is Class and Instance, Instance, and never the twain shall meet for why the edit is here):
1 2 |
irb(main):013:0> MethodsTest.private_methods.include? "initialize" => true |
So let’s look at the private methods:
1 2 3 4 |
irb(main):014:0> m.private_methods => ["exit!", "chomp!", "initialize", "fail", "print", "binding", "split", "Array", "format", "chop", "iterator?", "catch", "readlines", "trap", "remove_instance_variable", "getc", "irb_binding", "singleton_method_added", "caller", "putc", "fix_it", "autoload?", "proc", "chomp", "block_given?", "throw", "p", "sub!", "loop", "syscall", "trace_var", "exec", "Integer", "callcc", "puts", "initialize_copy", "load", "singleton_method_removed", "exit", "srand", "lambda", "global_variables", "gsub!", "untrace_var", "foo=", "open", "`", "system", "Float", "method_missing", "singleton_method_undefined", "sub", "abort", "gets", "require", "rand", "test", "warn", "eval", "local_variables", "chop!", "scan", "raise", "printf", "set_trace_func", "fork", "String", "select", "sleep", "gsub", "sprintf", "autoload", "readline", "at_exit", "__method__"] irb(main):015:0> (m.private_methods - Object.private_methods).sort => ["autoload", "autoload?", "fix_it", "foo="] |
Huh, there’s a couple of methods we didn’t define. Nor are they part of Object
. The reason for this is that we’ve eliminated the private class methods of Object
, but we haven’t eliminated the private instance methods:
1 2 3 4 |
irb(main):018:0> Object.new.private_methods.sort => ["Array", "Float", "Integer", "String", "__method__", "`", "abort", "at_exit", "autoload", "autoload?", "binding", "block_given?", "callcc", "caller", "catch", "chomp", "chomp!", "chop", "chop!", "eval", "exec", "exit", "exit!", "fail", "fork", "format", "getc", "gets", "global_variables", "gsub", "gsub!", "initialize", "initialize_copy", "irb_binding", "iterator?", "lambda", "load", "local_variables", "loop", "method_missing", "open", "p", "print", "printf", "proc", "putc", "puts", "raise", "rand", "readline", "readlines", "remove_instance_variable", "require", "scan", "select", "set_trace_func", "singleton_method_added", "singleton_method_removed", "singleton_method_undefined", "sleep", "split", "sprintf", "srand", "sub", "sub!", "syscall", "system", "test", "throw", "trace_var", "trap", "untrace_var", "warn"] irb(main):019:0> (m.private_methods - Object.new.private_methods).sort => ["fix_it", "foo="] |
So these methods are private. That means we can’t invoke them from outside the instance, right?
1 2 3 4 |
irb(main):020:0> m.fix_it NoMethodError: private method `fix_it' called for #<MethodsTest:0x2a9567e480 @foo=1> from (irb):20 from :0 |
Well, not precisely — there’s a trick we can use to do so:
1 2 |
irb(main):022:0> m.send(:fix_it) => 6 |
In this case it works. That’s because we’re invoking the public method send
, which then, from inside the instance invokes the private method. Is it necessarily a good idea to take advantage of this? No, usually methods are private for a reason. But, it still works.
2 comments
1 ping
Pit Capitain
September 12, 2008 at 7:07 am (UTC -5) Link to this comment
Matt, #initialize is a private *instance* method, as you can see in your irb output of m.private_methods. It wouldn’t make sense for #initialize to be a class method. The class MethodsTest also has a method #initialize, which is the instance method defined in Class, the class of MethodsTest.
Matt Williams
September 12, 2008 at 2:34 pm (UTC -5) Link to this comment
Hmmm. Playing around some more with irb, I see you’re right:
irb(main):010:0> class Foo
irb(main):011:1> def Foo.bar
irb(main):012:2> end
irb(main):013:1> end
=> nil
irb(main):014:0> class Fud end
=> nil
irb(main):016:0> f=Fud.new
=> #
irb(main):017:0> f.methods
=> [“inspect”, “my_methods”, “tap”, “clone”, “public_methods”, “__send__”, “object_id”, “instance_variable_defined?”, “equal?”, “freeze”, “extend”, “send”, “methods”, “where_is_this_defined”, “hash”, “dup”, “to_enum”, “instance_variables”, “eql?”, “instance_eval”, “id”, “singleton_methods”, “taint”, “enum_for”, “frozen?”, “instance_variable_get”, “instance_of?”, “display”, “to_a”, “method”, “type”, “instance_exec”, “protected_methods”, “==”, “===”, “instance_variable_set”, “kind_of?”, “respond_to?”, “to_s”, “class”, “__id__”, “tainted?”, “=~”, “private_methods”, “untaint”, “nil?”, “is_a?”]
irb(main):018:0> f.methods.include? “bar”
=> false
irb(main):019:0> Fud.methods.include? “bar”
=> true
irb(main):020:0> g=Foo.new
=> #
irb(main):021:0> g.methods.include? “bar”
=> false
irb(main):022:0> class Groo
irb(main):023:1> def Groo.bar
irb(main):024:2> puts “I’ve got class”
irb(main):025:2> end
irb(main):026:1> def bar
irb(main):027:2> puts “I don’t have class”
irb(main):028:2> end
irb(main):029:1> end
=> nil
irb(main):030:0> Groo.bar
I’ve got class
=> nil
irb(main):031:0> g=Groo.new
=> #
irb(main):032:0> g.bar
I don’t have class
=> nil
irb(main):033:0> class Gruff end
=> nil
irb(main):035:0> Gruff.bar
I’ve got class
=> nil
irb(main):036:0> h=Gruff.new
=> #
irb(main):037:0> h.bar
I don’t have class
=> nil
It still would make sense to me that the “constructor” is a class level method, but given that #initialize is an instance variable that could lend itself to interesting side effects…
Ramblings » Blog Archive » Class is Class, and Instance, Instance, and never the twain shall meet
September 12, 2008 at 3:09 pm (UTC -5) Link to this comment
[…] about methods, it’s inspired/spurred by a comment on methods, public_methods, and private_methods by Pit Captain. It also corrects some misconceptions I had (and may have (wrongly) given […]