inspect attributes
This commit is contained in:
@@ -116,6 +116,8 @@ raise unless fa_user.persisted?
|
|||||||
raise unless fa_user.username == "Alice"
|
raise unless fa_user.username == "Alice"
|
||||||
raise unless fa_user.url_name == "alice"
|
raise unless fa_user.url_name == "alice"
|
||||||
|
|
||||||
|
fa_user.reload
|
||||||
|
|
||||||
fa_user_found = FaUser.find_by(username: "Alice")
|
fa_user_found = FaUser.find_by(username: "Alice")
|
||||||
raise unless fa_user_found.id == fa_user_id
|
raise unless fa_user_found.id == fa_user_id
|
||||||
raise unless fa_user_found.username == "Alice"
|
raise unless fa_user_found.username == "Alice"
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ module HasAuxTable
|
|||||||
requires_ancestor { T.class_of(ActiveRecord::Base) }
|
requires_ancestor { T.class_of(ActiveRecord::Base) }
|
||||||
requires_ancestor { ActiveRecord::ModelSchema::ClassMethods }
|
requires_ancestor { ActiveRecord::ModelSchema::ClassMethods }
|
||||||
requires_ancestor { ActiveRecord::Associations::ClassMethods }
|
requires_ancestor { ActiveRecord::Associations::ClassMethods }
|
||||||
|
requires_ancestor { ActiveModel::Attributes::ClassMethods }
|
||||||
|
|
||||||
include RelationExtensions
|
include RelationExtensions
|
||||||
|
|
||||||
@@ -48,179 +49,13 @@ module HasAuxTable
|
|||||||
setup_attribute_types_hook!(aux_config)
|
setup_attribute_types_hook!(aux_config)
|
||||||
setup_schema_loading_hook!(aux_config)
|
setup_schema_loading_hook!(aux_config)
|
||||||
setup_relation_extensions!(aux_config)
|
setup_relation_extensions!(aux_config)
|
||||||
|
setup_attribute_getter_setter_hooks!(aux_config)
|
||||||
|
|
||||||
aux_config
|
aux_config
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
sig { params(aux_config: AuxTableConfig).void }
|
|
||||||
def setup_attribute_types_hook!(aux_config)
|
|
||||||
original_method = aux_config.main_class.method(:attribute_types)
|
|
||||||
aux_config
|
|
||||||
.main_class
|
|
||||||
.define_singleton_method(:attribute_types) do
|
|
||||||
@aux_config_attribute_types_cache ||= {}
|
|
||||||
@aux_config_attribute_types_cache[aux_config.aux_table_name] ||= begin
|
|
||||||
original_types = original_method.call.dup
|
|
||||||
|
|
||||||
aux_types =
|
|
||||||
aux_config.model_class.attribute_types.filter do |k, _|
|
|
||||||
aux_config.is_aux_column?(k)
|
|
||||||
end
|
|
||||||
|
|
||||||
original_types.merge!(aux_types)
|
|
||||||
original_types
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Hook into schema loading to generate attribute accessors when schema is loaded
|
|
||||||
sig { params(aux_config: AuxTableConfig).void }
|
|
||||||
def setup_schema_loading_hook!(aux_config)
|
|
||||||
aux_table_name = aux_config.aux_table_name
|
|
||||||
|
|
||||||
# Override load_schema to also generate auxiliary attribute accessors when schema is loaded
|
|
||||||
load_schema_method = self.method(:load_schema!)
|
|
||||||
self.define_singleton_method(:load_schema!) do
|
|
||||||
T.bind(
|
|
||||||
self,
|
|
||||||
T.all(T.class_of(ActiveRecord::Base), HasAuxTable::ClassMethods)
|
|
||||||
)
|
|
||||||
|
|
||||||
# first, load the main and aux table schemas like normal
|
|
||||||
result = load_schema_method.call
|
|
||||||
aux_config.load_aux_schema
|
|
||||||
|
|
||||||
main_columns_hash = self.columns_hash
|
|
||||||
aux_columns_hash =
|
|
||||||
aux_config.model_class.columns_hash.select do |col|
|
|
||||||
aux_config.is_aux_column?(col)
|
|
||||||
end
|
|
||||||
|
|
||||||
main_column_names = main_columns_hash.keys
|
|
||||||
aux_column_names = aux_columns_hash.keys
|
|
||||||
|
|
||||||
check_for_overlapping_columns!(
|
|
||||||
aux_table_name,
|
|
||||||
main_column_names,
|
|
||||||
aux_column_names
|
|
||||||
)
|
|
||||||
|
|
||||||
aux_attributes = aux_config.model_class._default_attributes
|
|
||||||
aux_table_filtered_attributes =
|
|
||||||
aux_attributes
|
|
||||||
.keys
|
|
||||||
.filter_map do |k|
|
|
||||||
[k, aux_attributes[k]] if aux_column_names.include?(k)
|
|
||||||
end
|
|
||||||
.to_h
|
|
||||||
|
|
||||||
# set attributes that exist on the aux table to also exist on this table
|
|
||||||
aux_table_filtered_attributes.each do |name, attr|
|
|
||||||
@default_attributes[name] = attr
|
|
||||||
end
|
|
||||||
|
|
||||||
# Generate attribute accessors for each auxiliary column
|
|
||||||
aux_columns_hash.each do |column_name, column|
|
|
||||||
column_name = column_name.to_sym
|
|
||||||
if self.method_defined?(column_name.to_sym)
|
|
||||||
raise "invariant: method #{column_name} already defined"
|
|
||||||
end
|
|
||||||
|
|
||||||
aux_config.define_aux_attribute_delegate(self, column_name)
|
|
||||||
aux_config.define_aux_attribute_delegate(self, :"#{column_name}?")
|
|
||||||
aux_config.define_aux_attribute_delegate(self, :"#{column_name}=")
|
|
||||||
end
|
|
||||||
|
|
||||||
%i[save save!].each do |method_name|
|
|
||||||
save_method = self.instance_method(method_name)
|
|
||||||
self.define_method(method_name) do |*args, **kwargs|
|
|
||||||
T.bind(self, ActiveRecord::Base)
|
|
||||||
result = save_method.bind(self).call(*args, **kwargs)
|
|
||||||
result &&=
|
|
||||||
self
|
|
||||||
.association(aux_config.aux_association_name)
|
|
||||||
.target
|
|
||||||
.send(method_name, *args, **kwargs)
|
|
||||||
result
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
%i[
|
|
||||||
_read_attribute
|
|
||||||
read_attribute
|
|
||||||
_write_attribute
|
|
||||||
write_attribute
|
|
||||||
].each do |method_name|
|
|
||||||
read_attribute_method = self.instance_method(method_name)
|
|
||||||
self.define_method(method_name) do |name, *args, **kwargs|
|
|
||||||
T.bind(self, ActiveRecord::Base)
|
|
||||||
if aux_config.is_aux_column?(name)
|
|
||||||
target = aux_config.ensure_aux_target(self)
|
|
||||||
T.unsafe(target).send(method_name, name, *args, **kwargs)
|
|
||||||
else
|
|
||||||
T
|
|
||||||
.unsafe(read_attribute_method)
|
|
||||||
.bind(self)
|
|
||||||
.call(name, *args, **kwargs)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
initialize_method = self.instance_method(:initialize)
|
|
||||||
self.define_method(:initialize) do |args|
|
|
||||||
T.bind(self, ActiveRecord::Base)
|
|
||||||
aux_args, main_args =
|
|
||||||
args.partition { |k, _| aux_config.is_aux_column?(k) }.map(&:to_h)
|
|
||||||
initialize_method.bind(self).call(main_args)
|
|
||||||
aux_config.assign_aux_attributes(self, aux_args)
|
|
||||||
end
|
|
||||||
|
|
||||||
self.define_method(:reload) do |*args|
|
|
||||||
T.bind(self, ActiveRecord::Base)
|
|
||||||
aux_model = aux_config.ensure_aux_target(self)
|
|
||||||
fresh_model = self.class.find(id)
|
|
||||||
@attributes = fresh_model.instance_variable_get(:@attributes)
|
|
||||||
aux_model.instance_variable_set(
|
|
||||||
:@attributes,
|
|
||||||
fresh_model
|
|
||||||
.association(aux_config.aux_association_name)
|
|
||||||
.target
|
|
||||||
.instance_variable_get(:@attributes)
|
|
||||||
)
|
|
||||||
self
|
|
||||||
end
|
|
||||||
|
|
||||||
result
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
sig do
|
|
||||||
params(
|
|
||||||
aux_table_name: Symbol,
|
|
||||||
main_columns: T::Array[String],
|
|
||||||
aux_columns: T::Array[String]
|
|
||||||
).void
|
|
||||||
end
|
|
||||||
def check_for_overlapping_columns!(
|
|
||||||
aux_table_name,
|
|
||||||
main_columns,
|
|
||||||
aux_columns
|
|
||||||
)
|
|
||||||
# Find overlapping columns (excluding system columns and foreign keys)
|
|
||||||
overlapping_columns =
|
|
||||||
aux_columns.select { |col| main_columns.include?(col) }
|
|
||||||
|
|
||||||
if overlapping_columns.any?
|
|
||||||
column_list = overlapping_columns.map { |col| "'#{col}'" }.join(", ")
|
|
||||||
Kernel.raise ArgumentError,
|
|
||||||
"Auxiliary table '#{aux_table_name}' defines column(s) #{column_list} " \
|
|
||||||
"that already exist(s) in main table '#{self.table_name}'. " \
|
|
||||||
"Auxiliary table columns must not overlap with main table columns."
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Generate auxiliary model class dynamically
|
# Generate auxiliary model class dynamically
|
||||||
sig do
|
sig do
|
||||||
params(
|
params(
|
||||||
@@ -235,8 +70,6 @@ module HasAuxTable
|
|||||||
foreign_key: :base_table_id,
|
foreign_key: :base_table_id,
|
||||||
primary_key: self.primary_key
|
primary_key: self.primary_key
|
||||||
)
|
)
|
||||||
T.bind(self, T.all(T.class_of(ActiveRecord::Base), Class))
|
|
||||||
|
|
||||||
base_table = self.table_name
|
base_table = self.table_name
|
||||||
aux_table_name = :"#{base_table}_#{aux_name}_aux"
|
aux_table_name = :"#{base_table}_#{aux_name}_aux"
|
||||||
|
|
||||||
@@ -250,7 +83,7 @@ module HasAuxTable
|
|||||||
end
|
end
|
||||||
|
|
||||||
# Get the current class for the association
|
# Get the current class for the association
|
||||||
main_class = self
|
main_class = T.cast(self, T.class_of(ActiveRecord::Base))
|
||||||
main_association_name = foreign_key.to_s.delete_suffix("_id").to_sym
|
main_association_name = foreign_key.to_s.delete_suffix("_id").to_sym
|
||||||
|
|
||||||
# Create the auxiliary model class
|
# Create the auxiliary model class
|
||||||
@@ -297,6 +130,194 @@ module HasAuxTable
|
|||||||
primary_key:
|
primary_key:
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
sig { params(aux_config: AuxTableConfig).void }
|
||||||
|
def setup_attribute_types_hook!(aux_config)
|
||||||
|
original_method = aux_config.main_class.method(:attribute_types)
|
||||||
|
aux_config
|
||||||
|
.main_class
|
||||||
|
.define_singleton_method(:attribute_types) do
|
||||||
|
@aux_config_attribute_types_cache ||= {}
|
||||||
|
@aux_config_attribute_types_cache[aux_config.aux_table_name] ||= begin
|
||||||
|
original_types = original_method.call.dup
|
||||||
|
|
||||||
|
aux_types =
|
||||||
|
aux_config.model_class.attribute_types.filter do |k, _|
|
||||||
|
aux_config.is_aux_column?(k)
|
||||||
|
end
|
||||||
|
|
||||||
|
# move 'created_at', 'updated_at' etc to the end of the list
|
||||||
|
at_types = {}
|
||||||
|
original_types.each do |k, v|
|
||||||
|
if k.end_with?("_at") && v.type == :datetime
|
||||||
|
at_types[k] = v
|
||||||
|
original_types.delete(k)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
original_types.merge!(aux_types)
|
||||||
|
original_types.merge!(at_types)
|
||||||
|
original_types
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
aux_config.main_class.attributes_for_inspect =
|
||||||
|
Util.attributes_for_inspect(aux_config)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Hook into schema loading to generate attribute accessors when schema is loaded
|
||||||
|
sig { params(aux_config: AuxTableConfig).void }
|
||||||
|
def setup_schema_loading_hook!(aux_config)
|
||||||
|
# Override load_schema to also generate auxiliary attribute accessors when schema is loaded
|
||||||
|
load_schema_method = self.method(:load_schema!)
|
||||||
|
self.define_singleton_method(:load_schema!) do
|
||||||
|
T.bind(
|
||||||
|
self,
|
||||||
|
T.all(T.class_of(ActiveRecord::Base), HasAuxTable::ClassMethods)
|
||||||
|
)
|
||||||
|
|
||||||
|
aux_config_load_schema!(load_schema_method, aux_config)
|
||||||
|
end
|
||||||
|
|
||||||
|
self.load_schema! if self.schema_loaded?
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { params(load_schema_method: Method, aux_config: AuxTableConfig).void }
|
||||||
|
def aux_config_load_schema!(load_schema_method, aux_config)
|
||||||
|
# first, load the main and aux table schemas like normal
|
||||||
|
result = load_schema_method.call
|
||||||
|
aux_config.load_aux_schema
|
||||||
|
|
||||||
|
aux_table_name = aux_config.aux_table_name
|
||||||
|
|
||||||
|
main_columns_hash = self.columns_hash
|
||||||
|
aux_columns_hash =
|
||||||
|
aux_config.model_class.columns_hash.select do |col|
|
||||||
|
aux_config.is_aux_column?(col)
|
||||||
|
end
|
||||||
|
|
||||||
|
main_column_names = main_columns_hash.keys
|
||||||
|
aux_column_names = aux_columns_hash.keys
|
||||||
|
|
||||||
|
check_for_overlapping_columns!(
|
||||||
|
aux_table_name,
|
||||||
|
main_column_names,
|
||||||
|
aux_column_names
|
||||||
|
)
|
||||||
|
|
||||||
|
aux_attributes = aux_config.model_class._default_attributes
|
||||||
|
aux_table_filtered_attributes =
|
||||||
|
aux_attributes
|
||||||
|
.keys
|
||||||
|
.filter_map do |k|
|
||||||
|
[k, aux_attributes[k]] if aux_column_names.include?(k)
|
||||||
|
end
|
||||||
|
.to_h
|
||||||
|
|
||||||
|
# set attributes that exist on the aux table to also exist on this table
|
||||||
|
aux_table_filtered_attributes.each do |name, attr|
|
||||||
|
@default_attributes[name] = attr
|
||||||
|
end
|
||||||
|
|
||||||
|
# Generate attribute accessors for each auxiliary column
|
||||||
|
aux_columns_hash.each do |column_name, column|
|
||||||
|
column_name = column_name.to_sym
|
||||||
|
if self.method_defined?(column_name.to_sym)
|
||||||
|
raise "invariant: method #{column_name} already defined"
|
||||||
|
end
|
||||||
|
|
||||||
|
aux_config.define_aux_attribute_delegate(column_name)
|
||||||
|
aux_config.define_aux_attribute_delegate(:"#{column_name}?")
|
||||||
|
aux_config.define_aux_attribute_delegate(:"#{column_name}=")
|
||||||
|
end
|
||||||
|
|
||||||
|
%i[save save!].each do |method_name|
|
||||||
|
save_method = self.instance_method(method_name)
|
||||||
|
self.define_method(method_name) do |*args, **kwargs|
|
||||||
|
T.bind(self, ActiveRecord::Base)
|
||||||
|
result = save_method.bind(self).call(*args, **kwargs)
|
||||||
|
result &&=
|
||||||
|
self
|
||||||
|
.association(aux_config.aux_association_name)
|
||||||
|
.target
|
||||||
|
.send(method_name, *args, **kwargs)
|
||||||
|
result
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
initialize_method = self.instance_method(:initialize)
|
||||||
|
self.define_method(:initialize) do |args|
|
||||||
|
T.bind(self, ActiveRecord::Base)
|
||||||
|
aux_args, main_args =
|
||||||
|
args.partition { |k, _| aux_config.is_aux_column?(k) }.map(&:to_h)
|
||||||
|
initialize_method.bind(self).call(main_args)
|
||||||
|
aux_config.assign_aux_attributes(self, aux_args)
|
||||||
|
end
|
||||||
|
|
||||||
|
self.define_method(:reload) do |*args|
|
||||||
|
T.bind(self, ActiveRecord::Base)
|
||||||
|
aux_model = aux_config.ensure_aux_target(self)
|
||||||
|
fresh_model = self.class.find(id)
|
||||||
|
@attributes = fresh_model.instance_variable_get(:@attributes)
|
||||||
|
aux_model.instance_variable_set(
|
||||||
|
:@attributes,
|
||||||
|
fresh_model
|
||||||
|
.association(aux_config.aux_association_name)
|
||||||
|
.target
|
||||||
|
.instance_variable_get(:@attributes)
|
||||||
|
)
|
||||||
|
self
|
||||||
|
end
|
||||||
|
|
||||||
|
result
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { params(aux_config: AuxTableConfig).void }
|
||||||
|
def setup_attribute_getter_setter_hooks!(aux_config)
|
||||||
|
%i[
|
||||||
|
_read_attribute
|
||||||
|
read_attribute
|
||||||
|
_write_attribute
|
||||||
|
write_attribute
|
||||||
|
_assign_attribute
|
||||||
|
].each do |method_name|
|
||||||
|
method = self.instance_method(method_name)
|
||||||
|
self.define_method(method_name) do |name, *args, **kwargs, &block|
|
||||||
|
T.bind(self, ActiveRecord::Base)
|
||||||
|
if aux_config.is_aux_column?(name)
|
||||||
|
target = aux_config.ensure_aux_target(self)
|
||||||
|
T.unsafe(target).send(method_name, name, *args, **kwargs, &block)
|
||||||
|
else
|
||||||
|
T.unsafe(method).bind(self).call(name, *args, **kwargs, &block)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
sig do
|
||||||
|
params(
|
||||||
|
aux_table_name: Symbol,
|
||||||
|
main_columns: T::Array[String],
|
||||||
|
aux_columns: T::Array[String]
|
||||||
|
).void
|
||||||
|
end
|
||||||
|
def check_for_overlapping_columns!(
|
||||||
|
aux_table_name,
|
||||||
|
main_columns,
|
||||||
|
aux_columns
|
||||||
|
)
|
||||||
|
# Find overlapping columns (excluding system columns and foreign keys)
|
||||||
|
overlapping_columns =
|
||||||
|
aux_columns.select { |col| main_columns.include?(col) }
|
||||||
|
|
||||||
|
if overlapping_columns.any?
|
||||||
|
column_list = overlapping_columns.map { |col| "'#{col}'" }.join(", ")
|
||||||
|
Kernel.raise ArgumentError,
|
||||||
|
"Auxiliary table '#{aux_table_name}' defines column(s) #{column_list} " \
|
||||||
|
"that already exist(s) in main table '#{self.table_name}'. " \
|
||||||
|
"Auxiliary table columns must not overlap with main table columns."
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
mixes_in_class_methods(ClassMethods)
|
mixes_in_class_methods(ClassMethods)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -45,24 +45,16 @@ module HasAuxTable
|
|||||||
block.call(arel_attr, aux_bind)
|
block.call(arel_attr, aux_bind)
|
||||||
end
|
end
|
||||||
|
|
||||||
sig do
|
sig { params(method_name: Symbol).void }
|
||||||
params(
|
def define_aux_attribute_delegate(method_name)
|
||||||
main_class:
|
|
||||||
T.all(
|
|
||||||
T.class_of(ActiveRecord::Base),
|
|
||||||
ActiveRecord::Associations::ClassMethods,
|
|
||||||
Module
|
|
||||||
),
|
|
||||||
method_name: Symbol
|
|
||||||
).void
|
|
||||||
end
|
|
||||||
def define_aux_attribute_delegate(main_class, method_name)
|
|
||||||
aux_config = self
|
aux_config = self
|
||||||
main_class.define_method(method_name) do |*args, **kwargs|
|
aux_config
|
||||||
T.bind(self, ActiveRecord::Base)
|
.main_class
|
||||||
aux_model = aux_config.ensure_aux_target(self)
|
.define_method(method_name) do |*args, **kwargs|
|
||||||
T.unsafe(aux_model).public_send(method_name, *args, **kwargs)
|
T.bind(self, ActiveRecord::Base)
|
||||||
end
|
aux_model = aux_config.ensure_aux_target(self)
|
||||||
|
T.unsafe(aux_model).public_send(method_name, *args, **kwargs)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
sig do
|
sig do
|
||||||
|
|||||||
@@ -36,29 +36,20 @@ module HasAuxTable
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# sig do
|
sig do
|
||||||
# type_parameters(:T)
|
params(aux_config: HasAuxTable::AuxTableConfig).returns(T::Array[String])
|
||||||
# .params(
|
end
|
||||||
# obj: T::Class[T.type_parameter(:T)],
|
def self.attributes_for_inspect(aux_config)
|
||||||
# name: Symbol,
|
main_class = aux_config.main_class
|
||||||
# block: T.proc.bind(:T).void
|
|
||||||
# )
|
|
||||||
# .returns(Symbol)
|
|
||||||
# end
|
|
||||||
# def self.safe_define_method(obj, name, &block)
|
|
||||||
# end
|
|
||||||
|
|
||||||
# sig do
|
main_class_attributes =
|
||||||
# type_parameters(:T)
|
if main_class.attributes_for_inspect == :all
|
||||||
# .params(
|
main_class.attribute_names
|
||||||
# obj: T::Class[T.type_parameter(:T)],
|
else
|
||||||
# name: Symbol,
|
main_class.attributes_for_inspect
|
||||||
# block: T.proc.bind(T.attached_class).void
|
end
|
||||||
# )
|
|
||||||
# .returns(Symbol)
|
main_class_attributes
|
||||||
# end
|
end
|
||||||
# def self.safe_define_singleton_method(obj, name, &block)
|
|
||||||
# Module.safe_define_singleton_method(name, &block)
|
|
||||||
# end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -165,6 +165,25 @@ RSpec.describe HasAuxTable do
|
|||||||
it "does not include the aux table foreign key" do
|
it "does not include the aux table foreign key" do
|
||||||
expect(Car.inspect).not_to include("base_table_id")
|
expect(Car.inspect).not_to include("base_table_id")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "reports created_at, updated_at timestamp columns at the end of the list" do
|
||||||
|
expect(Car.inspect).to match(/\bfuel_type\b.+\bupdated_at\b/)
|
||||||
|
expect(Car.inspect).to match(/\bname\b.+\bupdated_at\b/)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "includes columns in instances of the model" do
|
||||||
|
car = Car.create!(name: "Honda Civic")
|
||||||
|
expect(car.inspect).to include("fuel_type")
|
||||||
|
expect(car.inspect).to include("engine_size")
|
||||||
|
expect(car.inspect).to include("created_at")
|
||||||
|
expect(car.inspect).to include("updated_at")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "puts _at columns at the end of the list on instances" do
|
||||||
|
car = Car.create!(name: "Honda Civic")
|
||||||
|
expect(car.inspect).to match(/\bfuel_type\b.+\bupdated_at\b/)
|
||||||
|
expect(car.inspect).to match(/\bname\b.+\bupdated_at\b/)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "database integration" do
|
describe "database integration" do
|
||||||
|
|||||||
Reference in New Issue
Block a user