-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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
WIP: Restore legacy ams::model behavior #1998
Conversation
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.
Just posted some thoughts.
# Configuration to avoid a breaking change with older versions of this class which lacked defined attributes. | ||
# Previous behavior was: 1) initialized attributes were the | ||
class_attribute :attributes_are_always_the_initialization_data, instance_writer: false, instance_reader: false | ||
self.attributes_are_always_the_initialization_data = false # remove this commit, just for demonstration |
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.
remove this commit?
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.
So it looks like if someone wanted the old functionality, they'd just set attributes_are_always_the_initialization_data
to true at the top of their class inheriting from AMS::Model?
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.
But, I don't see an include AttributesAreAlwaysTheInitializationData
anywhere. Do you want to add tests for this?
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.
sorry to be unclear, I just added Hey look it passes to show that I didn't break the existing test suite when 'false'. I'm going to remove that commit :)
@@ -456,7 +456,7 @@ def use_adapter? | |||
end | |||
end | |||
|
|||
def test_render_event_is_emmited |
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.
so many spelling errors, nice finds.
def self.inherited(subclass) | ||
if subclass.attributes_are_always_the_initialization_data | ||
unless subclass.included_modules.include?(AttributesAreAlwaysTheInitializationData) | ||
subclass.prepend(AttributesAreAlwaysTheInitializationData) |
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.
This is curious. Is this changing the default include Module
functionality to make the module be a kind of superclass to the class using the module?
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.
prepend
is basically the same as include
except it inserts the module below instead of above the class in the inheritance chain, such that the module can override the class' methods.
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.
oh ok, I thought it went the other way.
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.
@NullVoxPopuli this is just a compatibility layer to avoid a breaking change. I should add some docs around setting it true vs. false and how to do both at same time and why :)
bba724c
to
c641ba1
Compare
c641ba1
to
faed3b0
Compare
@@ -6,7 +6,21 @@ class Model | |||
include ActiveModel::Serializers::JSON | |||
include ActiveModel::Model | |||
|
|||
class_attribute :attribute_names | |||
# Configuration to avoid a breaking change with older versions of this class which lacked defined attributes. | |||
# Previous behavior was: 1) initialized attributes were the |
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.
what in the world happened to this comment?
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 what it's trying to say is that the 'legacy' behavior of the attributes
hash was that it was always the value of the attributes passed in at initialization, regardless of what attr_*
methods were defined.
This change now lets you define the attributes so that 1) unset attributes are now nil and 2) you can change the attributes after initialization by using the attr_writer, rather than hackily mutating the return value of the attributes
method 3) the return value of the attributes
method is now frozen. since it is always derived, trying to mutate it is strongly discouraged :)
The details is that the 'old' behavior is only mixed in when inheriting and attributes_are_always_the_initialization_data
is true
, which is the default, which is good for backward compatibility. However, new apps probably want to change the default to false
, and old apps want to create a special new model subclass where it is false for use in new models.
For now 'attributes' and associations are both treated the same, since I'm not sure that it makes sense for a PORO to have associations outside of our tests.
@@ -1,3 +1,4 @@ | |||
ActiveModelSerializers::Model.attributes_are_always_the_initialization_data = false |
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.
see https://github.com/rails-api/active_model_serializers/pull/1998/files#r93893962 but the test poro superclass can't have the legacy behavior included because there's no simple way to remove it. Instead, the test poro superclass has the 'new' behavior, but, subclasses of it, by default, will have the 'old' behavior. Basically, it kind of extends the AMS::Model in the test context so that we can test it with the legacy behavior on and off.
module ActiveModelSerializersWithoutLegacyModelSupport | ||
module_function | ||
|
||
def poro_without_legacy_model_support(superklass = ActiveModelSerializers::Model, &block) |
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.
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.
An example test helper I derived from one I wrote in some unmerged commits
@@ -3,6 +3,14 @@ | |||
module ActionController | |||
module Serialization | |||
class AdapterSelectorTest < ActionController::TestCase | |||
Profile = poro_without_legacy_model_support(::Model) do |
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.
|
||
# Defaults to the downcased model name. | ||
def id | ||
@initialized_attributes.fetch(:id) { self.class.model_name.name && self.class.model_name.name.downcase } |
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.
probably not a good default, but that was the old behavior.
end | ||
|
||
def attributes | ||
@initialized_attributes |
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.
yup, they can be mutated https://github.com/rails-api/active_model_serializers/pull/1998/files#r93893962
@@ -37,7 +51,7 @@ def initialize(attributes = {}) | |||
# +attributes+ are returned frozen to prevent any expectations that mutation affects | |||
# the actual values in the model. | |||
def attributes | |||
attribute_names.each_with_object({}) do |attribute_name, result| | |||
self.class.attribute_names.each_with_object({}) do |attribute_name, result| |
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.
removed the instance reader/writer since the ActiveModel::Serialization mixin in Rails uses an attribute_names
local variable, which I found because it conflicted in some scenario I've forgotten
faed3b0
to
b0bf140
Compare
b0bf140
to
ffcc3e4
Compare
Closed by #2022 |
Needed to make #1982 and #1984 not a breaking change.