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