Skip to content
This repository
Browse code

Make many parts of Rails lazy. In order to facilitate this,

add lazy_load_hooks.rb, which allows us to declare code that
should be run at some later time. For instance, this allows
us to defer requiring ActiveRecord::Base at boot time purely
to apply configuration. Instead, we register a hook that should
apply configuration once ActiveRecord::Base is loaded.

With these changes, brings down total boot time of a
new app to 300ms in production and 400ms in dev.

TODO: rename base_hook
  • Loading branch information...
commit 39d6f9e112f2320d8c2006ee3bcc160cfa761d0a 1 parent a424f19
Yehuda Katz authored March 07, 2010

Showing 34 changed files with 299 additions and 312 deletions. Show diff stats Hide diff stats

  1. 1  actionmailer/lib/action_mailer.rb
  2. 2  actionmailer/lib/action_mailer/base.rb
  3. 10  actionmailer/lib/action_mailer/railtie.rb
  4. 2  actionpack/lib/action_controller/base.rb
  5. 14  actionpack/lib/action_controller/railtie.rb
  6. 1  actionpack/lib/action_dispatch/middleware/params_parser.rb
  7. 1  actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
  8. 2  actionpack/lib/action_dispatch/middleware/stack.rb
  9. 4  actionpack/lib/action_dispatch/routing/mapper.rb
  10. 2  actionpack/lib/action_view.rb
  11. 2  actionpack/lib/action_view/base.rb
  12. 8  actionpack/lib/action_view/helpers/active_model_helper.rb
  13. 8  actionpack/lib/action_view/helpers/form_helper.rb
  14. 4  actionpack/lib/action_view/railtie.rb
  15. 1  actionpack/lib/action_view/test_case.rb
  16. 1  actionpack/test/abstract_unit.rb
  17. 13  activerecord/lib/active_record.rb
  18. 170  activerecord/lib/active_record/base.rb
  19. 165  activerecord/lib/active_record/errors.rb
  20. 42  activerecord/lib/active_record/railtie.rb
  21. 1  activerecord/test/cases/helper.rb
  22. 3  activesupport/lib/active_support.rb
  23. 1  activesupport/lib/active_support/all.rb
  24. 1  activesupport/lib/active_support/core_ext/array/conversions.rb
  25. 92  activesupport/lib/active_support/core_ext/string/interpolation.rb
  26. 5  activesupport/lib/active_support/dependencies/autoload.rb
  27. 3  activesupport/lib/active_support/i18n.rb
  28. 25  activesupport/lib/active_support/lazy_load_hooks.rb
  29. 8  activesupport/lib/active_support/railtie.rb
  30. 7  activesupport/lib/active_support/whiny_nil.rb
  31. 2  activesupport/test/whiny_nil_test.rb
  32. 2  railties/lib/rails/application/routes_reloader.rb
  33. 4  railties/lib/rails/console/helpers.rb
  34. 4  railties/lib/rails/engine.rb
1  actionmailer/lib/action_mailer.rb
@@ -34,6 +34,7 @@
34 34
 require 'active_support/core_ext/module/attr_internal'
35 35
 require 'active_support/core_ext/module/delegation'
36 36
 require 'active_support/core_ext/string/inflections'
  37
+require 'active_support/lazy_load_hooks'
37 38
 
38 39
 module ActionMailer
39 40
   extend ::ActiveSupport::Autoload
2  actionmailer/lib/action_mailer/base.rb
@@ -291,6 +291,8 @@ class Base < AbstractController::Base
291 291
       :parts_order  => [ "text/plain", "text/enriched", "text/html" ]
292 292
     }.freeze
293 293
 
  294
+    ActionMailer.run_base_hooks(self)
  295
+
294 296
     class << self
295 297
 
296 298
       def mailer_name
10  actionmailer/lib/action_mailer/railtie.rb
@@ -6,19 +6,21 @@ class Railtie < Rails::Railtie
6 6
     railtie_name :action_mailer
7 7
 
8 8
     initializer "action_mailer.url_for", :before => :load_environment_config do |app|
9  
-      ActionMailer::Base.send(:include, app.routes.url_helpers)
  9
+      ActionMailer.base_hook { include app.routes.url_helpers }
10 10
     end
11 11
 
12 12
     require "action_mailer/railties/log_subscriber"
13 13
     log_subscriber ActionMailer::Railties::LogSubscriber.new
14 14
 
15 15
     initializer "action_mailer.logger" do
16  
-      ActionMailer::Base.logger ||= Rails.logger
  16
+      ActionMailer.base_hook { self.logger ||= Rails.logger }
17 17
     end
18 18
 
19 19
     initializer "action_mailer.set_configs" do |app|
20  
-      app.config.action_mailer.each do |k,v|
21  
-        ActionMailer::Base.send "#{k}=", v
  20
+      ActionMailer.base_hook do
  21
+        app.config.action_mailer.each do |k,v|
  22
+          send "#{k}=", v
  23
+        end
22 24
       end
23 25
     end
24 26
   end
2  actionpack/lib/action_controller/base.rb
@@ -58,6 +58,8 @@ def self.filter_parameter_logging(*args, &block)
58 58
       filter
59 59
     end
60 60
 
  61
+    ActionController.run_base_hooks(self)
  62
+
61 63
   end
62 64
 end
63 65
 
14  actionpack/lib/action_controller/railtie.rb
@@ -40,7 +40,7 @@ class Railtie < Rails::Railtie
40 40
     log_subscriber ActionController::Railties::LogSubscriber.new
41 41
 
42 42
     initializer "action_controller.logger" do
43  
-      ActionController::Base.logger ||= Rails.logger
  43
+      ActionController.base_hook { self.logger ||= Rails.logger }
44 44
     end
45 45
 
46 46
     initializer "action_controller.set_configs" do |app|
@@ -51,19 +51,23 @@ class Railtie < Rails::Railtie
51 51
       ac.stylesheets_dir = paths.public.stylesheets.to_a.first
