自动加载机制
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功能实现。上面代码使用了ActiveSupport::Autoload模块,该模块定义了autoload方法。代码首次引用模块时,这个方法通过命名约定自动识别和加载该模块。
我们先看下Autoload模块代码的具体实现:
# 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参数的情况下,autoload会根据当前的类(模块)名,和传入的const_name,组成一个path,最后根据传入的const_name,和path,调用ruby原生的autoload加载模块。如果通过传递block的方式调用eager_autoload,这种情况下调用autoload,会把相应的模块加入到@_eager_autoload中,然后可以通过eager_load!方法直接require对应path下的文件。以及相应的autoload_at和autoload_under,也可以通过block的方式传递path,改变默认的@_at_path,执行autoload,真的是非常巧妙的方式。
ruby原生的autoload部分,参考:https://www.jianshu.com/p/d9dcbed59a82
Validations模块
ActiveRecord::Base包含了 ActiveRecord::Validations模块,里面却找不到我们要用的validate方法。这个模块来自Active Model,Active Record的一个依赖库。为什么作者要把validate方法定义在别的库呢,其实早期的rails没有Active Model库,那时候validate方法定义在Active Record里,但是随着Active Record的不断壮大,开发者发现其实这是两项独立的工作,一项与数据库操作相关,比如保存和加载数据库。但另一项是处理对象模型的,比如维护对象属性,或者跟踪对象属性的有效性。于是就被分成了两个库。