Instance Variables in Modules

Instance variables in modules?

Think of the instance variable as something which will exist in any class that includes your module, and things make a bit more sense:

module Stacklike
def stack
@stack ||= []
end

def add_to_stack(obj)
stack.push(obj)
end

def take_from_stack
stack.pop
end
end

class ClownStack
include Stacklike

def size
@stack.length
end
end

cs = ClownStack.new
cs.add_to_stack(1)
puts cs.size

will output "1"

Is a bad practice to use instance variable in a module?

It seems to me that Bar is highly coupled to the implementation of Foo if it uses a variable defined in Foo. What if there is no @a defined, or worse still it it defined but has a different purpose.

What if another class, say Baz, includes Bar and it renames @a to something more descriptive, then we also need to change Bar and thus we also need to change every other class which includes Bar too.

The module can expect the class it is included in to provide an interface, i.e. a set of methods. But this, again, this is coupling. But this is better than relying on @a variables. The interface can be defined, tested and documented. See duck typing.

There is a saying, "program to interfaces not implementations", from the GOF book. Here is an interview describing it.

Ideally a module adds behaviour to a class without any care for the kind of class it is included in.

It is hard to say what the best approach would be without a concrete example.

Ruby mixin - accessing module instance variables in class instances

Instance variables belong to instances (which is why they are called "instance variables"). You have three objects of interest here, every single one of which has its own @b which has nothing to do with any of the other @bs of the other objects: ModuleA has its own @b, as does ModuleB, and instA also has its own @b.

Here is a more sensible implementation of what you are trying to do:

module ModuleB
def initialize(browser)
puts "initialize from ModuleB"
@browser = browser
@b = 5
end
end

module ModuleA
include ModuleB

def initialize(browser)
super
puts "initialize from ModuleA"
@a = @b
end

def action_1
@a = @b + 1
end
end

class ClassA
include ModuleA

def initialize(browser)
super
puts 'initialize - method in ClassA'
@c = @a
@d = @b
puts "a = #@a"
puts "b = #@b"
puts "c = #@c"
puts "d = #@d"
end
end

s = 'hello'
instA = ClassA.new(s)
# initialize from ModuleB
# initialize from ModuleA
# initialize - method in ClassA
# a = 5
# b = 5
# c = 5
# d = 5
#=> #<ClassA:0x007f8b5f12e110 @a=5, @b=5, @browser="hello", @c=5, @d=5>

puts instA.action_1
# 6

How to access instance variable of a module from abother module?

Instance variables are called instance variables, because they belong to objects (aka "instances"). There are two objects in your example: SomeModule and Reporter. Both have their own instance variables. SomeModule has an instance variable named @param and Reporter has an instance variable named @param. Those two instance variables are completely unrelated, even though the have the same name. Imagine the chaos, if every object had to come up with its own names for instance variables!

Is there any way to access that variable from Reporter module?

No. You cannot access instance variables from different objects. An object only has access to its own instance variables.

