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

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

Related

Inheritance on different namespace

I have self-writed gem
module GemNamespace
class Foo; end
class Bar
def foo
#foo ||= Foo.new
end
end
end
Also I have application
module ApplicationNamespace
class Foo < GemNamespace::Foo; end
class Bar < GemNamespace::Bar; end
end
When I call foo method at my application it returned me instanceof GemNamespace object:
bar = ApplicationNamespace::Bar.new
puts bar.foo
=> #<GemNamespace::Foo:0x007f849d8169f0>
But I want get object of ApplicationNamespace how I can do this without redefine foo method
Your Problem is not, that you have several Namespaces, but that GemNamespace::Bar is tightly coupled to GemNamespace::Foo.
You could use something like this:
class Bar
def initialize(klass)
#klass = klass
end
def foo
#foo ||= #klass.new
end
end
So instead of only ever using GemNamespace::Foo within Bar, you could pass any class.
Your current version of the foo method will allways refer to GemNamespace::Foo because its context is set at definition (not at execution). Instead you could get the module of the current executing class dynamically. I don't think there is a build-in method that does this so you have to get it manually:
def foo
#foo ||= self.class.name.split("::")[0..-2].inject(Kernel) { |s, c| s.const_get c }.const_get("Foo").new
end
This will work for any number of nested modules.

Can you omit the # when calling instance variables

