MyModel.descendants returns [] in a view after the first call?

[I posted this initially to the StackOverflow list. There have been no nibbles, so I’m coming here to consult the real experts! :)]

I want to display a selection list of MyModel subclasses in a view. It’s
not working yet, so for sanity checking, I included this in my view:

<%= MyModel.descendants %>

The first time I render this page after re-starting the server, it shows
the list of descendants (there are six). All subsequent times, it shows
up as an empty list []. When invoked from the console,
MyModel.descendants reliably returns all six subclasses.

FWIW, I have a require statement in my initializers:

Dir[Rails.root.join(“app/models/my_models/**/*.rb”).to_s].each {|f|
require f}
… and I’ve verified that they’re getting required.

What the @($%& is going on?

I am guessing you are in development mode with class caching off :slight_smile:
and that this behavior does not occur in test nor production mode …

Try the code with either test or production if you haven’t already …
I had gone to the place you are in as well, initially my workaround
was for my class to read directories and parse .rb files to find
sub-classes - LOL and bleyech!

If you are seeing that things work just fin in test and production
modes, you and I have the same issue - I just posted my solution to
this (my fix is not pretty) to the ruby talk mailing list - it works
consistently, maybe it will help you.

Let me know if you have trouble finding it.

Max

@Max:

Max S. wrote in post #996743:

I am guessing you are in development mode with class caching off :slight_smile:
and that this behavior does not occur in test nor production mode …

Try the code with either test or production if you haven’t already …

Thanks for the tip. Rather than try to get my db migrated over to
production mode, I simply edited the line in
config/environments/development.rb to read:

 config.cache_classes = true

… and now it works. BUT: this is a bug, right?

  • ff

P.S.: Max: what’s your handle on ruby talk? I poked around and didn’t
see anything obvious

Fearless,

On Thu, May 5, 2011 at 1:22 AM, Fearless F. [email protected]
wrote:

Thanks for the tip. Rather than try to get my db migrated over to
production mode, I simply edited the line in
config/environments/development.rb to read:

config.cache_classes = true

… and now it works. BUT: this is a bug, right?

Well, not really a bug - it is ‘behavior as designed’ but it is also a
‘fail’ for those of us using ‘meta-programming as designed LOL’
because meta-programming requires that classes be loaded and that the
callbacks associated with Object and Class get called!

P.S.: Max: what’s your handle on ruby talk? I poked around and didn’t
see anything obvious

I am using the Google group to respond, so no handle :). Were you
able to find the code I used to work around the class caching issue?
It is not pretty but it works.

  • Max

Max S. wrote in post #996758:

Fearless,
Well, not really a bug - it is ‘behavior as designed’ but it is also a
‘fail’ for those of us using ‘meta-programming as designed LOL’
because meta-programming requires that classes be loaded and that the
callbacks associated with Object and Class get called!

Now I’m intrigued, but that doesn’t explain to me why
MyModel.descendants returns a valid list of descendants on the first
call but an empty list thereafter. Can you explain why this happens
(and why it’s considered behavior as designed)?

P.S.: I poked around and didn’t see anything obvious

I am using the Google group to respond, so no handle :). Were you
able to find the code I used to work around the class caching issue?

I couldn’t find your code. Without your patch, I can (a) restart the
server frequently to avoid the MyModel.descendants bug that I observed,
or (b) turn on class caching, which requires me to restart the server
between changes. So your patch would be handy.

(…I’m still astonished this is how it’s designed to work!!)

On 5 May 2011, at 07:37, Fearless F. [email protected] wrote:

(and why it’s considered behavior as designed)?
descendants returns all loaded subclasses, but when class caching is
off, classes are cleared out, so the freshly loaded copy of MyModel has
no loaded subclasses.

About the best thing you can do is (at the bottom of the base class) use
require_dependency to make all the subclasses be loaded when the base
class is.

By design is perhaps not quite the right word - it’s something people
have long been aware, with no obvious solution, but it is obviously not
desirable behaviour.

Fred

Use require_dependency to load subclasses.

product.rb

class Produtct < AR::Base
end
require_dependency ‘book’
require_dependency ‘movie’

movie.rb

class Movie < Product
end

book.rb

class Book < Product
end

This way it will work in development and production environment + code
reloading will work fine.

Another solution is to keep list of class names in database (aka
‘dictionary’ table), retrive it and constantize those names.

Robert Pankowecki
http://robert.pankowecki.pl

Fearless, here is the code pattern I chose. it adds a bunch of custom
code to

config/environments/development.rb

in order to not have to pollute your class heirarchies with
‘development only hacks’ code …

I put this below the configure do block in development.rb:

i personally prefer this because it is just generating special
behavior for the development environment instead of altering behavior
on all environments.

