Ответ 1
Почему бы просто не наследовать класс контроллера Engine в вашем приложении (и указать маршруты на новых дочерних контроллерах)? Звучит концептуально аналогично тому, как вы расширяете встроенные контроллеры Devise.
Я использую Rails-движок в качестве драгоценного камня в своем приложении. Двигатель имеет PostsController
с рядом методов, и я хотел бы расширить логику контроллера в своем основном приложении, например. добавить некоторые методы. Если я просто создаю PostsController
в главном приложении, тогда контроллер двигателя не будет загружен.
Существует решение, предложенное под вопросом Расширение функциональности Rails, основанных на изменении ActiveSupport::Dependencies#require_or_load
Это единственный/правильный способ сделать это? Если да, где я могу поместить этот фрагмент кода?
EDIT1:
Это код предложенный Andrius для Rails 2.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
Почему бы просто не наследовать класс контроллера Engine в вашем приложении (и указать маршруты на новых дочерних контроллерах)? Звучит концептуально аналогично тому, как вы расширяете встроенные контроллеры Devise.
По дизайну классы в Rails:: Engine должны быть привязаны к движку. Таким образом, они не вводят странные ошибки, случайно топая весь код, загружаемый в основное приложение или другие двигатели. Monkeypatching ActiveSupport:: Зависимости для смешивания движков по всему миру - очень плохое решение.
Вместо этого используйте 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
Что касается макета файла, ренки выглядят точно так же, как и двигатели.
Дальнейшее чтение: Расширение Rails 3 с рельсами
И если вы все еще смущены, посмотрите на этот проект git, который имеет полную реализацию: https://github.com/jamezilla/bcms_pubcookie
Вот что я вложил в приложение Rails 3 в application.rb
после 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
Если вы не хотите делать это для всех контроллеров двигателей и т.д., вы можете загрузить контроллер двигателя до определения в главном приложении
require PostsEngine::Engine.config.root + 'app' + 'controllers' + 'posts_controller'
class PostsController < ApplicationController
# extended methods
end
Я создал драгоценный камень на основе кода Андрюса и Андрея выше. Вместо того, чтобы копировать этот код, просто требуйте драгоценный камень mixable_engines. Сейчас работает только с рельсами 3.
https://github.com/asee/mixable_engines
https://rubygems.org/gems/mixable_engines
@Andrei и @Artrius: Я зачислил вас в файл лицензии, дайте мне знать, хотите ли вы свое настоящее имя или какой-либо другой кредит.
Если вы не хотите поддерживать активную поддержку патчей, чтобы изменить порядок загрузки, как это предлагается в расширять функциональность движков Rails, вы можете использовать промежуточное программное обеспечение стойки для аутентификации. Если аутентификация выполняется как часть действия каждого контроллера, этот подход может сэкономить вам много кода и времени.
@cowboycoded метод 2 в сочетании с require_dependency
и config.reload_plugins
работал у меня на Rails 3.2.2/Ruby 1.9.
Вы можете использовать метод 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
Этот метод избавляет вас от необходимости переопределять наследование контроллера в основном приложении.