Feature request: allow formats with dot in resourceful routing, like '/users.xml.zip'

Hello,

would it be ok to allow formats with a dot in routing requests, like “http://example.com/users.xml.zip”?

If i am not mistaken, in this situation rails is only trying to match “zip” for format. I doubt many people are using such routes now because the controller part is not correctly recognized and the route is not matched automatically at all, so this change should not break many applications.

I can think about a PR if i have time, i would appreciate an indication which source file to look at.

I understand that formats with a dot may have other issues, but i do not think they are serious:


respond_to do |format|

format.public_send(:'xml.zip') { render :'xml.zip' => @users}

end

What do you think?

  • Alexey Muranov.

What about using a routing constraint for allowing the entire file extension?

E.g. match “/download/:filename”, :to => “downloads#download”, :constraints => { :filename => /.+/ }

I haven’t tried this, but i see no reason it should be a separate route. If

ressources :users

already match /users, /users.html, /users.xml, etc., why not to allow it match /users.xml.zip as well?

  • Alexey.

Also, there are request parameters, like sorting and filtering parameters, which are common to all formats, so i would like all formats and downloads to be processed by the same controller action.

  • Alexey.

I’m having a hard time thinking of use cases for this outside of zip or other archive/compression formats. In the case of user/1.xml.zip, likely you’d want an existing users_controller#show that returns xml to continue to operate as it currently does, but you’d want the result to be zipped. If there aren’t lot (any?) of other situations where stack formatting is used, perhaps this would be better handled outside the scope of rails; let it keep doing what it’s doing and maybe check the request URL for compression in rack on the way in and do the compression on the way out. If there are more general uses of this, it’s still unlikely that you’d want a single controller action to handle both creating the xml and zipping it if necessary.

Maybe “csv.zip” or “pdf.zip” make better sense.

I can of course define formats “csv_zip” and “pdf_zip” and use them, but i would prefer “csv.zip” and “pdf.zip”.

  • Alexey.

You can already do something like this:

In your routes:

get “foo(.:format)(.:compression)” => “foo#index”

Then in your controller:

respond_to do |format|

format.xml do

if params[:compression] == “zip”

render …

else

render …

end

end

end

That being said a format of csv.zip is not csv since it is zipped and it is not purely zip since that gives no indication of the actual contents of the file. Having some kind of built in support for this would be expected by me if I was trying this out for the first time.

Though if we were to add this feature I would expect it to be greedy so if you visit foo.csv.zip it would first look for a csv.zip format and if that was not found, it would look for a csv format as a backup. I haven’t checked into the implementation implications. If it is simple to implement with no performance impact I would be :+1: otherwise it’s not enough of a win as you can already do something like this (as above).

If someone implements this cc/ me on the PR and I’ll be happy to comment.

There's a tension here between content types and 'format' too. A
foo.csv.zip would have a Content-Type of application/zip, so having a
format of 'csv' (which has a type of text/csv) is a little strange.

I see no better way to name this format than ‘csv.zip’ (with Content-Type ‘application/zip’).

I see no better way to name this format than ‘csv.zip’ (with Content-Type ‘application/zip’).

But then you’d have ‘csv.zip’, ‘xml.zip’, etc be application/zip.

If you want to support both in a controller action, what happens when someone requests /users with Accept application/zip? Which one wins?

The .format notation is just a workaround for things that can’t add/change the Accept header to the request (like HTML links), but it’s just a workaround.

Cheers,

-foca

Sure, but the 'txt.zip' would also have a type of application/zip.

I know nothing about Accept header. Since it needs a workaround, i prefer not to rely on it too much. I see no problem that all ‘txt.zip’, ‘xml.zip’, ‘csv.zip’ have the same Content-Type. This means they all should be accepted if Accept header is application/zip, the one with the correct ‘format’ should be rendered.

  • Alexey.

Matt, i was only proposing to parse the URL slightly differently, probably without impact for existing applications. I am already using both “csv” and “csv.zip” formats in my application (mostly for testing and experimenting), i only do not like that i have to access “csv.zip” like this: “/users.csv_zip”.

  • Alexey.

I meant, parsing resourceful routes by default. I know i can define routes with my own parsing rules by hand.

  • Alexey.

Of all the suggestions thus far I think the one that makes the most sense is a Rack middleware (or even further out on Apache/Nginx/etc). That watches for the request to expect compressed results and does the compression removing the extension on the way in and re-applying it on the way out. Not involving Rails at all.

Note that there is a module for nginx that (may) solve this already:
http://wiki.nginx.org/NgxZip (I haven’t used it at all, it probably doesn’t do any form of url re-writing to handle the .zip extension as necessitated to make this Rails transparent)

