That can lead to confusion with regard to the class names.
I know, but that shouldn’t cause me problems in this particular case.
Unfortunately, I’d like to avoid storing the class in a variable and
then referring to it because then the code inside what I called
Evaluator would have to be aware of the mechanics. The method
Evaluator#evaluate might also refer to more than one constant and,
ideally, shouldn’t be at all aware that one of the constant it’s using
is not what it originally was defined:
class A; end
class B; end
class C; end
class Evaluator
def evaluate #complex expression using one or more among A,B,C, like for example
A.method1 == :test && @myvar.is_a?(B) && C.method2 == :other_test
end
end
mind that the swap should happen only for a given instance of Evaluator,
not at the class level, in a way such as
ev1 = Evaluator.new
ev2 = Evaluator.new
ev1.set(A).to(B) #something like this, or worse named ev1.set(A,B) for
simplicity
ev1.evaluate #here, A.is_a?(B)#true.
ev2.evaluate #here, not so
I’m not even sure if that’s possible to do in ruby at this point.
Opening the singleton class (class << ev1) and defining the constant
there works (in the sense that the singleton class has the constant
correctly changed) but the methods defined on the class correctly
ignore it.
In any case, thanks for your time
Andrea
From: “Robert K.” [email protected]
To: [email protected]
Cc:
Date: Mon, 6 Aug 2012 02:28:09 +0900
Subject: Re: Redefining constants for a given instance only
ev.evaluate #ClassA
class Evaluator
ClassA = ClassB
end
ev.evaluate #ClassB
Ideas?
Andrea
Constants are only storage, that method is returning a class object. The
normal implementation would be
def evaluate
if some condition
ClassA
else
ClassB
end
end
because the caller gets a class object, the constant (or variable, or
method call, or whatever) #evaluate happened to take the class object
from
is irrelevant to the caller.
Now, I guess from your question that approach is not good for your
problem,
but in that case I’d like to understand why to suggest another one.
Sooo… how badly do you need this? Because if the answer is “very
badly”, then you could do this:
(If someone asks, I didn’t recommend this. The code that follows is
braindead, wrong on multiple levels, and will fail in unpredictable
ways in production. You have been warned.)
class ClassA
end
class ClassB
end
class Evaluator
def evaluate
# to make the source more interesting
if true
return ClassA
else
return nil
end
end
end
Here, Evaluator#run returns a module, and that sets the namespace for
the constant resolution in the eval of code.
However, as Robert points out, classes are just regular objects in Ruby,
and so can be passed around and used as such. It’s perfectly normal and
reasonable to return classes in variables, so you don’t have to mess
around with eval (which is often a security risk anyway)
class Evaluator
def run(a)
a ? [Ctx1::A,Ctx1::B] : [Ctx2::A,Ctx2::B]
end
end
a, b = Evaluator.new.run(true)
puts a.new.foo + b.new.bar
a, b = Evaluator.new.run(false)
puts a.new.foo + b.new.bar
That is, the variables a and b contain classes, and you use them
directly, independent of the fact that they happen to be bound to
constants like Ctx1::A.
In fact, you can also have completely anonymous classes (using
Class.new)
Unfortunately, I’d like to avoid storing the class in a variable and then
referring to it because then the code inside what I called Evaluator would
have to be aware of the mechanics.
The method Evaluator#evaluate might also
refer to more than one constant
From the rest of your posting it’s clear that you mean “classes” and
not “constants”.
and, ideally, shouldn’t be at all aware that
one of the constant it’s using is not what it originally was defined:
I think you should step back a bit and rethink. Then, what is the
logic you actually want to implement?
mind that the swap should happen only for a given instance of Evaluator, not
at the class level, in a way such as
Why then are you insisting on constants? Constants are definitively
the wrong mechanism in this case.
the methods defined on the class correctly ignore it.
If you want something constantish then I suggest you use a Hash with
Symbols as keys in Evaluator like
class Evaluator
def initialize @classes = {
:a => A,
:b => B,
}
end
def evaluate
a_instance = @classes[:a].new
…
end
def set_class(label, class_object)
raise “Wrong arguments” unless label.class == Symbol &&
class_object.class == Class @classes[label] = class_object
end
end
Unfortunately, I’d like to avoid storing the class in a variable and then
referring to it because then the code inside what I called Evaluator would
have to be aware of the mechanics. The method Evaluator#evaluate might also
refer to more than one constant and, ideally, shouldn’t be at all aware
that one of the constant it’s using is not what it originally was defined:
I don’t really understand why you can’t pass it in when you initialize.
Are
you saying that the implementation of #evaluate isn’t known? Can’t you
just
scroll down a bit and see it? Or are you overriding it in subclasses or
something?
If so, it sounds like you need to pull out an object that wraps the
evaluate method, then you can initialize the evaluator (which would
perhaps
be misnamed if you do this) with that object, and your evaluate method
could just delegate to it:
class MoreThanN
def initialize(n) @n = n
end
def call(value)
value > @n
end
end
class IsEven
def call(value)
value.even?
end
end
in this contrived example, this class is irrelevant,
but presumably in your more complex domain, it does something useful
class Evaluator
def initialize(strategy) @strategy = strategy
end