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.
I think you have a typo, and concerns include some magic that lets you transcend the limitations on mix-ins.
Also, if you're working in a directory under something that's already autoloaded, like 'models', just namespace everything to that directory name.
Try this:
module Concerns
module Quxable
extend ActiveSupport::Concern
included do
def self.new_method
end
end
end
end
module Foo
class Baz < ActiveRecord::Base
include Concerns::Quxable
end
end
As far as I remember you shouldn't need the extra autoload directive, as using the namespace in a directory under models will just work.
Edit after comments:
I've set up a Rails project with the following added files:
app/models/foo/doer.rb
app/models/foo/thinker.rb
app/models/concerns/thingable.rb
thingable.rb is:
module Concerns
module Thingable
extend ActiveSupport::Concern
included do
def self.thing
end
end
end
end
doer.rb is:
module Foo
class Doer < ActiveRecord::Base
include Concerns::Thingable
end
end
thinker.rb is:
module Foo
class Thinker < ActiveRecord::Base
include Concerns::Thingable
end
end
In a console:
Loading development environment (Rails 3.2.22)
2.1.3 :001 > Foo::Doer.respond_to? :thing
=> true
2.1.3 :002 > Foo::Thinker.respond_to? :thing
=> true
2.1.3 :003 > reload!
Reloading...
=> true
2.1.3 :004 > Foo::Doer.respond_to? :thing
=> true
2.1.3 :005 > Foo::Thinker.respond_to? :thing
=> true
2.1.3 :006 >
I did not change the autoloading at all, I relied on Rails to find files based on namespacing. (Use a namespace for directories under known directories like 'models')
I would reset your autoloading to default, then use Rails conventions for file locations and namespacing. If that doesn't work, there may be other things your project is doing that I don't know about.
Let me know if you can provide any more details.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.