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
Introduce an Attribute object to handle the type casting dance #15593
Conversation
@@ -249,10 +249,12 @@ def relation #:nodoc: | |||
# # Instantiates a single new object | |||
# User.new(first_name: 'Jamie') | |||
def initialize(attributes = nil, options = {}) | |||
defaults = self.class.raw_column_defaults.dup | |||
defaults.each { |k, v| defaults[k] = v.dup if v.duplicable? } | |||
defaults = Hash[self.class.raw_column_defaults.map { |k, v| |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pretty heavy logic here. could it be extracted to private method like
@attributes = attributes_from_raw_column_defaults
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed, not happy with defaults right now, planning much more work on those specifically. Needed to cut the PR at some point. Would rather this stay as close to the original structure as possible for the time being.
Do I read it correctly that this will now allocate an |
And allocate one less string per attribute, one less hash per Base object,
|
One hash for multiple Where do you save a string? |
The keys of raw_attributes
|
Damn, I remember a PR that attempted to pre- |
Unsure. This is also a first step, which should enable me to reduce object allocations overall once I start tackling |
AR object or AR class? Instantiating an attribute should happen once per column (or user defined) attribute definition, but not every time an AR object is instantiated and attributes populated, right? |
AR object (not class).
No, it seems to be 1x |
That is correct. However, for each |
Does the attribute hold any instance data? If not, it can be a singleton (and this can be optimized later) |
Yes, it does hold instance data. It keeps the value before and after type casting, and is responsible for managing that. |
end | ||
|
||
def value | ||
@value = type_cast(value_before_type_cast) unless defined?(@value) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is not better to set @value
to nil
on the initializer and avoid the call to defined?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess the type_cast
could return nil
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My concern was that type_cast
can return falsy values, and we'd like to avoid performing that computation multiple times. That said, I can't imagine any branch that ends with nil
or false
being that expensive. Any thoughts? Worth a benchmark?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think is worth to benchmark. If it is only an optimisation we need to make clear because I imagine if I change it to use ||=
tests will not fail and since there is no information about this optimisation it will be lost.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Benchmark showed defined?
is faster when a value is falsy, even when the type does no work but check if it got nil
. I do have an explicit test for this case, I'll add a comment as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated. Any more feedback?
There's a lot more that can be moved to these, but this felt like a good place to introduce the object. Plans are: - Remove all knowledge of type casting from the columns, beyond a reference to the cast_type - Move type_cast_for_database to these objects - Potentially make them mutable, introduce a state machine, and have dirty checking handled here as well - Move `attribute`, `decorate_attribute`, and anything else that modifies types to mess with this object, not the columns hash - Introduce a collection object to manage these, reduce allocations, and not require serializing the types
Introduce an Attribute object to handle the type casting dance
Depends on #15429 and includes the changes from that PR as well. There's a lot more that can be moved to these, but this felt like a good place to introduce the object. Plans are:
reference to the
cast_type
type_cast_for_database
to these objectsdirty checking handled here as well
attribute
,decorate_attribute
, and anything else thatmodifies types to mess with this object, not the columns hash