Ruby Module to create Class Variable with default value in included class - ruby

I want to create a ruby module that, when included by a class, sets up a class variable with a default value that can be overridden in the class definition. I've been twisting myself in knots with metaprogramming trying to get it to work but I just can't get it right.
What I Want:
class A
include Nickname
end
A.nickname # => 'A'
A.new.nickname # => 'A'
class B
include Nickname
set_nickname "Bubba"
end
B.nickname # => 'Bubba'
B.new.nickname # => 'Bubba'
class BB < B
end
BB.nickname # => 'Bubba'
BB.new.nickname # => 'Bubba'
A.nickname # => 'A'
A.new.nickname # => 'A'
I tried
module Nickname
def self.included(klass)
klass.class_eval <<-EOS
##nickname = "#{klass.name}"
def self.nickname
##nickname
end
def nickname
##nickname
end
def self.set_nickname(new_nick)
##nickname = new_nick
end
EOS
end
end
but that set the nickname on the Nickname module, so every class that included the module would have the same Nickname (whatever was set last). Turns out the code was right. After 7stud pointed that out, I reviewed my work. It seems that because I was doing my testing in irb and had previously defined Nickname several other ways, I got the undesirable behavior. In fact, on a fresh load of irb, the above code works exactly like I want and expected. Sorry about that.
Then I tried using a class instance variable
module Nickname
def self.included(klass)
klass.send(:define_singleton_method, :nickname) do
#nickname
end
klass.send(:define_method, :nickname) do
klass.nickname
end
klass.send(:define_singleton_method, :set_nickname) do |new_nick|
#nickname = new_nick
end
klass.set_nickname klass.name
end
end
That almost worked, but it uses a Class Instance Variable, so BB.nickname is nil.
I've tried various combinations of class_eval and instance_eval inside self.included and I cannot get it all to work together. If I manage to get the value stored as a class variable in the including class, I can't get the class to be able to override the value and/or I cannot set a default value.
EDIT
Moral 1 of the story, be careful about doing metaprogramming in irb. It's quite possible your failed experiments will ruin your next experiments.
Moral 2 of the story is that if you think class variables are strange, look at class instance variables with inheritance and instance_variable_set/get. When a subclass inherits the superclass' class instance variable, the variable is still attached to the superclass part of the subclass instance, so you cannot access it via #var in the subclass, and you can't access from a superclass function that accesses #var, but, as 7stud illustrates, you can access it from a superclass function that calls instance_variable_get on itself.

