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

[RFC] Deprecate ActiveModelSerializers::Model #1926

Open
richmolj opened this issue Sep 14, 2016 · 0 comments
Open

[RFC] Deprecate ActiveModelSerializers::Model #1926

richmolj opened this issue Sep 14, 2016 · 0 comments
Labels

Comments

@richmolj
Copy link
Contributor

richmolj commented Sep 14, 2016

Summary

The documentation on serializing POROs leads one to believe the PORO should subclass ActiveModelSerializers::Model. There are three issues with this:

  • Subclassing is a bad practice when a mixin will do. What if I already have a superclass?
  • ActiveModelSerializers::Model provides a bunch of extra functionality that is not needed by this library. So POROs are polluted for no reason.
  • This gives the false impression that ActiveModelSerializers::Model or the contract that it provides is somehow relevant to the rest of this library.

Motivation

  • Because as far as I can tell AMS does not need objects to implement this interface. Maybe at one point it did, but this test shows POROs can be serialized without any special mixin, interface, etc.

    The only code change to make that work is:

    def read_attribute_for_serialization(attr)
      if respond_to?(attr)
        send(attr)
      else
    -     object.read_attribute_for_serialization(attr)
    +     if object.respond_to?(:read_attribute_for_serialization)
    +       object.read_attribute_for_serialization(attr)
    +     else
    +       object.send(attr)
    +     end
      end
    end

    So to me it's like we might as well lint that these objects respond to foo; it's arbitrary.

  • This provides a better experience to end users. No special steps required to serialize POROs.

  • Currently the documentation is misleading / point of ActiveModelSerializers::Model is misleading. It says in order to serialize POROs I should subclass this, but it actually adds a lot of stuff to my object I may not want (polluting my PORO). For instance these objects now respond to updated_at...I would never expect that as an end user. In addition, most of my objects are Virtus objects, which has its own @attributes implementation that just happens to work right now but is ripe for future conflicts.

  • The logic for constructors/attributes/errors etc might be nice to have, but it's not relevant to a serialization library. I would rather this move to something like an extension.

  • The fact that we're having this discussion is one of the problems. Yeah, we lint these objects quack like a certain kind of duck, but nowhere do we assert why they have to quack that way. If I implemented a feature that needed objects to respond to as_json, but a few months later that feature gets removed, nobody knows if we can remove as_json from the lint or not.

  • Begins to remove Rails dependencies, so this library could be used in Sinatra et al without requiring ActiveModel and friends.

  • I prefer a more composable approach so I pollute my POROs the minimal amount possible. I'll start with a PORO that can be serialized by default, but I'll add ActiveModelSerializers::CacheSerializable if my serializer needs the caching feature. Or (this would be my preference) we can implement caching so that it doesn't have this requirement at all. For instance it seems easy to put cache_key as a method on a serializer, instead of on the object we are serializing.

Detailed design

Once again, make this code change:

  def read_attribute_for_serialization(attr)
    if respond_to?(attr)
      send(attr)
    else
-     object.read_attribute_for_serialization(attr)
+     if object.respond_to?(:read_attribute_for_serialization)
+       object.read_attribute_for_serialization(attr)
+     else
+       object.send(attr)
+     end
      end
    end

Then, create ActiveModelSerializers::CacheSerializable and ActiveModelSerializers::ErrorSerializable. These will implement only the bits of ActiveModelSerializers::Model that are needed for those purposes.

If possible, both those mixins should be immediately deprecated as well. For instance it seems like an easy change to use the serializer's updated_at instead of requiring the object respond to updated_at. This would be better code in any case. Suggest these conversations happen in separate, targeted PRs.

Finally, remove the lint tests since these are both unnecessary and misleading.

Drawbacks

We'd probably add something to our test POROs so they could keep the otherwise-irrelevant constructor functionality ActiveModelSerializers::Model provides. So you could make a case our tests wouldn't be testing on true POROs.

Suggest handling this by adding a separate test(s) specific to vanilla POROs.

Alternatives

We could avoid the code change mentioned above, and instead implement a mixin that does the same:

module ActiveModelSerializers
  module Serializable
    def self.included(klass)
      klass.class_eval do
        alias :read_attribute_for_serialization, :send
      end
    end
  end
end

I would support this alternative, though I think it's kind of silly and puts extra work on our users without justification.

Unresolved questions

In the 0.8x series, my_model.as_json would use AMS to serialize. This functionality is not currently in 0.10.x, though there are many comments about doing so.

I suggest this is the true purpose of a future ActiveModelSerializers::Serializable mixin, that should be independent of this issue (and completely optional to users). If that's not the case, I imagine we would double down on ActiveModelSerializers::Model and monkey-patch ActiveRecord...so the unresolved question would be "is that really a good idea"...

Additional Information

Prior discussion here: #1911

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants