-
Notifications
You must be signed in to change notification settings - Fork 109
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
Add support to render with a root key and meta attributes #135
Conversation
ritikesh
commented
Feb 7, 2019
•
edited
Loading
edited
- Since Most JSON specs allow a root key to be configurable, we should allow that as well.
- Added option to support passing meta information along with the root element
@mcclayton Hey, I've got another one for you guys! Let me know your thoughts. 😄 |
@@ -167,6 +167,11 @@ def self.association(method, options = {}, &block) | |||
# @option options [Symbol] :view Defaults to :default. | |||
# The view name that corresponds to the group of | |||
# fields to be serialized. | |||
# @option options [Symbol|String] :root Defaults to nil. |
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 not enforced, currently [Any] :root
is more accurate.
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.
Wouldn't [Any]
refer to any object whereas, we would only allow for a symbol / string as a hash key? Documentation should reflect on what is the expected input, I wouldn't want to add a is_a
check in the method for that sake.
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'd rather deliberately decide to be open to different input or not. A type check is completely reasonable if we want to restrict to symbol/string. Is there a reason you don't want to enforce typing here?
A type coercion does happens regardless in .render
and .render_as_json
, but not in .render_as_hash
. This is because the underlying .as_json
and .jsonify
turn { 1 => "hello" }
into { "1" => "hello"}
. I think it's confusing and dishonest to allow that to happen while .render_as_hash
's underlying .prepare_for_render
leaves us with the original { 1 => "hello" }
.
I'm torn here because I don't think it would be wise for anyone to pass in an array, hash, or any random object, but technically now they can and it will not blow up. In those cases I would prefer to either document and fully support any
input or restrict the input and treat it as an error and handle it gracefully by returning an instructive error message. One of my biggest pains migrating to AMS v0.10 was the lack of helpful error messages and confusion about the breaking changes surrounding root
behavior specifically...
So let's look at some of the behavior this introduces:
# Hash as root with render_as_hash
# Expected?
UserBlueprint.render_as_hash(
user.where(id: 1, name: "wat"),
root: {"My object as a root" => "yeah it's crazy"}
)
{
{"My object as a root" => "yeah it's crazy"} => {
"id": 1,
"name": "wat"
}
}
# Hash as root with vanilla render
# Unexpected behavior of object becoming a string and then a key in the response.
UserBlueprint.render(
User.where(id: 1, name: "wat"),
root: {"My object as a root" => "yeah it's crazy"}
)
{
"{\"My object as a root\"=>\"yeah it's crazy\"}": {
"id": 1,
"name": "wat"
}
}
# Integer as root with render_as_hash
# Expected because it's a hash and maintains type
UserBlueprint.render_as_hash(
User.where(id: 1, name: "wat"),
root: 1
)
{
1: {
"id": 1,
"name": "wat"
}
}
# Integer as root with vanilla render
# Possibly unexpected coercion rather than an exception being thrown
UserBlueprint.render(
User.where(id: 1, name: "wat"),
root: 1
)
{
"1": {
"id": 1,
"name": "wat"
}
}
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 it's confusing and dishonest to allow that to happen
Overall, I understand where you are coming from, but I would disagree here, because the method definitions/documentations both suggest a json
or json like
output - which is pretty much self-explanatory to have strings as keys in the output.
Is there a reason you don't want to enforce typing here?
Adding extra .is_a?(Symbol) || .is_a?(String)
for every render call does seem like an unnecessary overhead for something that should be the implementers' job. I'm however, okay with changing the type to Any
in the comments.
I'm torn here because I don't think it would be wise for anyone to pass in an array, hash, or any random object
However, I still feel strongly about being indicative and not strict in this case - that you need to pass a symbol/string - This would cover most use-cases anyway.
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.
On the contrary, I agree with you, we need to have proper documentation and exceptions being raised when those are not followed. Have made the changes along with some refactoring. Please let me know if you have any other concerns.
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.
@AllPurposeName I think its ok to have it as [Symbol|String]
because that is what we want to support. We don't want to support [Any]
even though it is technically possible in this implementation.
Users can of course pass in Array or Hash or some other crazy type, and they would do so at their own risk. If we don't document passing in those types, we will not be responsible for breaking it in future updates.
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.
@AllPurposeName, @philipqnguyen, I've retained the Symbol | String
documentation and have added a check to raise exception if anything else is passed for root along with a test case for the same.
I agree with @AllPurposeName that it'd be counter intuitive not raising an exception when calling out that limited types are supported.
Along with that, I've done some other refactoring as well. The base class was becoming heavier with lot of helper methods and active record helper has not grown as it was anticipated. Hence, I have combined them into one and have moved all private helper methods there. You can refer to my most recent commit.
Let me know your thoughts.
@ritikesh thank you for another PR! I wonder if we can specify root key and/or meta data in the blueprint class itself? Maybe root key would be specified like this? class UserBlueprint
root :users
fields :first_name, last_name, :email
end Do you agree? As for meta, I'm not quite sure I like the meta being passed in to |
On second thought, passing in root and meta to |
@philipqnguyen I agree. That was the intention behind passing them to render. Better control and easier manipulation. Apart from plural differences ( |
Hi guys, any updates on this? |
@@ -0,0 +1,101 @@ | |||
module Blueprinter |
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 a great step in organization!
def prepare_for_render(object, options) | ||
view_name = options.delete(:view) || :default | ||
root = options.delete(:root) | ||
symbol_or_string_or_nil?(root, :root) |
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.
Not blocking: Not a fan of this method name, but I like what it does!
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.
Thanks for hashing it out to get to an even better solution.
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.
Awesome, looks great to me -- thanks so much for your work on this @ritikesh!
Just want one more re-review from @AllPurposeName or @philipqnguyen and I'll merge / release the this new Blueprinter version 🎉