-= Max

if Rails.env.development?

Bootstrap classes being referenced as we use meta-programming that

does not get called in development because class caching

is off. Merely accessing the classes by name triggers the

callbacks that otherwise get called immediately with class

caching on in non-development environments.

def bootstrap_x_callbacks
X1.nil?
X2.nil?
X3.nil?
X4.nil?
X5.nil?
X6.nil?
end

Run once on startup after Rails environment is all warmed up and

ready to rock.

MyApp::Application.configure do
config.after_initialize do
bootstrap_x_callbacks
end
end

Schedule it to be called after every reload!

ActionDispatch::Callbacks.after do
bootstrap_x_callbacks
end

end

Frederick C. wrote in post #996771:

descendants returns all loaded subclasses, but when class caching is
off, classes are cleared out, so the freshly loaded copy of MyModel has
no loaded subclasses.

Robert Pankowecki wrote in post #996778:

Use require_dependency to load subclasses.

@ Fred: Ah! That makes sense – now I see why my initialize-time
require worked only the first time.

@Robert & @Fred: require_dependency it shall be.

Thanks all!

  • ff

Fearless F. wrote in post #996867:

[@Max: our messages just crossed. With all due respect, I think I like
my approach better!]

So now I overload MeteredService.descendants, calling ‘require’ instead
of ‘require_dependency’. This seems to work just fine:

Let me know if I’m missing anything important.

Aaaaannnnnnd, there just one little problem: it doesn’t work.

So my next approach will be some magick with const_defined? – this time
for sure!!! :wink:

Fearless F. wrote in post #996869:

So my next approach will be some magick with const_defined? – this time
for sure!!! :wink:

I tried that and now I’m really confused. @Fred: would you expect it to
be the case that:

class MeteredService < ActiveRecord::Base ; end
class PGEBusiness < MeteredService ; end

Object.const_defined?(“PGEBusiness”) => true
yet
MeteredService.descendants => []
?

That is, the system still knows about the class PGEBusiness, but it
doesn’t show up as a descendant of MeteredService? That is, in fact,
what I’m seeing.

More to the point, how can I tell when I need to reload the subclasses?
(And if require_dependencies is the answer, how to I avoid the
superclass mismatch error that I’m seeing?)

(Feh. Maybe I should just start running in production mode.)

TIA.

Resistance is futile.

Until someone chimes in with a better idea, I’m adopting Max’s approach
(above).

  • ff

[@Max: our messages just crossed. With all due respect, I think I like
my approach better!]

Okay. I’m out of the weeds, but I’d like to know if I’ve done it the
right way.

My code references subclasses of MeteredService several transactions
after metered_service.rb gets loaded (e.g. the result of posting a
form), at which point the sub-classes had been flushed and there was
nothing that forced a reload of metered_service.rb. Ergo, adding
require_dependency statements at the bottom of metered_service.rb didn’t
help.

My next gambit was to overload MeteredService.descendants:

class MeteredService

def self.descendants
require_dependency ‘metered_services/pge_business’
require_dependency ‘metered_services/pge_residential’
require_dependency ‘metered_services/sce_residential’
require_dependency ‘metered_services/sdge_residential’
require_dependency ‘metered_services/socalgas_residential’
require_dependency ‘metered_services/srp_residential’
super
end
end

… but that had the problem that it threw “TypeError (superclass
mismatch for class PGEBusiness)” when it got called. Perhaps it’s in
the wrong context when it calls require_dependency? I don’t know, and I
couldn’t find any documentation that helped.

So now I overload MeteredService.descendants, calling ‘require’ instead
of ‘require_dependency’. This seems to work just fine:

class MeteredService

if Rails.env.development?
def self.descendants
require ‘metered_services/pge_business’
require ‘metered_services/pge_residential’
require ‘metered_services/sce_residential’
require ‘metered_services/sdge_residential’
require ‘metered_services/socalgas_residential’
require ‘metered_services/srp_residential’
super
end
end
end

Yes, I will generalize this with a method that reads the contents of the
metered_services directory so I don’t have to manually add each one.

Let me know if I’m missing anything important.

On Thu, May 5, 2011 at 2:17 PM, Fearless F. [email protected]
wrote:

But still no joy.

Maybe I should take up macram instead.

Could you respond with your development.rb file (with names sanitized
as needed?) … what version of Rails are you using? We have been
using 3.0.5, just upgrading to 3.0.7 this week …

@Max:

Fearless F. wrote in post #996874:

Until someone chimes in with a better idea, I’m adopting Max’s approach
(above).

