RESTful Content Negotiation in Rails - ruby-on-rails

I'm looking to implement content negotiation on some resources in a Rails app. I'm using Mootools and will likely be able to tweak the content type accepted by an XMLHTTPRequest to "application/json".
Is there any way to pick up on this information in my controller and generate JSON responses instead of XHTML?
I'm trying to avoid doing something like:
http://site/resource/1?format=JSON
...as it dirties up my URL, imposes a certain degree of redundancy and is not as flexible.
Thanks!

http://site/resource/1.json is not correct use of content-negotiation. The point is that the URL should remain the same, but the client asks for a specific representation (JSON, PDF, HTML etc.) based on HTTP headers it sends with the request.

You can use a respond_to stanza in your controller method, like this:
respond_to do |format|
format.html { # Generate an HTML response... }
format.json { # Generate a JSON response... }
end
Rails determines the response format based on the value of the HTTP Accept header submitted by the client.

Surely http://site/resource/1.json should work? you may need to set it up in your Rails Environment, though, depending on how current the version of Rails you have is, I doubt it.

After much research, while rails has everything to automatically select a template for output, it still requires the call to respond_to for each one you wish to support.

Related

Why does Rails render templates for HEAD requests?

For HEAD requests, Rails seems to perform all the usual steps including rendering templates and then discards the respective output (sends back an empty response).
I can't think of any way that rendering templates in case of a HEAD request makes any sense (unless you have some actual business logic that gets executed in the templates, but that should never be the case).
So my question would be: Do you agree with me or are there any possible side-effects I didn't foresee?
The reason I'm asking is because I'm thinking of filing a Rails issue and possibly submit a pull request for a feature which disables default (non-explicit) template rendering for HEAD requests.
Good point Remo, however, I am not completely agree.
For every http verb, we manually need to write code to handle things. Similar thing with HEAD. HEAD request will follow execution style of GET request unless we don't handle it.
An example can be:
def index
if request.head?
head :created
else
# handle GET request
Rails.logger.info "Derp #{request.method}"
end
end

Using response_with in Rails, how can I prevent execution of create action on not accepted MIME types?

I'm building a RESTful API using Rails 3.2.21. The API should only response to xml or json for now. I have a simple resource called Users with a create action, that creates a new user on a POST Request.
Here is the Code for doing that:
respond_to :json, :xml
def create
#user = User.new(params[:user])
#user.save
respond_with(#user)
end
Everything goes fine so far, but then I tried to check the error cases. So if I do a POST request to /users.html, the answer is '406 Not Acceptable'. What is correct. But then I saw in the database, that the user was created anyway. So the create action is executed although the requested accept format (html) is not supported and a 406 error is responded.
I don't know if this is intended. Until now I really liked response_with from Rails, because it does lots of stuff for me, but this behaviour seems to be odd. From the perspective of a client you try to create a new user, receive 406 and you obviously assume that the request failed, so the user is not created, right?
Since I've defined the accepted MIME types in the class method respond_to, it should be possible for Rails, to prevent execution of the entire action. Sure, respond_to is only related to the http response, but then for the client it is still unknown which part of the POST request succeeded and which failed.
Are there any setting or additional functions in Rails what I've overseen or is this just 'not thought to the end' in case of POST requests using respond_with or is this behaviour even intended to react like that? Of course I can add some custom before_filters for checking the MIME types, but then I can also remove respond_with and handle everything on my own.
I'm looking for a nice and clean solution for that problem using respond_with.

Generate XML dynamically and post it to a web service in Rails

I am currently developing a Rails app in which I need to dynamically send XML request to an external web service. I've never done this before and I a bit lost.
More precisely I need to send requests to my logistic partner when the status of an order is updated. For instance when an order is confirmed I need to send data such as the customer's address, the pickup address, etc...
I intended to use the XML builder to dynamically generate the request and Net:HTTP or HTTParty to post the request, based on this example.
Is that the right way to do so? How can I generate the XML request outside the controller and then use it in HTTParty or Net:HTTP?
Thanks for your help,
Clem
That method will work just fine.
As for how to get the XML where you need it, just pass it around like any other data. You can use the Builder representation, which will automatically convert to a String as appropriate, or you can pass around a stringified (to_s) version of the Builder object.
If, for example, it makes sense for your model (which we'll call OrderStatus) to generate the XML, and for your controller to post the request:
# Model (order_status.rb)
def to_xml
xml = Builder::XmlMarkup.new
... # Your code here
xml
end
# Controller (order_statuses_controller.rb)
def some_method
#order_status = OrderStatus.find(:some_criteria)
... # Your code here
http = Net::HTTP.new("www.thewebservicedomain.com")
response = http.post("/some/path/here", #order_status.to_xml)
end
You may want to wrap the HTTP calls in a begin/rescue/end block and do something with the response, but otherwise it's all pretty straightforward and simple.
Make XML with Builder, then send it down the wire.
In your case it sounds like you may need to send several different requests as the order evolves; in that case:
Plan out what your possible order states are.
Determine what data needs to be sent for each state.
Decide how to represent that state within your models, so you can send the appropriate request when the state changes.
Where my example uses one method to generate XML, maybe you'll want 5 methods to handle 5 possible order states.

How to render a view normally after using render_to_string?

In my Rails application I have an action which creates a XML document using an XML Builder template (rxml) template and render_to_string. The XML document is forwarded to a backend server.
After creating the XML document I want to send a normal HTML response to the browser, but somehow Rails is remembering the first call to render_to_string.
For example:
Rails cannot find the default view show.html.erb because it looks for a show.rxml.
Simply putting a render 'mycontroller/show.html.erb' at the bottom of my action handler makes Rails find the template, but the browser doesn't work because the response header's content type is text/xml.
Is there any way to use render_to_string without "tainting" the actual browser response?
EDIT: It seems that in Rails 2 erase_render_results would do the trick, but in Rails 3 it is no longer available.
The pragmatic answer is that using a view file and two calls to render is Not The Rails Way: views are generally something that is sent to the client, and ActionPack is engineered to work that way.
That said, there's an easy way to achieve what you're trying to do. Rather than using ActionView, you could use Builder::XmlMarkup directly to generate your XML as a string:
def action_in_controller
buffer = ""
xml = Builder::XmlMarkup.new(buffer)
# build your XML - essentially copy your view.xml.builder file here
xml.element("value")
xml.element("value")
# send the contents of buffer to your 3rd server
# allow your controller to render your view normally
end
Have a look at the Builder documentation to see how it works.
The other feature of Builder that you can take advantage of is the fact that XML content is appended to the buffer using <<, so any IO stream can be used. Depending how you're sending content to the other server, you could wrap it all up quite nicely.
Of course, this could end up very messy and long, which is why you'd want to encapsulate this bit of functionality in another class, or as a method in your model.
Seems as if this may be a bug in rails 3 (at least compared to the behavior of 2.3.x render_to_string). In the source for 2.3.8 they clearly take extra steps to reset content_type and set the response body to nil (among other things).
def render_to_string
...
ensure
response.content_type = nil
erase_render_results
reset_variables_added_to_assigns
end
but in the 3.0.3 source for AbstractController::Rendering
def render_to_string(*args, &block)
options = _normalize_args(*args, &block)
_normalize_options(options)
render_to_body(options)
end
You can see there is no explicit resetting of variables, render_to_body just returns view_context.render. It is possible that content-type, response_body, etc are handled elsewhere and this is a red herring, but my first instinct would be to set
response.headers['Content-Type'] = 'text/html'
after your render_to_string before actually rendering.
In migrating the actionwebservice gem I encountered the same error. In their code they circumvent the double render exception by calling the function erase_render_results.
This function is no longer available in rails3. Luckily the fix is quite easy (but it took me a while to find).
Inside actionwebservice the following function was called inside a controller to allow a second render:
def reset_invocation_response
erase_render_results
response.instance_variable_set :#header, Rack::Utils::HeaderHash.new(::ActionController::Response::DEFAULT_HEADERS.merge("cookie" => []))
end
To make this work in rails3, you just have to write:
def reset_invocation_response
self.instance_variable_set(:#_response_body, nil)
response.instance_variable_set :#header, Rack::Utils::HeaderHash.new("cookie" => [], 'Content-Type' => 'text/html')
end
Hope this helps.

How to append to an XML response an error attribute using Ruby on Rails 3?

I am trying to implement REST APIs, so in my RoR3 application I have XML responses. Before to pass to a consumer the XML, I would like to check if there are errors somewhere and, if so, append and send back a response with error messages.
I read "Active Record Validations and Callbacks" guides on the RoR website, but it seems not work in my case.
I extract from the database a resource doing
#response = User.find_by_id(1)
and I would like, if possible, to access #response.errors after a "validation".
Seeing some examples I have seen how to report errors in an XML file
format.xml { render :xml => #response.errors }
but how can I add new errors to the #response?
Maybe something like this:
errors.add(:password, "is invalid")
this works too:
errors.add_to_base('your text')
but you should put it in the model.

Resources