1
votes

Setting

In my gem there is a Component base class:

module Component
  class Base
    def self.inherited(component_class)
      Component.class_eval do
        define_method(component_class.to_s.underscore) do |*args, &block|
          component_class.new(*args, &block)
        end
      end
    end
  end
end

For every class inheriting from this base class (e.g. FancyComponent < Component::Base), a helper should be defined in the Component module (e.g. fancy_component).

This does work for any components delivered with my gem, i.e. Component.instance_methods.include? :fancy_component #=> true

Now Rails comes into play

I want users of my gem to be able to add components. These are stored in app/components.

This folder is included in all of the following:

  • MyApp::Application.config.load_paths
  • MyApp::Application.config.autoload_paths
  • MyApp::Application.config.eager_load_paths

A new component UserComponent < Component::Baseis stored in app/components/user_component.rb.

The Problem

If I launch the rails console, the situation is as follows:

Loading development environment (Rails 3.0.4, ruby-1.9.2-p0)  
Component.instance_methods.include? :fancy_component   #=> true 
Component.instance_methods.include? :user_component    #=> false
UserComponent                                          #=> UserComponent
Component.instance_methods.include? :user_component    #=> true

So the helper method is not available until the component class is somehow accessed.

So how to force eager loading of that class so that inherited is executed?

2

2 Answers

0
votes

Your idea is nice but I'd greatly advise you against implementing something like this, `cos this would usually be done by pre-loading the models before Rails takes notice of them and this could lead to hard to figure loading issues in your app (like classes that should have been re-loaded but were not).

Here's a basic example, of the ways for you to implement this feature would be, at your "root" gem file (if your gem is named "my_component", the "lib/my_component.rb" file) do something like this:

require 'component/base/component'
# require here all other classes necessary for your gem

rb_files = File.join( Rails.root, 'app', 'components', '**', '*.rb' )
Dir.glob( rb_files ).each do |file|
  require file.gsub('.rb')
end

With this you'd load all files under "app/components" but then Rails would not reload these objects, as they were not required by Rails, but by your own gem. If you don't mind not being able to change these files, this might be ok, but then you could have issues in the development environment (or any environment that has "cache_classes" set to false).

0
votes

This is a bug in rails (but wontfix), see also this ticket here:

https://github.com/rails/rails/issues/3364