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
Related
I have a class "C". I'd like C to run a method A that takes in a block. I'd then like the block to have the context of the class provided.
C.a do
b # runs main.b instead of C.b
end
Currently, the method b is running in the context of main. I'd like for it to run in the context of the class C
How can this be done?
class C
class << self
def a(&block)
block.bind self # NOPE!
block.binding = self # NOPE!
yield # NOPE!
end
def b
end
end
end
PS. This is the same pattern as Rails routes.
You need to eval the block in the context of the Class:
class C
class << self
def a(&block)
self.instance_eval(&block)
end
def b
puts "hello"
end
end
end
C.a do
b
end
=> "hello"
I'm trying to create a method that passes the caller as the default last argument. According to this, I only need:
class A
def initialize(object = self)
# work with object
end
end
so that in:
class B
def initialize
A.new # self is a B instance here
end
end
self will be B rather than A;
However, this doesn't seem to work. Here's some test code:
class A
def self.test test, t=self
puts t
end
end
class B
def test test,t=self
puts t
end
end
class T
def a
A.test 'hey'
end
def b
B.new.test 'hey'
end
def self.a
A.test 'hey'
end
def self.b
B.new.test'hey'
end
end
and I get:
T.new.a # => A
T.new.b # => #<B:0x000000015fef00>
T.a # => A
T.b # => #<B:0x000000015fed98>
whereas I expect it to be T or #<T:0x000000015fdf08>. Is there a way to set the default last argument to the caller?
EDIT:
class Registry
class << self
def add(component, base=self)
self.send(component).update( base.to_s.split('::').last => base)
end
end
end
The idea is pretty simple, you would use it like this
class Asset_Manager
Registry.add :utilities
end
and you access it like:
include Registry.utilities 'Debugger'
I'm trying to de-couple classes by having a middle-man management type class that takes care of inter-class communications, auto-loading of missing classes and erroring when it doesn't exist, it works but I just want to be able to use the above rather than:
class Asset_Manager
Registry.add :utilities, self
end
It just feels cleaner, that and I wanted to know if such a thing was possible.
You can't escape the explicit self. But you can hide it with some ruby magic.
class Registry
def self.add(group, klass)
puts "registering #{klass} in #{group}"
end
end
module Registrable
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def register_in(group)
Registry.add(group, self)
end
end
end
class AssetManager
include Registrable
register_in :utilities
end
# >> registering AssetManager in utilities
In short, you can't.
Ruby resolves the default arguments in the context of the receiver. That is, the object before the . in a method call. What you called the receiver should be the caller, actually.
class A
def test1(value = a)
puts a
end
def test2(value = b)
puts b
end
def a
"a"
end
end
a = A.new
a.test1 #=> a
def a.b; "b" end
a.test2 #=> b
If I were you, I would use the extended (or included) hook, where both the extending class and the extended module can be accessed. You can program what ever logic you want based on the information.
module Registry
module Utilities
def self.extended(cls)
#puts cls
::Registry.send(component).update( cls.to_s.split('::').last => cls)
end
end
end
class Asset_Manager
extend Registry::Utilities
end
I have a list of names and values I'm trying to read in and turn into classes so I'm using Class.new.
The end result I want is a number of classes that work as if defined like:
module MyMod
class AA < Base
def self.value
value1
end
end
class AB < Base
def self.value
value2
end
end
...
end
My current code looks like:
name = 'AA'
value = 'test'
MyMod.const_set name, Class.new(Base) do
???
end
Setting the name works great, but haven't figured out what I need in the block for get value in. Calling def doesn't work because the closure for value gets lost.
I have managed to get things working with:
temp = const_set name, Class.new(Base)
temp.define_singleton_method(:value) { value }
However, it seems like there should be a way to do it with the block of Class.new. Also, I'm really not sure define_singleton_method is actually putting the method in the right place. It works in my tests, but I'm not sure if the method is actually where I think it is or somewhere else up the call chain. I've tried various combinations of class_variable_set, attr_reader, class_eval, instance_eval, and others, but it got to a point where it was just guess and check. I think I still haven't quite wrapped my head around metaprogramming :-/
if i correctly understood your question, this should work for you:
class Base
end
class AA < Base
name = :Blah
klass = self.const_set name, Class.new(Base)
class << klass
def value
__method__
end
end
end
p AA::Blah.value
#=> :value
UPDATE: seems you want it defined in the block:
class Base
end
class AA < Base
name = :Blah
klass = Class.new(Base) do
class << self
def value
__method__
end
end
end
self.const_set name, klass
end
p AA::Blah.value
you trying this:
const_set name, Class.new(Base) do
...
end
it does not work cause the block is referring to const_set rather than to Class.new
If you prefer define_singleton_method over class << self:
class Base
end
class AA < Base
name = :Blah
klass = Class.new(Base) do
self.define_singleton_method :value do
__method__
end
end
self.const_set name, klass
end
And finally if you really want to define them at once, use brackets instead of do...end:
class Base
end
class AA < Base
name = :Blah
self.const_set name, Class.new(Base) {
self.define_singleton_method :value do
__method__
end
}
end
Here is a working demo
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.
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.