Accessing Class method from another file in Ruby - ruby

Just for learning , how we can access a class method from another file in ruby. for example
file1.rb
class Test1
def abc
# ...
end
def xyz
# ...
end
end
for example if i have to access method abc from class Test1 in file1.rb in another file lets say in file2.rb,
file2.rb
require "file1.rb"
class Test2
# here I would like to call method abc in class Test1
end

Instantiate the class and then call the method. You can do this in two lines or one.
# file1.rb
class Test1
def abc
...
end
end
# file2.rb
require "file1"
class Test2
def a_method
# altogether
Test1.new.abc
# to store an object of Test1
test1 = Test1.new
test1.abc
end
end

Related

How to work through name collisions in ruby

Two modules Foo and Baa respectively define a method with the same name name, and I did include Foo and include Baa in a particular context.
When I call name, how can I disambiguate whether to call the name method of Foo or Baa?
Only the order of modules inclusion decides which one will get called. Can't have both with the same name - the latter will override the former.
Of course, you can do any tricks, just from the top of my head:
module A
def foo
:foo_from_A
end
end
module B
def foo
:foo_from_B
end
end
class C
def initialize(from)
#from = from
end
def foo
from.instance_method(__method__).bind(self).call
end
private
attr_reader :from
end
C.new(A).foo #=> :a_from_A
C.new(B).foo #=> :a_from_B
But that's no good for real life use cases :)
Technically, there is no name collision because the method foo is redefined.
In the following exemple, A.foo is redefined and is never called
module A
def foo
raise "I'm never called"
end
end
module B
def foo
puts :foo_from_B
end
end
class C
include A
include B
end
C.new.foo
# =>
# foo_from_B
If you write A and B module, you can use super to call previous definition of foo. As if it where an inherited method.
module A
def foo
puts :foo_from_A
end
end
module B
def foo
super
puts :foo_from_B
end
end
class C
include A
include B
end
C.new.foo
# =>
# foo_from_A
# foo_from_B
There are side effects and I would not use this but this is doing the trick :
module A
def foo
puts :foo_from_A
end
end
module B
def foo
puts :foo_from_B
end
end
class C
def self.include_with_suffix(m, suffix)
m.instance_methods.each do |method_name|
define_method("#{method_name}#{suffix}", m.instance_method(method_name))
end
end
include_with_suffix A, "_from_A"
include_with_suffix B, "_from_B"
end
c= C.new
c.foo_from_A
c.foo_from_B
begin
c.foo
rescue NoMethodError
puts "foo is not defined"
end
# =>
# foo_from_A
# foo_from_B
# foo is not defined
Provided none of the methods of Foo or Baa call name (which seems a reasonable assumption), one can simply create aliases.
module Foo
def name; "Foo#name"; end
end
module Baa
def name; "Baa#name"; end
end
class C
include Foo
alias :foo_name :name
include Baa
alias :baa_name :name
undef_method :name
end
c = C.new
c.foo_name
#=> "Foo#name"
c.baa_name
#=> "Baa#name"
C.instance_methods & [:foo_name, :baa_name, :name]
#=> [:foo_name, :baa_name]
The keyword alias is documented here. One may alternatively use the method #alias_method. See this blog for a comparison of the two.
Module#undef_method is not strictly necessary. It's just to ensure that an exception is raised if name is called.
You should definetely read about method lookups.
Anyway, I would do it this way:
module Foo
def name
:foo
end
end
module Bar
def name
:bar
end
end
class MyClass
include Foo
include Bar
def foo_name
Foo.instance_method(:name).bind(self).call
end
def bar_name
Bar.instance_method(:name).bind(self).call
end
#
# or even like this: obj.name(Foo)
#
def name(mod)
mod.instance_method(:name).bind(self).call
end
end
BTW if you are using Module#instance_method and UnboundMethod#bind you don't really need to include specific module. This code works:
Foo.instance_method(:name).bind('any object (e.g. string)').call

How do I access an instance variable from a module that was included dynamically?

