Skip to content
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

Merged
merged 24 commits into from Nov 4, 2017
Merged

Conversation

elorest
Copy link
Member

@elorest elorest commented Oct 18, 2017

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

respond_with do
 html render("index.slang")
 json posts.to_json
 xml render("index.xml.slang")
 text "hello world"
end

Provides and easier and cleaner way to set content_types.

@watzon
Copy link

watzon commented Oct 18, 2017

Very happy to see this being worked on so quickly

@@ -2,6 +2,24 @@ module Amber::Controller
module Render
LAYOUT = "application.slang"

protected def render_text(body, status_code = 200)
Copy link
Member

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?

Copy link
Contributor

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.

Copy link
Member

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.

Copy link

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

Copy link
Member Author

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.

Copy link
Member Author

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?

Copy link
Member

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")
)

Copy link
Contributor

@eliasjpr eliasjpr Oct 21, 2017

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

Copy link
Member Author

@elorest elorest Oct 23, 2017

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")
)

Copy link

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")
) 

@eliasjpr eliasjpr added the WIP label Oct 21, 2017
eliasjpr
eliasjpr previously approved these changes Oct 25, 2017
Copy link
Contributor

@eliasjpr eliasjpr left a comment

Choose a reason for hiding this comment

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

LGTM

@eliasjpr eliasjpr dismissed their stale review October 27, 2017 11:08

Additional questions?

@eliasjpr
Copy link
Contributor

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...

@elorest
Copy link
Member Author

elorest commented Oct 29, 2017

Could you look over this and let me know if you see any issues? @drujensen @eliasjpr
I like that it's simple and easy to use, not sure if the accepts logic is missing anything though.

@elorest elorest removed the WIP label Oct 29, 2017
@elorest elorest changed the title Methods to set content type and content [WIP] Methods to set content type and content Oct 29, 2017
@elorest
Copy link
Member Author

elorest commented Oct 29, 2017

@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.

Copy link
Contributor

@eliasjpr eliasjpr left a 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

@@ -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(","))
Copy link
Contributor

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

Copy link
Member Author

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?

Copy link
Contributor

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.

@elorest
Copy link
Member Author

elorest commented Oct 30, 2017

@drujensen @eliasjpr What would the expectation be when accepts and available response types don't match up? For instance it asks for text but the only thing available on the action is html

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
Copy link
Contributor

@eliasjpr eliasjpr Oct 30, 2017

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]
...

Copy link
Member Author

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)
Copy link
Contributor

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.

Copy link
Member Author

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.

Copy link
Member Author

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)
Copy link
Member

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.

Copy link
Member Author

@elorest elorest Nov 3, 2017

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.

Copy link
Member

@drujensen drujensen left a 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.

@elorest
Copy link
Member Author

elorest commented Nov 3, 2017

@drujensen It does allow for hashes and namedtuples right now. Just not nested.

@elorest elorest merged commit 19574c1 into master Nov 4, 2017
@watzon
Copy link

watzon commented Nov 4, 2017

Yay! Great job guys!

@eliasjpr eliasjpr deleted the is/render_content_types branch November 5, 2017 16:49
elorest added a commit that referenced this pull request Nov 17, 2017
* 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
elorest added a commit that referenced this pull request Nov 17, 2017
* 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
@faustinoaq faustinoaq added this to Done in Framework 2018 May 5, 2018
@faustinoaq faustinoaq removed this from Done in Framework 2018 Jun 12, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
No open projects
Development

Successfully merging this pull request may close these issues.

None yet

6 participants