Generate XML Request Payload with Template in Rails - ruby-on-rails

I'm working on a rails app that integrates with a 3rd party on the backend. To give a quick overview, a user will create an order in our web interface and that will persist a delayed job. So, the job runs outside of a web context and will send a request to this 3rd party. The 3rd party expects a POST with an XML payload in the request body.
I'm currently using Faraday to do the request. I have a class that looks something like this:
class FooRequest
def response
connection.post("/foo", xml)
end
private
def xml
xml = Builder::XmlMarkup.new
xml.root do
xml.foo "something something"
end
xml.target!
end
def connection
#connection ||= Faraday.new(url: "http://example.org")
end
end
The XML is actually bigger and more complex than that and so I don't want to build the xml in this class. I think having some sort of template is best. I'd either like to use Builder in a xml.builder file or just have raw XML in a erb file. Not sure which is best and how best to render a template in this context. I know that rails recently added the ability to render views outside of a controller context (https://medium.com/evil-martians/new-feature-in-rails-5-render-views-outside-of-actions-2fc1181e86a8#.s344qg5ue) but I feel like that feature wasn't built for this use case. I also know that there are other templating languages like Liquid and maybe that's a better fit.
Has anyone done this sort of thing in the past? Is there something I'm missing?
Also, if I do create a template file, should I put it in /app/views? It's not a view that my webapp would render, maybe I should have a /app/templates dir?
PS: Rails's to_xml method is not robust enough for what I want (I need tag attributes and I don't want to represent a deep complex XML tree with a ruby hash).

I've done a similar thing, the easiest way I found was to implement whatever XML classes I needed into a separated gem (you don't really have to do this you can just add the classes somewhere in the models folder, it made sense for my use case).
I used ROXML for this; after that you can simple call root.to_xml.to_s to render your string, see the examples on github, it's really straightforward.

Related

ACRCloud webapi in Rails

I'm a newb hobbyist developer. Can I just throw this repo of their ACRCloud's ruby example code into a controller? I'd like to use an audio fingerprinting song recognition database as a name validation for songs users are uploading using paperclip. Not sure if it's possible, just starting to research it, any hints or suggestions would be appreciated.
Obviously I'd have to replace
file_name = ARGV[0]
etc, but I'm also wondering about the require 'openssl' etc
Definitely! But there are few points to be taken care of. That's a pure ruby script, when it comes to rails there are certain rules/best practices. One of which is thin controller and fat model..
You need to create a route/action in your app which will ask the app to execute this request with required params.
Write a method in your model which contains the code and call it from controller and pass the permitted params to it.
Instead of hardcoding your credentials in the model, make them environment variables.
Would suggest using Httparty gem wgich will reduce many lines of your code and you just need to pass headers, params, etc. as hash in the arguments.
Last, but not the least...if you notice..there's a puts in the end however, rails uses mvc and so you need to have a view for the controller action you created in step1. Return and save the response.body in the class variable like #response = res.body and you can play with the body depending on the response type.
Hope it helps..
P.S. I wish I could write few lines of code/optimise it for you but i m using my mobile right now. But I think this much information should be enough to convert that script to mvc rails structure..

Where to store editable content?

I am building a Rails app that is intended to be eventually used by non-technical people. It consists of a few pages with blocks of text and a special page with interactive canvas drawings.
I want to allow them to easily edit any piece of text contained in the application. What are the best ways to achieve that? Currently, text is written in the different views of the application, which does not allow them to edit it without having to connect via FTP or similar and search for the right file.
I am thinking of three solutions:
Store all blocks of text in the database. On each page, fetch the requires blocks and insert them before rendering. Build a page that lists all blocks in the database in editable areas with a save button.
Store all blocks of text in a json file. Create a model that can read the file and fetch the blocks required by the views. Build a page that lets you edit each block and save it in the file.
Create some kind of password-protected admin interface that fetches all file in the views directory, use regexp to find blocks of text and allow the users to edit each block and save.
From my point of view, all of my three solutions look pretty bad. It does not feel okay to do so many calls to the database? Store your entire website text in a file? Parse HTML with regexps?
What are the usual approaches used to solve this problem?
There's a great book out there: Crafting Rails 4 Applications. Here's the link to source code from the book. You will find example in templater folder. Basically, you will be able to create custom templates based on the request parameters (just like Rails does).
Update. Here's a couple of links:
Default views in Rails 3.0 with custom resolvers by José Valim (author of the book, by the way).
Implementing a Rails 3 View Resolver.
Also, here's 5 coins from me. Basically, it works like this. You need to define your own resolver and connect it to your ApplicationController (or any other controller you want):
class Resolver < ActionView::Resolver
# some code here
end
class ApplicationController < ActionController::Base
append_view_path Resolver.new
end
During the rendering process, Rails will ask your controller's resolvers to provide a template (it will go through each of them, until it finds template or until there won't be any resolvers left). In order to provide template, your resolver needs a find_templates method:
def def find_templates(name, prefix, partial, details)
# some processing here
end
So, based on this method parameters, you're going to provide some database records. But even if you have some kind of model already, Rails expects this method to return ActionView::Template instance. It can be initialized like this:
ActionView::Template.new(source, identifier, handler, details)
So, that's how your find_templates should look like:
def find_templates(name, prefix, partial, details)
template = DatabaseTemplate.find... # your custom model for DB templates
ActionView::Template.new... # initializing actual template
end
Both model and resolver in detail are presented in the book's source code (templater/3_final/app/models/sql_template.rb).
I have done that a couple times with awesome user satisfaction by using this:
http://jejacks0n.github.io/mercury/
There is also a Railscast available which gives you a good overview and step by step instructions:
http://railscasts.com/episodes/296-mercury-editor
Hope it helps. It looks good and is easy to use for end users.

