I would like to use #include at an instance level, such that it
behaves just as it does at a class level. After a number of
experiments I thought for sure it would work if I ran the include
through the object’s singleton. Alas, submodules remain inaccessible,
eg.
module M
def m1; “m1”; end
module N
def self.n1; “n1”; end
end
end
class X
def initialize(*mods)
(class << self; self; end).class_eval do
include *mods
end
end
def m ; m1 ; end
def n ; N.n1 ; end
end
x = X.new(M)
p x.m
p x.n #=> uninitialized constant X::N (NameError)
I guess constants not like methods are not searched within eigenclass, and that’s the reason why you are getting an error.
You are right. That appears to be the case. I tried setting a constant
directly in the eigenclass and the instance could not see it either.
The behavior surprises me.
No no. You got it exactly. Problem is the module’s constants are not
coming through the #extend (which is effectively the same as the
include in the singleton class). Let say I have a library:
module MyApp
module SomeSpace
class FooClass
…
end
end
end
In the test cases, instead of having to spell out
MyApp::SomeSpace::FooClass everywhere it is needed, it would be nice
to include MyApp::SomeSpace, and then just reference FooClass in the
tests.
Instead of making an instance of TestCase for each case, at this point
it looks like I’ll have to create a new subclass of it.
Sorry, you lost me here.
Instead of the code you presented I’d have to do something like:
class TestCase
alias :_new, :new
def self.new(&block)
Class.new(self, &block)._new
end
end
Haven’t tested it yet, but that should allow #include to work no
problem. Unfortunately it means defining all my dsl methods at the
class level --not even sure the instance level would be of any use in
this case either, in which case the ._new can be dropped – kind of
stupid, just to get #include to work. But what else can I do?
I almost feel like I’m having a mental block and there is actually an
easy way to do this.
I see to be a bit slow today: how do you want to use modules there?
Do you want to include them in the test code? What about:
class TestCase
def new(cl, &bl)
Btw, that method’s name should have read “initialize”.
end
end
Sorry, you lost me here.
Instead of the code you presented I’d have to do something like:
class TestCase
alias :_new, :new
def self.new(&block)
Class.new(self, &block)._new
end
end
Then your block needs to contain a class definition. You can use the
block only for one thing - either class / module def or code you
want to execute. Granted, you can have code executed in a class
definition as well but the definition becomes only usable after the
definition has been executed in its entirety. Maybe you haven’t
decided yet what you want the block for and that is causing your
headaches.
Haven’t tested it yet, but that should allow #include to work no
problem. Unfortunately it means defining all my dsl methods at the
class level --not even sure the instance level would be of any use in
this case either, in which case the ._new can be dropped – kind of
stupid, just to get #include to work. But what else can I do?
I almost feel like I’m having a mental block and there is actually an
easy way to do this.
What stops you from doing the include outside?
include Your::Module::Of::Choice
TestCase.new Foo do
x = ChoiceClass.new
x.method_invocation(123)
end
Then your block needs to contain a class definition. You can use the
block only for one thing - either class / module def or code you
want to execute. Granted, you can have code executed in a class
definition as well but the definition becomes only usable after the
definition has been executed in its entirety.
That’s true. But the execution of the block is only for the definition
of things which get run later.
Maybe you haven’t
decided yet what you want the block for and that is causing your
headaches.
The block is for defining unit tests. Eg.
testcase SomeClass do
testunit :somemethod => “some concern” do
…
end
end
include Your::Module::Of::Choice
TestCase.new Foo do
x = ChoiceClass.new
x.method_invocation(123)
end
Yes, I am able to do that. But I want to avoid polluting the top
level.
You know what really gets me is that even though I get an
“uninitialized constant” error, there is no place I seem to be able to
define a #const_missing method to catch it.
This is really becoming annoying. I can’t even do it via a dynamic
class definitions either.
module M
def m1; “m1”; end
module N
def self.n1; “n1”; end
end
end
class X
class << self
alias _new new
def new(&block)
klass = Class.new(self)
klass.class_eval(&block)
klass._new
end
end
def m ; m1 ; end
def n ; N.n1 ; end
end
x = X.new do
include M
end
p x.m
p x.n #=> uninitialized constant X::N (NameError)
Did constant lookup change between 1.8.6 and 1.8.7? Seems to me this
used to be possible, and I find it unacceptable that normal constant
lookup would not apply to dynamic class definitions, let alone the
singleton classes.
class X
def n ; N.n1 ; end
used to be possible, and I find it unacceptable that normal constant
lookup would not apply to dynamic class definitions, let alone the
singleton classes.
AFAIK it hasn’t changed and your code could never work in any version
of Ruby because the const lookup in method #n is done statically.
Apart from that you are defining method X#n and not #n
so the lookup could never work if you would instantiate X.
Also, on a more abstract level: it is at least a bit odd to define
class X which can only ever work if you make sure you include “N”
(whichever way).
I believe you haven’t yet fully analyzed the problem you want to
solve. I suggest to put it to sleep for a while and then get back
later to it. I can’t help you any more because to me it is not clear
what you are trying to achieve. So far we only went through technical
issues but the problem you are trying to solve isn’t cleat to me yet.
AFAIK it hasn’t changed and your code could never work in any version
of Ruby because the const lookup in method #n is done statically.
But it should not, consider:
module M
module N
end
end
#Class.new do <---- This does not work in 1.8.7!
class B < Object
include M
N
end
When ‘class B < Object’ replaced with ‘Class.new do’ the Ruby 1.8.7
interpreter complains about uninitialized constant N. What is the big
reason for dynamically defined class to behave so differently? It would
be very non obvious and unexpected behavior. Actually both cases work as
expected in Ruby 1.9.1 and this is correct behavior according to the
Ruby Draft.
p x.m
so the lookup could never work if you would instantiate X.
Also, on a more abstract level: it is at least a bit odd to define
class X which can only ever work if you make sure you include “N”
(whichever way).
I believe you haven’t yet fully analyzed the problem you want to
solve. I suggest to put it to sleep for a while and then get back
later to it. I can’t help you any more because to me it is not clear
what you are trying to achieve. So far we only went through technical
issues but the problem you are trying to solve isn’t cleat to me yet.
All I am trying to do is emulate test/unit but using a DSL.
Essentially:
class MyTest < Test::Unit::TestCase
include M
def test_N_n1
assert_equal(N.n1, "n1")
end
end
Becomes:
testcase :MyTest do
include M
testunit :N_n1 do
assert_equal(N.n1, "n1")
end
end
That’s it. But I can’t do it exactly b/c I can’t make the include
work.
#Class.new do <---- This does not work in 1.8.7!
class B < Object
 include M
 N