52 52
       ac.secret = app.config.cookie_secret
53 53
 
54  
-      ActionController::Base.config.replace(ac)
  54
+      ActionController.base_hook { self.config.replace(ac) }
55 55
     end
56 56
 
57 57
     initializer "action_controller.initialize_framework_caches" do
58  
-      ActionController::Base.cache_store ||= RAILS_CACHE
  58
+      ActionController.base_hook { self.cache_store ||= RAILS_CACHE }
59 59
     end
60 60
 
61 61
     initializer "action_controller.set_helpers_path" do |app|
62  
-      ActionController::Base.helpers_path = app.config.paths.app.helpers.to_a
  62
+      ActionController.base_hook do
  63
+        self.helpers_path = app.config.paths.app.helpers.to_a
  64
+      end
63 65
     end
64 66
 
65 67
     initializer "action_controller.url_helpers" do |app|
66  
-      ActionController::Base.extend ::ActionController::Railtie::UrlHelpers.with(app.routes)
  68
+      ActionController.base_hook do
  69
+        extend ::ActionController::Railtie::UrlHelpers.with(app.routes)
  70
+      end
67 71
 
68 72
       message = "ActionController::Routing::Routes is deprecated. " \
69 73
                 "Instead, use Rails.application.routes"
1  actionpack/lib/action_dispatch/middleware/params_parser.rb
... ...
@@ -1,4 +1,3 @@
1  
-require 'active_support/json'
2 1
 require 'action_dispatch/http/request'
3 2
 
4 3
 module ActionDispatch
1  actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
... ...
@@ -1,5 +1,4 @@
2  actionpack/lib/action_dispatch/middleware/stack.rb
@@ -58,7 +58,7 @@ def ==(middleware)
58 58
           if lazy_compare?(@klass) && lazy_compare?(middleware)
59 59
             normalize(@klass) == normalize(middleware)
60 60
           else
61  
-            klass == ActiveSupport::Inflector.constantize(middleware.to_s)
  61
+            klass.name == middleware.to_s
62 62
           end
63 63
         end
64 64
       end
4  actionpack/lib/action_dispatch/routing/mapper.rb
... ...
@@ -1,3 +1,5 @@
  1
+require "active_support/core_ext/hash/except"
  2
+
1 3
 module ActionDispatch
2 4
   module Routing
3 5
     class Mapper
@@ -85,7 +87,7 @@ def conditions
85 87
           end
86 88
 
87 89
           def requirements
88  
-            @requirements ||= returning(@options[:constraints] || {}) do |requirements|
  90
+            @requirements ||= (@options[:constraints] || {}).tap do |requirements|
89 91
               requirements.reverse_merge!(@scope[:constraints]) if @scope[:constraints]
90 92
               @options.each { |k, v| requirements[k] = v if v.is_a?(Regexp) }
91 93
             end
2  actionpack/lib/action_view.rb
@@ -41,6 +41,7 @@ module ActionView
41 41
       autoload :Rendering
42 42
     end
43 43
 
  44
+    autoload :Base
44 45
     autoload :MissingTemplate,   'action_view/base'
45 46
     autoload :Resolver,          'action_view/template/resolver'
46 47
     autoload :PathResolver,      'action_view/template/resolver'
@@ -56,6 +57,5 @@ module ActionView
56 57
 end
57 58
 
58 59
 require 'active_support/core_ext/string/output_safety'
59  
-require 'action_view/base'
60 60
 
61 61
 I18n.load_path << "#{File.dirname(__FILE__)}/action_view/locale/en.yml"
2  actionpack/lib/action_view/base.rb
@@ -177,6 +177,8 @@ module Subclasses
177 177
 
178 178
     extend ActiveSupport::Memoizable
179 179
 
  180
+    ActionView.run_base_hooks(self)
  181
+
180 182
     attr_accessor :base_path, :assigns, :template_extension
181 183
     attr_internal :captures
182 184
 
8  actionpack/lib/action_view/helpers/active_model_helper.rb
@@ -5,9 +5,11 @@
5 5
 require 'active_support/core_ext/kernel/reporting'
6 6
 
7 7
 module ActionView
8  
-  class Base
9  
-    @@field_error_proc = Proc.new{ |html_tag, instance| "<div class=\"fieldWithErrors\">#{html_tag}</div>".html_safe }
10  
-    cattr_accessor :field_error_proc
  8
+  ActionView.base_hook do
  9
+    class ActionView::Base
  10
+      @@field_error_proc = Proc.new{ |html_tag, instance| "<div class=\"fieldWithErrors\">#{html_tag}</div>".html_safe }
  11
+      cattr_accessor :field_error_proc
  12
+    end
11 13
   end
12 14
 
13 15
   module Helpers
8  actionpack/lib/action_view/helpers/form_helper.rb
@@ -1211,8 +1211,10 @@ def nested_child_index(name)
1211 1211
     end
1212 1212
   end
1213 1213
 
1214  
-  class Base
1215  
-    cattr_accessor :default_form_builder
1216  
-    @@default_form_builder = ::ActionView::Helpers::FormBuilder
  1214
+  ActionView.base_hook do
  1215
+    class ActionView::Base
  1216
+      cattr_accessor :default_form_builder
  1217
+      @@default_form_builder = ::ActionView::Helpers::FormBuilder
  1218
+    end
1217 1219
   end
1218 1220
 end
4  actionpack/lib/action_view/railtie.rb
@@ -10,7 +10,9 @@ class Railtie < Rails::Railtie
10 10
 
11 11
     initializer "action_view.cache_asset_timestamps" do |app|
12 12
       unless app.config.cache_classes
13  
-        ActionView::Helpers::AssetTagHelper.cache_asset_timestamps = false
  13
+        ActionView.base_hook do
  14
+          ActionView::Helpers::AssetTagHelper.cache_asset_timestamps = false
  15
+        end
14 16
       end
15 17
     end
16 18
   end
