As an example, all serializers implement `#object` as a reference to the
object being esrialized, but this was preventing adding a key to the
serialized representation with the `object` name.
Instead of having attributes directly map to methods on the serializer,
we introduce one layer of abstraction: the `_attributes_map`. This hash
maps the key names expected in the output to the names of the
implementing methods.
This simplifies some things (removing the need to maintain both
`_attributes` and `_attribute_keys`), but does add some complexity in
order to support overriding attributes by defining methods on the
serializer. It seems that with the addition of the inline-block format,
we may want to remove the usage of programatically defining methods on
the serializer for this kind of customization.
Breaking change:
- Adapters now inherit Adapter::Base
- 'Adapter' is now a module, no longer a class
Why?
- using a class as a namespace that you also inherit from is complicated and circular at time i.e.
buggy (see https://github.com/rails-api/active_model_serializers/pull/1177)
- The class methods on Adapter aren't necessarily related to the instance methods, they're more
Adapter functions
- named `Base` because it's a Rails-ism
- It helps to isolate and highlight what the Adapter interface actually is
I was seeing transient failures where adapters may not be registered.
e.g. https://travis-ci.org/rails-api/active_model_serializers/builds/77735382
Since we're using the Adapter, JsonApi, and Json classes
as namespaces, some of the conventions we use for modules don't apply.
Basically, we don't want to define the class anywhere besides itself.
Otherwise, the inherited hooks may not run, and some adapters may not
be registered.
For example:
If we have a class Api `class Api; end`
And Api is also used as a namespace for `Api::Product`
And the classes are defined in different files.
In one file:
```ruby
class Api
autoload :Product
def self.inherited(subclass)
puts
p [:inherited, subclass.name]
puts
end
end
```
And in another:
```ruby
class Api
class Product < Api
def sell_sell_sell!
# TODO: sell
end
end
end
```
If we load the Api class file first, the inherited hook will be defined on the class
so that when we load the Api::Product class, we'll see the output:
```plain
[ :inherited, Api::Product]
```
However, if we load the Api::Product class first, since it defines the `Api` class
and then inherited from it, the Api file was never loaded, the hook never defined,
and thus never run.
By defining the class as `class Api::Product < Api` We ensure the the Api class
MUST be defined, and thus, the hook will be defined and run and so sunshine and unicorns.
Appendix:
The below would work, but triggers a circular reference warning.
It's also not recommended to mix require with autoload.
```ruby
require 'api'
class Api
class Product < Api
def sell_sell_sell!
# TODO: sell
end
end
end
```
This failure scenario was introduced by removing the circular reference warnings in
https://github.com/rails-api/active_model_serializers/pull/1067
Style note:
To make diffs on the adapters smalleer and easier to read, I've maintained the same
identention that was in the original file. I've decided to prefer ease of reading
the diff over style, esp. since we may later return to the preferred class declaration style.
with '#' will be ignored, and an empty message aborts the commit.
Changes:
- Introduce Adapter::get for use by Serializer.adapter
- Move Adapter-finding logic from Adapter::adapter_class into Adapter::get
Introduced interfaces:
- non-inherited methods
```ruby
ActiveModel::Serializer::Adapter.adapter_map # a Hash<adapter_name, adapter_class>
ActiveModel::Serializer::Adapter.adapters # an Array<adapter_name>
ActiveModel::Serializer::Adapter.register(name, klass) # adds an adapter to the adapter_map
ActiveModel::Serializer::Adapter.get(name_or_klass) # raises Argument error when adapter not found
```
- Automatically register adapters when subclassing
```ruby
def self.inherited(subclass)
ActiveModel::Serializer::Adapter.register(subclass.to_s.demodulize, subclass)
end
```
- Preserves subclass method `::adapter_class(adapter)`
```ruby
def self.adapter_class(adapter)
ActiveModel::Serializer::Adapter.get(adapter)
end
```
- Serializer.adapter now uses `Adapter.get(config.adapter)` rather than have duplicate logic
* Move all associations related code from Serializer class to Associations module
* Introduce Reflection class hierarchy
* Introduce Association class
* Rid off Serializer#each_association
* Introduce Serializer#associations enumerator
Comparing as a JSON string vs. as the Hash that is convert to JSON
works around the different Hash representations.
This likely has to do with the introduction of
config.action_dispatch.perform_deep_munge in Rails 4.1
See Rails issue 13420
1) Failure:
ActiveModel::Serializer::Adapter::Json::HasManyTestTest#test_has_many_with_no_serializer
[active_model_serializers/test/adapter/json/has_many_test.rb:36]:
--- expected
+++ actual
@@ -1 +1 @@
-{:id=>42, :tags=>[{"attributes"=>{"id"=>1, "name"=>"#hash_tag"}}]}
+{:id=>42, :tags=>[{"attributes"=>{:id=>1, :name=>"#hash_tag"}}]}
2) Failure:
ActiveModel::Serializer::AssociationsTest#test_has_many_with_no_serializer
[active_model_serializers/test/serializers/associations_test.rb:74]:
--- expected
+++ actual
@@ -1 +1 @@
-[{"attributes"=>{"name"=>"#hashtagged"}}]
+[{"attributes"=>{:name=>"#hashtagged"}}]
It's an upgrade based on the new Cache implementation #693.
It allows to use the Rails conventions to cache
specific attributes or associations.
It's based on the Cache Composition implementation.
In some cases, we want to pass arguments from the controller and we want
to serializer a resource according to that. This allows serializers to
use the `options` method to retrieve whatever was passed in via
arguments.
It's a new implementation of cache based on ActiveSupport::Cache.
The implementation abstracts the cache in Adapter class on a
private method called cached_object, this method is intended
to be used on Adapters inside serializable_hash method in order
to cache each instance of the object that will be returned by
the serializer.
Some of its features are:
- A different syntax. (no longer need the cache_key method).
- An options argument that have the same arguments of ActiveSupport::Cache::Store, plus a key option that will be the prefix of the object cache on a pattern "#{key}-#{object.id}".
- It cache the objects individually and not the whole Serializer return, re-using it in different requests (as a show and a index method for example.)
Currently, 0.10.0.pre doesn't support `meta` option in `render`. This
way, there's no way to support features such as pagination. `0.9` had
this feature in place.
This adds support for it, as well as fixes small things in README.md.
This won't support `meta` in array responses because arrays don't have
keys, obviously. Also, the response should have a `root` key, otherwise
no `meta` will be included.
In some cases, for example using JsonApi, ArraySerializer will result in
a response with a `root`. In that case, `meta` will be included.
Currently, doing `include: author.bio` would work correctly, but not for
has_many associations such as `include: author.roles`. This fixes it.
The problem was basically that we were not handling arrays for has_many linked,
as happens for ArraySerializers.
The options passed to the render are partitioned into adapter options
and serializer options. 'include' and 'root' are sent to the adapter,
not sure what options would go directly to serializer, but leaving this
in until I understand that better.