Weird class-loading issue (or maybe a problem with define_method?)

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

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

···

On 5/15/06, Pat Maddox <pergesu@gmail.com> wrote:
--
Austin Ziegler * halostatue@gmail.com
               * Alternate: austin@halostatue.ca

> 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"

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

···

On 5/15/06, Austin Ziegler <halostatue@gmail.com> wrote:

On 5/15/06, Pat Maddox <pergesu@gmail.com> wrote:

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

···

On Tue, 16 May 2006, Pat Maddox 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

--
be kind whenever possible... it is always possible.
- h.h. the 14th dali lama

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"

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

···

On 5/15/06, Pat Maddox <pergesu@gmail.com> wrote:

On 5/15/06, Austin Ziegler <halostatue@gmail.com> wrote:

On 5/15/06, Pat Maddox <pergesu@gmail.com> wrote:

--
Austin Ziegler * halostatue@gmail.com
               * Alternate: austin@halostatue.ca

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

···

On 5/15/06, ara.t.howard@noaa.gov <ara.t.howard@noaa.gov> wrote:

On Tue, 16 May 2006, Pat Maddox 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.

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

···

On 5/15/06, Austin Ziegler <halostatue@gmail.com> 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.

Pat Maddox 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