Add support to render with a root key and meta attributes#135
Add support to render with a root key and meta attributes#135mcclayton merged 10 commits intoprocore-oss:masterfrom ritikesh:master
Conversation
|
@mcclayton Hey, I've got another one for you guys! Let me know your thoughts. 😄 |
| # @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.
This is not enforced, currently [Any] :root is more accurate.
There was a problem hiding this comment.
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.
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.
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.
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.
@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.
@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
endDo 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.
💯 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.
Not blocking: Not a fan of this method name, but I like what it does!
AllPurposeName
left a comment
There was a problem hiding this comment.
Thanks for hashing it out to get to an even better solution.
mcclayton
left a comment
There was a problem hiding this comment.
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 🎉
Uh oh!
There was an error while loading. Please reload this page.