Of all the suggestions thus far I think the one that makes the most sense is
a Rack middleware (or even further out on Apache/Nginx/etc). That watches
for the request to expect compressed results and does the compression
removing the extension on the way in and re-applying it on the way out. Not
involving Rails at all.

Yes, we call this deflate:
http://httpd.apache.org/docs/2.2/mod/mod_deflate.html
http://apidock.com/rails/Rack/Deflater/deflate/class

This thread isn't really about zipping, it's about multiple extensions.

Ahaha my second link sucks. Whatever, you know what I mean. (I need to
get an email filter that requires me to drink at least one cup before
hitting send.)

Thanks for the ideas to use middleware to compress the response and to pre-parse the URL, but i think i would like that it be possible for the application to parse URLs with multiple extensions as well.

If multiple extensions are not considered a ‘bad practice’ and if they can be used to indicate ‘composite formats’, for the use by middleware for example, why not to allow rails to take multiple extensions into account as well? Also, the views are generated by the application, so the application has to know anyway at which url and with which Accept header to access zipped CSV or PDF, to be able to generate a correct link or button element. So compression cannot be viewed as just an optional middleware configuration independent of the applicaiton.

If multiple extensions are discouraged and i need to use Accept header, then

a. how to set it from an html element? (I’ve seen that in HTML5 there is the “type” attribute, but i’ve read “it is purely advisory”, does it work?)

b. URL does not anymore determine the response completely, which i do not like.

c. requesting /users.csv with Accept header application/zip looks to me either strange or no better than requesting /users.csv.zip directly.

Just out of curiosity, do you have any examples of multiple extensions where those other than the root extension aren’t a compression or archive format? I ask in the context of this being considered a bad practice. To me, naming something anImage.jpg.zip.tar.gz means I started with a JPEG, and then stuck that inside a zip, then put the zip in a tar, then the tar in a gzip. Those “extra” extensions are all container formats, so it makes sense to show that there’s a file of a different format nested inside them. If things become more arbitrary, say anImage.jpg.txt, which just returns the JPEG’s binary data as text/plain, that doesn’t really hold up for me. That seems presentational, not a text file containing a jpeg.

If the only time that multiple extensions ever come into play is archive/compression, middleware still seems like a good option. The URL helpers could remain fairly consistent (eg, resource_path(@resource, :format => [:xml, :zip]) and the additional functionality be transparent. Rails really doesn’t need to know it’s happening as long as it’s generating correct URLs. If zipping is far and away the most likely case for this (which I think is likely), resource_path(@resource, :zipped => :xml) may even make sense.

Basically, for all the examples I’ve thought of, anytime multiple extensions are being used appropriately, the root extension is the only one that it makes sense to Rails to actually handle. But if there are examples to indicate otherwise I’d be interested to hear them.

The only things I’ve gotten close on is like:

/runs/1 => web view of a GPS recorded running route

/runs/1.json => JSON version
/runs/1.xml => XML version

/runs/1.xml.gpx => XML that adheres to GPX standard

/runs/1.xml.tcx => XML that adheres to TCX standard

But those last two, to me, are simply improperly name.

Just out of curiosity, do you have any examples of multiple extensions where those other than the root extension aren’t a compression or archive format? I ask in the context of this being considered a bad practice. To me, naming something anImage.jpg.zip.tar.gz means I started with a JPEG, and then stuck that inside a zip, then put the zip in a tar, then the tar in a gzip. Those “extra” extensions are all container formats, so it makes sense to show that there’s a file of a different format nested inside them. If things become more arbitrary, say anImage.jpg.txt, which just returns the JPEG’s binary data as text/plain, that doesn’t really hold up for me. That seems presentational, not a text file containing a jpeg.

If the only time that multiple extensions ever come into play is archive/compression, middleware still seems like a good option. The URL helpers could remain fairly consistent (eg, resource_path(@resource, :format => [:xml, :zip]) and the additional functionality be transparent. Rails really doesn’t need to know it’s happening as long as it’s generating correct URLs. If zipping is far and away the most likely case for this (which I think is likely), resource_path(@resource, :zipped => :xml) may even make sense.

Basically, for all the examples I’ve thought of, anytime multiple extensions are being used appropriately, the root extension is the only one that it makes sense to Rails to actually handle. But if there are examples to indicate otherwise I’d be interested to hear them.

The only things I’ve gotten close on is like:

/runs/1 => web view of a GPS recorded running route

/runs/1.json => JSON version
/runs/1.xml => XML version

/runs/1.xml.gpx => XML that adheres to GPX standard

/runs/1.xml.tcx => XML that adheres to TCX standard

But those last two, to me, are simply improperly name.