GitHub Sale: sign up for any paid plan this week and pay nothing until January 1, 2009!  [ hide ]

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