Reputation: 12202
In ruby, is there a way to define a method that is visible by every class in the file (or in the module), but not by files that require the file ?
Related, but not quite the same: can we redefine a method (for instance a method from a class of the standard library) such that this redefinition is visible only in the current file ? All other files should view the original definition.
Upvotes: 3
Views: 1636
Reputation: 1
Yes and yes, since ruby 2.0.
Ruby is really all about objects, not files, so you probably shouldn't do this. In general, you should just use classes and objects to accomplish whatever it is you want.
However, ruby does have a mechanism to accomplish both of your questions: refinements.
Unfortunately, although refinements have been in ruby for a decade, most developers still consider them experimental and esoteric. Refinements have been changed in backwards incompatible ways several times since they were introduced. More significantly, they can impose a significant performance penalty. Although I have run benchmarks on some simple refinement-using code and seen little to no performance impact, refinements break ruby's internal method caches in ways that can cause very significant slow downs (as of ruby 3.2). And, as far as I know, the existing JIT compilers do not even attempt to optimize refined method calls. See https://shopify.engineering/the-case-against-monkey-patching for other reasons not to do this. In particular, please read the "A Note on Refinements" section in that article.
Anyway, with all of those caveats and warnings put aside, here is a demonstration:
# refine_file_scoped_methods.rb
using(Module.new do
refine Object do
def hello_file! = puts "In refined file: hello world! :)"
end
refine Integer do
def + rhs
return super unless self == 2 && rhs == 2
5
end
end
end)
class Integer
def plus_two = self + 2
end
hello_file!
puts "In refined file: 2 + 2 = #{2 + 2}"
puts "In refined file: 2.plus_two = #{2.plus_two}"
# unrefined.rb
require_relative "refine_file_scoped_methods"
begin
hello_file!
rescue NoMethodError
puts "In unrefined file: No hello... :("
end
puts "In unrefined file: 2 + 2 = #{2 + 2}"
puts "In unrefined file: 2.plus_two = #{2.plus_two}"
And this is the output of ruby -I. unrefined.rb
:
$ ruby -I. unrefined.rb
In refined file: hello world! :)
In refined file: 2 + 2 = 5
In refined file: 2.plus_two = 5
In unrefined file: No hello... :(
In unrefined file: 2 + 2 = 4
In unrefined file: 2.plus_two = 5
Upvotes: 0
Reputation: 15284
Define a new method in Object class(like an attribute). If you do not want to mess up the Object class, you can use another name, and Foo should inherit that class.
class Object
@@file_only_methods = []
def file_only(method_name)
method_name = method_name.to_sym
new_method_name = "file_only_#{method_name}".to_sym
self.send(:alias_method, new_method_name, method_name)
self.send(:undef_method, method_name)
self.send(:private, new_method_name)
@@file_only_methods << method_name
end
def method_missing(method_name, *arg, &block)
if @@file_only_methods.include? method_name
if __FILE__ == $0
self.send("file_only_#{method_name}".to_sym,*arg,&block)
else
raise "Method #{method_name} is called outside the definition file."
end
else
raise "Method #{method_name} does not exist."
end
end
end
class Foo
def bar
puts 'bar method'
end
file_only :bar
end
Foo.new.bar
#output:bar method
Foo.new.x
#output:no method
In file2.rb,
require_relative 'file1'
Foo.new.bar
#output: Method bar is called outside the definition file.
Upvotes: 1
Reputation: 96934
No and no.
The only visibilities in Ruby are public, protected, and private. There is no concept of file-level visibility. You could maybe "cheat" and and do something like this:
# In some file foobar.rb
class Foo
def to_bar
Bar.new.file_private
end
end
class Bar
def file_private
raise unless caller[0].split(':')[0] == __FILE__
end
end
# In IRB or some other file
Foo.new.to_bar #=> nil
Bar.new.file_private #=> RuntimeError
But this is a bad idea. A file of the same name in a different directory might work. It also isn't true visibility, but rather enforces it in the method itself.
Really, though, you should mostly have your classes each in their own file. It makes organization better. Further, you should not depend on public/protected/private. You can always just use send
to call a private method, but the above breaks that expectation. If user of your code really wants to do something with your code, there's next to nothing from letting them do it, that's the nature of dynamic languages. If you don't document a method, most users will never even know it's there anyway :P
.
As for your second question, there is no way to have two methods of the same name in the same class with different visibility, the second method will always overwrite the original. You could do something similar to what I've done above, and run different code depending on the condition instead of raising, but as above I don't really think this is a good idea.
Upvotes: 8