How do I render two html documents per action? - ruby-on-rails

After user push save button, I need to render new page and render_to_string preview of this page in same time. To store it into DB.
So i got DoubleRenderError exception.
I try to stub #performed?
But Layouts purging after first render. Any ideas?
Thank you for answers!

I've successfully used both render_to_string and render on the same request.
I think you need to make sure you call render_to_string first. YMMV

I would probably do this using rack middleware.
class ResponseLoggerMiddleware
def initialize(app)
#app = app
end
def call(env)
status, headers, response = #app.call(env)
... save your response to the database ...
[status, headers, response]
end
end
You can install it like this:
# environment.rb
Rails::Initializer.run do |config|
...
config.middleware.use ResponseLoggerMiddleware
end

Related

Overriding the default Content-Type

I have a Rails API which accepts only JSON as input. If I fail to include a header of Content-Type: application/json, then request.headers['Content-Type'] defaults to application/x-www-form-urlencoded and the params do not get parsed properly. The whole json body becomes a key in the params. The result is a 422, which is confusing to API users.
How can I change this to default to parsing as json if no Content-Type header is supplied?
Lots of other questions answer how to do this with the response format. To change this default, you can specify it in the controller with:
request.format = :json
Or in a route namespace with something like:
namespace :api, defaults: {format: :json} do
This, however, changes the default response format and does not change the default request format. What I need to do is to change the default request format for parsing parameters.
Here is my admittedly terrible solution derived from the suggestion in Micael Nussbaumer's answer. I'd love it if some Rubyists could magically turn this ugly hack into a pithy one liner.
module Api
class BaseApiController < ActionController::API
private
# This is an ugly hack needed to make it default to json if you do not
# specify a Content-Type. If you see this and know of a better way please
# say so!
def params
if !#params
if request.headers["Content-Type"]=="application/x-www-form-urlencoded"
body_string = request.body.read
begin
hash = JSON.parse(body_string)
#params = ActionController::Parameters.new(hash)
rescue
# do nothing
end
end
if !#params
#params = super
end
end
#params
end
...
end
I've solved it with middleware this way for Rails API (rails new my_project --api)
config:
# config/application.rb
# ...
require './lib/middleware/consider_all_request_json_middleware'
# ...
module MyApplication
# ...
class Application < Rails::Application
# ...
config.middleware.insert_before(ActionDispatch::Static,ConsiderAllRequestJsonMiddleware)
# ...
middleware:
# lib/middleware/consider_all_request_json_middleware.rb
class ConsiderAllRequestJsonMiddleware
def initialize app
#app = app
end
def call(env)
if env["CONTENT_TYPE"] == 'application/x-www-form-urlencoded'
env["CONTENT_TYPE"] = 'application/json'
end
#app.call(env)
end
end
original: https://blog.eq8.eu/til/content-type-applicationjson-by-default-in-rails-5.html
parsed = JSON.parse(json_body) unless request.headers["Content-Type"] == 'application/json'

Rails overwriting params value

I'm trying to up receive updates on my Trello model when a change occurs, which I'm using their webhooks for. The problem is that one of the parameter's name is "action", which seems to be overwritten by Rails depending on the value in the Routes.rb. Is there any way to avoid this or do I just have to live with it?
Routes.rb
match "/trello" => "trello_updates#index", via: [:get,:post]
Webhook reponse
Parameters: {"model"=>{...},"action"=>"index"}
You can write a middleware in initializers and update the params coming from trello webhooks. like below -
class TrelloWebhooks
def initialize(app)
#app = app
end
def call(env)
request = Rack::Request.new(env)
trello_action = request.params['action']
request.update_param('trello_action', trello_action)
status, headers, response = #app.call(env)
[status, headers, response]
end
end
Rails.application.config.middleware.use 'TrelloWebhooks'
I had to modify the code from Vishnu, which is the accepted answer to make it work with a post request, so if you have a post request, you need to fetch the params out from the body of the response instead:
class TrelloWebhooks
def initialize(app)
#app = app
end
def call(env)
request = Rack::Request.new(env)
body = JSON.parse(request.body.string)
trello_action = body["action"]
request.update_param('trello_action', trello_action)
status, headers, response = #app.call(env)
[status, headers, response]
end
end
Rails.application.config.middleware.use 'TrelloWebhooks'

Rails 4 - How to render JSON regardless of requested format?

I'd like a Rails controller (all of them, actually, it's an API) to render JSON always always.
I don't want Rails to return "route not found", or try and fail to find an HTML template, or return 406. I just want it to automatically and always render JSON, e.g. from a RABL or JBuilder view.
Is this possible? Related questions seem to have answers that have the aforementioned downsides.
You can add a before_filter in your controller to set the request format to json:
# app/controllers/foos_controller.rb
before_action :set_default_response_format
protected
def set_default_response_format
request.format = :json
end
This will set all response format to json. If you want to allow other formats, you could check for the presence of format parameter when setting request.format, for e.g:
def set_default_response_format
request.format = :json unless params[:format]
end
You can use format.any:
def action
respond_to do |format|
format.any { render json: your_json, content_type: 'application/json' }
end
end
It's just:
render formats: :json
I had similar issue but with '.js' extension. To solve I did the following in the view:
<%= params.except!(:format) %>
<%= will_paginate #posts %>
I tried the above solutions and it didn't solve my use case.
In some of the controllers of my Rails 4.2 app, there was no explicit render called. For example, a service object was called and nothing was returned. Since they are json api controllers, rails was complaining with a missing template error. To resolve I added this to our base controller.
def render(*args)
options = args.first
options.present? ? super : super(json: {}, status: :ok)
end
It's a large app I'm converting to Rails 5, so this is just a safety measure as I removed the RocketPants gem that seemed to do this automatically.
As a note, my controllers inherit from ActionController::Base
Of course:
before_filter :always_json
protected
def always_json
params[:format] = "json"
end
You should probably put this in a root controller for your API.

