Weird class-loading issue (or maybe a problem with define_me


#1

I’m working on an app and having a strange class loading problem.
I’ve extracted it to the most basic code possible.

foo.rb

require “bar”
class Foo
FOO = “foo”
end

bar.rb

require “foo”
class Bar
def self.set_foo(val)
define_method(:foo) { val }
end
end

class FooBar < Bar
set_foo Foo::FOO
end

This works fine if I include bar.rb first:
baggio:~/work/test pergesu$ irb
irb(main):001:0> require “bar”
=> true

but it blows up if I do foo.rb:
irb(main):001:0> require “foo”
NameError: uninitialized constant FooBar::Foo
from ./bar.rb:10
from ./foo.rb:1
from (irb):1

What’s even more strange to me is that the FooBar class has no problem
accessing Foo::FOO from within a method, either class or instance:
class FooBar < Bar
def foo; Foo::FOO; end
def self.class_foo; Foo::FOO; end
end

irb(main):001:0> require “foo”
=> true
irb(main):002:0> FooBar.new.foo
=> “foo”
irb(main):003:0> FooBar.class_foo
=> “foo”

I’ve also moved self.class_foo into the Bar superclass, and there were
no problems. The only issue that pops up then is when I write a class
method in Bar that ends up calling define_method passing in a constant
from the other file. It only happens when I require “foo”, but
require “bar” works fine.

Can anyone tell me what’s going on here? I really don’t understand
it. Also I’ve foundt that my program works if I require “bar” before
everything…but I don’t like that solution. Most classes don’t even
know that that file ever gets required, so it feels ugly that I’d have
to require it at the beginning of every file I use.

Pat


#2

On 5/15/06, Pat M. removed_email_address@domain.invalid wrote:

I’m working on an app and having a strange class loading problem.
I’ve extracted it to the most basic code possible.

Change this:

foo.rb

require “bar”
class Foo
FOO = “foo”
end

to this:

foo.rb

class Foo
FOO = “foo”
end
require “bar”

Better yet, get rid of your circular dependency.

FooBar should be defined in a third file that requires foo.rb and
bar.rb.

-austin


#3

On 5/15/06, Austin Z. removed_email_address@domain.invalid wrote:

to this:

foo.rb

class Foo
FOO = “foo”
end
require “bar”

I can’t, because some of the methods in the Foo class call Bar methods.

Better yet, get rid of your circular dependency.

FooBar should be defined in a third file that requires foo.rb and bar.rb.

I’m not sure how that fixes anything. Foo has to create some Bar
objects, and Bar has to have access to some of the Foo constants. I
didn’t talk about that in my initial post, but each class interacts
with each other.

Pat


#4

On Tue, 16 May 2006, Pat M. wrote:

I’m not sure how that fixes anything. Foo has to create some Bar
objects, and Bar has to have access to some of the Foo constants. I
didn’t talk about that in my initial post, but each class interacts
with each other.

Pat

a setup like this will give you that

harp:~ > cat foo_constants.rb
module FooConstants
FOO = 42
end

harp:~ > cat foo.rb
require ‘foo_constants’
class Foo
include FooConstants
end

harp:~ > cat bar.rb
require ‘foo_constants’
class Bar
include FooConstants
def self.set_foo(val)
define_method(:foo) { val }
end
end

harp:~ > ruby foobar.rb
42

hard to understand why you’d do that - but factoring module-wise
will
certainly lead to an elegant answer.

regards.

-a


#5

On 5/15/06, removed_email_address@domain.invalid removed_email_address@domain.invalid wrote:

42

hard to understand why you’d do that - but factoring module-wise will
certainly lead to an elegant answer.

Thanks for the response Ara.

Do you mean it’s hard to understand why I’d set things up the way I
have them? Or do you mean that factoring the constants out is a non
obvious solution?

Anyway what really interests me is why I only have this problem when
I’m calling the class method that calls define_method inside it. If
you look at it, instance methods and class methods all work fine. If
it were purely a class loading issue, shouldn’t it blow up in other
cases as well?

Pat


#6

On 5/15/06, Austin Z. removed_email_address@domain.invalid wrote:

I think you’re going to need to be a bit clearer about what you’re
actually trying to do, because this sounds like an incredibly bad
design.

Circular dependencies are bad.

I went through Ch1 of Refactoring, using rspec to write the tests.
After I was done with all the refactorings, I wanted to be able to
specify a price code for each Price class by calling a class method,
rather than defining one. I’ve got all the source code at
http://svn.flpr.org/public/refactoringbdd It’s very small, so it
wouldn’t take more than a few minutes to look at.

Here’s the original code for one class, with extraneous methods ripped
out:

class ChildrensPrice < Price
def get_price_code
Movie::CHILDRENS
end
end

Here’s what I’d like to do:
class Price
def self.price(val)
define_method(:get_price_code) { val }
end
end

class ChildrensPrice < Price
price Movie::CHILDRENS
end

As you can see, instead of defining get_price_code, I just call
Class.price and it defines the method for me.

Currently the test suite runs fine. If you open
specs/customer_spec.rb and delete the require “price” line though, it
gives errors.

Pat


#7

On 5/15/06, Pat M. removed_email_address@domain.invalid wrote:

end

to this:

foo.rb

class Foo
FOO = “foo”
end
require “bar”
I can’t, because some of the methods in the Foo class call Bar methods.

Do you mean:

class Foo
FOO = foo
def initialize(mybar)
@bar = mybar
end

def baz
  # ... stuff ...
  @bar.baz
  # ... stuff ...
end

end

If that’s the case, then you can do it.

Better yet, get rid of your circular dependency.

FooBar should be defined in a third file that requires foo.rb and
bar.rb.
I’m not sure how that fixes anything. Foo has to create some Bar
objects, and Bar has to have access to some of the Foo constants. I
didn’t talk about that in my initial post, but each class interacts
with each other.

I think you’re going to need to be a bit clearer about what you’re
actually trying to do, because this sounds like an incredibly bad
design.

Circular dependencies are bad.

If you must absolutely have it, take advantage of Ruby’s open classes.
But try to avoid it.

-austin


#8

Pat M. schrieb:

I’ve got all the source code at
http://svn.flpr.org/public/refactoringbdd It’s very small, so it
wouldn’t take more than a few minutes to look at.

Pat, I haven’t looked very carefully, but noted two things:

Too many requires: for example, in customer.rb you require “rental”, but
you don’t use the Rental class. Skip those require statements.

Duplicated knowledge: the connection between price codes and price
subclasses is duplicated in Movie#price_code= and in the price
subclasses themselves. Try to remove this duplication. I don’t think it
is a good idea that the Price class has to know something about price
codes, which seems to be a concept of the Movie class.

Regards,
Pit