When you have a class with a attr_accessor, can't you omit the # symbol when calling your instance variables since self.instance_variable would be implied if you don't use the # symbol?
attr_accessor :foo is basically just a macro that generates two methods (a getter and a setter) for you:
def foo
#foo
end
def foo=(foo)
#foo = foo
end
Can you omit the # when calling your instance variable? Kind of – calling the instance variable name without the # means you are calling the instance method generated by attr_accessor instead of calling the instance variable. This works as long as the getter method is not overridden or extended.
But when you would try to set an instance variable without the #, it would not work that way, because then Ruby would set a local variable with that name. To set the instance variable through the instance method generated by attr_accessor you need to write self. (or another receiver) instead of the #:
#foo = 'bar' # assigns 'bar' to the instance variable `#foo`
foo = 'bar' # assigns 'bar' to a local variable `#foo`
But to use the setter method generated by attr_accessor:
self.foo = 'bar' # passes 'bar' to the instance method `foo=`
In the case where you have an attr_accessor and you want to read the value, you'll get the same value. It isn't exactly that you're omitting the # but that you're calling a method with the same name that returns the instance variable.
Omitting the # when trying to set the value will not work the same way; it will just set a local variable with the same name. Using an object's setter within the object requires that you precede it with self..
class Foo
attr_accessor :bar
def getter_equivalent?
bar.equal?(#bar) # Returns true. They are the same object.
end
def set_with_at(value)
#bar = value # Will set the instance variable
end
def set_without_at(value)
bar = value # Will not set the instance variable
end
def set_with_self(value)
self.bar = value # Will set the instance variable
end
end
A class example with attr_accessor expanded for clarity. The usual initialize method has been omitted to focus on the task at hand:
class Foo
def bar
#bar
end
def bar=(bar)
#bar = bar
end
#the above two methods represent attr_accessor :bar
def call_method
bar
#same as self.bar
#self refers to the current object i.e. the current instance of Foo
#bar refers to the method bar defined above
end
def call_instance_var
#bar
#refers directly to the the instance variable
end
end
You can use either one, I personally prefer calling the method rather than the instance variable.
Example
foo = Foo.new
foo.bar = "bar"
foo.call_method #=> "bar"
foo.call_instance_var #=> "bar"
Although it's possible to use accessor methods within the class, it's better practice to use the # symbol to refer to instance variables.
Here's an example where using a reader method within a class would produce unexpected results:
class A
def var
#var
end
def defined_using_method?
defined?(var)
end
def defined_using_at?
defined?(#var)
end
end
A.new.defined_using_method? # => "method"
A.new.defined_using_at? # => nil

Making a method available within a block without changing its context?

I would like to create a class that does the following:
Its instance accepts a block.
During instance initialization, it executes certain actions, then calls the block, then executes more actions.
Within the block, another method from that class should be available.
Here is how i want it to work:
Foo.new do
puts "Hell, I can get you a toe by 3 o'clock this afternoon..."
bar
puts "...with nail polish."
end
I have managed to achieve it with the following class:
class Foo
def initialize(&block)
puts "This represents a beginning action"
instance_eval &block
puts "This symbolizes an ending action"
end
def bar
puts "I should be available within the block."
end
end
As you see, i use the instance_eval trick. It enables using bar within the block.
It works fine, but the problem here is that instance_eval makes current local context unavailable. If i use it from within another class, i lose access to that class' methods. For example:
class Baz
def initialize
Foo.new do
bar # -> Works
quux # -> Fails with "no such method"
end
end
def quux
puts "Quux"
end
end
The question is: how do i allow executing bar within the block without losing access to quux?
The only way that comes to my newbie mind is passing bar as an argument into the block. But that requires more typing, so i would like to aviod that if possible.
instance_eval does not consider the scope of where the block is called, so every method call is only relative to what is defined inside Foo.
So you have 2 options. Either
def initialize
baz = self
Foo.new do
bar # -> Works
baz.quux # -> Works
end
end
or
def initialize
puts "This represents a beginning action"
yield self
puts "This symbolizes an ending action"
end
....
def initialize
Foo.new do |b|
b.bar # -> Works too
quux # -> Works too
end
end
I am not sure which one would be better performance wise, but the option you pick is based on your own preference.
It works fine, but the problem here is that instance_eval makes
current local context unavailable
instance_eval() does no such thing. The code inside all blocks, i.e something that looks like:
{ code here }
can see the variables that existed in the surrounding scope at the time the block was CREATED. A block cannot see the variables in the surrounding scope at the time the block is EXECUTED. In computer science jargon, a block is known as a closure because it 'closes over' the variables in the surrounding scope at the time it is created.
What instance_eval does do is assign a new value to the self variable that the block closed over. Here is an example:
puts self #=>main
func = Proc.new {puts self}
func.call #=>main
class Dog
def do_stuff(f)
puts self
f.call
end
end
d = Dog.new
d.do_stuff(func)
--output:--
#<Dog:0x000001019325b8>
main #The block still sees self=main because self was equal to main when the block was created and nothing changed the value of that self variable
Now with instance_eval:
class Dog
def do_stuff(f)
puts self
instance_eval &f
end
end
d = Dog.new
d.do_stuff(func)
--output:--
#<Dog:0x000001011425b0>
#<Dog:0x000001011425b0> #instance_eval() changed the value of a variable called self that the block `closed over` at the time the block was created
You also need to realize that when you call a method and you don't specify a 'receiver', e.g.
quux()
...then ruby converts that line to:
self.quux()
So, it is important to know the value of the variable self. Examine this code:
class Dog
def do_stuff(f)
puts self #Dog_instance
instance_eval &f #equivalent to self.instance_val &f,
#which is equivalent to Dog_instance.instance_eval &f
end
end
Because instance_eval() sets the value of the self variable inside the block to instance_eval()'s 'receiver', the value of self inside the block is set equal to a Dog_instance.
Examine your code here:
puts self #=> main
Foo.new do
puts self #=>main
bar #equivalent to self.bar--and self is not a Foo or Baz instance
#so self cannot call methods in those classes
end
Examine your code here:
class Foo
def initialize(&block)
instance_eval &block #equivalent to self.instance_eval &block
end
end
And inside Foo#initialize() self is equal to the new Foo instance. That means inside the block self is set equal to a Foo instance, and therefore if you write the following inside the block:
quux()
That is equivalent to:
self.quux()
which is equivalent to:
Foo_instance.quux()
which means quux() must be defined in Foo.
In this answer:
class Baz
def initialize
puts self #=>Baz_instance
baz = self
Foo.new do
bar # -> Works
baz.quux # -> Works
end
end
def quux
puts "Quux"
end
end
b = Baz.new
...the bar and baz lines seem to have identical 'receivers':
puts self #=>Baz_instance
baz = self #To evaluate that assignment ruby has to replace the variable self
#with its current value, so this is equivalent to baz = Baz_instance
#and baz no longer has any connection to a variable called self.
Foo.new do
bar #=> equivalent to self.bar, which is equivalent to Baz_instance.bar
baz.quux #=> equivalent to Baz_instance.quux
end
But when instance_eval() executes that block, which is everything between the do and end, instance_eval() changes the value of self:
Foo.new do #instance_eval changes self inside the block so that self = Foo_instance
bar #=> equivalent to self.bar which is now equivalent to Foo_instance.bar
baz.quux #=> the block still sees baz = Baz_instance, so equivalent to Baz_instance.bar
end

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.

Variables in Ruby method names created by attr_accessor

I have an object that is using attr_accessor. I want to be able to call a method on that object with a variable with #send. My problem is that the = method doesn't seem to work.
class Foo
attr_accessor :bar
end
class Test_class
def test_method(x)
f = Foo.new
f.send(x)
f.send(x) = "test" #this doesnt work
f.send("#{x} =") "test" #this also doesn't work
# How can I set bar?
end
end
t = Test_class.new
t.test_method("bar")
You want f.send "#{x}=", "test". In Ruby, method names may include punctuation, such as = or !. The methods created by attr_accessor :bar are simply named bar and bar=. In fact, attr_accessor :bar is just shorthand for:
def bar
#bar
end
def bar=(value)
#bar = value
end
When you're calling foo.bar = "baz", you're actually calling the #bar= method with foo as the receiver and "bar" as the first parameter to the function - that is, foo.bar=("baz"). Ruby just provides syntactic sugar for methods ending in = so that you can write the more natural-looking form.

Resources