To check out this repository please hg clone the following URL, or open the URL using EasyMercurial or your preferred Mercurial client.

Statistics Download as Zip
| Branch: | Tag: | Revision:

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