active_model_serializers/test/serializers/adapter_for_test.rb
Benjamin Fleischer af99c0d9e6 Ensure inheritance hooks run
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.
2015-09-09 08:55:20 -05:00

171 lines
6.6 KiB
Ruby

module ActiveModel
class Serializer
class AdapterForTest < Minitest::Test
UnknownAdapterError = ::ActiveModel::Serializer::Adapter::UnknownAdapterError
def setup
@previous_adapter = ActiveModel::Serializer.config.adapter
# Eager load adapters
ActiveModel::Serializer::Adapter.eager_load!
[:json_api, :flatten_json, :null, :json].each do |adapter_name|
ActiveModel::Serializer::Adapter.lookup(adapter_name)
end
end
def teardown
ActiveModel::Serializer.config.adapter = @previous_adapter
end
def test_returns_default_adapter
adapter = ActiveModel::Serializer.adapter
assert_equal ActiveModel::Serializer::Adapter::FlattenJson, adapter
end
def test_overwrite_adapter_with_symbol
ActiveModel::Serializer.config.adapter = :null
adapter = ActiveModel::Serializer.adapter
assert_equal ActiveModel::Serializer::Adapter::Null, adapter
ensure
ActiveModel::Serializer.config.adapter = @previous_adapter
end
def test_overwrite_adapter_with_class
ActiveModel::Serializer.config.adapter = ActiveModel::Serializer::Adapter::Null
adapter = ActiveModel::Serializer.adapter
assert_equal ActiveModel::Serializer::Adapter::Null, adapter
end
def test_raises_exception_if_invalid_symbol_given
ActiveModel::Serializer.config.adapter = :unknown
assert_raises UnknownAdapterError do
ActiveModel::Serializer.adapter
end
end
def test_raises_exception_if_it_does_not_know_hot_to_infer_adapter
ActiveModel::Serializer.config.adapter = 42
assert_raises UnknownAdapterError do
ActiveModel::Serializer.adapter
end
end
def test_adapter_class_for_known_adapter
klass = ActiveModel::Serializer::Adapter.adapter_class(:json_api)
assert_equal ActiveModel::Serializer::Adapter::JsonApi, klass
end
def test_adapter_class_for_unknown_adapter
assert_raises UnknownAdapterError do
ActiveModel::Serializer::Adapter.adapter_class(:json_simple)
end
end
def test_adapter_map
expected_adapter_map = {
'json'.freeze => ActiveModel::Serializer::Adapter::Json,
'json_api'.freeze => ActiveModel::Serializer::Adapter::JsonApi,
'flatten_json'.freeze => ActiveModel::Serializer::Adapter::FlattenJson,
'null'.freeze => ActiveModel::Serializer::Adapter::Null
}
assert_equal ActiveModel::Serializer::Adapter.adapter_map, expected_adapter_map
end
def test_adapters
assert_equal ActiveModel::Serializer::Adapter.adapters.sort, [
'flatten_json'.freeze,
'json'.freeze,
'json_api'.freeze,
'null'.freeze
]
end
def test_get_adapter_by_string_name
assert_equal ActiveModel::Serializer::Adapter.get('json'.freeze), ActiveModel::Serializer::Adapter::Json
end
def test_get_adapter_by_symbol_name
assert_equal ActiveModel::Serializer::Adapter.get(:json), ActiveModel::Serializer::Adapter::Json
end
def test_get_adapter_by_class
klass = ActiveModel::Serializer::Adapter::Json
assert_equal ActiveModel::Serializer::Adapter.get(klass), klass
end
def test_get_adapter_from_environment_registers_adapter
ActiveModel::Serializer::Adapter.const_set(:AdapterFromEnvironment, Class.new)
klass = ::ActiveModel::Serializer::Adapter::AdapterFromEnvironment
name = 'adapter_from_environment'.freeze
assert_equal ActiveModel::Serializer::Adapter.get(name), klass
assert ActiveModel::Serializer::Adapter.adapters.include?(name)
ensure
ActiveModel::Serializer::Adapter.adapter_map.delete(name)
ActiveModel::Serializer::Adapter.send(:remove_const, :AdapterFromEnvironment)
end
def test_get_adapter_for_unknown_name
assert_raises UnknownAdapterError do
ActiveModel::Serializer::Adapter.get(:json_simple)
end
end
def test_adapter
assert_equal ActiveModel::Serializer.config.adapter, :flatten_json
assert_equal ActiveModel::Serializer.adapter, ActiveModel::Serializer::Adapter::FlattenJson
end
def test_register_adapter
new_adapter_name = :foo
new_adapter_klass = Class.new
ActiveModel::Serializer::Adapter.register(new_adapter_name, new_adapter_klass)
assert ActiveModel::Serializer::Adapter.adapters.include?('foo'.freeze)
assert ActiveModel::Serializer::Adapter.get(:foo), new_adapter_klass
ensure
ActiveModel::Serializer::Adapter.adapter_map.delete(new_adapter_name.to_s)
end
def test_inherited_adapter_hooks_register_adapter
Object.const_set(:MyAdapter, Class.new)
my_adapter = MyAdapter
ActiveModel::Serializer::Adapter.inherited(my_adapter)
assert_equal ActiveModel::Serializer::Adapter.get(:my_adapter), my_adapter
ensure
ActiveModel::Serializer::Adapter.adapter_map.delete('my_adapter'.freeze)
Object.send(:remove_const, :MyAdapter)
end
def test_inherited_adapter_hooks_register_demodulized_adapter
Object.const_set(:MyNamespace, Module.new)
MyNamespace.const_set(:MyAdapter, Class.new)
my_adapter = MyNamespace::MyAdapter
ActiveModel::Serializer::Adapter.inherited(my_adapter)
assert_equal ActiveModel::Serializer::Adapter.get(:my_adapter), my_adapter
ensure
ActiveModel::Serializer::Adapter.adapter_map.delete('my_adapter'.freeze)
MyNamespace.send(:remove_const, :MyAdapter)
Object.send(:remove_const, :MyNamespace)
end
def test_inherited_adapter_hooks_register_subclass_of_registered_adapter
Object.const_set(:MyAdapter, Class.new)
my_adapter = MyAdapter
Object.const_set(:MySubclassedAdapter, Class.new(MyAdapter))
my_subclassed_adapter = MySubclassedAdapter
ActiveModel::Serializer::Adapter.inherited(my_adapter)
ActiveModel::Serializer::Adapter.inherited(my_subclassed_adapter)
assert_equal ActiveModel::Serializer::Adapter.get(:my_adapter), my_adapter
assert_equal ActiveModel::Serializer::Adapter.get(:my_subclassed_adapter), my_subclassed_adapter
ensure
ActiveModel::Serializer::Adapter.adapter_map.delete('my_adapter'.freeze)
ActiveModel::Serializer::Adapter.adapter_map.delete('my_subclassed_adapter'.freeze)
Object.send(:remove_const, :MyAdapter)
Object.send(:remove_const, :MySubclassedAdapter)
end
end
end
end