Skip to content

Namespace separator setting #1609

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

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions docs/general/configuration_options.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,21 @@ Possible values:
- `:singular`
- `:plural` (default)

##### jsonapi_namespace_separator

Sets separator string for namespaced models to render `type` attribute. Default value is `--`.

Copy link
Member

Choose a reason for hiding this comment

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

An alternative name or perhaps alias would be dashed_namespace_separator (which might also make a nice PR into Rails, to make it easier to configure the replacement string from / to --)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not sure I understand. It's supposed to be a setting for any custom separator, not only dasherized. Why should it be called dashed_namespace_separator? The point, that I didn't use model_name from rails. Just plain and simple class name processing.

##### jsonapi_type_transform

Provides transform for `type` attribute. Class name `NicePost` gets converted into `nice_post`, `nice-post`, `NicePost` or `nicePost` depending on selected setting.

Possible values:

- `:underscore` (default)
- `:dashed`
- `:camel`
- `:snake`

##### jsonapi_include_toplevel_object

Include a [top level jsonapi member](http://jsonapi.org/format/#document-jsonapi-object)
Expand Down
2 changes: 2 additions & 0 deletions lib/active_model/serializer/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ def config.array_serializer

config.adapter = :attributes
config.jsonapi_resource_type = :plural
config.jsonapi_namespace_separator = '--'.freeze
config.jsonapi_type_transform = :underscore
config.jsonapi_version = '1.0'
config.jsonapi_toplevel_meta = {}
# Make JSON API top-level jsonapi member opt-in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@ def as_json

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
type = serializer.object.class.to_s.split('::')
type.map! { |t| KeyTransform.transform(t, ActiveModelSerializers.config.jsonapi_type_transform) }
type = type.join ActiveModelSerializers.config.jsonapi_namespace_separator
Copy link
Member

Choose a reason for hiding this comment

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

I was thinking this would be a good use-case for gsub, but it looks like that is would be marginally slower and a little harder to read, so just sharing in case it's of interest

Benchmark.realtime { 1000.times { 'AdminUser::Person::THING'.gsub(/[a-zA-Z]+(::|\z)/) do |sub| sub.underscore.downcase.sub('/','--') end } }
# => 0.07714433899673168
>> Benchmark.realtime { 1000.times { 'AdminUser::Person::THING'.split('::').map! {|t| t.underscore.downcase }.join('--') } }     
# => 0.07239663798827678

Copy link
Member

Choose a reason for hiding this comment

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

basically, this method can just piggy back on #1645 right @remear @youroff

Copy link
Member

Choose a reason for hiding this comment

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

that's what i'm thinking

if ActiveModelSerializers.config.jsonapi_resource_type == :plural
type = type.pluralize
end
type
end

def id_for(serializer)
Expand Down
21 changes: 21 additions & 0 deletions lib/active_model_serializers/key_transform.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,26 @@ def underscore(hash)
def unaltered(hash)
hash
end

# Transforms string to selected case
# Accepts string in any case: 'camel', 'undescore', 'dashed'.
#
# @example:
# transform('SomeClass', :underscore) => 'some_class'
# transform('some_class', :snake) => 'someClass'
# etc...
def transform(string, type)
string = string.underscore
case type.to_sym
when :dashed
string.dasherize
when :camel
string.camelize
when :snake
string.camelize(:lower)
else
string
end
end
end
end
2 changes: 1 addition & 1 deletion test/adapter/json_api/linked_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ def test_underscore_model_namespace_for_linked_resource_type
expected = {
related: {
data: [{
type: 'spam_unrelated_links',
type: 'spam--unrelated_links',
id: '456'
}]
}
Expand Down
60 changes: 29 additions & 31 deletions test/adapter/json_api/resource_identifier_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,62 +22,60 @@ class FragmentedSerializer < ActiveModel::Serializer; end
end

def test_defined_type
test_type(WithDefinedTypeSerializer, 'with_defined_type')
assert_identifier(WithDefinedTypeSerializer.new(@model), type: 'with_defined_type')
end

def test_singular_type
test_type_inflection(AuthorSerializer, 'author', :singular)
assert_with_confing(AuthorSerializer.new(@model), type: 'author', inflection: :singular)
end

def test_plural_type
test_type_inflection(AuthorSerializer, 'authors', :plural)
assert_with_confing(AuthorSerializer.new(@model), type: 'authors', inflection: :plural)
end

def test_type_with_namespace
spam = Spam::UnrelatedLink.new
assert_identifier(Spam::UnrelatedLinkSerializer.new(spam), type: 'spam--unrelated_links')
end

def test_type_with_custom_namespace
spam = Spam::UnrelatedLink.new
assert_with_confing(Spam::UnrelatedLinkSerializer.new(spam), type: 'spam/unrelated_links', namespace_separator: '/')
end

def test_id_defined_on_object
test_id(AuthorSerializer, @model.id.to_s)
assert_identifier(AuthorSerializer.new(@model), id: @model.id.to_s)
end

def test_id_defined_on_serializer
test_id(WithDefinedIdSerializer, 'special_id')
assert_identifier(WithDefinedIdSerializer.new(@model), id: 'special_id')
end

def test_id_defined_on_fragmented
FragmentedSerializer.fragmented(WithDefinedIdSerializer.new(@model))
test_id(FragmentedSerializer, 'special_id')
assert_identifier(FragmentedSerializer.new(@model), id: 'special_id')
end

private

def test_type_inflection(serializer_class, expected_type, inflection)
original_inflection = ActiveModelSerializers.config.jsonapi_resource_type
ActiveModelSerializers.config.jsonapi_resource_type = inflection
test_type(serializer_class, expected_type)
def assert_with_confing(serializer, opts = {})
inflection = ActiveModelSerializers.config.jsonapi_resource_type
namespace_separator = ActiveModelSerializers.config.jsonapi_namespace_separator
ActiveModelSerializers.config.jsonapi_resource_type = opts.fetch(:inflection, inflection)
ActiveModelSerializers.config.jsonapi_namespace_separator = opts.fetch(:namespace_separator, namespace_separator)
assert_identifier(serializer, opts)
ensure
ActiveModelSerializers.config.jsonapi_resource_type = original_inflection
end

def test_type(serializer_class, expected_type)
serializer = serializer_class.new(@model)
resource_identifier = ResourceIdentifier.new(serializer)
expected = {
id: @model.id.to_s,
type: expected_type
}

assert_equal(expected, resource_identifier.as_json)
ActiveModelSerializers.config.jsonapi_resource_type = inflection
ActiveModelSerializers.config.jsonapi_namespace_separator = namespace_separator
end

def test_id(serializer_class, id)
serializer = serializer_class.new(@model)
resource_identifier = ResourceIdentifier.new(serializer)
inflection = ActiveModelSerializers.config.jsonapi_resource_type
type = @model.class.model_name.send(inflection)
def assert_identifier(serializer, opts = {})
identifier = ResourceIdentifier.new(serializer)
expected = {
id: id,
type: type
id: opts.fetch(:id, identifier.as_json[:id]),
type: opts.fetch(:type, identifier.as_json[:type])
}

assert_equal(expected, resource_identifier.as_json)
assert_equal(expected, identifier.as_json)
end
end
end
Expand Down