1  actionpack/lib/action_view/test_case.rb
... ...
@@ -1,4 +1,5 @@
1 1
 require 'action_controller/test_case'
  2
+require 'action_view'
2 3
 
3 4
 module ActionView
4 5
   class Base
1  actionpack/test/abstract_unit.rb
@@ -16,7 +16,6 @@
16 16
 require 'abstract_controller'
17 17
 require 'action_controller'
18 18
 require 'action_view'
19  
-require 'action_view/base'
20 19
 require 'action_dispatch'
21 20
 require 'fixture_template'
22 21
 require 'active_support/dependencies'
13  activerecord/lib/active_record.rb
@@ -30,7 +30,6 @@
30 30
 
31 31
 require 'active_support'
32 32
 require 'active_model'
33  
-require 'arel'
34 33
 
35 34
 module ActiveRecord
36 35
   extend ActiveSupport::Autoload
@@ -38,8 +37,8 @@ module ActiveRecord
38 37
   eager_autoload do
39 38
     autoload :VERSION
40 39
 
41  
-    autoload :ActiveRecordError, 'active_record/base'
42  
-    autoload :ConnectionNotEstablished, 'active_record/base'
  40
+    autoload :ActiveRecordError, 'active_record/errors'
  41
+    autoload :ConnectionNotEstablished, 'active_record/errors'
43 42
 
44 43
     autoload :Aggregations
45 44
     autoload :AssociationPreload
@@ -106,12 +105,16 @@ module ConnectionAdapters
106 105
 
107 106
     eager_autoload do
108 107
       autoload :AbstractAdapter
  108
+      autoload :ConnectionManagement, "active_record/connection_adapters/abstract/connection_pool"
109 109
     end
110 110
   end
111 111
 
112 112
   autoload :TestCase
113 113
   autoload :TestFixtures, 'active_record/fixtures'
  114
+
  115
+  base_hook do
  116
+    Arel::Table.engine = Arel::Sql::Engine.new(self)
  117
+  end
114 118
 end
115 119
 
116  
-Arel::Table.engine = Arel::Sql::Engine.new(ActiveRecord::Base)
117  
-I18n.load_path << File.dirname(__FILE__) + '/active_record/locale/en.yml'
  120
+I18n.load_path << File.dirname(__FILE__) + '/active_record/locale/en.yml'
170  activerecord/lib/active_record/base.rb
@@ -13,172 +13,10 @@
13 13
 require 'active_support/core_ext/string/behavior'
14 14
 require 'active_support/core_ext/object/singleton_class'
15 15
 require 'active_support/core_ext/module/delegation'
  16
+require 'arel'
  17
+require 'active_record/errors'
16 18
 
17 19
 module ActiveRecord #:nodoc:
