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
Methods to set content type and content #313
Conversation
Very happy to see this being worked on so quickly |
src/amber/controller/render.cr
Outdated
@@ -2,6 +2,24 @@ module Amber::Controller | |||
module Render | |||
LAYOUT = "application.slang" | |||
|
|||
protected def render_text(body, status_code = 200) |
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.
@elorest what do you think of dropping the render_
and just using text
, json
, html
?
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 would be better if their names are verbs, since verbs are more readable and nouns may collide with local variables.
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.
yup, your right. It reads better.
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 know how you all feel about it, but I like the idea of just using the normal render
command with named params like Rails does.
def index
render json: { key: "Value }
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.
IMHO render is already complicated enough. Also text and json aren't truly rendered, but rather just copied over into the response without changes. As these helper methods currently are they don't have to be macro's or complicate the already complex workings of the render macro.
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 ok with that. @drujensen @eliasjpr @fridgerator WDYT?
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.
@watzon that looks like a good solution.
Should it support the ability to detect the Accept
header and allow you to define multiple response types?
respond_with(
json: {"hello" => "world"},
text: "hello world",
html: render("index.slang")
)
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 like the signature above is very intuitive. Crystal supports defining method with external names so the above can be written as
respond with: { "json" }
def respond(with type)
...
end
See External Names here https://crystal-lang.org/docs/syntax_and_semantics/default_values_named_arguments_splats_tuples_and_overloading.html
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.
Yeah it's on my crystal lang coffee mug lol.
@eliasjpr In your code block above there's nothing that defines which type it's responding with though as the value of with
is the json body
.
I think that this makes more sense.
respond_with(
json: {"hello" => "world"},
text: "hello world",
html: render("index.slang")
)
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 also the possibility of doing something like this
respond with: :json { json }
# or
respond with: :json do
json
end
# or as above
respond_with(
json: {"hello" => "world"},
text: "hello world",
html: render("index.slang")
)
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.
LGTM
Was thinking about the new methods, they dont seem to be calling the render. What do you think of organizing them in such: module Render
module Helpers
def render_text end;
def render_html end;
def render_json end;
end
end And include them in the controllers... |
Could you look over this and let me know if you see any issues? @drujensen @eliasjpr |
@eliasjpr There is already a module called render. I would just prefer to leave them in there so they're included with the render macros. We can talk about it though. I'm more concerned with the functionality right now. |
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.
overall i like the implementation, i did left a comment after reading about default accept headers https://developer.mozilla.org/en-US/docs/Web/HTTP/Content_negotiation/List_of_default_Accept_values
src/amber/controller/render.cr
Outdated
@@ -37,5 +37,43 @@ module Amber::Controller | |||
Kilt.render("#{{{path}}}/{{filename.id}}") | |||
{% end %} | |||
end | |||
|
|||
protected def respond_with(html : String? = nil, json : Hash | String? = nil, xml : String? = nil, text : String? = nil) | |||
accepts = context.request.headers["Accept"].split(";").try(&.split(",")) |
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 should probably be a guard check for when Accept is */*
. also sometimes the accept header could be in this formattext/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Also we have some helper methods that you can make use of in this case, see https://github.com/amberframework/amber/blob/master/src/amber/support/mime_types.cr
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.
@eliasjpr Thanks. I'm thinking that we should probably just default to html or text or something it doesn't match any of accept types. Seems like pretty much all browsers have / as the last set of accepts parameters.
How would you suggest the mime_type helpers could be used: It seems like Amber::Support::Mime.mime_type("json")
is more complicated than "application/json"
. Since all of the ones we care about only point to one mime type I feel that it's probably bests just to hardcode the string. Thoughts?
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.
Well, the Amber::Support::Mime has a Hash of mime-types you can probably write a nice matcher method in that module to match the one from the header, this will keep the code centralized in one place instead of hardcoding the string.
@drujensen @eliasjpr What would the expectation be when accepts and available response types don't match up? For instance it asks for |
src/amber/controller/render.cr
Outdated
protected def respond_with(html : String? = nil, json : Hash | String? = nil, xml : String? = nil, text : String? = nil) | ||
accepts = context.request.headers["Accept"].split(";").try(&.split(/,|,\s/)) | ||
|
||
if accepts.includes?("text/html") && html |
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.
Can this be a constant if you want to keep the string as such:
RESPOND = {
html: "text/html",
json: "application/json",
text: "text/plain",
xml: "application/xml"
}
accepts.includes? RESPOND[:html]
accepts.includes? RESPOND[:json]
...
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.
Yeah that looks good.
@@ -37,5 +37,30 @@ module Amber::Controller | |||
Kilt.render("#{{{path}}}/{{filename.id}}") | |||
{% end %} | |||
end | |||
|
|||
protected def respond_with(html : String? = nil, json : Hash | String? = nil, xml : String? = nil, text : String? = 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.
Can we remove this method?responders.cr
does the format very nicely.
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.
Yeah. Weird that shouldn't be there.
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.
Done!
elsif xml | ||
respond_with_xml(xml) | ||
else | ||
respond_with_text("Response not acceptable", 406) |
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 tried curl -H "Accept: application/pdf" http://localhost:3000
against this branch but for some reason the html was still returned instead of an error 406.
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.
We decided that since all browsers include */*
as a final parameter that if nothing matched up just to return the first defined content 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.
This is excellent work. I think we can make a couple improvements by allowing arrays and hashes to be passed into json
but that can wait until its requested.
@drujensen It does allow for hashes and namedtuples right now. Just not nested. |
…ber into is/render_content_types
Yay! Great job guys! |
* methods to set content type and content * respond_with * respond with works * organized methods better * allow hash or json for json response * formatting * small changes * moved content_types out to constant * tested IRL * refactored file structure * works after many hours * good for now, got it working as good as it used to. * added tests * tests work and fixes empty headers' * removed vestigul code
* methods to set content type and content * respond_with * respond with works * organized methods better * allow hash or json for json response * formatting * small changes * moved content_types out to constant * tested IRL * refactored file structure * works after many hours * good for now, got it working as good as it used to. * added tests * tests work and fixes empty headers' * removed vestigul code Former-commit-id: 6d2d14d
Description of the Change
Resolves discussion in this issue. #306
Adds
respond_with
block as a helper method to set content_types.Returning values and render by itself still work unless a value has already been set via these methods.
Benefits
Provides and easier and cleaner way to set content_types.