I have a folder structure that looks like the following:
app/models/
concerns/
quxable.rb
foo/
bar.rb
baz.rb
I'm in Rails 3 so I've autoloaded my concerns with:
config.autoload_paths += Dir[Rails.root.join('app', 'models', "concerns", '**/')]
And the files are as follows:
quxable.rb
module Quxable
extend ActiveSupport::Concern
module ClassMethods
def new_method
end
end
end
bar.rb
class Foo::Bar < ActiveRecord::Base
include Quxable
end
baz.rb
class Foo::Baz < ActiveRecord::Base
include Quxable
end
Now in the console if do this, I get the following outputs:
Foo::Bar.respond_to? :new_method #=> true
Foo::Baz.respond_to? :new_method #=> false
reload!
Foo::Baz.respond_to? :new_method #=> true
Foo::Bar.respond_to? :new_method #=> false
So it would seem to only be properly included on the model that is first accessed. And yet, If I run the following:
ActiveRecord::Base.descendants.select{ |c| c.included_modules.include?(Quxable) }.map(&:name)
I get ["Foo::Bar", "Foo::Baz"]
.
Any idea what's going on here? I'm guessing something with autoloading/eagerloading, but I'm not sure why both models aren't getting the new class method.
PS - I've tried rewriting the module without ActiveSupport::Concern
(just because I'm on an old Rails version and I'm taking shots in the dark) using:
def include(base)
base.send :extend, ClassMethods
end
but I still have the same problem.
EDIT
I initially left this out (just trying to present the simplest problem), so I apologize to those trying to help earlier. But quxable.rb
actually looks like this:
module Quxable
extend ActiveSupport::Concern
LOOKUP = {
Foo::Bar => "something",
Foo::Baz => "something else"
}
module ClassMethods
def new_method
end
end
end
So I'm guessing I created some kind of circular dependency defining a constant with the Class objects. Can anyone confirm? Weird that it just fails silently by not defining the class methods on the class that's accessed second though. I don't know why that is?
Based on your edit, this code is problematic:
LOOKUP = {
Foo::Bar => "something",
Foo::Baz => "something else"
}
It will instantiate Foo::Bar before the module is complete. Thus new_method
will be omitted. Tradition in this case is to use strings in the lookup and .constantize them to turn them into classes when needed.
LOOKUP = {
"Foo::Bar" => "something",
"Foo::Baz" => "something else"
}
then
LOOKUP.keys.first.constantize.new_method
or
result = LOOKUP[Foo::Bar.name]
to use it.