Ruby metaprogramming, defining multiple "inherited" functions - ruby

I want the following module to be included in a class I have:
module InheritanceEnumerator
def self.included(klass)
klass.instance_eval do
instance_variable_set('#subclasses',[])
def self.subclasses
#subclasses
end
original_method = self.respond_to?(:inherited) ? self.public_method(:inherited) : nil
instance_variable_set('#original_inherited_method', original_method)
def self.inherited(subclass)
#original_inherited_method.call(subclass) if #original_inherited_method
#subclasses<<subclass
end
end
end
end
What I'm trying to achieve is that I want my parent class to have references to direct children. I also need any other previous "inherited" methods set on my class by other stuff to stay in place. What am I doing wrong?

Your code works in this situation (for me):
class C; include InheritanceEnumerator; end
C.subclasses #=> []
class C1 < C; end
class C2 < C; end
C.subclasses #=> [C1, C2]
But fails in the following situation:
class C11 < C1; end
C1.subclasses => NoMethodError: undefined method `<<' for nil:NilClass
This is because you are only initializing #subclasses when the module is included; but you are forgetting that subclasses of C also have access to the modules methods but do not explictly include it.
You fix this by doing the following:
def self.subclasses
#subclasses ||= []
#subclasses
end
def self.inherited(subclass)
#original_inherited_method.call(subclass) if #original_inherited_method
#subclasses ||= []
#subclasses << subclass
end
EDIT:
Okay, in future, please state what your problem is more fully and provide the test code you are using; as this was an exercise in frustration.
The following works fine with your code:
class C
def self.inherited(s)
puts "inherited by #{s}!"
end
include InheritanceEnumerator
end
class D < C; end #=> "inherited by D!"
C.subclasses #=> [D]
Perhaps the reason it wasn't working for you is that you included InheritanceEnumerator before you had defined the inherited method?

Related

What is Ruby current class actually is?

In Metaprogramming Ruby, it told us that not only self but also a current class exists in the Ruby.
My question is: how to know what the current class is now? if we define a method, where the definition is put? What confuses me is the difference between these two codes. The running result is not the same.
It indicates that maybe when we want to define a method, we use class to open a class, and then define self.method, is not the same as using class << self to open a class, and then define methods. Actually, it would not use in practical work, but I just want to know.
class C
def self.m1
puts "when in m1, self is #{self}"
def m2; end
end
end
class D < C
end
C.m1
# when in m1, self is C
C.instance_methods false
#=> [:m2]
C.methods false
#=> [:m1]
class C
class << self
def m1
puts "when in m1, self is #{self}"
def m2
end
end
end
end
class D < C; end
C.m1
# when in m1, self is C
C.instance_methods false
#=> []
C.methods false
#=> [:m1, :m2]
The current class of first code snippet is C. Although you define m2 in self.m1, the current class is C. m2 is the instance method.
The current class of the second snippet is singleton_class of C. when you write class << self, you open a singleton class of the current class, C in this case. So the m2 is an instance method of singleton_class of C.

Ruby: Open Module's singleton

So in a Ruby class, you can use an idiom such as class << self like the following:
class SalesOrganization
def self.find_new_leads
...
end
class << self
include ::NewRelic::Agent::Instrumentation::ControllerInstrumentation
add_transaction_tracer :find_new_leads, :category => :task
end
end
My question is what if SalesOrganization is actually a Module instead of Class. Is this doing what I'm hoping it would do, or am I opening up some black magic that I shouldn't be dabbling with?
# Potentially terrible code
module SalesOrganization
def self.find_new_leads
...
end
class << self
include ::NewRelic::Agent::Instrumentation::ControllerInstrumentation
add_transaction_tracer :find_new_leads, :category => :task
end
end
How do I access a modules singleton class?
No, you're not releasing any black magic. You can define singleton methods on any object, including a module (an instance of the Module class):
module M; end
def M.a
"a"
end
M.a # => "a"
The approaches you suggest work too:
module M
def self.b
"b"
end
end
M.b # => "b"
module M
class << self
def c
"c"
end
end
end
M.c # => "c"
You can also use instance_eval if your method definitions aren't known until runtime:
module M; end
M.instance_eval <<EOF
def d
"d"
end
EOF
M.d # => "d"
Of course, modules like NewRelic... may make assumptions about the classes/modules into which they're included, so you have to be careful there.
I am not sure if I understood what you want to archive. But if you want to write the definition of including C in a module B. And than use C in A after including B, than you can do that this way:
module B
def self.included(base)
base.include C
end
end
class A
include B
# use C
end
That is for your example:
module SalesOrganization
def self.included(base)
base.include ::NewRelic::Agent::Instrumentation::ControllerInstrumentation
base.add_transaction_tracer :find_new_leads, :category => :task
end
def self.find_new_leads
...
end
end
If you now include that SalesOrganization module into a class the class will have the Newrelic stuff included.

How to pass a method to instance_eval?

I want to call instance_eval on this class:
class A
attr_reader :att
end
passing this method b:
class B
def b(*args)
att
end
end
but this is happening:
a = A.new
bb = B.new
a.instance_eval(&bb.method(:b)) # NameError: undefined local variable or method `att' for #<B:0x007fb39ad0d568>
When b is a block it works, but b as a method isn't working. How can I make it work?
It's not clear exactly what you goal is. You can easily share methods between classes by defining them in a module and including the module in each class
module ABCommon
def a
'a'
end
end
class A
include ABCommon
end
Anything = Hash
class B < Anything
include ABCommon
def b(*args)
a
end
def run
puts b
end
end
This answer does not use a real method as asked, but I didn't need to return a Proc or change A. This is a DSL, def_b should have a meaningful name to the domain, like configure, and it is more likely to be defined in a module or base class.
class B
class << self
def def_b(&block)
(#b_blocks ||= []) << block
end
def run
return if #b_blocks.nil?
a = A.new
#b_blocks.each { |block| a.instance_eval(&block) }
end
end
def_b do
a
end
end
And it accepts multiple definitions. It could be made accept only a single definition like this:
class B
class << self
def def_b(&block)
raise "b defined twice!" unless #b_block.nil?
#b_block = block
end
def run
A.new.instance_eval(&#b_block) unless #b_block.nil?
end
end
def_b do
a
end
end

How to replicate nested class structure in Ruby

Suppose I have a class A as follows:
class A
class B
end
class C < B
end
...
end
And I want to create a class D that has the same nested class structure as A
class D
Replicate nested class structure of A here
end
I want D to look like:
class D
class B
end
class C < B
end
...
end
So that I can do A::B and D::B with different results
How can I achieve this? Thanks in advance.
class Module
def replicate m
m.constants.each do |sym|
case mm = m.const_get(sym)
when Class then const_set(sym, Class.new(mm.superclass)).replicate(mm)
when Module then const_set(sym, Module.new(mm.superclass)).replicate(mm)
end
end
end
end
class D
replicate A
end
But the superclass part may not be correct with this code.
class A
end
class D
end
[A, D].each do |c|
c.class_eval %Q(
class B
def bar; puts "B#bar in #{c} is not shared" end # <--- new
end
class C < B
def foo; puts "C#foo in #{c}" end
end
)
end
p A.constants
p A::C.instance_methods(false)
p D.constants
p D::C.instance_methods(false)
A::C.new.foo
D::C.new.foo
New
A::B.new.bar
D::B.new.bar
=begin
class B # creates a new class B
def quux; puts "A::B#quux in #{self}" end
end
A::B.new.quux #=> undefined method `quux' for #<A::B:0x101358a98> (NoMethodError)
=end
class A::B # reopens B in A
def quux; puts "A::B#quux in #{self}" end
end
A::B.new.quux
Execution :
$ ruby -w t.rb
["B", "C"]
["foo"]
["B", "C"]
["foo"]
C#foo in A
C#foo in D
New
B#bar in A is not shared
B#bar in D is not shared
A::B#quux in #<A::B:0x10402da28>
It's more duplicating than replicating the whole internal structure, including the methods and possible variables. For this you need reflection, or maybe marshall out and in.
New : If you put something in the text inside %Q(), class_eval will evaluate it for each class, hence it is not shared. B is not independent, you have two different classes A::B and D::B.
If you want to add the same code to both classes, create a module and include it. Ruby creates a proxy which points to the module and inserts the proxy in the chain of pointers starting from the class of the object to its superclass, so that the search method mechanism will look for module's methods after methods of the class and before methods in the superclass.
class D
extend A
end
will define instance methods of A as class (singleton) methods of D. Sounds ugly. I think that you should experiment, display what happens with puts, p, instance_methods, singleton_methods and the like.

Ruby module with a static method call from includer class

I need to define the constant in the module that use the method from the class that includes this module:
module B
def self.included(base)
class << base
CONST = self.find
end
end
end
class A
def self.find
"AAA"
end
include B
end
puts A::CONST
But the compiler gives the error on the 4th line.
Is there any other way to define the constant?
The more idiomatic way to achieve this in Ruby is:
module B
def self.included(klass)
klass.class_eval <<-ruby_eval
CONST = find
ruby_eval
# note that the block form of class_eval won't work
# because you can't assign a constant inside a method
end
end
class A
def self.find
"AAA"
end
include B
end
puts A::CONST
What you were doing (class << base) actually puts you into the context of A's metaclass, not A itself. The find method is on A itself, not its metaclass. The thing to keep in mind is that classes are themselves objects, and so have their own metaclasses.
To try to make it clearer:
class Human
def parent
# this method is on the Human class and available
# to all instances of Human.
end
class << self
def build
# this method is on the Human metaclass, and
# available to its instance, Human itself.
end
# the "self" here is Human's metaclass, so build
# cannot be called.
end
def self.build
# exactly the same as the above
end
build # the "self" here is Human itself, so build can
# be called
end
Not sure if that helps, but if you don't understand it, you can still use the class_eval idiom above.
In your specific case.
module B
def self.included(base)
base.const_set("CONST", base.find)
end
end
class A
def self.find
"AAA"
end
include B
end
puts A::CONST
Despite it works, it's a little bit messy. Are you sure you can't follow a different way to achieve your goal?
module B
def self.included(base)
class << base
CONST = self.find
end
end
end
class A
class << self
def self.find
"AAA"
end
end
include B
end
then the compiler error is fixed, pls try.

Resources