18  
-  # Generic Active Record exception class.
19  
-  class ActiveRecordError < StandardError
20  
-  end
21  
-
22  
-  # Raised when the single-table inheritance mechanism fails to locate the subclass
23  
-  # (for example due to improper usage of column that +inheritance_column+ points to).
24  
-  class SubclassNotFound < ActiveRecordError #:nodoc:
25  
-  end
26  
-
27  
-  # Raised when an object assigned to an association has an incorrect type.
28  
-  #
29  
-  #   class Ticket < ActiveRecord::Base
30  
-  #     has_many :patches
31  
-  #   end
32  
-  #
33  
-  #   class Patch < ActiveRecord::Base
34  
-  #     belongs_to :ticket
35  
-  #   end
36  
-  #
37  
-  #   # Comments are not patches, this assignment raises AssociationTypeMismatch.
38  
-  #   @ticket.patches << Comment.new(:content => "Please attach tests to your patch.")
39  
-  class AssociationTypeMismatch < ActiveRecordError
40  
-  end
41  
-
42  
-  # Raised when unserialized object's type mismatches one specified for serializable field.
43  
-  class SerializationTypeMismatch < ActiveRecordError
44  
-  end
45  
-
46  
-  # Raised when adapter not specified on connection (or configuration file <tt>config/database.yml</tt> misses adapter field).
47  
-  class AdapterNotSpecified < ActiveRecordError
48  
-  end
49  
-
50  
-  # Raised when Active Record cannot find database adapter specified in <tt>config/database.yml</tt> or programmatically.
51  
-  class AdapterNotFound < ActiveRecordError
52  
-  end
53  
-
54  
-  # Raised when connection to the database could not been established (for example when <tt>connection=</tt> is given a nil object).
55  
-  class ConnectionNotEstablished < ActiveRecordError
56  
-  end
57  
-
58  
-  # Raised when Active Record cannot find record by given id or set of ids.
59  
-  class RecordNotFound < ActiveRecordError
60  
-  end
61  
-
62  
-  # Raised by ActiveRecord::Base.save! and ActiveRecord::Base.create! methods when record cannot be
63  
-  # saved because record is invalid.
64  
-  class RecordNotSaved < ActiveRecordError
65  
-  end
66  
-
67  
-  # Raised when SQL statement cannot be executed by the database (for example, it's often the case for MySQL when Ruby driver used is too old).
68  
-  class StatementInvalid < ActiveRecordError
69  
-  end
70  
-
71  
-  # Raised when SQL statement is invalid and the application gets a blank result.
72  
-  class ThrowResult < ActiveRecordError
73  
-  end
74  
-
75  
-  # Parent class for all specific exceptions which wrap database driver exceptions
76  
-  # provides access to the original exception also.
77  
-  class WrappedDatabaseException < StatementInvalid
78  
-    attr_reader :original_exception
79  
-
80  
-    def initialize(message, original_exception)
81  
-      super(message)
82  
-      @original_exception = original_exception
83  
-    end
84  
-  end
85  
-
86  
-  # Raised when a record cannot be inserted because it would violate a uniqueness constraint.
87  
-  class RecordNotUnique < WrappedDatabaseException
88  
-  end
89  
-
90  
-  # Raised when a record cannot be inserted or updated because it references a non-existent record.
91  
-  class InvalidForeignKey < WrappedDatabaseException
92  
-  end
93  
-
94  
-  # Raised when number of bind variables in statement given to <tt>:condition</tt> key (for example, when using +find+ method)
95  
-  # does not match number of expected variables.
96  
-  #
97  
-  # For example, in
98  
-  #
99  
-  #   Location.find :all, :conditions => ["lat = ? AND lng = ?", 53.7362]
100  
-  #
101  
-  # two placeholders are given but only one variable to fill them.
102  
-  class PreparedStatementInvalid < ActiveRecordError
103  
-  end
104  
-
105  
-  # Raised on attempt to save stale record. Record is stale when it's being saved in another query after
106  
-  # instantiation, for example, when two users edit the same wiki page and one starts editing and saves
107  
-  # the page before the other.
108  
-  #
109  
-  # Read more about optimistic locking in ActiveRecord::Locking module RDoc.
110  
-  class StaleObjectError < ActiveRecordError
111  
-  end
112  
-
113  
-  # Raised when association is being configured improperly or
114  
-  # user tries to use offset and limit together with has_many or has_and_belongs_to_many associations.
115  
-  class ConfigurationError < ActiveRecordError
116  
-  end
117  
-
118  
-  # Raised on attempt to update record that is instantiated as read only.
119  
-  class ReadOnlyRecord < ActiveRecordError
120  
-  end
121  
-
122  
-  # ActiveRecord::Transactions::ClassMethods.transaction uses this exception
123  
-  # to distinguish a deliberate rollback from other exceptional situations.
124  
-  # Normally, raising an exception will cause the +transaction+ method to rollback
125  
-  # the database transaction *and* pass on the exception. But if you raise an
126  
-  # ActiveRecord::Rollback exception, then the database transaction will be rolled back,
127  
-  # without passing on the exception.
128  
-  #
129  
-  # For example, you could do this in your controller to rollback a transaction:
130  
-  #
131  
-  #   class BooksController < ActionController::Base
132  
-  #     def create
133  
-  #       Book.transaction do
134  
-  #         book = Book.new(params[:book])
135  
-  #         book.save!
136  
-  #         if today_is_friday?
137  
-  #           # The system must fail on Friday so that our support department
138  
-  #           # won't be out of job. We silently rollback this transaction
139  
-  #           # without telling the user.
140  
-  #           raise ActiveRecord::Rollback, "Call tech support!"
141  
-  #         end
142  
-  #       end
143  
-  #       # ActiveRecord::Rollback is the only exception that won't be passed on
144  
-  #       # by ActiveRecord::Base.transaction, so this line will still be reached
145  
-  #       # even on Friday.
146  
-  #       redirect_to root_url
147  
-  #     end
148  
-  #   end
149  
-  class Rollback < ActiveRecordError
150  
-  end
151  
-
152  
-  # Raised when attribute has a name reserved by Active Record (when attribute has name of one of Active Record instance methods).
153  
-  class DangerousAttributeError < ActiveRecordError
154  
-  end
155  
-
156  
-  # Raised when unknown attributes are supplied via mass assignment.
157  
-  class UnknownAttributeError < NoMethodError
158  
-  end
159  
-
160  
-  # Raised when an error occurred while doing a mass assignment to an attribute through the
161  
-  # <tt>attributes=</tt> method. The exception has an +attribute+ property that is the name of the
162  
-  # offending attribute.
163  
-  class AttributeAssignmentError < ActiveRecordError
164  
-    attr_reader :exception, :attribute
165  
-    def initialize(message, exception, attribute)
166  
-      @exception = exception
167  
-      @attribute = attribute
168  
-      @message = message
169  
-    end
170  
-  end
171  
-
172  
-  # Raised when there are multiple errors while doing a mass assignment through the +attributes+
173  
-  # method. The exception has an +errors+ property that contains an array of AttributeAssignmentError
174  
-  # objects, each corresponding to the error while assigning to an attribute.
175  
-  class MultiparameterAssignmentErrors < ActiveRecordError
176  
-    attr_reader :errors
177  
-    def initialize(errors)
178  
-      @errors = errors
179  
-    end
180  
-  end
181  
-
182 20
   # Active Record objects don't specify their attributes directly, but rather infer them from the table definition with
183 21
   # which they're linked. Adding, removing, and changing attributes and their type is done directly in the database. Any change
184 22
   # is instantly reflected in the Active Record objects. The mapping that binds a given Active Record class to a certain
@@ -551,7 +389,7 @@ def self.reset_subclasses #:nodoc:
551 389
     class << self # Class methods
552 390
       def colorize_logging(*args)
553 391
         ActiveSupport::Deprecation.warn "ActiveRecord::Base.colorize_logging and " <<
554  
-          "config.active_record.colorize_logging are deprecated. Please use " << 
  392
+          "config.active_record.colorize_logging are deprecated. Please use " <<
555 393
           "Rails::LogSubscriber.colorize_logging or config.colorize_logging instead", caller
556 394
       end
557 395
       alias :colorize_logging= :colorize_logging
@@ -2401,8 +2239,10 @@ def object_from_yaml(string)
2401 2239
 
2402 2240
     include Aggregations, Transactions, Reflection, Serialization
2403 2241
 
  2242
+    NilClass.add_whiner(self) if NilClass.respond_to?(:add_whiner)
2404 2243
   end
2405 2244
 end
2406 2245
 
2407 2246
 # TODO: Remove this and make it work with LAZY flag
2408 2247
 require 'active_record/connection_adapters/abstract_adapter'
  2248
+ActiveRecord.run_base_hooks(ActiveRecord::Base)
165  activerecord/lib/active_record/errors.rb
... ...
@@ -0,0 +1,165 @@
  1
+module ActiveRecord
  2
+  # Generic Active Record exception class.
  3
+  class ActiveRecordError < StandardError
  4
+  end
  5
+
  6
