Module and class variable scope in ruby - ruby

I am still trying to clearly understand Module/Class/Instance variables ...
My code currently looks something like this ...
module Foo
##var1 ={}
##var2 =[]
##var3 = nil
def m1(value)
##var2 << value
end
def m2(value)
##var1[##var3]=value
end
end
class Bar
include Foo
p ##var1
end
class Bar2
include Foo
p #var1
end
I am trying to create a module that contains a class-wide configuration for how each class will behave. The configuration is stored in ##var1 and ##var2. Using this code the variables are shared across ALL classes that include the module. This is not the desire result, I want each class to have it's own behavior configuration.
I have also tried creating a single class that includes the module and also creates the variables but then the variables are not accessible by the module.
module Foo
def m1(value)
##var2 << value
end
def m2(value)
##var1[##var3]=value
end
end
class T
##var1 ={}
##var2 =[]
##var3 = nil
include foo
end
class Bar < T
p ##var1
end
class Bar2 < T
p #var1
end
I have also read that having modules with class variables is not good coding style but I cannot think of a way to achieve my functionality with this ...
Thanks in advance for any help

Firstly - class variables are evil and should be avoided (because they are also inherited by all subclasses and usually causes more harm than good.
You want to create a class instance variable (not class variable) on a class or module which is including given module. It is easy to do with included method:
module Foo
#default_settings = {}
module ClassMethods
def foo_settings
#foo_settings
end
end
def self.included(target)
target.instance_variable_set('#foo_settings', #default_settings.dup)
target.extend ClassMethods
end
end

Related

Include a Ruby module but change the root module reference

Given a Module Foo, I would like to extend this module, but not preserve the module name Foo.
# sscce (irb)
module Foo
class Foo; end
end
module Bar
include Foo
end
> Foo::Foo
Foo::Foo
> Bar.ancestors
[Bar, Foo]
> Bar::Foo
Foo::Foo
How can I get Bar::Foo to be Bar::Foo (not Foo::Foo) without redefining the class?
I've attempted include and prepend as well as include Foo.clone with the same result. I think it's safe to assume that it's referencing the original class definition, fine, but I'd still like to have the class Foo actually belong to Bar.
maybe something like this could work?
module Foo
class Foo
def foo
puts "inside: #{self.class}"
end
end
end
module Bar
class Foo < ::Foo::Foo; end
end
now when you do:
a = Foo::Foo.new
a.foo # inside: Foo::Foo
and if you do
b = Bar::Foo.new
b.foo # inside: Bar::Foo
instead of trying to include/extend Foo in a tricky way just create a new class inside the Bar module and rely on inheritance?
You cannot make a class belong to a module by inclusion. Even more - a class doesn't belong to a module even without inclusion (only a reference to a class is available as a module constant). When you import Foo to Bar, you bring there the constants and instance methods from Foo, that's why you have access to Foo::Foo by using the Bar::Foo constant and it points to the uniquely identified class Foo::Foo.
If you want to have the class Bar::Foo, it will be a different class uniquely identified by this "address". And if you want to have it identical to Foo::Foo, you'll have to use the included hook in module Foo and then do some heavy metaprogramming stuff to create a new class from the old one and assign it to a constant with the same name from the new module.
Or, maybe it could be enough just to clone the class:
module Foo
class Foo; end
def included(mod)
mod.const_set('Foo', self::Foo.clone)
end
end
module Bar
include ::Foo
end
obj1 = Foo::Foo.new
obj2 = Bar::Foo.new
Foo::Foo == Bar::Foo # false
But I'm not sure if cloning is enough for all the cases.

Initializing instance variables in Mixins

Is there any clean way to initialize instance variables in a Module intended to be used as Mixin? For example, I have the following:
module Example
def on(...)
#handlers ||= {}
# do something with #handlers
end
def all(...)
#all_handlers ||= []
# do something with #all_handlers
end
def unhandled(...)
#unhandled ||= []
# do something with unhandled
end
def do_something(..)
#handlers ||= {}
#unhandled ||= []
#all_handlers ||= []
# potentially do something with any of the 3 above
end
end
Notice that I have to check again and again if each #member has been properly initialized in each function -- this is mildly irritating. I would much rather write:
module Example
def initialize
#handlers = {}
#unhandled = []
#all_handlers = []
end
# or
#handlers = {}
#unhandled = []
# ...
end
And not have to repeatedly make sure things are initialized correctly. However, from what I can tell this is not possible. Is there any way around this, besides adding a initialize_me method to Example and calling initialize_me from the extended Class? I did see this example, but there's no way I'm monkey-patching things into Class just to accomplish this.
module Example
def self.included(base)
base.instance_variable_set :#example_ivar, :foo
end
end
Edit: Note that this is setting a class instance variable. Instance variables on the instance can't be created when the module is mixed into the class, since those instances haven't been created yet. You can, though, create an initialize method in the mixin, e.g.:
module Example
def self.included(base)
base.class_exec do
def initialize
#example_ivar = :foo
end
end
end
end
There may be a way to do this while calling the including class's initialize method (anybody?). Not sure. But here's an alternative:
class Foo
include Example
def initialize
#foo = :bar
after_initialize
end
end
module Example
def after_initialize
#example_ivar = :foo
end
end
Perhaps this is a little hacky, but you can use prepend to get the desired behavior:
module Foo
def initialize(*args)
#instance_var = []
super
end
end
class A
prepend Foo
end
Here is the output from the console:
2.1.1 :011 > A.new
=> #<A:0x00000101131788 #instance_var=[]>
modules provides hooks, as Module#included. I suggest you check out ruby doc on the topic, or use ActiveSupport::Concern, which provides some helpers on modules.
I think there may be a simpler answer to this. The module should have an initializer that initialises the variables as you normally would do. In the initializer for the class that includes the module, invoke super() to invoke the initializer in the included module. This is simply following the method dispatch rules in Ruby.
On reflection, this will not work so well if the class including the module also has a superclass that needs to be initialised. The initializer in the module would need to accept a variable parameter list and pass this up to the superclass. It looks like a good avenue to explore though.

How to access class variables in included ruby modules?

I need to know if it is possible for included ruby modules to have access to class variables. Lets say:
require 'bar'
class Foo
#i_am_important
Bar.do_stuff
end
Module Bar
def Bar.do_stuff
#i_am_important.stuff...
end
end
Is there a way to make the above working?
edit: improved example,
edit2: solved problem
I just changed my approach: Bar became a class of its own and gets "i_am_important" passed when initialized. Might not be the best solution, but works at last. Thanks for you help.
You can include module inside the class to get access like
module MyModule
##my_val = 4
end
class MyClass
include MyModule
value = ##my_val
end
Why you want to use variable across the gates of classes and module? I think there is such way:
module Bar
def do_stuff
puts im_am_important
end
end
class Foo
include Bar
def im_am_important
100
end
end
Foo.new.do_stuff # => 100
How about:
#foo.rb
#var
module My_foo
var = #var
def My_foo.my_method(var)
puts(var)
end
end
#bar.rb
require 'foo'
class Bar
extend My_foo
#important_var = "bla"
My_foo.my_method(#important_var)
end
ruby bar.rb => bla

Ruby metaprogramming question

When I call self.class.instance_variable_set("#var", ...) from inside a class method, where is that variable actually stored? Is it on the class itself? On the instance of that class? I can't seem to find it with any of the following:
e = Example.new
e.instance_variables
e.class.class_variables
I even tried using the (class << self; self; end) trick but I can't find anything (http://ruby-metaprogramming.rubylearning.com/html/seeingMetaclassesClearly.html).
Here is the code snippet (which works as I need) but I'm not sure why it works :)
module HandyModule
def self.included(base)
base.extend ClassMethods
end
module ClassMethods
def use_template(template_name)
self.class.instance_variable_set("#_template", template_name)
end
def build(attributes = {})
template_name = self.class.instance_variable_get("#_template")
# do stuff with the template
end
end
end
class Example
include HandyModule
use_template :contact_form
end
Essentially I can include this handy module, and then I have a class method called use_template with which I can specify which template to be used by my build method.
When you call use_template inside the class definition, the self is the class Example. When you call self.class, it is Example.class, or Class. You define the instance variable to the class of the classes.
class Class
p #_template
end
# prints :contact_form
You probably should use just self instead of self.class.

ruby modules: using data from a class definition in a module

i've got a module that wants to use data provided by the class that included it - but at the class level, not the instance level.
the goal is to have class 'metadata' provided to a module that the class includes, so that the module can use the metadata during the included call.
this works:
module Bar
def value
#value
end
def baz
puts "the value is: #{value}"
end
end
module Foo
def self.included(mod)
mod.extend(Bar)
mod.baz
end
end
class MyClass
#value = "my class defined this"
include Foo
end
the output of this code is
the value is: my class defined this
i'm not sure if the use of #value is good or not... it seems odd to me that i require this to be set before the include Foo happens, not from a technical perspective (i know why it's required to be done in this order) but from an idiomatic or usability perspective.
... is there a better way / more idiomatic way of accomplishing this?
If you really want to use the class metadata in the moment you're including a module, given the 'included' method runs on its own scope, it's best to have a class method providing the metadata to it.
Also, if the metadata is not going to be manipulated, its better to declare it as a constant.
module Bar
def self.included(base)
puts "the value is: #{base.metadata}"
end
end
class MyClass
VALUE = "MyClass metadata"
def self.metadata
VALUE
end
include Bar
end
class OtherClass
VALUE = "OtherClass metadata"
def self.metadata
VALUE
end
include Bar
end
Of course you can declare the metadata anyway you want, as long as its accessible by a class method to your Module.
Also, its not common to do these kind of metadata manipulation in the module's 'included' method and the necessity of ordering your statements on the class level is a bit brittle, so you might want to try to find a different solution to your original problem instead.
If you want to the class to pass an argument to the mixin, then why not use one of the Ruby constructs that actually does allow passing an argument?
class Object
private
def Bar(metadata)
Module.new do
include Bar
define_singleton_method(:included) do |base|
puts "the value is: #{metadata}"
end
end
end
end
module Bar
# put common behavior here
end
class MyClass
include Bar 'MyClass metadata'
end
class OtherClass
include Bar 'OtherClass metadata'
end
This is a pretty common idiom that is for example used by the delegate library in the stdlib.

Resources