is there a way to re-render? - ruby-on-rails

I have written an API in which I am rescuing from all StandardError. Incase of any StandardError, I send an exception email and render json with error message.
class ApplicationController < ActionController::Base
rescue_from StandardError, with: :respond_with_error
def respond_with_error(e)
ExceptionNotifier.notify_exception(e)
respond_to do |format|
format.html { render json: {error: e.message}, status: :unprocessable_entity, content_type: 'application/json' }
format.json { render json: {error: e.message}, status: :unprocessable_entity }
end
end
end
class UsersController < ApplicationController
def create
User.transaction do
#user = User.new(user_params)
authorize #user
#user.save!
end
respond_to do |format|
format.json { render :show, status: :created, location: #user }
end
end
end
This works great except when the exception is raised from render :show in UserController#create. As then when I again render json with error message from respond_with_error in ApplicationController, it raises double render exception cause render has already been called in controller.
Is there a way to override/dismiss the initial render call?
ruby 2.1.8
rails 4.2.6

Try adding the rescue block in the action itself.
def create
User.transaction do
#user = User.new(user_params)
authorize #user
#user.save!
end
flash.now[:success] = "successfully updated"
redirect_to #user and return
rescue StandardError => e
respond_with_error(e)
end
Just use redirect_to #user to render show page.

It's hard to say without looking at any code. But in general DoubleRenderException can be avoided with return, i.e
render :json => response_hash and return

Related

how to show the user an error after raise ActiveRecord::Rollback in rails?

I am creating a user after I create a client, but if the user already exists I roll back the transaction, but how do I tell the user something happened with a notice?
So far I have
def create
ActiveRecord::Base.transaction do
#affiliate = Affiliate.new(affiliate_params)
respond_to do |format|
if #affiliate.save
if User.find_by_email(user_params[:email]).nil?
#user = User.create!(user_parameter)
format.html {redirect_to :back, notice: 'Thanks for your submission'}
format.json {render :show, status: :created, location: #affiliate}
else
raise ActiveRecord::Rollback
end
else
format.html {render :signup, layout: "sign-ups"}
format.json {render json: #affiliate.errors, status: :unprocessable_entity}
end
end
end
end
I tried using the render and redirect but none worked.
Generally speaking, both raising exceptions while encountering expected conditions and manual rollbacks are considered code-smells in a Rails application.
A more idomatic approach might involve checking the existence of the user before saving the affiliate in the controller, or moving the logic into a model validation.
To answer your specific question w/o re-engineering everything, you could simply check performed? since all other paths render.
def create
ActiveRecord::Base.transaction do
...
format.html {redirect_to :back, notice: 'Thanks for your submission'}
format.json {render :show, status: :created, location: #affiliate}
...
else
...
format.html {render :signup, layout: "sign-ups"}
format.json {render json: #affiliate.errors, status: :unprocessable_entity}
...
end
end
unless performed?
# render duplicate email notice, re-display form
end
end
That said, below is a more typical approach. There are still some subtle issues but they would require diving into the specifics of your application.
# Add #email unique validation to User model, if not already implemented.
# class User < ApplicationRecord
# ...
# before_validation :normalize_email # downcase, etc
# validates :email, unique: true # should unique constraint on DB also
# ...
# end
def create
success = nil
ActiveRecord::Base.transaction do
#affiliate = Affiliate.new(affiliate_params)
#affiliate.save
# I'm guessing there is a relationship between the Affiliate and
# the User which is not being captured here?
#user = User.new(user_params)
#user.save
# Validation errors will populate on both #affiliate and #user which can
# be presented on re-display (including duplicate User#email).
# Did both the #affiliate and #user validate and save?
success = #affiliate.persisted? && #user.persisted?
# If one or both failed validation then roll back
raise ActiveRecord::Rollback unless success
end
respond_to do |format|
if success
format.html { redirect_to :back, notice: 'Thanks for your submission' }
format.json { render :show, status: :created, location: #affiliate }
else
# Either #affiliate or #user didn't validate. Either way, errors should
# be presented on re-display of the form. Json response needs work.
format.html { render :signup, layout: "sign-ups" }
format.json { render json: #affiliate.errors, status: :unprocessable_entity }
end
end
end

Rails - Intercept respond_with

I am using this 3rd party controller:
class LibController
def update
# 29 lines of code
respond_with resource
end
end
I want to do something other than the respond_with at the end. But I don't want to just copy/paste all 29 lines into MyController.update. Unfortunately I can't figure out a way to render or redirect anywhere else:
class MyController < LibController
def update
super
redirect_to somewhere_else
end
end
I get a DoubleRenderError: Render and/or redirect were called multiple times in this action. I assume this is because respond_with calls render immediately. Is there a way to block/prevent that?
Thanks!
I think you are doing a twice redirection.
Try to remove one redirection on your update method.
Check sample code below that shows equivalent response when using respond_with.
def create
#user = User.new(params[:user])
flash[:notice] = 'User was successfully created.' if #user.save
respond_with(#user)
end
Which is exactly the same as:
def create
#user = User.new(params[:user])
respond_to do |format|
if #user.save
flash[:notice] = 'User was successfully created.'
format.html { redirect_to(#user) }
format.xml { render xml: #user, status: :created, location: #user }
else
format.html { render action: "new" }
format.xml { render xml: #user.errors, status: :unprocessable_entity }
end
end
end

How to redirect to create action of another controller

I understand that I cannot POST on an HTML redirect, but my situation requires that I redirect to create action after authenticating user. I would like to know how to bypass this restriction:
In particular, I would like to allow an user to fill out a post without logging in using Omniauth. I save the post to session[:post] using an AJAX call. Then, the user can login using omniauth and persist the post.
I have a PostsController with create action that handle initial ajax call, and also handle html request after authenticating user:
def create
#post = Post.new(params[:post])
respond_to do |format|
format.html{
if #post.save
redirect_to #post, notice: 'Post was successfully created.'
else
render action: "new"
end
}
format.json {
if session[:post] = #post
render json: #post, status: :created, location: #post
else
render json: #post.errors, status: :unprocessable_entity
end
}
end
end
end
Then, in my controller that handles callback from Facebook, I have:
class ServicesController < ApplicationController
def create
... authentication logic here ...
sign_in(:user, service.user)
redirect_to :controller => "posts", :action =>"create"
end
method_alias: :facebook, :create
However, this doesn't work, because I can't redirect to a "create" action. How can I accomplish this task?
In the code you posted, you never read the content of the session. I think it can work if you change your code with this :
Change initialization of #post:
#post = Post.new(params[:post]) || session[:post] # Find object in session if not
And add after post.save :
session.delete :post # clean session after successful creation
New full method:
def create
#post = Post.new(params[:post]) || session[:post] # Find object in session if not in params
respond_to do |format|
format.html{
if #post.save
redirect_to #post, notice: 'Post was successfully created.'
session.delete :post # clean session after successful creation
else
render action: "new"
end
}
format.json {
if session[:post] = #post
render json: #post, status: :created, location: #post
else
render json: #post.errors, status: :unprocessable_entity
end
}
end
end
end
You can create a method on a Post model and call it both from PostsController and ServicesController to save the post (though in this case it's quite trivial: new, then save, so you achieve nothing in terms of DRY, may be some encapsulation benefits). Or create a common mixin containing the create_post method with all the logic. Then and include it into SessionsController and PostsController and call it from 'create'.
In the mixin module:
def create_post(allow_json=false)
#post = Post.new(params[:post])
respond_to do |format|
format.html {
if #post.save
redirect_to #post, notice: 'Post was successfully created.'
else
render "posts/new"
end
}
if allow_json
... your post-saving & json-processing logic ...
end
end
end
In PostsController:
def create
create_post(true)
end
In SessionsController:
def create
... authentication logic here ...
sign_in(:user, service.user)
create_post(false)
end
I didn't compile and try, so I only hope it works. In general, I must say there's something basically wrong, so I'd look for other architectural solutions to achieve the same results, but as a quick-and-dirty approach it should probably work.
I found a hack to avoid this issue: I let #create handling AJAX call and write to session, then create another action to persist it into my database after user get authenticated:
class PostsController < ApplicationController
def create
#post = Post.new(params[:post])
respond_to do |format|
format.json {
if session[:post] = #post
render json: #post, status: :created, location: #post
else
render json: #post.errors, status: :unprocessable_entity
end
}
end
end
def persist
#post = session[:post]
if #post.save
session.delete :post
redirect_to #post, notice: 'Post was successfully created.'
else
render action: "new"
end
end
Then in my routes.rb, I have:
resources :posts do
collection do
get 'persist'
end
end
Finally, in my ServicesController:
sign_in(:user, service.user)
redirect_to persist_posts_path

Rails: Pass Parameters to New Controller during 'redirect_to'

I'm using Ryan Bates' Rails Cast on Wicked Wizard Forms to create a multi-step form. I don't have a current_user method defined (not using an authentication gem) - so, I'm trying to pass the user.id parameter during the redirect_to - unfortunately, I can't seem to get it to work. Any help is appreciated!
My user controller create method
def create
#user = User.new(params[:user])
respond_to do |format|
if #user.save
format.html { redirect_to controller: 'user_steps', id: 'user.id' }
#format.html { redirect_to #user, notice: 'User was successfully created.' }#
format.json { render json: #user, status: :created, location: #user }
else
format.html { render action: "new" }
format.json { render json: #user.errors, status: :unprocessable_entity }
end
end
end
The user_steps controller that to which I am redirecting:
class UserStepsController < ApplicationController
include Wicked::Wizard
steps :gender, :items, :brands, :final
def show
render_wizard
end
end
You should pass it through as a param, ideally, which the redirect_to method will do for you if you use a proper route path.
Example:
redirect_to(user_steps_path(#user))
In your case, if you don't have a named route, you might do this:
redirect_to(controller: 'user_steps', id: #user.to_param)
In URLs it's advisable to use the to_param method. id is used for database queries.
What you're passing in is literally 'user.id' as a parameter. It will not be evaluated.

ActiveModel::MassAssignmentSecurity

I get a ActiveModel::MassAssignmentSecurity::Error when I try to running my app to save the login and password details.
got the following error
Can't mass-assign protected attributes: name, password, password_confirmation, salt
app/controllers/users_controller.rb:43:in new'
app/controllers/users_controller.rb:43:increate'
here is the code from the control file
class UsersController < ApplicationController
# GET /users
# GET /users.json
def index
#users = User.all
respond_to do |format|
format.html # index.html.erb
format.json { render json: #users }
end
end
# GET /users/1
# GET /users/1.json
def show
#user = User.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.json { render json: #user }
end
end
# GET /users/new
# GET /users/new.json
def new
#user = User.new
respond_to do |format|
format.html # new.html.erb
format.json { render json: #user }
end
end
# GET /users/1/edit
def edit
#user = User.find(params[:id])
end
# POST /users
# POST /users.json
def create
#user = User.new(params[:user])
respond_to do |format|
if #user.save
format.html { redirect_to #user, notice: 'User was successfully created.' }
format.json { render json: #user, status: :created, location: #user }
else
format.html { render action: "new" }
format.json { render json: #user.errors, status: :unprocessable_entity }
end
end
end
# PUT /users/1
# PUT /users/1.json
def update
#user = User.find(params[:id])
respond_to do |format|
if #user.update_attributes(params[:user])
format.html { redirect_to #user, notice: 'User was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: #user.errors, status: :unprocessable_entity }
end
end
end
# DELETE /users/1
# DELETE /users/1.json
def destroy
#user = User.find(params[:id])
#user.destroy
respond_to do |format|
format.html { redirect_to users_url }
format.json { head :no_content }
end
end
end
Answer in Stack Overflow and credits for Damien Mathieu
In your model, you need to add tag_attributes to the attr_accessible call.
For example :
class User < ActiveRecord::Base
attr_accessible :tags_attributes
end
If you already call it once, you can either add this field as an argument of the method, or make a second call. Both options are equivalent.
Having to specify all accessible parameters wasn't a default until a few months.
This guide has been updated to reflect the change of default. But the new version hasn't been deployed yet, this is why it's not specified.
I think you forgot to add the attr_accessible parameters in your model. Check out Rails API for more information regarding attr_accessbile and what it protects from.
Like waldyr.ar said, also you can use attr_protected

Resources