Chris@909: # One of the magic features that that engines plugin provides is the ability to Chris@909: # override selected methods in controllers and helpers from your application. Chris@909: # This is achieved by trapping requests to load those files, and then mixing in Chris@909: # code from plugins (in the order the plugins were loaded) before finally loading Chris@909: # any versions from the main +app+ directory. Chris@909: # Chris@909: # The behaviour of this extension is output to the log file for help when Chris@909: # debugging. Chris@909: # Chris@909: # == Example Chris@909: # Chris@909: # A plugin contains the following controller in plugin/app/controllers/my_controller.rb: Chris@909: # Chris@909: # class MyController < ApplicationController Chris@909: # def index Chris@909: # @name = "HAL 9000" Chris@909: # end Chris@909: # def list Chris@909: # @robots = Robot.find(:all) Chris@909: # end Chris@909: # end Chris@909: # Chris@909: # In one application that uses this plugin, we decide that the name used in the Chris@909: # index action should be "Robbie", not "HAL 9000". To override this single method, Chris@909: # we create the corresponding controller in our application Chris@909: # (RAILS_ROOT/app/controllers/my_controller.rb), and redefine the method: Chris@909: # Chris@909: # class MyController < ApplicationController Chris@909: # def index Chris@909: # @name = "Robbie" Chris@909: # end Chris@909: # end Chris@909: # Chris@909: # The list method remains as it was defined in the plugin controller. Chris@909: # Chris@909: # The same basic principle applies to helpers, and also views and partials (although Chris@909: # view overriding is performed in Engines::RailsExtensions::Templates; see that Chris@909: # module for more information). Chris@909: # Chris@909: # === What about models? Chris@909: # Chris@909: # Unfortunately, it's not possible to provide this kind of magic for models. Chris@909: # The only reason why it's possible for controllers and helpers is because Chris@909: # they can be recognised by their filenames ("whatever_controller", "jazz_helper"), Chris@909: # whereas models appear the same as any other typical Ruby library ("node", Chris@909: # "user", "image", etc.). Chris@909: # Chris@909: # If mixing were allowed in models, it would mean code mixing for *every* Chris@909: # file that was loaded via +require_or_load+, and this could result in Chris@909: # problems where, for example, a Node model might start to include Chris@909: # functionality from another file called "node" somewhere else in the Chris@909: # $LOAD_PATH. Chris@909: # Chris@909: # One way to overcome this is to provide model functionality as a module in Chris@909: # a plugin, which developers can then include into their own model Chris@909: # implementations. Chris@909: # Chris@909: # Another option is to provide an abstract model (see the ActiveRecord::Base Chris@909: # documentation) and have developers subclass this model in their own Chris@909: # application if they must. Chris@909: # Chris@909: # --- Chris@909: # Chris@909: # The Engines::RailsExtensions::Dependencies module includes a method to Chris@909: # override Dependencies.require_or_load, which is called to load code needed Chris@909: # by Rails as it encounters constants that aren't defined. Chris@909: # Chris@909: # This method is enhanced with the code-mixing features described above. Chris@909: # Chris@909: module Engines::RailsExtensions::Dependencies Chris@909: def self.included(base) #:nodoc: Chris@909: base.class_eval { alias_method_chain :require_or_load, :engine_additions } Chris@909: end Chris@909: Chris@909: # Attempt to load the given file from any plugins, as well as the application. Chris@909: # This performs the 'code mixing' magic, allowing application controllers and Chris@909: # helpers to override single methods from those in plugins. Chris@909: # If the file can be found in any plugins, it will be loaded first from those Chris@909: # locations. Finally, the application version is loaded, using Ruby's behaviour Chris@909: # to replace existing methods with their new definitions. Chris@909: # Chris@909: # If Engines.disable_code_mixing == true, the first controller/helper on the Chris@909: # $LOAD_PATH will be used (plugins' +app+ directories are always lower on the Chris@909: # $LOAD_PATH than the main +app+ directory). Chris@909: # Chris@909: # If Engines.disable_application_code_loading == true, controllers will Chris@909: # not be loaded from the main +app+ directory *if* they are present in any Chris@909: # plugins. Chris@909: # Chris@909: # Returns true if the file could be loaded (from anywhere); false otherwise - Chris@909: # mirroring the behaviour of +require_or_load+ from Rails (which mirrors Chris@909: # that of Ruby's own +require+, I believe). Chris@909: def require_or_load_with_engine_additions(file_name, const_path=nil) Chris@909: return require_or_load_without_engine_additions(file_name, const_path) if Engines.disable_code_mixing Chris@909: Chris@909: file_loaded = false Chris@909: Chris@909: # try and load the plugin code first Chris@909: # can't use model, as there's nothing in the name to indicate that the file is a 'model' file Chris@909: # rather than a library or anything else. Chris@909: Engines.code_mixing_file_types.each do |file_type| Chris@909: # if we recognise this type Chris@909: # (this regexp splits out the module/filename from any instances of app/#{type}, so that Chris@909: # modules are still respected.) Chris@909: if file_name =~ /^(.*app\/#{file_type}s\/)+(.*_#{file_type})(\.rb)?$/ Chris@909: base_name = $2 Chris@909: # ... go through the plugins from first started to last, so that Chris@909: # code with a high precedence (started later) will override lower precedence Chris@909: # implementations Chris@909: Engines.plugins.each do |plugin| Chris@909: plugin_file_name = File.expand_path(File.join(plugin.directory, 'app', "#{file_type}s", base_name)) Chris@909: if File.file?("#{plugin_file_name}.rb") Chris@909: file_loaded = true if require_or_load_without_engine_additions(plugin_file_name, const_path) Chris@909: end Chris@909: end Chris@909: Chris@909: # finally, load any application-specific controller classes using the 'proper' Chris@909: # rails load mechanism, EXCEPT when we're testing engines and could load this file Chris@909: # from an engine Chris@909: unless Engines.disable_application_code_loading Chris@909: # Ensure we are only loading from the /app directory at this point Chris@909: app_file_name = File.join(RAILS_ROOT, 'app', "#{file_type}s", "#{base_name}") Chris@909: if File.file?("#{app_file_name}.rb") Chris@909: file_loaded = true if require_or_load_without_engine_additions(app_file_name, const_path) Chris@909: end Chris@909: end Chris@909: end Chris@909: end Chris@909: Chris@909: # if we managed to load a file, return true. If not, default to the original method. Chris@909: # Note that this relies on the RHS of a boolean || not to be evaluated if the LHS is true. Chris@909: file_loaded || require_or_load_without_engine_additions(file_name, const_path) Chris@909: end Chris@909: end Chris@909: Chris@909: module ActiveSupport::Dependencies #:nodoc: Chris@909: include Engines::RailsExtensions::Dependencies Chris@909: end