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 @b
s 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
Transforming Datetime into Month, Day and Year
Ruby: Sum Corresponding Members of Two or More Arrays
Ruby - Determine If a Number Is a Prime
Dynamically Creating a Multi-Dimensional Hash in Ruby
Heroku Wrongly Detecting My Node App as a Ruby App
How to Share the Factories That I Have in a Gem and Use It in Other Project
Kernel#Gets Attempts to Read File Instead of Standard Input
Custom Ruby Gem in Gemfile on Heroku
Rails: Plus Sign in Get-Request Replaced by Space
Ruby: Character to Ascii from a String
How to Do Basic Authentication with Restclient
Ruby on Rails Error "Cannot Load Such File -- Less"
Eager Loading: the Right Way to Do Things
How to Use Redis in a Multi-Threaded Rails Environment? (Puma/Sidekiq)
How to Replace the Unicode Gem on Ruby 1.9
How to Wrap the Invocation of a Ruby Method by Including a Module