+  # Raised when the single-table inheritance mechanism fails to locate the subclass
  7
+  # (for example due to improper usage of column that +inheritance_column+ points to).
  8
+  class SubclassNotFound < ActiveRecordError #:nodoc:
  9
+  end
  10
+
  11
+  # Raised when an object assigned to an association has an incorrect type.
  12
+  #
  13
+  #   class Ticket < ActiveRecord::Base
  14
+  #     has_many :patches
  15
+  #   end
  16
+  #
  17
+  #   class Patch < ActiveRecord::Base
  18
+  #     belongs_to :ticket
  19
+  #   end
  20
+  #
  21
+  #   # Comments are not patches, this assignment raises AssociationTypeMismatch.
  22
+  #   @ticket.patches << Comment.new(:content => "Please attach tests to your patch.")
  23
+  class AssociationTypeMismatch < ActiveRecordError
  24
+  end
  25
+
  26
+  # Raised when unserialized object's type mismatches one specified for serializable field.
  27
+  class SerializationTypeMismatch < ActiveRecordError
  28
+  end
  29
+
  30
+  # Raised when adapter not specified on connection (or configuration file <tt>config/database.yml</tt> misses adapter field).
  31
+  class AdapterNotSpecified < ActiveRecordError
  32
+  end
  33
+
  34
+  # Raised when Active Record cannot find database adapter specified in <tt>config/database.yml</tt> or programmatically.
  35
+  class AdapterNotFound < ActiveRecordError
  36
+  end
  37
+
  38
+  # Raised when connection to the database could not been established (for example when <tt>connection=</tt> is given a nil object).
  39
+  class ConnectionNotEstablished < ActiveRecordError
  40
+  end
  41
+
  42
+  # Raised when Active Record cannot find record by given id or set of ids.
  43
+  class RecordNotFound < ActiveRecordError
  44
+  end
  45
+
  46
+  # Raised by ActiveRecord::Base.save! and ActiveRecord::Base.create! methods when record cannot be
  47
+  # saved because record is invalid.
  48
+  class RecordNotSaved < ActiveRecordError
  49
+  end
  50
+
  51
+  # Raised when SQL statement cannot be executed by the database (for example, it's often the case for MySQL when Ruby driver used is too old).
  52
+  class StatementInvalid < ActiveRecordError
  53
+  end
  54
+
  55
+  # Raised when SQL statement is invalid and the application gets a blank result.
  56
+  class ThrowResult < ActiveRecordError
  57
+  end
  58
+
  59
+  # Parent class for all specific exceptions which wrap database driver exceptions
  60
+  # provides access to the original exception also.
  61
+  class WrappedDatabaseException < StatementInvalid
  62
+    attr_reader :original_exception
  63
+
  64
+    def initialize(message, original_exception)
  65
+      super(message)
  66
+      @original_exception = original_exception
  67
+    end
  68
+  end
  69
+
  70
+  # Raised when a record cannot be inserted because it would violate a uniqueness constraint.
  71
+  class RecordNotUnique < WrappedDatabaseException
  72
+  end
  73
+
  74
+  # Raised when a record cannot be inserted or updated because it references a non-existent record.
  75
+  class InvalidForeignKey < WrappedDatabaseException
  76
+  end
  77
+
  78
+  # Raised when number of bind variables in statement given to <tt>:condition</tt> key (for example, when using +find+ method)
  79
+  # does not match number of expected variables.
  80
+  #
  81
+  # For example, in
  82
+  #
  83
+  #   Location.find :all, :conditions => ["lat = ? AND lng = ?", 53.7362]
  84
+  #
  85
+  # two placeholders are given but only one variable to fill them.
  86
+  class PreparedStatementInvalid < ActiveRecordError
  87
+  end
  88
+
  89
+  # Raised on attempt to save stale record. Record is stale when it's being saved in another query after
  90
+  # instantiation, for example, when two users edit the same wiki page and one starts editing and saves
  91
+  # the page before the other.
  92
+  #
  93
+  # Read more about optimistic locking in ActiveRecord::Locking module RDoc.
  94
+  class StaleObjectError < ActiveRecordError
  95
+  end
  96
+
  97
+  # Raised when association is being configured improperly or
  98
+  # user tries to use offset and limit together with has_many or has_and_belongs_to_many associations.
  99
+  class ConfigurationError < ActiveRecordError
  100
+  end
  101
+
  102
+  # Raised on attempt to update record that is instantiated as read only.
  103
+  class ReadOnlyRecord < ActiveRecordError
  104
+  end
  105
+
  106
+  # ActiveRecord::Transactions::ClassMethods.transaction uses this exception
  107
+  # to distinguish a deliberate rollback from other exceptional situations.
  108
+  # Normally, raising an exception will cause the +transaction+ method to rollback
  109
+  # the database transaction *and* pass on the exception. But if you raise an
  110
+  # ActiveRecord::Rollback exception, then the database transaction will be rolled back,
  111
+  # without passing on the exception.
  112
+  #
  113
+  # For example, you could do this in your controller to rollback a transaction:
  114
+  #
  115
+  #   class BooksController < ActionController::Base
  116
+  #     def create
  117
+  #       Book.transaction do
  118
+  #         book = Book.new(params[:book])
  119
+  #         book.save!
  120
+  #         if today_is_friday?
  121
+  #           # The system must fail on Friday so that our support department
  122
+  #           # won't be out of job. We silently rollback this transaction
  123
+  #           # without telling the user.
  124
+  #           raise ActiveRecord::Rollback, "Call tech support!"
  125
+  #         end
  126
+  #       end
  127
+  #       # ActiveRecord::Rollback is the only exception that won't be passed on
  128
+  #       # by ActiveRecord::Base.transaction, so this line will still be reached
  129
+  #       # even on Friday.
  130
+  #       redirect_to root_url
  131
+  #     end
  132
+  #   end
  133
+  class Rollback < ActiveRecordError
  134
+  end
  135
