-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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
Provide key transformation #1574
Conversation
@rails-api/ams @rails-api/commit Thoughts on this approach? |
ref: AMS Rails JSON API
|
@@ -40,7 +40,7 @@ def test_toplevel_links | |||
stuff: 'value' | |||
} | |||
} | |||
}).serializable_hash | |||
}).serializable_hash(@options) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
❓ I don't think this should be here. Not defined anywhere and we haven't been passing anything interesting into the renderer options
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there's a bug in this right now #1572
Looks good. you up for benchmarking it? maybe https://github.com/bf4/book_code/blob/master/adrpo/code/chp8/assert_performance.rb or something.. can go in |
I added a Also, note that rubocop wanted a lot of changes that weren't actually within the scope of this PR. I went ahead and fixed them in a separate commit. |
The rubocop failures I see in travis right now look like the inverse of what it wanted locally, rubocop-0.38.0. |
#1577 I have a PR to fix the JRuby's.. should be green soon |
That guard clause needs an inline And I know there's a PR outstanding that appends |
@@ -51,6 +51,20 @@ def include_meta(json) | |||
json[meta_key] = meta if meta | |||
json | |||
end | |||
|
|||
def translate_key_casing!(value, serialization_context) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thoughts on writing these transforms in the style of active support inflections? #1566
at least for the sake of examples in comments,
maybe the key transforms could be in their own module?
module WhateverNamespace::KeyTransforms
module_function
# transforms keys to UpperCamelCase or PascalCase (this is what through me down this train of thought in the first place, though, this could be because I've been in non-ruby land at work)
#
# @example:
# "some_key" => "SomeKey",
# @see: active_support/inflector#camelize
def camel(hash)
hash.deep_transform_keys! { |key| key.to_s.camelize.to_sym }
end
...
end
and then this method would become:
return value unless serialization_context
transform = serialization_context.key_case
KeyTransforms.send(transform)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not necessarily opposed to it. Are there other benefits to this approach over what's in the PR?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it would give a place for users to add their own transformations if they wish. Rather than overwriting translate_key_casing, they write their own transform method -- without losing or having to re-implement the 3 you have. I could see this being especially useful, for APIs that have clients that don't all expect the same casing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair enough. I just prefer to focus efforts on making features customizable where there's demand, not just because it can be or to support rare edge cases. I can see some cases where it might be desired to add a custom key translator, but how often would someone want to do that with a default set of, camel
, camel_lower
, and dashed
? Is this a use case we want to spend time covering now and supporting in the future? Should we wait on supporting that until someone asks and/or submits a PR? If it's important, let's make a better interface happen.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand your point - what triggers this thought is partially the ?
Pr / issue. Like, if someone wants to render attribute with ?
they could keep / remove it as they desire.
But other than that one issue, I haven't heard of any desires for key formats other than what you have here.
@NullVoxPopuli Implemented. Let me know what you think. |
Also note that now only the |
@@ -0,0 +1,43 @@ | |||
module ActiveModelSerializers | |||
module KeyTransform |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it may be benificial to mention the calling method: https://github.com/rails-api/active_model_serializers/pull/1574/files#diff-426cb6b4484fa95a18649ea3cc74474aR62
it may help future devs, idk
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
perhaps module_function
here to allow public calling on the module but private methods on inclusion. I like me some utility function modules
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It used to be in this file. not sure what happened to it.
can you rebase, so you get the new JRuby modifications from @bf4 ? |
@@ -33,7 +33,7 @@ def serialization_scope_name=(scope_name) | |||
def adapter | |||
@adapter ||= ActiveModelSerializers::Adapter.create(serializer_instance, adapter_opts) | |||
end | |||
alias adapter_instance adapter | |||
alias_method :adapter_instance, :adapter |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
❗ revert, revert!
@remear changeset should include |
# @see: active_support/inflector#camelize | ||
def camel(hash) | ||
hash.deep_transform_keys! { |key| key.to_s.camelize.to_sym } | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think here you might mean to use a link like # @see {https://github.com/rails/rails/blob/v5.0.0.beta3/activesupport/lib/active_support/inflector/methods.rb#L66-L76 ActiveSupport::Inflector.camelize}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't know you could use that syntax for an actual link -- legit
I'm not sure how |
|
||
def self.method_missing(m, *args, &block) | ||
warn "\"#{m}\" is not a defined key transform. Defaulting to :unaltered." | ||
send(:unaltered, *args, &block) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is super dangerous to eat a method missing entirely. I think the proper behavior here is to raise. I mean, if I say KeyTransform.micky_mouse
it should fail, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you mean you don't want to silently fail with a warning and default behavior? and would rather have it barf?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It just seemed nicer to just give back what was supplied and warn if they called an undefined method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@remear @NullVoxPopuli So, before I asked:
If I say
KeyTransform.micky_mouse
it should fail, right?
Here's a better example, I think:
KeyTransform.camellower
. Person mistyped camel_lower
and now instead of a NoMethodError they're getting back :unaltered
. That's surprising.
I feel pretty strongly this should fail fast. The warning message is good, but should be a failure message.
Also, since you're defining the method missing on the module, which you are including in Adapter::Base
, you are also eating up all method missing calls on the Adapter::Base
. Just seems like asking for trouble with only benefit is a silent failure with a warning somewhere in the logs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oooo, that's a good point. that is a bad idea then.
@remear |
|
||
##### Adapter default | ||
|
||
Each adapter has a default key transform configured: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice, I was going to ask about that
LGTM 👍 |
- `:camel` - ExampleKey | ||
- `:camel_lower` - exampleKey | ||
- `:dashed` - example-key | ||
- `:unaltered` - the original, unaltered key |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
missing default. perhaps intentional, but the others have it so, should mention something
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was intentional because it's mentioned elsewhere, but a mention would be good.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok, I see that the missing default option is nil: use the adapter default
@remear 💯 we need to add a controller test. We've gotten in trouble before when not writing those :) |
# "some_key" => "SomeKey", | ||
# @see {https://github.com/rails/rails/blob/master/activesupport/lib/active_support/inflector/methods.rb#L66-L76 ActiveSupport::Inflector.camelize} | ||
def camel(hash) | ||
hash.deep_transform_keys! { |key| key.to_s.camelize.to_sym } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
at top of file we should add require 'active_support/core_ext/hash/keys'
to ensure the extension is loaded.
a56b942
to
e7438fe
Compare
|
||
##### jsonapi_resource_type | ||
|
||
Set the `type` attributes of resources to singular or plural. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Set the
type
attributes
Using attributes
here seems inappropriate. What about something like this:
Sets whether the [type](http://jsonapi.org/format/#document-resource-identifier-objects) of the JSON API resource should be `singularized` or `pluralized` when it is not [explicitly specified by the serializer](https://github.com/rails-api/active_model_serializers/blob/master/docs/general/serializers.md#type):
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It could use more clarification. I didn't go out of my way to refactor the content here.
This is already in the JSON API section so the copy should read within that context.
Sets whether the [type](http://jsonapi.org/format/#document-resource-identifier-objects) of the resource should be `singularized` or `pluralized` when it is not [explicitly specified by the serializer](https://github.com/rails-api/active_model_serializers/blob/master/docs/general/serializers.md#type)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good.
@bf4, @remear Basically, we're transforming keys only... This also suggests the action controller tests written for this were incomplete. |
It doesn't cover everything yet. For example, this, ideally, would have been transformed from |
Still - this was greatly needed. Thanks! |
Purpose
Provide key case translation on a per-request basis.
render json: posts, each_serializer: PostSerializer, key_transform: :camel_lower
A global config option may be set to configure the key transform.
ActiveModelSerializers.config.key_transform = :camel_lower
Changes
key_transform
attribute toSerializationContext
.key_transform
option toConfiguration
.JsonApi
adapter.Json
adapter.camel
-SpecialAttribute
camel_lower
-specialAttribute
dashed
-special-attribute
(default as per JSON API recommendation)unaltered
- the original, unaltered keySerializationContext
- Thekey_transform
is provided as an option via render.key_transform is set in
ActiveModelSerializers.config.key_transform`.key_transform
specified in the adapter.