自動加載機制
require "active_support"
require "active_support/rails"
require "active_model"
require "arel"
require "yaml"
require "active_record/version"
require "active_model/attribute_set"
module ActiveRecord
extend ActiveSupport::Autoload
autoload :Base
autoload :Callbacks
autoload :Core
autoload :ConnectionHandling
autoload :CounterCache
autoload :DynamicMatchers
autoload :Enum
autoload :InternalMetadata
autoload :Explain
autoload :Inheritance
autoload :Integration
autoload :Migration
autoload :Migrator, "active_record/migration"
autoload :ModelSchema
autoload :NestedAttributes
autoload :NoTouching
autoload :TouchLater
autoload :Persistence
autoload :QueryCache
autoload :Querying
autoload :CollectionCacheKey
autoload :ReadonlyAttributes
autoload :RecordInvalid, "active_record/validations"
autoload :Reflection
autoload :RuntimeRegistry
autoload :Sanitization
autoload :Schema
autoload :SchemaDumper
autoload :SchemaMigration
autoload :Scoping
autoload :Serialization
autoload :StatementCache
autoload :Store
autoload :Suppressor
autoload :Timestamp
autoload :Transactions
autoload :Translation
autoload :Validations
autoload :SecureToken
eager_autoload do
autoload :ActiveRecordError, "active_record/errors"
autoload :ConnectionNotEstablished, "active_record/errors"
autoload :ConnectionAdapters, "active_record/connection_adapters/abstract_adapter"
autoload :Aggregations
autoload :Associations
autoload :AttributeAssignment
autoload :AttributeMethods
autoload :AutosaveAssociation
autoload :LegacyYamlAdapter
autoload :Relation
autoload :AssociationRelation
autoload :NullRelation
autoload_under "relation" do
autoload :QueryMethods
autoload :FinderMethods
autoload :Calculations
autoload :PredicateBuilder
autoload :SpawnMethods
autoload :Batches
autoload :Delegation
end
autoload :Result
autoload :TableMetadata
autoload :Type
end
module Coders
autoload :YAMLColumn, "active_record/coders/yaml_column"
autoload :JSON, "active_record/coders/json"
end
module AttributeMethods
extend ActiveSupport::Autoload
eager_autoload do
autoload :BeforeTypeCast
autoload :Dirty
autoload :PrimaryKey
autoload :Query
autoload :Read
autoload :TimeZoneConversion
autoload :Write
autoload :Serialization
end
end
module Locking
extend ActiveSupport::Autoload
eager_autoload do
autoload :Optimistic
autoload :Pessimistic
end
end
module ConnectionAdapters
extend ActiveSupport::Autoload
eager_autoload do
autoload :AbstractAdapter
end
end
module Scoping
extend ActiveSupport::Autoload
eager_autoload do
autoload :Named
autoload :Default
end
end
module Tasks
extend ActiveSupport::Autoload
autoload :DatabaseTasks
autoload :SQLiteDatabaseTasks, "active_record/tasks/sqlite_database_tasks"
autoload :MySQLDatabaseTasks, "active_record/tasks/mysql_database_tasks"
autoload :PostgreSQLDatabaseTasks,
"active_record/tasks/postgresql_database_tasks"
end
autoload :TestFixtures, "active_record/fixtures"
def self.eager_load!
super
ActiveRecord::Locking.eager_load!
ActiveRecord::Scoping.eager_load!
ActiveRecord::Associations.eager_load!
ActiveRecord::AttributeMethods.eager_load!
ActiveRecord::ConnectionAdapters.eager_load!
end
end
ActiveSupport.on_load(:active_record) do
Arel::Table.engine = self
end
ActiveSupport.on_load(:i18n) do
I18n.load_path << File.expand_path("active_record/locale/en.yml", __dir__)
end
YAML.load_tags["!ruby/object:ActiveRecord::AttributeSet"] = "ActiveModel::AttributeSet"
YAML.load_tags["!ruby/object:ActiveRecord::Attribute::FromDatabase"] = "ActiveModel::Attribute::FromDatabase"
YAML.load_tags["!ruby/object:ActiveRecord::LazyAttributeHash"] = "ActiveModel::LazyAttributeHash"
Active Record是Rails的ORM功能實現(xiàn)涂身。上面代碼使用了ActiveSupport::Autoload模塊裸扶,該模塊定義了autoload方法左驾。代碼首次引用模塊時允坚,這個方法通過命名約定自動識別和加載該模塊匹层。
我們先看下Autoload模塊代碼的具體實現(xiàn):
# frozen_string_literal: true
require "active_support/inflector/methods"
module ActiveSupport
# Autoload and eager load conveniences for your library.
#
# This module allows you to define autoloads based on
# Rails conventions (i.e. no need to define the path
# it is automatically guessed based on the filename)
# and also define a set of constants that needs to be
# eager loaded:
#
# module MyLib
# extend ActiveSupport::Autoload
#
# autoload :Model
#
# eager_autoload do
# autoload :Cache
# end
# end
#
# Then your library can be eager loaded by simply calling:
#
# MyLib.eager_load!
module Autoload
def self.extended(base) # :nodoc:
base.class_eval do
@_autoloads = {}
@_under_path = nil
@_at_path = nil
@_eager_autoload = false
end
end
def autoload(const_name, path = @_at_path)
unless path
full = [name, @_under_path, const_name.to_s].compact.join("::")
path = Inflector.underscore(full)
end
if @_eager_autoload
@_autoloads[const_name] = path
end
super const_name, path
end
def autoload_under(path)
@_under_path, old_path = path, @_under_path
yield
ensure
@_under_path = old_path
end
def autoload_at(path)
@_at_path, old_path = path, @_at_path
yield
ensure
@_at_path = old_path
end
def eager_autoload
old_eager, @_eager_autoload = @_eager_autoload, true
yield
ensure
@_eager_autoload = old_eager
end
def eager_load!
@_autoloads.each_value { |file| require file }
end
def autoloads
@_autoloads
end
end
end
首先看到autoload方法隙笆,如果再沒有傳path參數(shù)的情況下,autoload會根據(jù)當(dāng)前的類(模塊)名升筏,和傳入的const_name,組成一個path仲器,最后根據(jù)傳入的const_name,和path,調(diào)用ruby原生的autoload加載模塊仰冠。如果通過傳遞block的方式調(diào)用eager_autoload,這種情況下調(diào)用autoload,會把相應(yīng)的模塊加入到@_eager_autoload中蝶糯,然后可以通過eager_load!方法直接require對應(yīng)path下的文件洋只。以及相應(yīng)的autoload_at和autoload_under,也可以通過block的方式傳遞path昼捍,改變默認(rèn)的@_at_path,執(zhí)行autoload识虚,真的是非常巧妙的方式。
ruby原生的autoload部分妒茬,參考:http://www.reibang.com/p/d9dcbed59a82
Validations模塊
ActiveRecord::Base包含了 ActiveRecord::Validations模塊担锤,里面卻找不到我們要用的validate方法。這個模塊來自Active Model乍钻,Active Record的一個依賴庫肛循。為什么作者要把validate方法定義在別的庫呢,其實早期的rails沒有Active Model庫银择,那時候validate方法定義在Active Record里多糠,但是隨著Active Record的不斷壯大,開發(fā)者發(fā)現(xiàn)其實這是兩項獨立的工作浩考,一項與數(shù)據(jù)庫操作相關(guān)夹孔,比如保存和加載數(shù)據(jù)庫。但另一項是處理對象模型的析孽,比如維護對象屬性搭伤,或者跟蹤對象屬性的有效性。于是就被分成了兩個庫袜瞬。