Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Are Ruby imported methods always private?

This is best explained with an example:

file1.rb:

def foo
  puts 123
end

file2.rb:

class A
  require 'file1'
end
A.new.foo

will give an error "': private method 'foo' called".

I can get around this by doing A.new.send("foo") but is there a way to make the imported methods public?

Edit: To clarify, I am not confusing include and require. Also, the reason why I cannot use normal inclusion (as many have rightly pointed out) is that this is part of a meta-programming setup. I need to allow the user to add functionality at run-time; eg he can say "run-this-app --include file1.rb" and the app will behave differently based on the code he wrote in file1.rb. Sorry should have explained clearer.

Edit: After reading Jorg's answer I realized my code does not behave exactly as intended, and he answers my (misguided) question perfectly. I am trying to do something more akin to str=(entire file1.rb as string); A.class_exec(str).

like image 242
alexloh Avatar asked Jan 10 '12 07:01

alexloh


2 Answers

This is a bad way to do this in Ruby. Try using mixins via modules instead:

file1.rb:

module IncludesFoo
  def foo
    puts 123
  end
end

file2.rb:

require 'file1.rb'

class A
  include IncludesFoo
end

A.new.foo
# => 123
like image 98
Michelle Tilley Avatar answered Sep 23 '22 08:09

Michelle Tilley


Global procedures in Ruby aren't really global procedures. They are methods, like everything else. In particular, when you define what looks like a global procedure, you are actually defining a private instance method of Object. Since every piece of code in Ruby is evaluated in the context of an object, this allows you to use those methods as if they were global procedures, since self is the default receiver, and self is an object whose class inherits from Object.

So, this:

# file1.rb

def foo
  puts 123
end

is actually equivalent to

# file1.rb

class Object
  private

  def foo
    puts 123
  end
end

Now you have a "global procedure" called foo, which you can call just like this:

foo

The reason why you can call it like this, is that this call is actually equivalent to

self.foo

and self is an object that includes Object in its ancestry chain, thus it inherits the private foo method.

[Note: to be precise, private methods cannot be called with an explicit receiver, even if that explicit receiver is self. So, to be really pedantic, it is actually equivalent to self.send(:foo) and not self.foo.]

The A.new.foo in your file2.rb is a red herring: you could just as well try Object.new.foo or [].foo or 42.foo and get the same result.

By the way: puts and require are themselves examples of such "global procedures", which are actually private methods on Object (or more precisely, they are private methods on Kernel which is mixed into Object).

On a sidenote: it is really bad style to put calls to require inside a class definition, because it makes it look like the required code is somehow scoped or namespaced inside the class, which is of course false. require simply runs the code in the file, nothing more.

So, while

# file2.rb

class A
  require 'file1.rb'
end

is perfectly valid code, it is also very confusing. It is much better to use the following, semantically equivalent, code:

# file2.rb

require 'file1.rb'

class A
end

That way it is perfectly clear to the reader of the code that file1.rb is in no way scoped or namespaced inside A.

Also, it is generally preferred to leave off the file extension, i.e. to use require 'file1' instead of require 'file1.rb'. This allows you to replace the Ruby file with, for example, native code (for MRI, YARV, Rubinius, MacRuby or JRuby), JVM byte code in a .jar or .class file (for JRuby), CIL byte code in .dll file (for IronRuby) and so on, without having to change any of your require calls.

One last comment: the idiomatic way to circumvent access protection is to use send, not instance_eval, i.e. use A.new.send(:foo) instead of A.new.instance_eval {foo}.

like image 42
Jörg W Mittag Avatar answered Sep 19 '22 08:09

Jörg W Mittag