annotate .svn/pristine/57/5743d81934695d3c76c9de1e610ada18ddc3f631.svn-base @ 1327:287f201c2802 redmine-2.2-integration

Add italic
author Chris Cannam <chris.cannam@soundsoftware.ac.uk>
date Wed, 19 Jun 2013 20:56:22 +0100
parents cbb26bc654de
children
rev   line source
Chris@909 1 # One of the magic features that that engines plugin provides is the ability to
Chris@909 2 # override selected methods in controllers and helpers from your application.
Chris@909 3 # This is achieved by trapping requests to load those files, and then mixing in
Chris@909 4 # code from plugins (in the order the plugins were loaded) before finally loading
Chris@909 5 # any versions from the main +app+ directory.
Chris@909 6 #
Chris@909 7 # The behaviour of this extension is output to the log file for help when
Chris@909 8 # debugging.
Chris@909 9 #
Chris@909 10 # == Example
Chris@909 11 #
Chris@909 12 # A plugin contains the following controller in <tt>plugin/app/controllers/my_controller.rb</tt>:
Chris@909 13 #
Chris@909 14 # class MyController < ApplicationController
Chris@909 15 # def index
Chris@909 16 # @name = "HAL 9000"
Chris@909 17 # end
Chris@909 18 # def list
Chris@909 19 # @robots = Robot.find(:all)
Chris@909 20 # end
Chris@909 21 # end
Chris@909 22 #
Chris@909 23 # In one application that uses this plugin, we decide that the name used in the
Chris@909 24 # index action should be "Robbie", not "HAL 9000". To override this single method,
Chris@909 25 # we create the corresponding controller in our application
Chris@909 26 # (<tt>RAILS_ROOT/app/controllers/my_controller.rb</tt>), and redefine the method:
Chris@909 27 #
Chris@909 28 # class MyController < ApplicationController
Chris@909 29 # def index
Chris@909 30 # @name = "Robbie"
Chris@909 31 # end
Chris@909 32 # end
Chris@909 33 #
Chris@909 34 # The list method remains as it was defined in the plugin controller.
Chris@909 35 #
Chris@909 36 # The same basic principle applies to helpers, and also views and partials (although
Chris@909 37 # view overriding is performed in Engines::RailsExtensions::Templates; see that
Chris@909 38 # module for more information).
Chris@909 39 #
Chris@909 40 # === What about models?
Chris@909 41 #
Chris@909 42 # Unfortunately, it's not possible to provide this kind of magic for models.
Chris@909 43 # The only reason why it's possible for controllers and helpers is because
Chris@909 44 # they can be recognised by their filenames ("whatever_controller", "jazz_helper"),
Chris@909 45 # whereas models appear the same as any other typical Ruby library ("node",
Chris@909 46 # "user", "image", etc.).
Chris@909 47 #
Chris@909 48 # If mixing were allowed in models, it would mean code mixing for *every*
Chris@909 49 # file that was loaded via +require_or_load+, and this could result in
Chris@909 50 # problems where, for example, a Node model might start to include
Chris@909 51 # functionality from another file called "node" somewhere else in the
Chris@909 52 # <tt>$LOAD_PATH</tt>.
Chris@909 53 #
Chris@909 54 # One way to overcome this is to provide model functionality as a module in
Chris@909 55 # a plugin, which developers can then include into their own model
Chris@909 56 # implementations.
Chris@909 57 #
Chris@909 58 # Another option is to provide an abstract model (see the ActiveRecord::Base
Chris@909 59 # documentation) and have developers subclass this model in their own
Chris@909 60 # application if they must.
Chris@909 61 #
Chris@909 62 # ---
Chris@909 63 #
Chris@909 64 # The Engines::RailsExtensions::Dependencies module includes a method to
Chris@909 65 # override Dependencies.require_or_load, which is called to load code needed
Chris@909 66 # by Rails as it encounters constants that aren't defined.
Chris@909 67 #
Chris@909 68 # This method is enhanced with the code-mixing features described above.
Chris@909 69 #
Chris@909 70 module Engines::RailsExtensions::Dependencies
Chris@909 71 def self.included(base) #:nodoc:
Chris@909 72 base.class_eval { alias_method_chain :require_or_load, :engine_additions }
Chris@909 73 end
Chris@909 74
Chris@909 75 # Attempt to load the given file from any plugins, as well as the application.
Chris@909 76 # This performs the 'code mixing' magic, allowing application controllers and
Chris@909 77 # helpers to override single methods from those in plugins.
Chris@909 78 # If the file can be found in any plugins, it will be loaded first from those
Chris@909 79 # locations. Finally, the application version is loaded, using Ruby's behaviour
Chris@909 80 # to replace existing methods with their new definitions.
Chris@909 81 #
Chris@909 82 # If <tt>Engines.disable_code_mixing == true</tt>, the first controller/helper on the
Chris@909 83 # <tt>$LOAD_PATH</tt> will be used (plugins' +app+ directories are always lower on the
Chris@909 84 # <tt>$LOAD_PATH</tt> than the main +app+ directory).
Chris@909 85 #
Chris@909 86 # If <tt>Engines.disable_application_code_loading == true</tt>, controllers will
Chris@909 87 # not be loaded from the main +app+ directory *if* they are present in any
Chris@909 88 # plugins.
Chris@909 89 #
Chris@909 90 # Returns true if the file could be loaded (from anywhere); false otherwise -
Chris@909 91 # mirroring the behaviour of +require_or_load+ from Rails (which mirrors
Chris@909 92 # that of Ruby's own +require+, I believe).
Chris@909 93 def require_or_load_with_engine_additions(file_name, const_path=nil)
Chris@909 94 return require_or_load_without_engine_additions(file_name, const_path) if Engines.disable_code_mixing
Chris@909 95
Chris@909 96 file_loaded = false
Chris@909 97
Chris@909 98 # try and load the plugin code first
Chris@909 99 # can't use model, as there's nothing in the name to indicate that the file is a 'model' file
Chris@909 100 # rather than a library or anything else.
Chris@909 101 Engines.code_mixing_file_types.each do |file_type|
Chris@909 102 # if we recognise this type
Chris@909 103 # (this regexp splits out the module/filename from any instances of app/#{type}, so that
Chris@909 104 # modules are still respected.)
Chris@909 105 if file_name =~ /^(.*app\/#{file_type}s\/)+(.*_#{file_type})(\.rb)?$/
Chris@909 106 base_name = $2
Chris@909 107 # ... go through the plugins from first started to last, so that
Chris@909 108 # code with a high precedence (started later) will override lower precedence
Chris@909 109 # implementations
Chris@909 110 Engines.plugins.each do |plugin|
Chris@909 111 plugin_file_name = File.expand_path(File.join(plugin.directory, 'app', "#{file_type}s", base_name))
Chris@909 112 if File.file?("#{plugin_file_name}.rb")
Chris@909 113 file_loaded = true if require_or_load_without_engine_additions(plugin_file_name, const_path)
Chris@909 114 end
Chris@909 115 end
Chris@909 116
Chris@909 117 # finally, load any application-specific controller classes using the 'proper'
Chris@909 118 # rails load mechanism, EXCEPT when we're testing engines and could load this file
Chris@909 119 # from an engine
Chris@909 120 unless Engines.disable_application_code_loading
Chris@909 121 # Ensure we are only loading from the /app directory at this point
Chris@909 122 app_file_name = File.join(RAILS_ROOT, 'app', "#{file_type}s", "#{base_name}")
Chris@909 123 if File.file?("#{app_file_name}.rb")
Chris@909 124 file_loaded = true if require_or_load_without_engine_additions(app_file_name, const_path)
Chris@909 125 end
Chris@909 126 end
Chris@909 127 end
Chris@909 128 end
Chris@909 129
Chris@909 130 # if we managed to load a file, return true. If not, default to the original method.
Chris@909 131 # Note that this relies on the RHS of a boolean || not to be evaluated if the LHS is true.
Chris@909 132 file_loaded || require_or_load_without_engine_additions(file_name, const_path)
Chris@909 133 end
Chris@909 134 end
Chris@909 135
Chris@909 136 module ActiveSupport::Dependencies #:nodoc:
Chris@909 137 include Engines::RailsExtensions::Dependencies
Chris@909 138 end