__FILE__ variable in imported files - ruby

I have two files in two different directories:
module MyModule
def my_method path
p File.join (File.dirname __FILE__), path
end
end
and
require_relative '../modules/mymodule' # definition of MyModule
class MyClass
extend MyModule
my_method 'my_file.yml'
end
I am getting output like my_home_dir/modules/my_file.yml but I want it to be my_home_dir/files/my_file.yml where files is the name of the directory where MyClass is defined.
I know I can use full path when I call my_method but is there a way for imported files to still have __FILE__ set to the name of the importing file?
Basically in my_method I need to have the full path of the file and I want to pass just a path relative to my calling file's path.

__FILE__ always is the name of the file containing the __FILE__ variable, so saying my_method will always return where my_method is defined, not where MyClass calls it.
You can probably get at the information you want using caller:
module MyModule
def my_method path
p caller
end
end
include MyModule # definition of MyModule
class MyClass
extend MyModule
my_method 'my_file.yml'
end
my_class = MyClass.new
Which outputs:
["test.rb:10:in `<class:MyClass>'", "test.rb:8:in `<main>'"]
Edit:
the caller array has only file names without paths...
Well, I'd hoped you'd know how to work around that but....
This is in test.rb:
require './test2'
class MyClass
extend MyModule
my_method __FILE__, 'my_file.yml'
end
my_class = MyClass.new
This is in test2.rb:
module MyModule
def my_method path, file
dir = File.dirname(path)
p caller.map{ |c| File.join(dir, c) }
end
end
Running test.rb outputs:
["./test.rb:4:in `<class:MyClass>'", "./test.rb:2:in `<main>'"]

There's a simpler method available if you can pass in a block; use the block's binding:
# In example.rb:
module Example
def execute_if_main(&block)
if $PROGRAM_NAME == eval("__FILE__", block.binding)
yield
end
end
end
Then in the test file:
# in test.rb:
require_relative 'example.rb'
include Example
execute_if_main do
puts "hi, I'm being loaded"
end
This will execute the block only if test.rb is being loaded directly by the Ruby interpreter.
If test.rb is loaded instead via some other file via require, the block at the end won't
be executed (which is the idea)

Ruby 2.7 adds Module#const_source_location
const_source_location(:MyMoudule)

Related

I came across this code which is about dropbox

Can anybody explain def self.extended(base), what does it mean here or any idea?
module Paperclip
module Storage
module Dropbox
def self.extended(base)
base.instance_eval do
#options[:dropbox_options] ||= {}
#options[:path] = nil if #options[:path] ==
self.class.default_options[:path]
#options[:dropbox_visibility] ||= "public"
#path_generator = PathGenerator.new(self, #options)
#dropbox_client # Force creation of dropbox_client
end
end
end
end
end
The self.extended method is called when the module is extended. It allows methods to be executed in the context of the base (where the module is extended).
You can try it yourself and understand this with a simple example code. Just paste in a file ruby file and run it.
Example for self.extended
module A
def self.extended(base)
puts "#{self} extended in #{base}"
end
end
class Apple
extend A
end
# This should print: "A extended in Apple"
Example for self.included
module A
def self.included(base)
puts "#{self} included in #{base}"
end
end
class Apple
include A
end
# This should print: "A included in Apple"
You can read more here: http://juixe.com/techknow/index.php/2006/06/15/mixins-in-ruby/

I can not understand "uninitialized constant" in ruby