...but that set the nickname on the Nickname module...
I'm not seeing that:
module Nickname
def self.included(klass)
klass.class_eval %Q{
##nickname = #{name}
def self.set_nickname(new_nick)
##nickname = new_nick
end
}
end
end
class A
include Nickname
end
p Nickname.class_variables(false)
p A.class_variables(false)
A.set_nickname("Joe")
p Nickname.class_variables(false)
p A.class_variables(false)
--output:--
[]
[:##nickname]
[]
[:##nickname]
every class
that included the module would have the same Nickname (whatever was
set last).
No, include isn't the culprit:
module Nickname
def self.included(includer)
includer.class_eval %Q{
##nickname = #{name}
def self.set_nickname(new_nick)
##nickname = new_nick
end
def self.nickname
##nickname
end
}
end
end
class A
include Nickname
end
A.set_nickname("Joe")
p A.nickname
class B
include Nickname
end
B.set_nickname("Sally")
p B.nickname
p A.nickname
--output:--
"Joe"
"Sally"
"Joe"
Rather, it's inheritance that's the problem:
module Nickname
def self.included(includer)
includer.class_eval %Q{
##nickname = #{name}
def self.set_nickname(new_nick)
##nickname = new_nick
end
def self.nickname
##nickname
end
}
end
end
class A
include Nickname
end
A.set_nickname("Joe")
p A.nickname
class B < A
end
B.set_nickname("Sally")
p B.nickname
p A.nickname
--output:--
"Joe"
"Sally"
"Sally"
An ##variable is shared by all the classes in an inheritance hierarchy, which is what Sergio Tulentsev was trying to warn you about in the comments. You probably don't want to use an ##variable (nobody does). Here it is with a class instance variable, which doesn't suffer that problem:
module Nickname
def self.included(includer)
includer.class_eval do
#nickname = includer.name #Instance variables attach themselves to whatever object is self, and class_eval sets self to the receiver, i.e. includer
define_method(:nickname) do #Using define_method() with a block makes the includer variable visible inside the nickname() method.
includer.instance_variable_get(:#nickname)
end
end
includer.singleton_class.class_eval do
define_method(:nickname) do
includer.instance_variable_get(:#nickname)
end
define_method(:set_nickname) do |new_val|
includer.instance_variable_set(:#nickname, new_val)
end
end
end
end
class A
include Nickname
end
p A.nickname
p A.new.nickname
class B
include Nickname
set_nickname "Bubba"
end
p B.nickname
p B.new.nickname
class BB < B
end
p BB.nickname
p BB.new.nickname
--output:--
"A"
"A"
"Bubba"
"Bubba"
"Bubba"
"Bubba"
Then you can set the class instance variable in BB, and it won't affect B's class instance variable:
...
...
class BB < B
include Nickname
set_nickname "Bubba Bubba"
end
p BB.nickname
p BB.new.nickname
p B.nickname
p B.new.nickname
--output:--
...
...
"Bubba Bubba"
"Bubba Bubba"
"Bubba"
"Bubba"

Module#class_variable_set comes to the rescue:
▶ module A
▷ def self.included base
▷ base.class_variable_set :##nickname, 'mudasobwa'
▷ class << base
▷ def nickname # extended class method
▷ class_variable_get :##nickname
▷ end
▷ end
▷ end
▷ end
▶ class B
▷ include A
▷ def nickname # instance method, might be put in `A`
▷ ##nickname
▷ end
▷ end
▶ B.nickname
#⇒ "mudasobwa"
▶ B.new.nickname
#⇒ "mudasobwa"
▶ B.class_variable_set :##nickname, 'google'
#⇒ "google"
▶ B.nickname
#⇒ "google"
▶ B.new.nickname
#⇒ "google"
Hope it helps.

Related

Avoid class pollution while including module

Is there a concise way to limit method visibility within the module while including it? In other words, I'd like to limit polluting the class with helper methods only used in the included module.
module Bar
def call
hide_me
end
private
# make this method only callable within this module
def hide_me
'visible'
end
end
class Foo
include Bar
def unrelated_method
hide_me
end
end
# that's ok
Foo.new.call #=> 'visible'
# that's not
Foo.new.unrelated_method #=> 'visible'
I'm ok with calling it via Bar.instance_method(:hide_me).bind(self).call, I just don't want to worry about accessing or redefining a helper method from some module.
You can wrap a class into the module and use private methods within the class, like so:
module Bar
def call
BarClass.new.call
end
class BarClass
def call
hide_me
end
private
def hide_me
puts "invisible"
end
end
end
class Foo
include Bar
def call_bar
call
end
def this_method_fails
hide_me
end
end
One can do what you want by including the module to the class and then undefining the unwanted included methods.
First construct a module.
module M
def cat
puts "meow"
end
def dog
puts "woof"
end
def owl
puts "who?"
end
private
def frog
puts "ribbit"
end
def pig
puts "oink"
end
end
Confirm the methods now exist.
M.instance_methods(false)
#=> [:cat, :dog, :owl]
M.private_instance_methods(false)
#=> [:frog, :pig]
Create the class, including the module M.
class C
def froggie
frog
end
def cat
puts "meow-meow"
end
include M
end
Check the instance methods.
C.instance_methods & [:cat, :dog, :owl, :froggie]
#=> [:cat, :froggie, :dog, :owl]
C.private_instance_methods & [:frog, :pig]
#=> [:frog, :pig]
and confirm :cat is owned by C and not by M.
C.instance_method(:cat).owner
#=> C
Now use the method Module#undef_method to undefine the unwanted methods from the module.
class C
[:cat, :owl, :pig].each { |m|
undef_method(m) unless instance_method(m).owner == self }
end
The unless... clause is needed so that the instance method :cat defined in C is not undefined.
Confirm the methods were undefined.
C.instance_methods & [[:cat, :dog, :owl, :froggie]
#=> [:cat, :froggie, :dog]
C.private_instance_methods & [:frog, :pig]
#=> [:frog]
Execute the methods.
c = C.new
c.cat
#=> "meow-meow"
c.dog
#=> "woof"
c.froggie
#=> "ribbit"

Adapter Pattern in ruby: Accessing Your Instance Variables

I am studying the adapter pattern implementation in ruby. I want to access an instance variable within the adapter module definition. Take a look at the following code:
module Adapter
module Dog
def self.speak
# I want to access the #name instance variable from my Animal instance
puts "#{name} says: woof!"
end
end
module Cat
def self.speak
# I want to access the #name instance variable from my Animal instance
puts "#{name} says: meow!"
end
end
end
class Animal
attr_accessor :name
def initialize(name)
#name = name
end
def speak
self.adapter.speak
end
def adapter
return #adapter if #adapter
self.adapter = :dog
#adapter
end
def adapter=(adapter)
#adapter = Adapter.const_get(adapter.to_s.capitalize)
end
end
To test it out I did the following:
animal = Animal.new("catdog")
animal.adapter = :cat
animal.speak
I want it to return the following:
catdog says: meow!
Instead it says:
Adapter::Cat says: meow!
Any tips on how I can get access to the Animal#name instance method from the adapter module? I think the issue is that my adapter methods are class-level methods.
Thanks!
You need to use your Module as a mixin and provide a way to keep track of which module is active, the methods don't seem to be overwritten by reincluding or reextending so I took the extend and remove methods I found here.
module Adapter
module Dog
def speak
puts "#{name} says: woof!"
end
end
module Cat
def speak
puts "#{name} says: meow!"
end
end
def extend mod
#ancestors ||= {}
return if #ancestors[mod]
mod_clone = mod.clone
#ancestors[mod] = mod_clone
super mod_clone
end
def remove mod
mod_clone = #ancestors[mod]
mod_clone.instance_methods.each {|m| mod_clone.module_eval {remove_method m } }
#ancestors[mod] = nil
end
end
class Animal
include Adapter
attr_accessor :name, :adapter
def initialize(name)
#name = name
#adapter = Adapter::Dog
extend Adapter::Dog
end
def adapter=(adapter)
remove #adapter
extend Adapter::const_get(adapter.capitalize)
#adapter = Adapter.const_get(adapter.capitalize)
end
end
animal = Animal.new("catdog")
animal.speak # catdog says: woof!
animal.adapter = :cat
animal.speak # catdog says: meow!
animal.adapter = :dog
animal.speak # catdog says: woof!
This is because name inside of the module context refers to something entirely different than the name you're expecting. The Animal class and the Cat module do not share data, they have no relationship. Coincidentally you're calling Module#name which happens to return Adapter::Cat as that's the name of the module.
In order to get around this you need to do one of two things. Either make your module a mix-in (remove self, then include it as necessary) or share the necessary data by passing it in as an argument to speak.
The first method looks like this:
module Adapter
module Dog
def self.speak(name)
puts "#{name} says: woof!"
end
end
end
class Animal
attr_accessor :name
attr_reader :adapter
def initialize(name)
#name = name
self.adapter = :dog
end
def speak
self.adapter.speak(#name)
end
def adapter=(adapter)
#adapter = Adapter.const_get(adapter.to_s.capitalize)
end
end
That doesn't seem as simple as it could be as they basically live in two different worlds. A more Ruby-esque way is this:
module Adapter
module Dog
def speak
puts "#{name} says: woof!"
end
end
end
class Animal
attr_accessor :name
attr_reader :adapter
def initialize(name)
#name = name
self.adapter = :dog
end
def adapter=(adapter)
#adapter = Adapter.const_get(adapter.to_s.capitalize)
extend(#adapter)
end
end

Insert code into the beginning of each method of a class

How can I dynamically and easily insert code into the beginning of each method of a class and subclasses without actually inserting it manually? I want something like a macros.
class C1
def m1
#i_am = __method__
end
def m2
#i_am = __method__
end
end
This is one of the examples where I want to avoid repetition.
I initially misinterpreted the question (but have left my original answer after the horizontal line below). I believe the following may be what you are looking for.
class C1
[:m1, :m2].each do |m|
define_method(m) do |name|
#i_am = __method__
puts "I'm #{name} from method #{#i_am}"
end
end
end
C1.instance_methods(false)
#=> [:m1, :m2]
c1 = C1.new
#=> #<C1:0x007f94a10c0b60>
c1.m1 "Bob"
# I'm Bob from method m1
c1.m2 "Lucy"
# I'm Lucy from method m2
My original solution follows.
class C1
def add_code_to_beginning(meth)
meth = meth.to_sym
self.class.send(:alias_method, "old_#{meth}".to_sym, meth)
self.class.send(:define_method, meth) do
yield
send("old_#{meth}".to_sym)
end
end
end
Module#alias_method
and Module#define_method are private; hence the need to use send.
c = C1.new
#=> #<C1:0x007ff5e3023650>
C1.instance_methods(false)
#=> [:m1, :m2, :add_code_to_beginning]
c.add_code_to_beginning(:m1) do
puts "hiya"
end
C1.instance_methods(false)
#=> [:m1, :m2, :add_code_to_beginning, :old_m1]
c.m1
# hiya
#=> :m1
You can use a rails like class decorators to that. The piece of code below is rendering a method called before_action defined in the Base class of the ActiveRecord module. The Test class is inherited from the ActiveRecord. The define_method is used if we want to call something explicitly from the Base class.
module ActiveRecord
class Base
def self.before_action(name)
puts "#{name}"
puts "inside before_action of class Base"
define_method(name) do
puts "Base: rendering code from Base class"
end
end
end
end
class Test < ActiveRecord::Base
before_action :hola
def render()
puts "inside render of class Test"
end
end
test = Test.new
test.render
test.hola
It has the output
hola
inside before_action of class Base
inside render of class Test
Base: rendering code from Base class
So, before running the render method it runs the before_action method in the Base class. It can be applied to all other methods in the Test class. This is a way of representing macros in ruby.
Assuming that the function is the same, you could create a module and include it in your classes.
Example:
module MyModule
def test_method
puts "abc"
end
end
class MyClass
include MyModule
def my_method
puts "my method"
end
end
inst = MyClass.new
inst.test_method # => should print "abc"
inst.my_method # => should print "my method"

Abstracting/generecizing class hierarchy variables in Ruby

I have several classes and I want each one to maintain on the class level a hash of all the instances that have been created for future lookup. Something akin to:
class A
def initialize(id, otherstuff)
# object creation logic
##my_hash[id]=self
end
def self.find(id)
##my_hash[id]
end
end
so I can then A.find(id) and get the right instance back.
There are several of these classes (A, B, etc), all having ids, all of which I want to have this functionality.
Can I have them all inherit from a superclass which has a generic version of this which they can leverage so I don't have to reimplement many things for every class?
Yes, you can either inherit from the same superclass, or use modules and include:
module M
def initialize(id)
##all ||= {}
##all[id] = self
end
def print
p ##all
end
end
class C
include M
def initialize(id)
super
puts "C instantiated"
end
end
If you want to keep separate indexes for each subclass, you can do something like:
def initialize(id)
##all ||= {}
##all[self.class] ||= {}
##all[self.class][id] = self
end
Edit: After your comment, I see that you need to keep per-class indexes. So:
class A
def initialize(id)
self.class.index(id, self)
end
def self.index id, instance
#all ||= {}
#all[id] = instance
end
def self.find(id)
#all[id]
end
end
class B < A
end
class C < A
end
a = A.new(1)
b = B.new(2)
c = C.new(3)
p A.find(1)
#=> #<A:0x10016c190>
p B.find(2)
#=> #<B:0x10016c140>
p C.find(3)
#=> #<C:0x10016c118>
p A.find(2)
#=> nil

Tagging methods in a module for reference from mixing-classes

I have a module M which I want to tag specific methods as "special", in such a way that classes which mix in this module can check whether a given method name is special. This is what I've tried:
module M
def specials
#specials ||= {}
end
def self.special name
specials[name] = true
end
def is_special? name
specials[name]
end
def meth1
...
end
special :meth1
end
class C
include M
def check name
is_special? name
end
end
Of course this doesn't work, because I can't call an instance method from the class method self.special. I suspect that if I want to keep the feature of being able to call special :<name> below the wanted methods in the module, I have no choice but to use class variables (e.g. ##specials) Can somebody prove me wrong?
You can make all these methods class methods and do the following:
module M
def self.specials
#specials ||= {}
end
def self.special name
self.specials[name] = true
end
def self.is_special? name
self.specials[name]
end
def meth1
'foo'
end
def meth2
'bar'
end
special :meth1
end
class C
include M
def check name
M.is_special? name
end
end
p C.new.check(:meth1)
#=> true
p C.new.check(:meth2)
#=> nil
Not sure if that would work for you.

Resources