Class instance variable initialize in extended module - ruby

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

Related

How to create class variable in module?

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

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)

call before methods in model on ruby

This my implementation to developing way to run code before all method in your model
The call "before_hook :months_used" method need to be on bottom of class to the ExecutionHooks can get the instance_method loaded in the module. I would like to load the instance methods on top
class BalanceChart < BalanceFind
include ExecutionHooks
attr_reader :options
def initialize(options = {})
#options = options
#begin_at = #options[:begin_at]
end
def months_used
range.map{|date| I18n.l date, format: :month_year}.uniq!
end
before_hook :months_used
end
module ExecutionHooks
def self.included(base)
base.send :extend, ClassMethods
end
module ClassMethods
def before
#hooks.each do |name|
m = instance_method(name)
define_method(name) do |*args, &block|
return if #begin_at.blank? ## the code you can execute before methods
m.bind(self).(*args, &block) ## your old code in the method of the class
end
end
end
def before_hook(*method_name)
#hooks = method_name
before
end
def hooks
#hooks ||= []
end
end
end
You can do this with prepend. prepend is like include in that it adds a module to the ancestors of the class, however instead of adding it after the class it adds it before.
This means that if a method exists both in the prepended module and the class then the module implementation is called first (and it can optionally call super if it wants to call the base class).
This allows you to write a hooks module like so:
module Hooks
def before(*method_names)
to_prepend = Module.new do
method_names.each do |name|
define_method(name) do |*args, &block|
puts "before #{name}"
super(*args,&block)
end
end
end
prepend to_prepend
end
end
class Example
extend Hooks
before :foo, :bar
def foo
puts "in foo"
end
def bar
puts "in bar"
end
end
In real use you would probably want to stash that module somewhere so that each call to before doesn't create a new module but that is just an inplementation detail
#rathrio This is my implementation using method_added that you talked. Thanks
module ExecutionHooks
def validation
p "works1"
end
def self.included(base)
base.send :extend, ClassMethods
end
end
module ClassMethods
attr_writer :hooked
def hooked
#hooked ||= []
end
def method_added(method)
return if #hooks.nil?
return unless #hooks.include?(method)
m = self.instance_method(method)
unless hooked.include?(method)
hooked << method
define_method(method) do |*args, &block|
validation
m.bind(self).(*args, &block) ## your old code in the method of the class
end
end
end
def before_hook(*method_name)
#hooks = method_name
end
def hooks
#hooks ||= []
end
end
end
class BalanceChart < BalanceFind
include ExecutionHooks
before_hook :months_data, :months_used, :debits_amount, :test
def test
"test"
end
end
Instead of redefining the method when calling before_hook, you could override the method_added hook to prepend your before hooks to a method right after it was defined. This way your before_hook calls can be (actually, must be) placed at the top of the class definition.

how to write a ruby module to allow methods in class definition

Having a hard time figuring this out. Suppose I wanted to write a module, and when included, it would allow classes to define methods by calling a method with symbols
class Anything
include Foo
initializers :hello, :goodbye
end
module Foo
# What goes in here? Its not
# def self.initializers(*symbols)
end
Same syntax idea as attr_accessible. Tried finding it in the Rails source, but, well..
module Foo
def self.included(base)
base.extend ClassMethods
end
module ClassMethods
def initializers *names
names.each do |name|
define_method name do
'ok'
end
end
end
end
def self.included(base)
base.extend ClassMethods
end
end
class Anything
include Foo
initializers :hello, :goodbye
end
puts Anything.new.hello #=> ok
for example:
module Foo
def self.included(base)
block = Proc.new do |*symbols|
puts symbols.inspect
end
base.class.send(:define_method, :initializers, block)
end
end
class Anything
include Foo
initializers :one, :two , :three
end

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