Ruby on Rails - Storing XML in a Temporary File - ruby-on-rails

I want to be able to store my xml into a temporary file and then send it to another method in another controller to be processed. Currently it will not allow me to read the file, once sent, because it is a private method.
Controller #1
xml_doc = Nokogiri::XML(#builder.to_xml)
#tempfile = Tempfile.new('xml')
#tempfile.write(xml_doc)
redirect_to upload_xml_admin_events_path(:file => #tempfile)
Controller #2
Version #1
xml = params[:file].read
xmldoc = Nokogiri::XML(xml)
Gives me this error: undefined method `read' for "File:0x6ebfb00":String
Version #2
xml = params[:file]
xml.open
xmldoc = Nokogiri::XML(xml)
Gives me this error: private method `open' called for "#File:0x6a12bd8":String

It seems like you are thinking that params can be objects, which can be forgiven due to Rails magic. In reality all params are strings with a key in the key=value format.
So the issue here is that when you redirect with the parameter 'file', it turns your Tempfile object into a string. This is why the error is telling you that there are no accessible methods called read or open for a string. I see a few options:
Do whatever you are going to do with the file on Controller 1 instead of redirecting to Controller 2. You won't have to create extra objects, hit a database, or have crazy parameters in your URL.
If the XML can be really large, it might be better to make an AR object called XmlFile or something and write it to the database in Controller 1, then redirect with that id in the parameters. This way, you won't have to send crazy long XML strings in the URL (which is bad):
# Controller 1
#xml = XmlFile.new(#builder.to_xml)
redirect_to upload_xml_admin_events_path(:xml => #xml) #implicitly #xml.to_s
# Controller 2
#xml = XmlFile.find(params[:xml])
Nokogiri::XML(#xml)
If the XML is always (very) small, you can send the XML as a param in plain text (this seems to be closest to what you are currently doing, but strikes me as less elegant) You might run into URL encoding issues here though.
# Controller 1
xml = #builder.to_xml
redirect_to upload_xml_admin_events_path(:xml => xml)
# Controller 2
#xml = Nokogiri::XML(params[:xml])

Related

How to pass large data set between controllers in Rails

Currently passing retrieved data using redirect_to which uses GET. Since in some cases data set is large, URI is too long and throws Bad Request error.
All online research says its a bad idea to pass data in GET body. Is there a better way to pass data to another controller?
Code Block
def create
response_body = http_get('/data/I/want')
parsed_result = JSON.parse(response_body)
check_response(parsed_result)
redirect_to controller: :search_results, action: :index, results: parsed_result
end
end point called in create is search results so need to check if results are empty before redirecting and passing the data. I omitted this part from the code block
Any reason to put these code in create method? From my point of view, your code doesn't really create anything. It is just get some JSON data from a remote URL and redirect to search_results#index. Why not just load the JSON in search_results#index directly?
Update
It is too generous to use more than 3 routes for a search action. I have two suggestions:
Search in remote JSON if you can control it. Just pass the searching keyword in URL and let remote resolve it.
If you cannot control the remote JSON, do the search with AJAX call. In your search_results#index, makes an AJAX call to your something like search#new JSON route and fetch the filtered result.

Rails display JSON from an api GET request

I'm a rails beginner and I'm trying to display a json object I get back from an external api. I'm using HTTParty and I'm almost positive I have that set up correctly. I have a function in my HTTParty class called
self.get_all()
How would I go about making a new page on which to display the JSON I get back from that function?
It all pretty much depends on the json that comes back. Aside from it being 'JSON`, what does it look like? If you haven't even inspected it yet, maybe that's a good place to start. You can call your method like so: (pick one)
puts your_httparty_class.get_all.inpsect # will be displayed in your logs (most likely)
raise your_httparty_class.get_all.inspect # will raise the response to the view
You may find yourself needing to do something like this to ensure it's a hash.
response = HTTParty.get('https://api.somesite.com/some_endpoint')
body = JSON.parse(response.body)
Now that you know and can see that the JSON is just a hash you can access it like so:
something = body[:something] # accessing a hash
nested_something = body[:something][:nested_something] # accessing a nested hash
You can then move something and nested_something around your app. So, you could pass it from your controller to your view as instance variables:
# # makes it an instance variable and therefore accessible to your controller's views
#something = body[:something]
#nested_something = body[:something][:nested_something]

Is there any gem to send request to some service in xml format

I want to send request to some third party service in xml and also expecting response in xml. I'm searching for some gem or any idea how to do this.
Thing which is in my mind is to
make some partail _example.xml.builder
onclick from my view to some button send ajax request to controller action and use render_to_string to render that xml doc and then
Save it in some variable
and then call to that service method in same action
But it is not proper thing as I expect there should be some thing more efficient than my suggested thing
RoR doesn't natively use XML so some degree of conversion is required.
Having said that, XML generation is very simple in RoR applications. There are several ways of doing this, my favourite being constructing the required data as a Hash (which is native to Ruby) then the_hash.to_xml.
The XML conversion can also be defined in a model Class if you wish a consistent result:
class Example < ActiveRecord::Base
# ensure that only column1, column2, etc are output as XML
def to_xml(options = {})
super( options.merge( select(:column1, :column2, etc) ) )
end
end
Then in your controller:
poster = Example.find(123)
request = Net::HTTP.new('www.example.com', 80)
request.post('/path', poster.to_xml)
Hopefully the above demonstrates a simple example of posting XML data to a remote host. As you mentioned, a more complicated XML can be constructed using xml.builder
HTH and good luck.

Accessing Variables in Rails 3 before_save Method

I have the following before_save method:
def get_data
url = "http://www.api-fetching-url.com/where_my_data_input_is=#{self.my_data}&output=json"
new_data = HTTParty.get(url)
#field_to_update = new_data['one']['two']['here']
self.field_to_update = #field_to_update
end
Unfortunately, the self.my_data doesn't appear to be working, because the JSON url doesn't produce any result. But, when I substitute my_data in the hardcoded way, it works just fine. Moreover, I can do a find in the Rails console and get the my_data field just fine. So, it's not an issue with that field not saving or something on the form side.
Is there an issue inserting data this way in a before_save method? If not, is there a different way of doing this that I'm missing?
Some remarks:
You don't have to (and actually can't) always call methods with the self receiver. Private methods for example can only be called without an explicit receiver, so no self. for private methods...
Why don't you inspect the url and check whether it is correct? Just add puts url after the line where you assign the url, run your program and check the output. Is the url correct?
You probably use HTTParty not correctly: HTTParty.get('...') returns a response object and you probably have to parse the response's body properly.
An example for a JSON service:
url = 'http://service.com/path/to/resource.json'
response = HTTParty.get url
data = JSON.parse(response.body)
# now you can use the data, e.g.
# bla = data['one']['foo']

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.

Resources