How to create class variable in module? - ruby

I have module
module M
def self.included(base)
base.extend ClassMethods
end
module ClassMethods
def m(val)
# todo
end
end
end
And I have class
class A
include M
end
Method m from module M must push val in class variable arr when I call it.
A.m(1)
A.m(2)
A.m(3)
A.arr # => [1, 2, 3]
How can I do it properly?
I have solution below, but I don't sure it the best.
module M
def self.included(base)
base.extend ClassMethods
end
module ClassMethods
attr_accessor :arr
def m (val)
self.arr ||= []
arr << val
end
end
end

Related

Class instance variable initialize in extended module

I know there is should be a way to initialize class instance variable that added by a module via extend
example:
module MyModule
def self.included(base)
base.extend ClassMethods
base.send :include, InstanceMethods
end
module ClassMethods
attr_accessor :count
def self.count
#count = 0
end
count
end
module InstanceMethods
def register
# self.class.count = 0 if self.class.count.nil?
self.class.count += 1
end
end
end
class Foo
include MyModule
private
def initialize
register
end
end
In ClassMethods should be a way to initialize count, but i always catch error "undefined method `+' for nil:NilClass"
When i perform
f = Foo.new
No problem in module InstanceMethods if i uncomment line
# self.class.count = 0 if self.class.count.nil?
Work correctly!
You could move the variable initialization into the included callback:
module MyModule
def self.included(base)
base.extend ClassMethods
base.include InstanceMethods
base.count = 0
end
module ClassMethods
attr_accessor :count
end
module InstanceMethods
def register
self.class.count += 1
end
end
end
One bit too many self in your code. You already extend your ClassMethods module. extend uses instance methods, not class methods. So your module should look like this (rest of the code unchanged).
module ClassMethods
def count
#count ||= 0
end
attr_writer :count
end
Then it works
Foo.count # => 0
Foo.new
Foo.count # => 1

Get access to class variable defined in a module

