Ruby scope at the bottom level of a module - ruby

I couldn't find any explanation that made sense for this.
I'm trying to figure out how to access a variable I'm creating in a module, but not inside any class, i.e.:
module Moddy
attr_accessor :var
var = "wibble"
class Squiggy
def Class_Method ()
end
end
end
I'm trying to figure out how (if it is at all possible) to access 'var' from inside the scope of 'Squiggy' as well as from the scope of the script requiring Moddy.

A module is kind of like a class with no instances. So instance methods defined in a module are useless on their own; you need to include that module in a class in order to use them.
That's important because attr_accessor :var essentially defines two instance methods:
def var
#var
end
def var= v
#var = v
end
If you want var = "wibble" to call the instance method you just created, you need self to be an instance of Moddy, but in this context self is just Moddy so you're actually just creating a local variable called var.
If you want Moddy to own var, you need to call attr_accessor from the singleton class (since Moddy is an instance of its singleton class).
module Moddy
class << self
attr_accessor :var
end
self.var = "wibble"
end
Moddy.var
# "wibble"

module Moddy
attr_accessor :action
def initialize
#action= "wibble"
end
class Squiggy
include Moddy
def initialize
super
end
def put_var
puts #action
end
end
end
reference: Accessing instance variables declared in ruby modules

Related

What happendd if I change the instance variable of subclass in Base class function

I wonder if I write a function of base class and modified the subclass instance variable inside that function, how can ruby distinguish them? For example:
class Parent
def change_name
#name = 'Parent'
end
end
class Subclass < Parent
def initialize
#name = "subclass"
end
def get_name
#name
end
end
sub_class = Subclass.new
sub_class.change_name
sub_class.get_name #=> Parent
What confused me is that #name is the instance variable of Subclass, how can the function change_name change it just like #name was belongs to
Base class. Is it in ruby shared the same public instance variable between Base and Sub class.
In Ruby, the parent and subclass class don't have separate objects. When you create an instance of the subclass class, it is also an instance of the parent class. There is one object and it is both classes at once.
Since there is only one object, there is only one set of instance variables. So in effect, you can think of this like the following:
class CombinedClass
def initialize
#name = "subclass"
end
def get_name
#name
end
def change_name
#name = 'Parent'
end
end
combined_class = CombinedClass.new
combined_class.change_name
combined_class.get_name #=> Parent
Hopefully it makes more sense now.

Instance variables and attr_accessor

When I define #foo=3 in the initialize method, I expect to access my variable like this.
class Object
def initialize(v)
#foo = v
end
Object.new.foo
That doesn't happen though. I need to write attr_accessor :foo. Why do I need to do this even though # already does that for me?
One instance variable in Ruby is not public by default. And access should be granted based on accessors.
For read only attr_reader
For write only attr_writer
For read write attr_accessor
It is not accessible by default
# does not automatically do that for you. That's why. attr_accessor creates getters and setters for your instance variables ("#-variables").
The instance variables are private. You need accessors and mutators to access them. A common accessor/mutator pair looks like:
def foo
#foo
end
def foo=(value)
#foo=value
end
This creates an abstraction which you can now use as:
Classname.new.foo
Classname.new.foo="OOP"
Since this is such a common need and also reults in lot of boilerplate(read: unnecessary) code, ruby provides a dynamic method which literally defines these two methods for you.
attr_accessor :foo
If you want only one of accessor or mutator method, then use the corresponding from following:
attr_reader :foo
attr_writer :foo
This will save a lot of copy/paste. I hope I was clear.
All Ruby attributes are "private" and are invisible outside the class's methods. You need accessor methods to read and write an attribute. So, in your example, you need
class MyClass
def initialize(v)
#foo = v
end
def foo
#foo
end
def foo=(v)
#foo = v
end
end
Then MyClass.new(4).foo will work, and return 4.
You can also add the accessor methods using the convenience methods
attr_reader :foo
attr_writer :foo
or
attr_accessor :foo
An instance variable starts with an # character. All instance variables are private, which means you can't read them and you can't change their value. So what to do?
class Dog
def initialize(name)
#name = name
end
def name #getter
#name
end
def name=(str) #setter
#name = str
end
end
Well, that gets to be a pain to type out, so ruby provides a shortcut:
class Dog
attr_accessor :name
def initialize(name)
#name = name
end
end

