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
|