Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unify shapes of ActiveModel::Attributes #47804

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
83 changes: 51 additions & 32 deletions activemodel/lib/active_model/attribute.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,30 +35,69 @@ def initialize(name, value_before_type_cast, type, original_attribute = nil, val
@value_before_type_cast = value_before_type_cast
@type = type
@original_attribute = original_attribute
@value = value unless value.nil?
if value.nil?
@value = nil
@value_set = false
else
@value = value
@value_set = true
end
@value_for_database = nil
@value_for_database_set = false
end

def value
# `defined?` is cheaper than `||=` when we get back falsy values
@value = type_cast(value_before_type_cast) unless defined?(@value)
unless @value_set
@value = type_cast(value_before_type_cast)
@value_set = true
end
@value
end

def original_value
if assigned?
original_attribute.original_value
else
type_cast(value_before_type_cast)
end
def has_been_read?
@value_set
end

def value_for_database
if !defined?(@value_for_database) || type.changed_in_place?(@value_for_database, value)
if !@value_for_database_set || type.changed_in_place?(@value_for_database, value)
@value_for_database = _value_for_database
@value_for_database_set = true
end
@value_for_database
end

def init_with(coder)
@name = coder["name"]
@value_before_type_cast = coder["value_before_type_cast"]
@type = coder["type"]
@original_attribute = coder["original_attribute"]
if coder.map.key?("value")
@value = coder["value"]
@value_set = true
else
@value = nil
@value_set = false
end
@value_for_database = nil
@value_for_database_set = false
end

def encode_with(coder)
coder["name"] = name
coder["value_before_type_cast"] = value_before_type_cast unless value_before_type_cast.nil?
coder["type"] = type if type
coder["original_attribute"] = original_attribute if original_attribute
coder["value"] = value if @value_set
end

def original_value
if assigned?
original_attribute.original_value
else
type_cast(value_before_type_cast)
end
end

def serializable?(&block)
type.serializable?(value, &block)
end
Expand Down Expand Up @@ -108,10 +147,6 @@ def came_from_user?
false
end

def has_been_read?
defined?(@value)
end

def ==(other)
self.class == other.class &&
name == other.name &&
Expand All @@ -124,22 +159,6 @@ def hash
[self.class, name, value_before_type_cast, type].hash
end

def init_with(coder)
@name = coder["name"]
@value_before_type_cast = coder["value_before_type_cast"]
@type = coder["type"]
@original_attribute = coder["original_attribute"]
@value = coder["value"] if coder.map.key?("value")
end

def encode_with(coder)
coder["name"] = name
coder["value_before_type_cast"] = value_before_type_cast unless value_before_type_cast.nil?
coder["type"] = type if type
coder["original_attribute"] = original_attribute if original_attribute
coder["value"] = value if defined?(@value)
end

def original_value_for_database
if assigned?
original_attribute.original_value_for_database
Expand All @@ -153,7 +172,7 @@ def original_value_for_database
alias :assigned? :original_attribute

def initialize_dup(other)
if defined?(@value) && @value.duplicable?
if has_been_read? && @value.duplicable?
@value = @value.dup
end
end
Expand Down Expand Up @@ -181,7 +200,7 @@ def forgetting_assignment
# changed in place, we can simply dup this attribute to avoid
# deserialize / cast / serialize calls from computing the new
# attribute's `value_before_type_cast`.
if !defined?(@value_for_database) && !changed_in_place?
if !@value_for_database_set && !changed_in_place?
dup
else
super
Expand Down
8 changes: 3 additions & 5 deletions activerecord/lib/active_record/marshalling.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,15 @@

module ActiveRecord
module Marshalling
@format_version = 6.1
@format_version = 7.1

class << self
attr_reader :format_version

def format_version=(version)
case version
when 6.1
Methods.remove_method(:marshal_dump) if Methods.method_defined?(:marshal_dump)
when 7.1
Methods.alias_method(:marshal_dump, :_marshal_dump_7_1)
# do nothing
else
raise ArgumentError, "Unknown marshalling format: #{version.inspect}"
end
Expand All @@ -21,7 +19,7 @@ def format_version=(version)
end

module Methods
def _marshal_dump_7_1
def marshal_dump
payload = [attributes_for_database, new_record?]

cached_associations = self.class.reflect_on_all_associations.select do |reflection|
Expand Down
5 changes: 4 additions & 1 deletion activerecord/lib/active_record/relation/query_attribute.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ def type_cast(value)
end

def value_for_database
@value_for_database = _value_for_database unless defined?(@value_for_database)
unless @value_for_database_set
@value_for_database = _value_for_database
@value_for_database_set = true
end
@value_for_database
end

Expand Down