Simple respond_with in rails that avoids 204 from PUT

I want to PUT to rails and avoid getting a 204. I am using this pattern:
class SomeController < ApplicationController
respond_to :json
def update
# ...
respond_with(some_object)
end
end
However, when I do a put to update, I get a 204 back. I realize this is completely valid etc, but I explicitly want the content back. I can override it to some extent like this:
def update
respond_with(some_object) do |format|
format.json{render json: some_object}
end
end
but this seems a bit too hands-on for rails. Is there any more idiomatic way of avoiding a 204 and requesting the full content to be sent back? This is Rails 3.2.
In summary: I want maximally idiomatic rails that avoids a 204.
I made a custom responder which always returns my JSON encoded resource even on PUT/POST.
I put this file in lib/responders/json_responder.rb. Your /lib dir should be autoloaded.
module Responders::JsonResponder
protected
# simply render the resource even on POST instead of redirecting for ajax
def api_behavior(error)
if post?
display resource, :status => :created
# render resource instead of 204 no content
elsif put?
display resource, :status => :ok
else
super
end
end
end
Now, explicitly modify the controller which requires this behavior, or place it in the application controller.
class ApplicationController < ActionController::Base
protect_from_forgery
responders :json
end
You should now get JSON encoded resources back on PUT.
As a less invasive alternative, you can pass a json: option to the respond_with method invocation inside your controller update action, like this:
def update
# ...
respond_with some_object, json: some_object
end
Granted it seems a bit unDRY having to repeat the object twice in the arguments, but it'll give you what you want, the json representation of the object in the response of a PUT request, and you don't need to use the render json: way, which won't give you the benefits of responders.
However, if you have a lot of controllers with this situation, then customizing the responders, as jpfuentes2 showed in the accepted anwser, is the way to go. But for a quick single case, this alternative may be easier.
Source: https://github.com/plataformatec/responders/pull/115#issuecomment-72517532
This behavior seems intentional to fall in line with the HTTP spec, and "ideally" you should be firing off an additional GET request to see the results. However, I agree in the real world I'd rather have it return the JSON.
#jpfuentes2's solution above should do the trick (it's very similar to the pull request below), but I'm hesitant to apply anything that's patching rails internals, as it could be a real pain to upgrade between major versions, especially if you don't have tests for it (and let's face it, developers often skimp on controller tests).
References
https://github.com/rails/rails/issues/9862
https://github.com/rails/rails/pull/9887
Just to clarify, you do not need the responders gem to do this... You can just do:
config/initializers/responder_with_put_content.rb
class ResponderWithPutContent < ActionController::Responder
def api_behavior(*args, &block)
if put?
display resource, :status => :ok
else
super
end
end
end
and then either (for all updates actions to be affected):
class ApplicationController < ActionController::Base
def self.responder
ResponderWithPutContent
end
end
or in your action:
def update
foo = Foo.find(params[:id])
foo.update_attributes(params[:foo])
respond_with foo, responder: ResponderWithPutContent
end
What's wrong with simply doing:
def update
some_object = SomeObject.update()
render json: some_object
end
Not a big fan of this behavior. To get around it, I had to avoid using the respond_with method:
class SomeController < ApplicationController
respond_to :json
def update
# ...
respond_to do |format|
format.json { render(json: some_object, status: 200) }
end
end
end

Is it possible to preprocess the URL before mapping routes?

We're migrating a site from a proprietary framework to Ruby on Rails (v2.3). The current framework sometimes puts /base/ at the start of the URL for no discernible reason, and I'd like the existing URL to work, even though we won't give it out any more.
My current solution, which I don't like, is to define the routes once on the main map and once on a 'base' scope:
def draw_routes(map)
# do my routing here
end
ActionController::Routing::Routes.draw do |map|
map.with_options :path_prefix => '/base' do |base|
draw_map(base)
end
draw_map(map)
end
what I'd like to do is something like:
ActionController::Routing::Routes.draw do |map|
map.strip 'base'
# do my routing here
end
is there a solution of that form?
You can write a middleware to remove the base from the url.
In lib/remove_base.rb:
class RemoveBase
def initialize(app)
#app = app
end
def call(env)
env['REQUEST_PATH'].gsub!(/^\/base/, '')
env['PATH_INFO'].gsub!(/^\/base/, '')
env['REQUEST_URI'].gsub!(/^\/base/, '')
#status, #headers, #response = #app.call(env)
[#status, #headers, self]
end
def each(&block)
#response.each(&block)
end
end
and add this line in config/environment.rb
config.middleware.use "RemoveBase"
I've tested it in 2.3.8 with mongrel, and it seems to work.
I think that you could simply do something like:
map.connect 'base/:controller/:action/:id'
That should route you to the right controller, action and id.

Resources