Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

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
@wycats wycats authored
Showing with 299 additions and 312 deletions.
  1. +1 −0  actionmailer/lib/action_mailer.rb
  2. +2 −0  actionmailer/lib/action_mailer/base.rb
  3. +6 −4 actionmailer/lib/action_mailer/railtie.rb
  4. +2 −0  actionpack/lib/action_controller/base.rb
  5. +9 −5 actionpack/lib/action_controller/railtie.rb
  6. +0 −1  actionpack/lib/action_dispatch/middleware/params_parser.rb
  7. +0 −1  actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
  8. +1 −1  actionpack/lib/action_dispatch/middleware/stack.rb
  9. +3 −1 actionpack/lib/action_dispatch/routing/mapper.rb
  10. +1 −1  actionpack/lib/action_view.rb
  11. +2 −0  actionpack/lib/action_view/base.rb
  12. +5 −3 actionpack/lib/action_view/helpers/active_model_helper.rb
  13. +5 −3 actionpack/lib/action_view/helpers/form_helper.rb
  14. +3 −1 actionpack/lib/action_view/railtie.rb
  15. +1 −0  actionpack/lib/action_view/test_case.rb
  16. +0 −1  actionpack/test/abstract_unit.rb
  17. +8 −5 activerecord/lib/active_record.rb
  18. +5 −165 activerecord/lib/active_record/base.rb
  19. +165 −0 activerecord/lib/active_record/errors.rb
  20. +27 −15 activerecord/lib/active_record/railtie.rb
  21. +1 −0  activerecord/test/cases/helper.rb
  22. +3 −0  activesupport/lib/active_support.rb
  23. +0 −1  activesupport/lib/active_support/all.rb
  24. +0 −1  activesupport/lib/active_support/core_ext/array/conversions.rb
  25. +1 −91 activesupport/lib/active_support/core_ext/string/interpolation.rb
  26. +5 −0 activesupport/lib/active_support/dependencies/autoload.rb
  27. +2 −1  activesupport/lib/active_support/i18n.rb
  28. +25 −0 activesupport/lib/active_support/lazy_load_hooks.rb
  29. +5 −3 activesupport/lib/active_support/railtie.rb
  30. +3 −4 activesupport/lib/active_support/whiny_nil.rb
  31. +2 −0  activesupport/test/whiny_nil_test.rb
  32. +1 −1  railties/lib/rails/application/routes_reloader.rb
  33. +3 −1 railties/lib/rails/console/helpers.rb
  34. +2 −2 railties/lib/rails/engine.rb
View
1  actionmailer/lib/action_mailer.rb
@@ -34,6 +34,7 @@
require 'active_support/core_ext/module/attr_internal'
require 'active_support/core_ext/module/delegation'
require 'active_support/core_ext/string/inflections'
+require 'active_support/lazy_load_hooks'
@pixeltrix Owner

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

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

5 comments on commit 39d6f9e

@michaelklishin

Looks very nice already!

@yfeldblum

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

@josh
Collaborator

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".

@michaelklishin

@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
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.

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