15

アプリの宝石としてRailsエンジンを使用しています。エンジンにはPostsControllerいくつかのメソッドがあり、メインアプリのコントローラーロジックを拡張したいと思います。たとえば、いくつかのメソッドを追加します。メインアプリで作成PostsControllerしただけでは、エンジンのコントローラーが読み込まれません。

問題の解決策が提案されています変更に基づいて機能を拡張するRailsエンジンActiveSupport::Dependencies#require_or_load

これを行う唯一の/正しい方法ですか?はいの場合、そのコードはどこに配置しますか?

編集1:

これは、Andrius forRails2.xによって提案されたコードです。

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
4

7 に答える 7

11

設計上、Rails::Engineのクラスはエンジンにスコープされることになっています。そうすれば、メインアプリや他のエンジンにロードされたコード全体を誤って踏みつけて奇妙なバグを引き起こすことはありません。ActiveSupport :: Dependencyにモンキーパッチを適用してエンジンを全面的に混合することは、非常に悪い回避策です。

代わりに、Rails::Railtieを使用してください。それらはすべて同じ機能を持っていますが、エンジンと同じようにスコープされていません。Railsアプリスタック全体(エンジンを含む)にアクセスできます。それはより外科的なアプローチです。

module MyModule

  module SomeModelExtensions
    # Called when this module is included on the given class.
    def self.included(base)
      base.send(:include, InstanceMethods)
      base.extend(ClassMethods)
    end

    module ClassMethods
      def some_new_class_method
        # do stuff...
      end
    end

    module InstanceMethods
      def some_new_instance_method
        # do stuff...
      end
    end

  end

  module SomeControllerExtensions
    def self.included(base)
      base.send(:include, InstanceMethods)
      base.alias_method_chain :new, :my_module
    end

    module InstanceMethods
      # override the 'new' method
      def new_with_my_module
        # do stuff
      end
    end
  end

  class Railtie < ::Rails::Railtie

    # The block you pass to this method will run for every request in
    # development mode, but only once in production.
    config.to_prepare do
      SomeModel.send(:include, MyModule::SomeModelExtensions)
      SomeController.send(:include, MyModule::SomeControllerExtensions)
    end

  end

end

ファイルレイアウトに関しては、railtiesはエンジンとまったく同じように見えます。

さらに読む:RailtiesでRails3を拡張する

それでも混乱する場合は、完全に実装されているこのgitプロジェクトをご覧ください:https ://github.com/jamezilla/bcms_pubcookie

于 2011-10-07T22:07:54.660 に答える
7

アプリケーションのエンジンのコントローラークラスから継承する(そしてルートを新しい子コントローラーに向ける)のはなぜですか?組み込みのDeviseコントローラーを拡張する方法と概念的に似ています。

于 2011-02-24T05:43:37.520 に答える
4

方法1

application.rbこれが私が後にRails3アプリに入れたものですrequire 'rails/all'(それを置くのが悪い場所かどうか教えてください)

require 'active_support/dependencies'
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.to_s + '/app')
      relative_name = file_name.gsub(Rails.root.to_s, '')
      #@engine_paths ||= Rails::Application.railties.engines.collect{|engine| engine.config.root.to_s }
      #EDIT: above line gives deprecation notice in Rails 3 (although it works in Rails 2), causing error in test env.  Change to:
      @engine_paths ||= YourAppName::Application.railties.engines.collect{|engine| engine.config.root.to_s }
      @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

しばらくの間、これはうまくいきませんでした

TypeError in PostsController#index

superclass mismatch for class PostsController

class PostsController < ActionController::Baseしかし、それはタイプミスのあるクラス定義によるものでした。class PostsController < ApplicationController

方法2

すべてのエンジンコントローラーなどでこれを実行したくない場合は、メインアプリで定義する前にエンジンのコントローラーをロードできます。

require PostsEngine::Engine.config.root + 'app' + 'controllers' + 'posts_controller'

class PostsController < ApplicationController
  # extended methods
end
于 2011-02-19T14:07:01.893 に答える
2

上記のAndriusとAndreiのコードに基づいてgemを作成しました。そのコードをコピーする代わりに、mixable_enginesgemが必要です。現在、Rails3でのみ機能します。

https://github.com/asee/mixable_engines

https://rubygems.org/gems/mixable_engines

@Andreiと@Artrius:ライセンスファイルにクレジットがあります。本名またはその他のクレジットが必要な場合はお知らせください。

于 2011-09-01T23:20:45.513 に答える
1

Railsエンジンの拡張機能で提案されているように、パッチアクティブサポートでロード順序を変更したくない場合は、認証にラックミドルウェアを利用できます。すべてのコントローラーアクションの一部として認証が行われる場合、このアプローチにより、コードと時間を大幅に節約できる可能性があります。

于 2011-02-18T18:56:56.150 に答える
1

Rubyのsend()メソッドを使用して、エンジンの作成時にコードをコントローラーに挿入できます...

# lib/cool_engine/engine.rb

module CoolEngine
  class Engine < ::Rails::Engine

    isolate_namespace CoolEngine

    initializer "cool_engine.load_helpers" do |app|
      # You can inject magic into all your controllers...
      ActionController::Base.send :include, CoolEngine::ActionControllerExtensions
      ActionController::Base.send :include, CoolEngine::FooBar

      # ...or add special sauce to models...
      ActiveRecord::Base.send :include, CoolEngine::ActiveRecordExtensions
      ActiveRecord::Base.send :include, CoolEngine::MoreStuff

      # ...even provide a base set of helpers
      ApplicationHelper.send :include, CoolEngine::Helpers
    end
  end
end

この方法により、メインアプリ内でコントローラーの継承を再定義する必要がなくなります。

于 2014-11-20T16:56:05.800 に答える
0

@cowboycodedメソッド2は、Rails 3.2.2 /Ruby1.9と組み合わせて使用require_dependency​​しました。config.reload_plugins

コードは次のとおりです:https ://stackoverflow.com/a/9790497/22237

于 2012-03-20T16:15:13.480 に答える