+
  136
+  # Raised when attribute has a name reserved by Active Record (when attribute has name of one of Active Record instance methods).
  137
+  class DangerousAttributeError < ActiveRecordError
  138
+  end
  139
+
  140
+  # Raised when unknown attributes are supplied via mass assignment.
  141
+  class UnknownAttributeError < NoMethodError
  142
+  end
  143
+
  144
+  # Raised when an error occurred while doing a mass assignment to an attribute through the
  145
+  # <tt>attributes=</tt> method. The exception has an +attribute+ property that is the name of the
  146
+  # offending attribute.
  147
+  class AttributeAssignmentError < ActiveRecordError
  148
+    attr_reader :exception, :attribute
  149
+    def initialize(message, exception, attribute)
  150
+      @exception = exception
  151
+      @attribute = attribute
  152
+      @message = message
  153
+    end
  154
+  end
  155
+
  156
+  # Raised when there are multiple errors while doing a mass assignment through the +attributes+
  157
+  # method. The exception has an +errors+ property that contains an array of AttributeAssignmentError
  158
+  # objects, each corresponding to the error while assigning to an attribute.
  159
+  class MultiparameterAssignmentErrors < ActiveRecordError
  160
+    attr_reader :errors
  161
+    def initialize(errors)
  162
+      @errors = errors
  163
+    end
  164
+  end
  165
+end
42  activerecord/lib/active_record/railtie.rb
@@ -24,31 +24,39 @@ class Railtie < Rails::Railtie
24 24
     log_subscriber ActiveRecord::Railties::LogSubscriber.new
25 25
 
26 26
     initializer "active_record.initialize_timezone" do
27  
-      ActiveRecord::Base.time_zone_aware_attributes = true
28  
-      ActiveRecord::Base.default_timezone = :utc
  27
+      ActiveRecord.base_hook do
  28
+        self.time_zone_aware_attributes = true
  29
+        self.default_timezone = :utc
  30
+      end
29 31
     end
30 32
 
31 33
     initializer "active_record.logger" do
32  
-      ActiveRecord::Base.logger ||= ::Rails.logger
  34
+      ActiveRecord.base_hook { self.logger ||= ::Rails.logger }
33 35
     end
34 36
 
35 37
     initializer "active_record.set_configs" do |app|
36  
-      app.config.active_record.each do |k,v|
37  
-        ActiveRecord::Base.send "#{k}=", v
  38
+      ActiveRecord.base_hook do
  39
+        app.config.active_record.each do |k,v|
  40
+          send "#{k}=", v
  41
+        end
38 42
       end
39 43
     end
40 44
 
41 45
     # This sets the database configuration from Configuration#database_configuration
42 46
     # and then establishes the connection.
43 47
     initializer "active_record.initialize_database" do |app|
44  
-      ActiveRecord::Base.configurations = app.config.database_configuration
45  
-      ActiveRecord::Base.establish_connection
  48
+      ActiveRecord.base_hook do
  49
+        self.configurations = app.config.database_configuration
  50
+        establish_connection
  51
+      end
46 52
     end
47 53
 
48 54
     # Expose database runtime to controller for logging.
49 55
     initializer "active_record.log_runtime" do |app|
50 56
       require "active_record/railties/controller_runtime"
51  
-      ActionController::Base.send :include, ActiveRecord::Railties::ControllerRuntime
  57
+      ActionController.base_hook do
  58
+        include ActiveRecord::Railties::ControllerRuntime
  59
+      end
52 60
     end
53 61
 
54 62
     # Setup database middleware after initializers have run
@@ -64,18 +72,22 @@ class Railtie < Rails::Railtie
64 72
     end
65 73
 
66 74
     initializer "active_record.load_observers" do
67  
-      ActiveRecord::Base.instantiate_observers
  75
+      ActiveRecord.base_hook { instantiate_observers }
68 76
 
69  
-      ActionDispatch::Callbacks.to_prepare(:activerecord_instantiate_observers) do
70  
-        ActiveRecord::Base.instantiate_observers
  77
+      ActiveRecord.base_hook do
  78
+        ActionDispatch::Callbacks.to_prepare(:activerecord_instantiate_observers) do
  79
+          ActiveRecord::Base.instantiate_observers
  80
+        end
71 81
       end
72 82
     end
73 83
 
74 84
     initializer "active_record.set_dispatch_hooks", :before => :set_clear_dependencies_hook do |app|
75  
-      unless app.config.cache_classes
76  
-        ActionDispatch::Callbacks.after do
77  
-          ActiveRecord::Base.reset_subclasses
78  
-          ActiveRecord::Base.clear_reloadable_connections!
  85
+      ActiveRecord.base_hook do
  86
+        unless app.config.cache_classes
  87
+          ActionDispatch::Callbacks.after do
  88
+            ActiveRecord::Base.reset_subclasses
  89
+            ActiveRecord::Base.clear_reloadable_connections!
  90
+          end
79 91
         end
80 92
       end
81 93
     end
1  activerecord/test/cases/helper.rb
@@ -9,6 +9,7 @@
9 9
 require 'stringio'
10 10
 
11 11
 require 'active_record'
  12
+require 'active_support/dependencies'
12 13
 require 'connection'
13 14
 
14 15
 begin
3  activesupport/lib/active_support.rb
@@ -53,6 +53,7 @@ module ActiveSupport
53 53
     autoload :Deprecation
54 54
     autoload :Gzip
55 55
     autoload :Inflector
  56
+    autoload :JSON
56 57
     autoload :Memoizable
57 58
     autoload :MessageEncryptor
58 59
     autoload :MessageVerifier
@@ -70,3 +71,5 @@ module ActiveSupport
70 71
   autoload :SafeBuffer, "active_support/core_ext/string/output_safety"
71 72
   autoload :TestCase
72 73
 end
  74
+
  75
+autoload :I18n, "active_support/i18n"
1  activesupport/lib/active_support/all.rb
... ...
@@ -1,4 +1,3 @@
1 1
 require 'active_support'