So while I'm all against extending existing classes this way, sometimes (hacking rspec) it's necessary to do something like:
module MyModule
module ClassMethods
def define_something(name)
##names ||= []
##names << name
end
end
def self.included(base)
base.extend ClassMethods
end
def all_names
##names
end
end
class Example
include MyModule
define_something "one"
define_something "two"
end
Example.new.all_names
and then it yields this error:
NameError: uninitialized class variable ##names in MyModule
and I understand that because at the time of writing MyModule::ClassMethods - we are working on instance not class (not self.), so I tried:
module MyModule
module ClassMethods
def define_something(name)
#names ||= []
#names << name
end
end
def self.included(base)
base.extend ClassMethods
end
def all_names
##names
end
end
class Example
include MyModule
define_something "one"
define_something "two"
end
Example.new.all_names
it does not work either, finally I ended up with:
module MyModule
module ClassMethods
def define_something(name)
#names << name
end
end
def self.included(base)
base.instance_variable_set(:#names, [])
base.send(:define_method, :all_names) { base.instance_variable_get(:#names) }
base.extend ClassMethods
end
end
class Example
include MyModule
define_something "one"
define_something "two"
end
Example.new.all_names
Is there a better way to do that?
It's usually better from a design perspective to avoid crossing the class/instance line using instance variable references. This is usually more clear:
module MyModule
module ClassMethods
def define_something(name)
self.defined_somethings << name
end
def defined_somethings
#_my_module_names ||= []
end
end
def self.included(base)
base.extend ClassMethods
end
def all_names
self.class.defined_somethings
end
end
class Example
include MyModule
define_something "one"
define_something "two"
end
Example.new.all_names.inspect
#=> ["one","two"]
I've taken care here to create a class-level instance variable with a verbose name. Calling it #name could put it into conflict with a variable defined by the class that includes this module. Remember, when designing mixin code you're a guest and you need to be extra polite.
Class-type instance variables like ##name are sometimes trouble because they can end up spanning inheritance chains depending on how they're used. Defining a method means you can override it at any point in the chain, something not possible with a shared variable.
In other words, treat the instance's class as a separate object and make clear, well-defined method calls to maintain that separation.
Try the following
module MyModule
module ClassMethods
def define_something(name)
#names ||= []
#names << name
end
end
def self.included(base)
base.extend ClassMethods
end
def all_names
self.class.instance_variable_get(:#names)
end
end
class Example
include MyModule
define_something "one"
define_something "two"
end
Example.new.all_names
A class variable can be accessed using ## operator from a class level method i.e. self. methods.
#tadman's answer has a generic approach for similar problems (generally found in gems that provide modules to implement a functionality)

Accessing class methods of module

I try to include/extend a module A into a module B which in turn gets included into a class C. Then I want to invoke a class method named cm of A but I don't know how.
module A
def self.included(klass)
klass.extend ClassMethods
end
module ClassMethods
def cm
puts "cm"
end
end
end
module B
include A
end
class C
extend B
end
c = C.new
c.cm # -> does not work
C.cm # -> does not work
You can really simplify this syntax with the augmentations plug-in:
https://github.com/henrik/augmentations
or the gem based on it:
https://github.com/chemica/augmentations-gem
Use:
class User
augment MyModule
end
with modules like
module MyModule
augmentation do
def self.a_class_method
# …
end
def an_instance_method
# …
end
end
end
The plug-in itself is tiny, just a few lines of code.
When including modules, you do this:
module A
def self.included(klass)
klass.include ClassMethods
puts :included
end
module ClassMethods
def cm
puts :cm
end
end
end
module B
include A
end
class C
include B
end
c = C.new
c.cm
Similarly when extending classes, you do this:
module X
def self.extended(klass)
klass.extend ClassMethods
end
module ClassMethods
def cm
puts :cm
end
end
end
module Y
def self.extended(klass)
klass.extend X
end
end
class Z
extend Y
end
Z.cm
See
Include vs Extend in Ruby

ruby mixin with class methods, instance methods, and class variables

Do you know how to define ##method_names class variable so that both my_macro and invoke_methods can use it as intended? Thank you!
module MyModule
module ClassMethods
def my_macro method_name, options = { }
define_method method_name do
puts "defining #{method_name} with #{options}"
end
##method_names << method_name
end
end
def invoke_methods
##method_names.each { |method_name| send method_name }
end
def self.included includer
includer.extend ClassMethods
end
end
class MyClass
include MyModule
my_macro :method_foo, :bar => 5
my_macro :method_baz, :wee => [3,4]
end
MyClass.new.invoke_methods
Here's a working version. Changes are commented:
module MyModule
module ClassMethods
##method_names ||= [] #move this up here
def my_macro method_name, options = { }
define_method method_name do
puts "defining #{method_name} with #{options}"
end
##method_names << method_name
end
#added this (rename as required)
def the_methods
##method_names
end
end
def invoke_methods
#changed this call
self.class.the_methods.each { |method_name| send method_name }
end
def self.included includer
includer.extend ClassMethods
end
end
class MyClass
include MyModule
my_macro :method_foo, :bar => 5
my_macro :method_baz, :wee => [3,4]
end
MyClass.new.invoke_methods
module MyModule
module ClassMethods
def my_macro method_name, options = { }
define_method method_name do
puts "defining #{method_name} with #{options}"
end
#method_names ||= []
#method_names << method_name
end
def method_names
#method_names
end
end
def invoke_methods
self.class.method_names.each { |method_name| send method_name }
end
def self.included includer
includer.extend ClassMethods
end
end
class MyClass
include MyModule
my_macro :method_foo, :bar => 5
my_macro :method_baz, :wee => [3,4]
end
MyClass.new.invoke_methods

Class variables and extending a module

I have a module like the following
module MyModule
def self.included(base)
base.extend ClassMethods
end
module ClassMethods
def foo
##var = 1
end
def bar
puts ##var
end
end
end
class A
include MyModule
foo
end
class B < A; end
so that
B.bar outputs '1'.
However, I would like to have .bar only be defined if .foo is called. I tried
module MyModule
def self.included(base)
base.extend ClassMethods
end
module ClassMethods
def foo
##var = 1
extend SingletonMethods
end
module SingletonMethods
def bar
puts ##var
end
end
end
The problem is that
B.bar
returns the error "uninitialized class variable ##var in MyModule::SingletonMethods". How can I make it so that a variable defined in .foo is available to .bar?
use mattr_accessor instead
I was able to access class variables from a module using the self syntax
class User
include Base
##attributes = [:slug, :email, :crypted_password]
end
module Base
def self.included(base)
base.extend ClassMethods
end
def headers
if defined? self.attributes
self.attributes
end
end
end
Now calling User.headers gives me the expected result of
[:slug, :email, :crypted_password]
If anyone can shed more light on why this works exactly so in ruby, please let me know!

Resources