How to call super in an initialize method when both class inheritance and Module include is being used?

How does the look-path decide where to call "super" if I have a module included along with class inheritance. My hunch is that by default it will use the initialize method in the module. Is this correct? And if so, how do I explicitly tell the code to use the initialize method in the inherited class instead?
Posted below is an example:
I want the Employee class to inherit initialize from Other and not Subject.
module Subject
def initialize
#observers = []
end
end
class Other
def initialize
#other_stuff = []
end
end
class Employee < Other
include Subject
attr_reader :name
def initialize(name)
super()
end
end
My hunch is that by default it will use the initialize method in the module.
Correct. If a class includes a module then the methods of that module will be replace inherited methods of the same name.
And if so, how do I explicitly tell the code to use the initialize method in the inherited class instead?
You're probably best off refactoring so that you don't have this problem.
However, there are several ways you could make Other's initialize method get called instead of Subject's.
How about something like this:
module Subject
def initialize
puts "subject initialize"
#observers = []
end
end
class Other
def initialize
puts "other initialize"
#other_stuff = []
end
end
class Employee < Other
alias_method :other_initialize, :initialize
include Subject
attr_reader :name
def initialize(name)
other_initialize
end
end
Employee.new('test')
If you run this, you'll see that Other's initialize method is called. Writing code like this is not a good idea, however.

Dynamically add (pre-defined) instance method in Ruby

I see how to dynamically add a method to an instance in Ruby with def [instance].[methodname]; [...]; end.
However, I'm interested in attaching a method that exists in another location to a given instance. e.g.
def my_meth
puts self.foo
end
class MyCls
attr_accessor :foo
end
my_obj = MyCls.new
my_obj.my_meth
How could I simply attach my_meth to my_obj so that the method call in the final line of the foregoing code would work?
You could use include or extend to add a module to your class, eg. extend:
module Foo
def my_meth
puts self.foo
end
end
class MyCls
attr_accessor :foo
end
my_obj = MyCls.new
my_obj.extend(Foo)
my_obj.foo = "hello"
my_obj.my_meth
Unless you have a need to mix-in a module on the fly like this it's generally better to include your module like so:
class MyCls
include Foo
attr_accessor :foo
end

How can a class method (inside a module) update an instance variable?

How can a class method (inside a module) update an instance variable? Consider the code bellow:
module Test
def self.included(klass)
klass.extend ClassMethods
end
module ClassMethods
def update_instance_variable
#temp = "It won't work, bc we are calling this on the class, not on the instance."
puts "How can I update the instance variable from here??"
end
end
end
class MyClass
include Test
attr_accessor :temp
update_instance_variable
end
m = MyClass.new # => How can I update the instance variable from here??
puts m.temp # => nil
You'd have to pass your object instance to the class method as a parameter, and then return the updated object from the method.
That does nto quite make sense.
You use the initialize method to set default values.
class MyClass
attr_accessor :temp
def initialize
#temp = "initial value"
end
end
The initialize method is automatically run for you when you create a new object.
When your class declaration is run, there are no, and cannot be any, instances of the class yet.
If you want to be able to change the default values later you can do something like this:
class MyClass
attr_accessor :temp
##default_temp = "initial value"
def initialize
#temp = ##default_temp
end
def self.update_temp_default value
##default_temp = value
end
end
a = MyClass.new
puts a.temp
MyClass.update_temp_default "hej"
b = MyClass.new
puts b.temp
prints
initial value
hej
If you also want that to change already created instances' variables you need additional magic. Please explain exactly what you wish to accomplish. You are probably doing it wrong :)

Resources