2  
-require 'active_support/i18n'
3 2
 require 'active_support/time'
4 3
 require 'active_support/core_ext'
1  activesupport/lib/active_support/core_ext/array/conversions.rb
... ...
@@ -1,7 +1,6 @@
1 1
 require 'active_support/core_ext/hash/keys'
2 2
 require 'active_support/core_ext/hash/reverse_merge'
3 3
 require 'active_support/inflector'
4  
-require 'active_support/i18n'
5 4
 
6 5
 class Array
7 6
   # Converts the array to a comma-separated sentence where the last element is joined by the connector word. Options:
92  activesupport/lib/active_support/core_ext/string/interpolation.rb
... ...
@@ -1,91 +1 @@
1  
-=begin
2  
-  heavily based on Masao Mutoh's gettext String interpolation extension
3  
-  http://github.com/mutoh/gettext/blob/f6566738b981fe0952548c421042ad1e0cdfb31e/lib/gettext/core_ext/string.rb
4  
-  Copyright (C) 2005-2010 Masao Mutoh
5  
-  You may redistribute it and/or modify it under the same license terms as Ruby.
6  
-=end
7  
-
8  
-if RUBY_VERSION < '1.9' && !"".respond_to?(:interpolate_without_ruby_19_syntax)
9  
-
10  
-  # KeyError is raised by String#% when the string contains a named placeholder
11  
-  # that is not contained in the given arguments hash. Ruby 1.9 includes and
12  
-  # raises this exception natively. We define it to mimic Ruby 1.9's behaviour
13  
-  # in Ruby 1.8.x
14  
-
15  
-  class KeyError < IndexError
16  
-    def initialize(message = nil)
17  
-      super(message || "key not found")
18  
-    end
19  
-  end unless defined?(KeyError)
20  
-
21  
-  # Extension for String class. This feature is included in Ruby 1.9 or later but not occur TypeError.
22  
-  #
23  
-  # String#% method which accept "named argument". The translator can know
24  
-  # the meaning of the msgids using "named argument" instead of %s/%d style.
25  
-
26  
-  class String
27  
-    alias :interpolate_without_ruby_19_syntax :% # :nodoc:
28  
-
29  
-    INTERPOLATION_PATTERN = Regexp.union(
30  
-      /%%/,
31  
-      /%\{(\w+)\}/,                               # matches placeholders like "%{foo}"
32  
-      /%<(\w+)>(.*?\d*\.?\d*[bBdiouxXeEfgGcps])/  # matches placeholders like "%<foo>.d"
33  
-    )
34  
-
35  
-    # % uses self (i.e. the String) as a format specification and returns the
36  
-    # result of applying it to the given arguments. In other words it interpolates
37  
-    # the given arguments to the string according to the formats the string
38  
-    # defines.
39  
-    #
40  
-    # There are three ways to use it:
41  
-    #
42  
-    # * Using a single argument or Array of arguments.
43  
-    #
44  
-    #   This is the default behaviour of the String class. See Kernel#sprintf for
45  
-    #   more details about the format string.
46  
-    #
47  
-    #   Example:
48  
-    #
49  
-    #     "%d %s" % [1, "message"]
50  
-    #     # => "1 message"
51  
-    #
52  
-    # * Using a Hash as an argument and unformatted, named placeholders.
53  
-    #
54  
-    #   When you pass a Hash as an argument and specify placeholders with %{foo}
55  
-    #   it will interpret the hash values as named arguments.
56  
-    #
57  
-    #   Example:
58  
-    #
59  
-    #     "%{firstname}, %{lastname}" % {:firstname => "Masao", :lastname => "Mutoh"}
60  
-    #     # => "Masao Mutoh"
61  
-    #
62  
-    # * Using a Hash as an argument and formatted, named placeholders.
63  
-    #
64  
-    #   When you pass a Hash as an argument and specify placeholders with %<foo>d
65  
-    #   it will interpret the hash values as named arguments and format the value
66  
-    #   according to the formatting instruction appended to the closing >.
67  
-    #
68  
-    #   Example:
69  
-    #
70  
-    #     "%<integer>d, %<float>.1f" % { :integer => 10, :float => 43.4 }
71  
-    #     # => "10, 43.3"
72  
-    def %(args)
73  
-      if args.kind_of?(Hash)
74  
-        dup.gsub(INTERPOLATION_PATTERN) do |match|
75  
-          if match == '%%'
76  
-            '%'
77  
-          else
78  
-            key = ($1 || $2).to_sym
79  
-            raise KeyError unless args.has_key?(key)
80  
-            $3 ? sprintf("%#{$3}", args[key]) : args[key]
81  
-          end
82  
-        end
83  
-      elsif self =~ INTERPOLATION_PATTERN
84  
-        raise ArgumentError.new('one hash required')
85  
-      else
86  
-        result = gsub(/%([{<])/, '%%\1')
87  
-        result.send :'interpolate_without_ruby_19_syntax', args
88  
-      end
89  
-    end
90  
-  end
91  
-end
  1
+require 'i18n/core_ext/string/interpolate'
5  activesupport/lib/active_support/dependencies/autoload.rb
... ...
@@ -1,7 +1,12 @@
1 1
 require "active_support/inflector/methods"
  2
+require "active_support/lazy_load_hooks"
2 3
 
3 4
 module ActiveSupport
4 5
   module Autoload
  6
+    def self.extended(base)
  7
+      base.extend(LazyLoadHooks)
  8
+    end
  9
+
5 10
     @@autoloads = {}
6 11
     @@under_path = nil
7 12
     @@at_path = nil
3  activesupport/lib/active_support/i18n.rb
... ...
@@ -1,2 +1,3 @@
1 1
 require 'i18n'
2  
-I18n.load_path << "#{File.dirname(__FILE__)}/locale/en.yml"
  2
+I18n.load_path << "#{File.dirname(__FILE__)}/locale/en.yml"
  3
+ActiveSupport.run_base_hooks(:i18n)
25  activesupport/lib/active_support/lazy_load_hooks.rb
... ...
@@ -0,0 +1,25 @@
  1
+module ActiveSupport
  2
+  module LazyLoadHooks
  3
+    def _setup_base_hooks
  4
+      @base_hooks ||= Hash.new {|h,k| h[k] = [] }
  5
+      @base ||= {}
  6
+    end
  7
+
  8
+    def base_hook(name = nil, &block)
  9
+      _setup_base_hooks
  10
+
  11
+      if base = @base[name]
  12
+        base.instance_eval(&block)
  13
+      else
  14
+        @base_hooks[name] << block
  15
+      end
  16
+    end
  17
+
  18
+    def run_base_hooks(base, name = nil)
  19
+      _setup_base_hooks
  20
+
  21
+      @base_hooks[name].each { |hook| base.instance_eval(&hook) } if @base_hooks
  22
+      @base[name] = base
  23
+    end
  24
+  end
  25
+end
8  activesupport/lib/active_support/railtie.rb
@@ -37,10 +37,12 @@ class Railtie < Rails::Railtie
37 37
     config.i18n.load_path = []
38 38
 
39 39
     initializer "i18n.initialize" do
40  
-      require 'active_support/i18n'
41  
-
42  
-      ActionDispatch::Callbacks.to_prepare do
  40
+      ActiveSupport.base_hook(:i18n) do
43 41
         I18n.reload!
  42
+
  43
+        ActionDispatch::Callbacks.to_prepare do
  44
+          I18n.reload!
  45
+        end
44 46
       end
45 47
     end
46 48
 
7  activesupport/lib/active_support/whiny_nil.rb
@@ -25,17 +25,16 @@
25 25
 # By default it is on in development and test modes, and it is off in production
26 26
 # mode.
27 27
 class NilClass
28  
-  WHINERS = [::Array]
29  
-  WHINERS << ::ActiveRecord::Base if defined? ::ActiveRecord::Base
30  
-
31 28
   METHOD_CLASS_MAP = Hash.new
32 29
 
33  
-  WHINERS.each do |klass|
  30
+  def self.add_whiner(klass)
34 31
     methods = klass.public_instance_methods - public_instance_methods
35 32
     class_name = klass.name
36 33
     methods.each { |method| METHOD_CLASS_MAP[method.to_sym] = class_name }
37 34
   end
38 35
 
  36
+  add_whiner ::Array
  37
+
39 38
   # Raises a RuntimeError when you attempt to call +id+ on +nil+.
40 39
   def id
41 40
     raise RuntimeError, "Called id for nil, which would mistakenly be 4 -- if you really wanted the id of nil, use object_id", caller
2  activesupport/test/whiny_nil_test.rb
@@ -9,6 +9,8 @@ def save!
9 9
 require 'abstract_unit'
10 10
 require 'active_support/whiny_nil'
11 11
 
  12
+NilClass.add_whiner ::ActiveRecord::Base
  13
+
12 14
 class WhinyNilTest < Test::Unit::TestCase
13 15
   def test_unchanged
14 16
     nil.method_thats_not_in_whiners
2  railties/lib/rails/application/routes_reloader.rb
@@ -27,7 +27,7 @@ def reload!
27 27
 
28 28
         routes.clear!
29 29
         paths.each { |path| load(path) }
30  
-        routes.finalize!
  30
+        ActionController.base_hook { routes.finalize! }
31 31
 
32 32
         nil
33 33
       ensure
4  railties/lib/rails/console/helpers.rb
@@ -2,4 +2,6 @@ def helper
2 2
   @helper ||= ApplicationController.helpers
3 3
 end
4 4
 
5  
-@controller = ApplicationController.new
  5
+def controller
  6
+  @controller ||= ApplicationController.new
  7
+end
4  railties/lib/rails/engine.rb
@@ -97,8 +97,8 @@ def load_tasks
97 97
 
98 98
     initializer :add_view_paths do
99 99
       views = paths.app.views.to_a
100  
-      ActionController::Base.prepend_view_path(views) if defined?(ActionController::Base)
101  
-      ActionMailer::Base.prepend_view_path(views) if defined?(ActionMailer::Base)
  100
+      ActionController.base_hook { prepend_view_path(views) } if defined?(ActionController)
  101
+      ActionMailer.base_hook { prepend_view_path(views) } if defined?(ActionMailer)
102 102
     end
103 103
 
104 104
     initializer :add_metals do |app|

5 notes on commit 39d6f9e

Michael Klishin

Looks very nice already!

Jay Feldblum

What effect does this have on threadsafe! mode, if any?

Joshua Peek
Collaborator
josh commented on 39d6f9e March 07, 2010

I'm all for pushing initializer stuff to be as lazy as possible. Nice for console when you may not even use the db connection. Just connect right before we have to.

Using base require as the trigger seems brittle. Anything that loads AR::Base before the initializers run will trigger base hooks with no callbacks. This seems more likely with the suggestion we all should be using Bundler.require.

Almost everything in the framework railties wrap their initializers in this base_hook hook. Why not make it a convention to defer all "active_record.*" initializers till ActiveRecord::Base is loaded? And if its already defined, run them immediately.

And hopefully base_hook is not "public api".

Michael Klishin

@yfeldblum: not much if any. This changeset affects (very) early framework loading. The only case for threading at boot time I can think of is initialization of EventMachine reactor in a thread, required by libraries like amqp gem. But EM startup really belongs to initializers, and they are executed later in the process.

Jeremy Kemper
Owner

Josh, could see this being plugin API.

It's natural for a plugin to want to say "if Action Mailer is used, load this stuff" without having to write an initializer that does defined? checks.

Andrew White

Is this line necessary? None of the other framework components have it and it's actually ActiveSupport::Autoload that's doing the extending.

Please sign in to comment.
Something went wrong with that request. Please try again.