I thought you might want to know: I’ve implemented your approach and I’m
STILL seeing the problem: the descendants are defined the first time,
but gone thereafter. I put a Rails.logger.debug() call in
bootstrap_x_callbacks and I can see it getting called on every reload.
But still no joy.

Maybe I should take up macramé instead.

  • ff

product.rb

class Produtct < AR::Base
end
require_dependency ‘book’
require_dependency ‘movie’

movie.rb

class Movie < Product
end

book.rb

class Book < Product
end

This works fine for me in development and production.

  1. Development:
    a) Request comes
    b) you call Product.dependencies()
    c) reading product.rb calls require_dependecy and reads movie.rb and
    book.rb
    d) Product.dependencies returns => [Movie, Book]
    e) request is finished. Product, Movie, Book constanst are forgotten.

  2. Development:
    a) Request comes
    b) you call Movie.something()
    c) Movie inherits from Product
    d) autoloading reads product.rb
    e) reading product.rb calls require_dependecy and reads movie.rb and
    book.rb
    f) Movie.something() returns something
    g) request finished. Product, Movie, Book constanst are forgotten.

  3. Production

a) Loading rails application leads to require all files in app/**/*.rb
for faster forking application.
b) movie.rb or product.rb is read first.
c) require_dependency behaves like require.
d) Everything works still fine.

Robert Pankowecki

On 5 May 2011, at 18:51, Fearless F. [email protected] wrote:

Object.const_defined?(“PGEBusiness”) => true
yet
MeteredService.descendants => []
?

That is, the system still knows about the class PGEBusiness, but it
doesn’t show up as a descendant of MeteredService? That is, in fact,
what I’m seeing.

Have you been mixing in calls to require with calls to
require_dependency/letting rails load things automatically? That can
make a giant mess of things where some classes are reloaded and others
aren’t, so for example PGEService could be a subclass of the previous
request’s MeteredService

Fred

Max S. wrote in post #996908:

Could you respond with your development.rb file (with names sanitized
as needed?) … what version of Rails are you using? We have been
using 3.0.5, just upgrading to 3.0.7 this week …

Sure. Here it is. Thanks.

===== versions (I’ll move to 3.0.7 when Heroku does):
% rake about
About your application’s environment
Ruby version 1.9.2 (x86_64-darwin10.6.0)
RubyGems version 1.3.7
Rack version 1.2
Rails version 3.0.5
Active Record version 3.0.5
Action Pack version 3.0.5
Active Resource version 3.0.5
Action Mailer version 3.0.5
Active Support version 3.0.5
Environment development

===== development.rb (it’s still vanilla):
MyApp::Application.configure do
config.cache_classes = false
config.whiny_nils = true
config.consider_all_requests_local = true
config.action_view.debug_rjs = true
config.action_controller.perform_caching = false
config.action_mailer.raise_delivery_errors = false
config.action_mailer.default_url_options = { :host => ‘localhost:3000’
}
config.active_support.deprecation = :log
config.action_dispatch.best_standards_support = :builtin
end

@ Fred, @Robert Pankowecki:

Frederick C. wrote in post #996972:

Have you been mixing in calls to require with calls to
require_dependency/letting rails load things automatically? That can
make a giant mess of things where some classes are reloaded and others
aren’t, so for example PGEService could be a subclass of the previous
request’s MeteredService

Fred:

Your explanation makes sense. I haven’t intentionally been mixing
calls – I swore that off back in college after mixing kahlua and
tequila. What I have tried:

[1] adding "require_dependency PGEBusiness’ at the end of its superclass
(MeteredService), as suggested by Fred and Robert. It didn’t fix the
problem, I THINK because I have a form that passes back “PGEBusiness”
several transactions after MeteredService was loaded, by which point the
subclasses has been flushed.

[2] overloading MeteredService.descendents method as described above:

class MeteredService
def self.descendants
require_dependency ‘metered_services/pge_business’

super
end
end

… which resulted in the “TypeError (superclass mismatch for class
PGEBusiness)” error I’ve seen.

[3] Same as [2], but using ‘require’ instead of ‘require_dependency’.
Um, I think that also got the TypeError message. That was several
commits ago – I can go back and verify that was the problem.

So I’m still open to suggestions…

Hello everyone!

During I probing Rails 3.1.rc1 noticed along the majors many little
changes and deprecations.
Most of them (changes and deprecations) easy fixable except ONE:

If we have:
Dog as ActiveRecord class

puppy = Dog.new
puppy.new_record?
=> true
dumped_puppy = Marshal.load(Marshal.dump(puppy))
dumped_puppy.new_record?
=> false

Why?

This forum is not affiliated to the Ruby language, Ruby on Rails framework, nor any Ruby applications discussed here.

| Privacy Policy | Terms of Service | Remote Ruby Jobs