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

WIP: Restore legacy ams::model behavior #1998

Closed
wants to merge 8 commits into from

Conversation

bf4
Copy link
Member

@bf4 bf4 commented Dec 12, 2016

Needed to make #1982 and #1984 not a breaking change.

  • Successfully tested in an app that broke in various ways on master without this PR, but works with it
  • Why this is wip? now we need tests for with and without the prepended module, for both the 'new' and 'legacy' behavior
  • Implementation-wise: Why it's prepended on inherited? That way, it can be easily disabled.
  • Next steps: I intend to do something similar for caching, so that it is optional to include caching concerns in the poro, where it doesn't usually make sense.

Copy link
Contributor

@NullVoxPopuli NullVoxPopuli left a 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove this commit?

Copy link
Contributor

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?

Copy link
Contributor

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?

Copy link
Member Author

@bf4 bf4 Dec 13, 2016

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
Copy link
Contributor

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)
Copy link
Contributor

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?

Copy link
Contributor

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.

Copy link
Contributor

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.

Copy link
Member Author

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 :)

@bf4 bf4 force-pushed the restore_legacy_ams_model_behavior branch from bba724c to c641ba1 Compare December 16, 2016 19:44
@bf4 bf4 force-pushed the restore_legacy_ams_model_behavior branch from c641ba1 to faed3b0 Compare December 26, 2016 15:21
@@ -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
Copy link
Member Author

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?

Copy link
Member Author

@bf4 bf4 Dec 27, 2016

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
Copy link
Member Author

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)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -3,6 +3,14 @@
module ActionController
module Serialization
class AdapterSelectorTest < ActionController::TestCase
Profile = poro_without_legacy_model_support(::Model) do
Copy link
Member Author

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 }
Copy link
Member Author

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
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -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|
Copy link
Member Author

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

@bf4 bf4 mentioned this pull request Jan 6, 2017
@bf4 bf4 force-pushed the restore_legacy_ams_model_behavior branch from faed3b0 to b0bf140 Compare January 7, 2017 04:13
@bf4
Copy link
Member Author

bf4 commented Jan 9, 2017

Closed by #2022

@bf4 bf4 closed this Jan 9, 2017
@bf4 bf4 deleted the restore_legacy_ams_model_behavior branch January 9, 2017 04:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants