From 6de3f31b6e6e845b92c07d1787ce48f6de8c2ab1 Mon Sep 17 00:00:00 2001 From: "L. Preston Sego III" Date: Fri, 12 Aug 2016 13:54:42 -0400 Subject: [PATCH] Namespace separator setting for json-api and tests (#1874) Adds jsonapi_namespace_separator configuration Also: * Enable getting type from record class without serializer Needs Followup: - https://github.com/rails-api/active_model_serializers/pull/1874#discussion_r74607042 - https://github.com/rails-api/active_model_serializers/pull/1874#discussion_r74607734 --- CHANGELOG.md | 3 ++ docs/general/configuration_options.md | 13 ++++++++ lib/active_model/serializer/configuration.rb | 3 +- .../adapter/json_api/resource_identifier.rb | 32 +++++++++++++------ test/adapter/json_api/linked_test.rb | 19 +++++++++++ .../json_api/resource_identifier_test.rb | 20 ++++++++++++ test/support/serialization_testing.rb | 8 +++++ 7 files changed, 88 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 129c0293..f0c937a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ Breaking changes: Features: +- [#1791](https://github.com/rails-api/active_model_serializers/pull/1791) (@bf4, @youroff, @NullVoxPopuli) + - Added `jsonapi_namespace_separator` config option. + Fixes: - [#1833](https://github.com/rails-api/active_model_serializers/pull/1833) Remove relationship links if they are null (@groyoh) diff --git a/docs/general/configuration_options.md b/docs/general/configuration_options.md index ba7974d9..e4ef79ae 100644 --- a/docs/general/configuration_options.md +++ b/docs/general/configuration_options.md @@ -72,6 +72,19 @@ Possible values: - `:singular` - `:plural` (default) +##### jsonapi_namespace_separator + +Sets separator string for namespaced models to render `type` attribute. + + +| Separator | Example: Admin::User | +|----|----| +| `'-'` (default) | 'admin-users' +| `'--'` (recommended) | 'admin--users' + +See [Recommendation for dasherizing (kebab-case-ing) namespaced object, such as `Admin::User`](https://github.com/json-api/json-api/issues/850) +for more discussion. + ##### jsonapi_include_toplevel_object Include a [top level jsonapi member](http://jsonapi.org/format/#document-jsonapi-object) diff --git a/lib/active_model/serializer/configuration.rb b/lib/active_model/serializer/configuration.rb index 7ee45fb4..2392883f 100644 --- a/lib/active_model/serializer/configuration.rb +++ b/lib/active_model/serializer/configuration.rb @@ -21,13 +21,14 @@ module ActiveModel config.default_includes = '*' config.adapter = :attributes + config.key_transform = nil config.jsonapi_resource_type = :plural + config.jsonapi_namespace_separator = '-'.freeze config.jsonapi_version = '1.0' config.jsonapi_toplevel_meta = {} # Make JSON API top-level jsonapi member opt-in # ref: http://jsonapi.org/format/#document-top-level config.jsonapi_include_toplevel_object = false - config.key_transform = nil config.schema_path = 'test/support/schemas' end diff --git a/lib/active_model_serializers/adapter/json_api/resource_identifier.rb b/lib/active_model_serializers/adapter/json_api/resource_identifier.rb index 4d07d11c..af6f5f9e 100644 --- a/lib/active_model_serializers/adapter/json_api/resource_identifier.rb +++ b/lib/active_model_serializers/adapter/json_api/resource_identifier.rb @@ -2,11 +2,30 @@ module ActiveModelSerializers module Adapter class JsonApi class ResourceIdentifier + def self.type_for(class_name, serializer_type = nil, transform_options = {}) + if serializer_type + raw_type = serializer_type + else + inflection = + if ActiveModelSerializers.config.jsonapi_resource_type == :singular + :singularize + else + :pluralize + end + + raw_type = class_name.underscore + raw_type = ActiveSupport::Inflector.public_send(inflection, raw_type) + raw_type + .gsub!('/'.freeze, ActiveModelSerializers.config.jsonapi_namespace_separator) + raw_type + end + JsonApi.send(:transform_key_casing!, raw_type, transform_options) + end + # {http://jsonapi.org/format/#document-resource-identifier-objects Resource Identifier Objects} def initialize(serializer, options) @id = id_for(serializer) - @type = JsonApi.send(:transform_key_casing!, type_for(serializer), - options) + @type = type_for(serializer, options) end def as_json @@ -19,13 +38,8 @@ module ActiveModelSerializers private - def type_for(serializer) - return serializer._type if serializer._type - if ActiveModelSerializers.config.jsonapi_resource_type == :singular - serializer.object.class.model_name.singular - else - serializer.object.class.model_name.plural - end + def type_for(serializer, transform_options) + self.class.type_for(serializer.object.class.name, serializer._type, transform_options) end def id_for(serializer) diff --git a/test/adapter/json_api/linked_test.rb b/test/adapter/json_api/linked_test.rb index 40317634..949bcf60 100644 --- a/test/adapter/json_api/linked_test.rb +++ b/test/adapter/json_api/linked_test.rb @@ -223,6 +223,25 @@ module ActiveModelSerializers assert_equal expected, relationships end + def test_underscore_model_namespace_with_namespace_separator_for_linked_resource_type + spammy_post = Post.new(id: 123) + spammy_post.related = [Spam::UnrelatedLink.new(id: 456)] + serializer = SpammyPostSerializer.new(spammy_post) + adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer) + relationships = with_namespace_separator '--' do + adapter.serializable_hash[:data][:relationships] + end + expected = { + related: { + data: [{ + type: 'spam--unrelated-links', + id: '456' + }] + } + } + assert_equal expected, relationships + end + def test_multiple_references_to_same_resource serializer = ActiveModel::Serializer::CollectionSerializer.new([@first_comment, @second_comment]) adapter = ActiveModelSerializers::Adapter::JsonApi.new( diff --git a/test/adapter/json_api/resource_identifier_test.rb b/test/adapter/json_api/resource_identifier_test.rb index fa91ff72..62b7d93b 100644 --- a/test/adapter/json_api/resource_identifier_test.rb +++ b/test/adapter/json_api/resource_identifier_test.rb @@ -39,6 +39,26 @@ module ActiveModelSerializers test_type_inflection(AuthorSerializer, 'authors', :plural) end + def test_type_with_namespace + Object.const_set(:Admin, Module.new) + model = Class.new(::Model) + Admin.const_set(:PowerUser, model) + serializer = Class.new(ActiveModel::Serializer) + Admin.const_set(:PowerUserSerializer, serializer) + with_namespace_separator '--' do + admin_user = Admin::PowerUser.new + serializer = Admin::PowerUserSerializer.new(admin_user) + expected = { + id: admin_user.id, + type: 'admin--power-users' + } + + identifier = ResourceIdentifier.new(serializer, {}) + actual = identifier.as_json + assert_equal(expected, actual) + end + end + def test_id_defined_on_object test_id(AuthorSerializer, @model.id.to_s) end diff --git a/test/support/serialization_testing.rb b/test/support/serialization_testing.rb index d696801d..7227b1b4 100644 --- a/test/support/serialization_testing.rb +++ b/test/support/serialization_testing.rb @@ -9,6 +9,14 @@ module SerializationTesting ActiveModelSerializers::SerializableResource.new(obj).to_json end + def with_namespace_separator(seperator) + original_seperator = ActiveModelSerializers.config.jsonapi_namespace_separator + ActiveModelSerializers.config.jsonapi_namespace_separator = seperator + yield + ensure + ActiveModelSerializers.config.jsonapi_namespace_separator = original_seperator + end + # Aliased as :with_configured_adapter to clarify that # this method tests the configured adapter. # When not testing configuration, it may be preferable