ruby-on-rails - Active Record 模型默认映射
问题描述
我对 Rails 相当陌生,我不明白的一件事是在 rails 官方文档中它说数据库默认映射到模型的位置。
例如,如果我在迁移中将默认值放在列上,我希望在将特定记录保存到数据库时插入默认值。但是我注意到,当我这样做时Record.new
,模型属性已经具有在数据库中设置的那些默认值!这很有用,因为这意味着我在实例化新模型对象时不必显式设置它,但是在文档中的什么地方说这种新对象的默认自动设置发生了?
解决方案
但是在文档中的哪个地方说这种新对象的默认自动设置发生了?
它没有。ActiveRecord 如何在实例化新记录时执行读取数据库模式并从那里定义默认值的魔力的低级实现分布在多个 API 中——其中一些是内部的。如果您想详细了解它的工作原理,则需要深入研究代码。但是你并不需要真正了解它来编写 Rails 应用程序。
您真正需要知道的是,ActiveRecord 在首次评估类时通过数据库适配器从数据库中读取模式。此架构信息缓存在类中,因此 AR 不必再次查询 DB,并且包含有关数据库列的类型和默认值的信息。
然后,此信息用于在模型和属性上定义列缓存,这是一个非常分散的术语,用于存储有关属性的元数据、类型转换前后的值以及用于访问它们的 setter 和 getter。不要被愚弄,这在任何方面都像一个简单的实例变量 - 你的foo
属性没有存储在@foo
.
ActiveRecord/ActiveModel 知道在实例化模型时设置默认值,因为它查看模型的属性。
ActiveRecord::ModelSchema
这是一个内部 API,主要负责将 DB 模式映射到模型上的列缓存:
# frozen_string_literal: true
require "monitor"
module ActiveRecord
module ModelSchema
# ...
module ClassMethods
# ....
# Returns a hash where the keys are column names and the values are
# default values when instantiating the Active Record object for this table.
def column_defaults
load_schema
@column_defaults ||= _default_attributes.deep_dup.to_hash.freeze
end
# ...
private
def inherited(child_class)
super
child_class.initialize_load_schema_monitor
end
def schema_loaded?
defined?(@schema_loaded) && @schema_loaded
end
def load_schema
return if schema_loaded?
@load_schema_monitor.synchronize do
return if defined?(@columns_hash) && @columns_hash
load_schema!
@schema_loaded = true
rescue
reload_schema_from_cache # If the schema loading failed half way through, we must reset the state.
raise
end
end
def load_schema!
unless table_name
raise ActiveRecord::TableNotSpecified, "#{self} has no table configured. Set one with #{self}.table_name="
end
columns_hash = connection.schema_cache.columns_hash(table_name)
columns_hash = columns_hash.except(*ignored_columns) unless ignored_columns.empty?
@columns_hash = columns_hash.freeze
@columns_hash.each do |name, column|
type = connection.lookup_cast_type_from_column(column)
type = _convert_type_from_options(type)
warn_if_deprecated_type(column)
define_attribute(
name,
type,
default: column.default,
user_provided_default: false
)
end
end
end
end
end
之后ActiveRecord::Attributes接管定义了您通过模式缓存中的 setter 和 getter 与之交互的实际属性。同样在这里,真正的魔力发生在文档很少的方法中,这是内部 API 所期望的:
module ActiveRecord
# See ActiveRecord::Attributes::ClassMethods for documentation
module Attributes
extend ActiveSupport::Concern
included do
class_attribute :attributes_to_define_after_schema_loads, instance_accessor: false, default: {} # :internal:
end
module ClassMethods
# This is the low level API which sits beneath +attribute+. It only
# accepts type objects, and will do its work immediately instead of
# waiting for the schema to load. Automatic schema detection and
# ClassMethods#attribute both call this under the hood. While this method
# is provided so it can be used by plugin authors, application code
# should probably use ClassMethods#attribute.
#
# +name+ The name of the attribute being defined. Expected to be a +String+.
#
# +cast_type+ The type object to use for this attribute.
#
# +default+ The default value to use when no value is provided. If this option
# is not passed, the previous default value (if any) will be used.
# Otherwise, the default will be +nil+. A proc can also be passed, and
# will be called once each time a new value is needed.
#
# +user_provided_default+ Whether the default value should be cast using
# +cast+ or +deserialize+.
def define_attribute(
name,
cast_type,
default: NO_DEFAULT_PROVIDED,
user_provided_default: true
)
attribute_types[name] = cast_type
define_default_attribute(name, default, cast_type, from_user: user_provided_default)
end
def load_schema! # :nodoc:
super
attributes_to_define_after_schema_loads.each do |name, (type, options)|
define_attribute(name, _lookup_cast_type(name, type, options), **options.slice(:default))
end
end
# ...
end
end
尽管持久的神话schema.rb
没有以任何方式参与。
推荐阅读
- ember.js - Emberjs:使用 args 作为跟踪属性
- google-apps-script - 谷歌表分叉(不适用于谷歌表单响应表)
- c++ - 在 C++ 中获取 uint64_t 的上半部分的指令/内在函数?
- c - 在 C 中创建一个读取文件但无限循环的函数
- android - Flutter DragAndDropGridView 预加载项数
- deep-learning - 为什么运行 run_squad.py 后看不到结果?
- javascript - 逐行检查语句是否有效并分配特定值
- python - 使用 excelwriter pandas 在现有 excel 中添加新列
- css - “阅读文档”主题中的 Sphinx TOC 未在子菜单中显示 +/- 图标以表示它们可以扩展
- java - Spring boot Cron 从第二天起就无法正常工作