end
When ‘class B < Object’ replaced with ‘Class.new do’ the Ruby 1.8.7 interpreter complains about uninitialized constant N. What is the big reason for dynamically defined class to behave so differently? It would be very non obvious and unexpected behavior. Actually both cases work as expected in Ruby 1.9.1 and this is correct behavior according to the Ruby Draft.
Thanks for the test case! It never occurred to me to do a const
lookup in a class defined with a class body so I never stumbled across
this. I did a bit of research and this is what I found:
To sum it up: Ruby 1.9 changed lookup rules from lexical (aka static)
to dynamic. IMHO this explains the observed different behavior.
Thanks again for poking.
testcase :MyTest do
work.
Ah, now I see. I believe your problem comes from the fact that you
are mixing two styles: DSL and “regular” Ruby. If you integrate
defining those modules into your DSL (i.e. not via “module … end”)
then integrating lookups might be easier as well. I don’t have a
clear idea yet how to do that but maybe it’s worth exploring.
To sum it up: Ruby 1.9 changed lookup rules from lexical (aka static)
to dynamic.
That change may not be permanent.
There was a long thread on ruby-core a month or two ago, prompted by a
question raised by Yehuda K. about the problems the change was
causing for existing DSLs like ActiveRecord and RSpec. After various
proposals to modify the 1.9 behavior to make it more compatible,
Maeda-san posted this:
Matz seems to be in favor of returning to the 1.8 behavior.
#Class.new do <---- This does not work in 1.8.7!
class B < Object
include M
N
end
When ‘class B < Object’ replaced with ‘Class.new do’ the Ruby 1.8.7 interpreter complains about uninitialized constant N. What is the big reason for dynamically defined class to behave so differently? It would be very non obvious and unexpected behavior. Actually both cases work as expected in Ruby 1.9.1 and this is correct behavior according to the Ruby Draft.
Nice clarification.
Are there two issues here? This issue and the resolution of constants
via instance_eval? Or are these two aspects of the same issue?
In either case, there is no doubt in my mind that ‘class B’ and ‘B =
Class.new do’ should work the same.
causing for existing DSLs like ActiveRecord and RSpec. After various
proposals to modify the 1.9 behavior to make it more compatible,
Maeda-san posted this:
Matz seems to be in favor of returning to the 1.8 behavior.
What a tangle.
Seems to me there are two desired behaviors: 1) domain behavior, where
by lookup follows from the point of evaluation, and 2) closure
behavior, whereby lookup follows from the point of definition. Both
are perfectly reasonably and depend solely on the need of the
developer’s usecase. There is also the compromise position, first try
domain behavior, failing that try closure behavior.
So if I understand correctly, 1.8 and older used closure behavior, 1.9
switched to domain behavior. There were issues with this change. Some
people suggested the compromise position, but Matz rejected that and
has decided to return to the original closure behavior. Is that a
correct summary?
Short of the compromise position, I would think the only complete
solution would be to allow for both approaches via different methods,
eg. instance_eval vs. instance_domain_eval, or perhaps an option,
instance_eval(&b, :closure=>false).