Create a generic template for JSON from a rails application

I'm writing a rails application with an AngularJS front-end, this is part of a tutorial series I'm writing on connecting rails and angularjs. This means my rails application communicates with the browser exclusively in JSON.
In the angularjs $http documentation it describes a potential json security vulnerability where the json request can be embedded into a script tag, plus some tricky use of jsonp, to allow something akin to a cross-site scripting attack. I've found a few other pages, one in particular I thought described this well, and dates from 2008, so this isn't a new issue.
Apparently this isn't a vulnerability in standard rails json rendering, as rails by default provides back an object containing an array. But when working with angularjs we appear to set root: false (although I have to confess I can't find where I did that, but it's definitely not giving the root node).
Anyway, the bottom line is that the angular documentation recommends prefixing any json response with )]}', so:
['one','two']
Becomes
)]}',
['one','two']
Angular then automatically strips that off again.
I'm looking for a way to do this elegantly. I've seen a lot of questions and answers on stackoverflow about this, but most of those either relate to much earlier versions of rails before JSON handling was more thoroughly embedded, or seem to require me to create a lot of boilerplate code. I'm looking for a method that I can apply to the application controller, or as a helper method, that will work everywhere.
The controller that I'm currently using looks as follows:
class ClubsController < ApplicationController
respond_to :json
# GET /clubs.json
def index
#clubs = Club.all
render json: #clubs
end
end
This doesn't call any templates - the render action skips the templating engine. I can get this working by changing the render line instead to:
respond_with json: #clubs
And creating a template file views/clubs/index.json.erb that contains
)]}',
<%= raw(#clubs.to_json) %>
But I'd then have to create a template for every action on every controller, which feels like boilerplate. I'd like instead to be able to change views/layouts/application.json.erb to have something like:
)]}',
<%= yield %>
But that doesn't work because we only get templating if we call respond_with. And if we call respond_with, we have no way to put the #clubs into the response - so we end up with:
)]}',
As the entirety of the response.
An alternative would perhaps be to override the as_json method to prepend what I want, but that seems a bit like a sledgehammer. Ideally there would be a place I could introduce a helper method, something like:
render prepend_vulnerability_protection(json: #clubs)
So, after all that, two questions:
Is this even a real problem, or does Rails already have some other protection that means I don't need to worry about this at all
Is there a way to do this centrally, or do I need to bite the bullet and create all the boilerplate templates? I can modify the scaffold generators to do it, so it's not the end of the world, but it does seem like a lot of boilerplate code
So, no responses as yet. I'm going to write down what I find from my research, and my current answer.
Firstly, I think this is a genuine vulnerability in rails. Unfortunately the rails and JSON/JSONP area has had some other recent vulnerabilities relating to the JSON parser at the Rails end. That has really drowned out any google search relating to this specific XSS issue.
There are a couple of approaches to resolving this:
Have your application only respond to put/post/delete requests. That's not really an option when integrating to Angular - well, it is, but it means overriding a bunch of standard behaviour
Insert something at the front of your returned JSON - this can be the root node (default rails behaviour in rails 3, no longer in 3.1), a closure like )]};, or a loop like while (1);. Angular expects and can deal with )]}',
I've looked at using a json template in my rails app. You can do this with one of many gems, the one I like the look of is JBuilder (railscast 320), but RABL is perhaps more powerful (railscast 322).
This does mean a template for each of the actions on each of the controllers. However, I've also just completed working out how to have rails scaffold those for me automatically, so it's not as scary as it was when I first asked the question, and I can see some other reasons that I might want more control over the json that is returned from my application.
Having said that, I couldn't immediately see a way to get JBuilder to prepend an arbitrary string - it seems to only want to prepare valid JSON (and this I think is not valid JSON). RABL looks like it can do it, but it is a bit more complex. It can definitely be done through just using ERB, but I feel kinda wrong in doing that.
The other alternative I've identified is a helper method in application_controller.rb, which I then call in each of my controller methods. This is reasonably elegant, and I can quite easily change my template to do it. So I'm going with this for now:
class ApplicationController < ActionController::Base
def render_with_protection(json_content, parameters = {})
render parameters.merge(content_type: 'application/json', text: ")]}',\n" + json_content)
end
end
class ClubsController < ApplicationController
respond_to :json
# GET /clubs.json
def index
#clubs = Club.all
render_with_protection #clubs.to_json
end
# GET /clubs/1.json
def show
#club = Club.find(params[:id])
render_with_protection #club.to_json
end
# POST /clubs.json
def create
#club = Club.new(params[:club])
if #club.save
render_with_protection #club.to_json, {status: :created, location: #club}
else
render_with_protection #club.errors.to_json, {status: :unprocessable_entity}
end
end
end
Note that you should be also including CSRF protection in your application controller - so see this as additive to the security precautions you were already taking, not a replacement.

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.

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