I am dynamically including a module into the Baz class in the foobarbaz method.
However, when I execute this in ruby, I get a nil puts. Doesn't the module have access to Foo's instance variables?
class Foo
attr_accessor :current_session
def initialize(current_session)
#current_session = current_session
end
def foobarbaz
Baz.send(:include, Bar) # For Ruby < 2.1
# Baz.include(Bar) # For Ruby > 2.1
end
end
class Baz
end
module Bar
def foobar
#current_session
# 'foobar'
end
end
puts Foo.new('current_session').foobarbaz.new.foobar # nil
NOTE, for this, I was using Ruby 2.0.0. The following also does not puts desired result in Ruby 2.1.2.
Here is a meta programming for you :
#!/usr/bin/env ruby
class Foo
attr_accessor :current_session
def initialize(current_session)
#current_session = current_session
end
def foobarbaz
session = current_session
Bar.module_eval { #current_session = session }
Baz.send(:include, Bar)
end
end
module_eval says
Evaluates the string or block in the context of mod, except that when a block is given, constant/class variable lookup is not affected....
Thus inside Bar.module_eval { #current_session = session }, #current_session is the instance variable of Bar only and I am setting the value of it to the instance variable value of the class Foo, which is #current_session.
Baz.send(:include, Bar) is helpfull, which returns class/module itself, which is including the other module. include(module, ...) → self.
class Baz
end
Read this post to understand the below stuff.
module Bar
class << self
attr_reader :current_session
end
def foobar
Bar.current_session
end
end
puts Foo.new('current_session').foobarbaz.new.foobar
# >> current_session
Update
As #Christian Fazzin gave a good suggestion :-
If you want Bar module to have write method also, then you have to do 2 changes -
Bar should contain then attr_accesor :current_session, instead of what it has now.
You don't need to use the power of module_eval there, rather use syntactic sugraness of write methods, like put Bar.current_session = current_session inside the method foobarbaz . Remove the lines session = current_session and Bar.module_eval { #current_session = session }.
After creating an instance of Foo (foo, say) and in doing so initializing foo's instance variable #current_session to 'current session', it appears to me that you want foo.foobarbaz to do the following:
cause Baz to include the module Bar
create an instance of Baz (baz, say)
create an instance variable named #current_session for baz and assign it the value of foo's instance variable of the same name
invoke baz.foobar to return the value of baz's instance variable #current_session.
If my understanding is correct, we can perform these four steps with four lines in Foo#foobarbaz:
class Baz
end
module Bar
def foobar
#current_session + ' in Baz'
end
end
class Foo
attr_accessor :current_session
def initialize(current_session)
#current_session = current_session
end
def foobarbaz
Baz.include(Bar)
baz = Baz.new
baz.instance_variable_set(:#current_session, self.current_session)
baz.foobar
end
end
foo = Foo.new('current session')
foo.foobarbaz
#=> "current session in Baz"
I've slightly modified what foobarbaz returns to show where it is coming from.
Note that the third line of foobarbaz could be changed to either of the following
baz.instance_variable_set(:#current_session, #current_session)
baz.instance_variable_set(:#current_session,
instance_variable_get(:#current_session))
If the latter of these were used, #current_session's accessor would not be needed.
You'd just need to set instance variable (not class instance variable!) #current_session of class Baz.
With slightest modification of your code without need of additional class/module methods the most straightforward way is to define initialization method that sets the required variable:
class Foo
attr_accessor :current_session
def initialize(current_session)
#current_session = current_session
end
def foobarbaz
  # define Baz#initialize on-the-fly, alternatively with define_method
Baz.class_eval "def initialize; #current_session = '#{#current_session}';end"
Baz.send(:include, Bar) # For Ruby < 2.1
end
end
class Baz
end
module Bar
def foobar
#current_session
# 'foobar'
end
end
puts Foo.new('current_session').foobarbaz.new.foobar
# current_session

Can I overwrite instance method from module?

I know, I can overwrite class method from module this way
class Foo
class << self
def some_static_method
puts 'some_static_method'
end
end
end
module BAR
class << Foo
def some_static_method
puts 'another_static_method'
end
end
end
class Foo
include BAR
end
Foo.some_static_method # => 'another_static_method'
Is it possible for an instance method?
You can do the following:
class Foo
def self.some_static_method; puts "Hello from Foo" end
end
module Bar
def self.included(base)
base.instance_eval do
def some_static_method; puts "Hello from Bar" end
end
end
end
class Foo
include Bar
end
Foo.some_static_method
This should work
UPDATE
To override instance method use:
class Foo
def some_instance_method; puts "Hello from Foo" end
end
module Bar
def self.included(base)
base.class_eval do
def some_instance_method; puts "Hello from Bar" end
end
end
end
class Foo
include Bar
end
Foo.new.some_instance_method
Your question is actually not about method overriding. It is about what class is referred to within a class ... construction in a module body.
When you do
module Bar
class << Foo
p self
end
end
# => #<Class:Foo>
the << Foo points to the singleton class of the Foo in the main environment because class << Foo cannot define the singleton class directly of a class Foo that has not been defined in advance. So it looks up for Foo that is already defined, and such class is found in the main environment.
When you do
module Bar
class Foo
p self
end
end
# => Bar::Foo
a new class Bar::Foo is created; the Foo points to this Bar::Foo that is newly created, and it does not point to the Foo in the main environment. In order to point to it, you have to explicitly specify that with ::.
module Bar
class ::Foo
p self
end
end
# => Foo
If you are using Ruby > 2.0.0 then what you can use is Module#prepend. Instead of include you can prepend an module and that way all of the module's methods are overriding any existing class instance methods with the same name. You can see a quick example here.
Prior to Ruby 2, Rails had introduced a similar hack: #alias_method_chain
Here is a nice comparison of the two approaches.

Get included method names

How I can get all instance method names in the baz method call, which are only present in the Bar module (without other instance methods of this class) ?
class Foo
include Bar
def a
end
def b
end
def baz
#HERE
end
end
class Foo
include Bar
def a
end
def b()
end
def baz
Bar.instance_methods(false)
end
end
puts Foo.new.baz

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

Resources