Rails engines extending functionality

前端 未结 6 742
夕颜
夕颜 2020-12-01 06:50

I have an engine which defines some models and controllers. I want to be able to extend functionality of some models/controllers in my application (eg. adding methods) witho

相关标签:
6条回答
  • 2020-12-01 07:20

    Just if anyone else runs into same issue some time in the future, this is the code I wrote that fixed my problem:

    module ActiveSupport::Dependencies
      alias_method :require_or_load_without_multiple, :require_or_load
      def require_or_load(file_name, const_path = nil)
        if file_name.starts_with?(RAILS_ROOT + '/app')
          relative_name = file_name.gsub(RAILS_ROOT, '')
          @engine_paths ||= Rails::Initializer.new(Rails.configuration).plugin_loader.engines.collect {|plugin| plugin.directory }
          @engine_paths.each do |path|
            engine_file = File.join(path, relative_name)
            require_or_load_without_multiple(engine_file, const_path) if File.file?(engine_file)
          end
        end
        require_or_load_without_multiple(file_name, const_path)
      end
    end
    

    This will automatically require files from engine before requiring from application if file path starts with 'app'.

    0 讨论(0)
  • 2020-12-01 07:29
    require MyEngine::Engine.root.join('app', 'models', 'my_engine', 'my_model')
    

    before the model class definition in your application.

    0 讨论(0)
  • 2020-12-01 07:29

    You can change the load order of the engine to avoid the require on each of your models.

    In config/application.rb add this line:

    module MyApp
      class Application
        config.railties_order = [MyEngine::Engine, :main_app, :all]
      end
    end
    

    This will ensure that the models from MyEngine are loaded before MyApp

    0 讨论(0)
  • 2020-12-01 07:31

    That is true. The controller that is found first will be used.

    So to make it work you might have two options:

    • create a local copy of the controller, and modify the method you need
    • if you have control over the plugin, you could create a Module containing the code and include the code in both controllers, only overriding the method in your local controller. According to me, since there is no multiple inheritance, that is the only way.

    Hope this helps.

    0 讨论(0)
  • 2020-12-01 07:32

    You can add these lines to you engine's module file in the lib root directory:

    def self.root
      File.expand_path(File.dirname(File.dirname(__FILE__)))
    end
    
    def self.models_dir
      "#{root}/app/models"
    end
    
    def self.controllers_dir
      "#{root}/app/controllers"
    end
    

    Then you have the ability in the main application (the app making use of the engine) to require the necessary files from the engine. This is nice because you maintain Rails Engines default functionality and also have an easy tool for making use of normal ruby inheritance, without the need for patching.

    EX:

    #ENGINE Model -
    
    class User < ActiveRecord::Base
      def testing_engine
        puts "Engine Method"  
      end
    end
    
    #MAIN APP Model -
    
    require "#{MyEngine.models_dir}/user"
    class User
      def testing_main_app
        puts "Main App Method"  
      end
    end
    
    #From the Main apps console
    
    user = User.new
    
    puts user.testing_engine #=>  "Engine Method"
    
    puts user.tesing_main_app #=> "Main App Method"
    
    0 讨论(0)
  • 2020-12-01 07:40

    I've never used Engines before but can't you define a new controller that inherits from the controller provided by the engine

    0 讨论(0)
提交回复
热议问题