[Note: it is possible using reflection (e.g. Object#instance_variable_get, BasicObject#instance_eval, or BasicObject#instance_exec), of course. Lots of things are possible using reflection.]

What you can do, however, is create a method that gives you access to read that instance variable. Such a method is called an attribute reader:

module SomeModule
def self.param; @param end
@param = 'Hello'
end

module Reporter
def self.put
puts SomeModule.param
end
end

There is even a method which automatically generates such methods for you, it is called Module#attr_reader:

module SomeModule
class << self; attr_reader :param end
@param = 'Hello'
end

module Reporter
def self.put
puts SomeModule.param
end
end

If you want to also write to the instance variable, you also need to define a corresponding attribute writer method:

module SomeModule
class << self
attr_reader :param

private

def param=(param)
@param = param
end
end

self.param = 'Hello'
end

module Reporter
def self.put
puts SomeModule.param
end
end

There is also a corresponding method named Module#attr_writer which generates an attribute writer method for you:

module SomeModule
class << self
attr_reader :param

private

attr_writer :param
end

self.param = 'Hello'
end

module Reporter
def self.put
puts SomeModule.param
end
end

Setting an instance variable inside of module

First of all your module structure is a bit unidiomatic. To define instance methods, we can simply put a def inside the module. To define class methods, one would typically define a nested module ClassMethods and call base.extend in the include hook. It saves a level of indentation - this doesn't seem like much but it can add up to quite awkward code. The following modules are equivalent:

module A
def self.included(base)
base.class_eval do
def instance_method
puts 'Hello from instance A!'
end

def self.class_method
puts 'Hello from class A!'
end

foo() # code evaluated in class body
end
end
end

module B
def self.included(base)
base.class_eval do
foo()
end

base.extend ClassMethods
end

def instance_method
puts 'Hello from instance B!'
end

module ClassMethods
def class_method
puts 'Hello from class B!'
end
end
end

If you're using Rails or require 'active_support/concern' you can even extend ActiveSupport::Concern to reduce the amount of boilerplate code:

module C
extend ActiveSupport::Concern

included do
foo()
end

def instance_method
puts 'Hello from instance B!'
end

module ClassMethods
def class_method
puts 'Hello from class B!'
end
end
end

The next thing you need to understand is the difference between instance variables and class instance variables. For example this is how to set an instance variable and then retrieve it in context of the instance

class C
def set_value
@x = 123
end

def get_value
@x
end
end

And this is how to set a class instance variable and then retrieve it in context of the instance. If you think of class D as instance of the class Class, then the term "class instance variable" makes kind of sense. I think the simplest way to access a class instance variable from an instance of the class is to define an attr_accessor in the class' Eigenclass.

class D
class << self
attr_accessor :x
end

def self.set_value
self.x = 123 # self refers to the class, we need to use self.x
end

def get_value
self.class.x # self refers to the instance, we need to use self.class.x
end
end

All of these hints result in the following code:

module ObjectInquiry
def self.included(base)
base.class_eval do
class << self
attr_accessor :evaluator
end
end

base.extend ClassMethods
end

def method_missing(method, *args, &block)
self.class.evaluator.call
end

module ClassMethods
def inquiry(method_name = nil, &block)
self.evaluator = block if block_given?
end
end
end

Testing:

class Post
include ObjectInquiry

inquiry do
true
end
end

Post.new.flabbergasted?
#=> true

Python: Loading instance variables from another module class

If you ensure the input is valid, and that the instance contains a member with that name

a.__dict__[name]

Will access the value stored there.

That is,

a.__dict__["x"]

Is the same as

a.x

Update:

The proper way to do this is

vars(a)[name]

Which is equivalent to a.__dict__[name].

How can I use class instance variables in an extended module in Ruby?

Rubocop complains about @@class_variable style. Class variables usage can be refactored into using instance variable of a class:

class SomeClass
@some_variable ||= []
end

instead of

class SomeClass
@@some_variable ||= []
end

You can define instance variable of a class in any place where self equals to the class object. For example, in a class method:

module MyModule
def self.included(base)
base.extend(ClassMethods)
end

def run_fixers
ClassMethods.fixers.each(&:call)
end

module ClassMethods
def self.fixers
@fixers ||= []
end

def fix(_name, &block)
ClassMethods.fixers << block
end
end
end

Does Instance Variables of a module shared between class with the mixin?

In Ruby modules and classes are objects, so it's possible to set instance variables for them.

module Test
@test = 'red'
def self.print_test
puts @test
end
end

Test.print_test #=> red

Your mistake is thinking that the variable @color is the same for:

module SharedVar
@color
end

and

module SharedVar
def show_color
@color
end
end

which is not.

In the first example, the instance variable belongs to the SharedVar object and in the second example the instance variable belongs to the object you include the module in.

Another explanation by self pointer. In the first example the self pointer is set to the module object SharedVar, so typing @color will refer to the object SharedVar and there's no connection with another object. In the second example, the method show_color can be called only on some object, i.e. ex1.show_color, so the self pointer will point to ex1 object. So in this case the instance variable will refer to ex1 object.



Related Topics



Leave a reply



Submit