I have two files in folder pc under my home directory.
First file:
class A
class << self
protected
def foo
puts "In foo"
end
end
end
Second file:
class B < A
def bar
self.class.class_eval { foo }
end
end
B.new.bar
My problem is when I run the second file I get the following error:
second.rb:1:in `<main>': uninitialized constant A (NameError)
Why is that?
B.new.bar
# => In foo
just works fine in my console. I guess you probably forgot to require the file containing A from the file B.
In file B use
require 'a'
(assuming the file containing A is called a.rb).
I read the various comments, and just to avoid confusion, here's the full content of the two files.
class_a.rb
class A
class << self
protected
def foo
puts "In foo"
end
end
end
class_b.rb
require_relative 'class_a'
class B < A
def bar
self.class.class_eval { foo }
end
end
puts B.new.bar
And here's how to execute them from the console
$ ruby class_b.rb
In foo
Of course, you should execute the file class_b.rb, not class_a.rb or you will not see any result.
Try require_relative 'class_a' or require class_a.
Note that the file's extension is not included.
This should work, assuming it's all in the same file. If not, you'll need to require the first file into the next:
# class_b.rb
require 'class_a.rb'
class B < A
def bar
self.class.class_eval { foo }
end
end
B.new.bar
#=> "In foo"
UPDATE:
In order to require the file, you may need to cite the path of the file relative to your current directory. For instance, if class_a.rb is located in ~/home and you're running irb (or class_b.rb is in ~/home), then you'd include class_a.rb by citing its relative path as follows:
require './class_a'

Execute methods with the same name of multiple modules included

I have two modules with the same method name. When I include both modules in some class, only the method of the last module is executed. I need instead both to be executed when I initialize the class:
class MyClass
include FirstModule
include SecondModule
def initialize
foo # foo is contained in both modules but only the one in SecondModules is executed
end
end
Is it doable?
As Yusuke Endoh might say, everything is doable in Ruby. In this case, you have to forget about convenience of just saying 'foo', and you have to be very explicit about what you actually want to do, like this:
class MyClass
include FirstModule
include SecondModule
def initialize
FirstModule.instance_method( :foo ).bind( self ).call
SecondModule.instance_method( :foo ).bind( self ).call
end
end
The line 'FirstModule.instance_method...' can be replaced by simply saying 'foo', but by being explicit, you ensure that no matter what, you are calling the method from that mixin, from which you think you do.
Can you modify the included modules? Perhaps you just call super in the second module?
module M1
def foo
p :M1
end
end
module M2
def foo
p :M2
defined?(super) && super
end
end
class SC
include M1
include M2
def initialize
foo
end
end
SC.new
Or perhaps you actually want to do this?
module M1
def bar; p :M1 end
end
module M2
include M1
def foo; bar; p :M2 end
end
class SC
include M2
def initialize; foo end
end
See live demo here

Callback when a class is loaded

Is there a callback which can be executed when a class is loaded. I am thinking something like this.
register_callback('Foo', :debug_message)
def debug_message
puts "Foo has been loaded"
end
require 'foo'
No, there is not. And there cannot be, for the simple reason that classes in Ruby are open: they are never fully "loaded", you can always add, remove, rename and overwrite methods at any later point in time.
For example, when is the following class "loaded"?
# foo.rb
class Foo
def some_method
end
end
# bar.rb
class Foo
def some_other_method
end
end
# baz.rb
class Foo
def some_method
end
end
require 'foo'
require 'bar'
require 'baz' if rand > 0.5

ruby: use module include in instance method of class

Have a look at the code below
initshared.rb
module InitShared
def init_shared
#shared_obj = "foobar"
end
end
myclass.rb
class MyClass
def initialize()
end
def init
file_name = Dir.pwd+"/initshared.rb"
if File.file?(file_name)
require file_name
include InitShared
if self.respond_to?'init_shared'
init_shared
puts #shared_obj
end
end
end
end
The include InitShared dosn't work since its inside the method .
I want to check for the file and then include the module and then access the variables in that module.
Instead of using Samnang's
singleton_class.send(:include, InitShared)
you can also use
extend InitShared
It does the same, but is version independent. It will include the module only into the objects own singleton class.
module InitShared
def init_shared
#shared_obj = "foobar"
end
end
class MyClass
def init
if true
self.class.send(:include, InitShared)
if self.respond_to?'init_shared'
init_shared
puts #shared_obj
end
end
end
end
MyClass.new.init
:include is a private class method, so you can't call it in instance level method. Another solution if you want to include that module only for specific instance you can replace the line with :include with this line:
# Ruby 1.9.2
self.singleton_class.send(:include, InitShared)
# Ruby 1.8.x
singleton_class = class << self; self; end
singleton_class.send(:include, InitShared)

Resources