respond_to is acting weird in my create action - ruby-on-rails

I have the following controller (pretty basic).
class ActivityTypesController < ApplicationController
respond_to :html
def show
#model=ActivityType.find(params[:id])
respond_with #model
end
def new
#folder = Folder.find(params[:folder_id])
#model = #folder.activity_types.build
respond_with #folder, #model
end
def create
#folder = Folder.find(params[:folder_id])
#model = #folder.activity_types.build(params[:activity_type])
if #model.save
flash[:notice] = 'hoorraaaaayyy'
end
respond_with #folder, #model, location: root_path
end
def edit
#folder = Folder.find(params[:folder_id])
#model = ActivityType.find(params[:id])
respond_with #folder, #model
end
def update
#folder = Folder.find(params[:folder_id])
#model = #folder.activity_types.find(params[:id])
if #model.update_attributes(params[:activity_type])
flash[:notice] = 'yeeeeaaaaaaah'
end
respond_with #folder, #model, location: root_path
end
The interesing thing is: the :location option within the #create action is ignored, while in #update, it is honored.
I have no idea, why this is the case. Even stranger: when removing the :location within #create, it seems to stay on the #update action and renders the edit view, instead of redirecting to the #show view.
Has anybody an idea how I could track this problem down?

I found the problem.
We had a view activity_types/create.html.haml which only rendered the _form.html.haml partial (this seems to be a leftover of a previous unclean implementation or workaround of the new/create action combo).
Because respond_with always checks for a corresponding view first, after creation, it rendered the create view which looked the same like the edit action. So if you run into something like this, too, make sure you don't have any views that prevent respond_to from redirecting to any other action.
Any by the way: if you only accept :html requests, then you only need a respond_with call in the create and update action, so it automatically renders the new and edit form when there are errors. If you, however, want to serve for example JSON or XML and have respond_to :xml, then you need a respond_with #my_model in the #show action, too (and any other action that should respond with data to a my_action.xml call).

Related

respond_with #model upon failing #destroy: append #model.errors[:base] to flash?

I'm using the following code to destroy a model, together with the responders gem and the FlashResponder:
def destroy
#model.destroy
respond_with #model
end
If the model fails to be destroyed, a flash[:alert] is displayed:
Alert: Model could not be destroyed.
Usually there is more information about why the model could not be destroyed available in #model.errors[:base]. Is there a way to add this to the flash? I'd love to have displayed both the original message, together the base error.
I tried setting the flash like this:
flash[:alert] = "#{#wcag_element.errors[:base].to_sentence}." if #wcag_element.errors[:base].any?
respond_with #model
But this doesn't display the original message anymore.
The easiest solution at the moment seems to be to simply add the flash to [:notice] instead of [:alert], but this doesn't really feel right:
flash[:notice] = ...
respond_with #wcag_element
respond_with also supports some options such as :alert and :notice as mentioned in their api here:
https://github.com/plataformatec/responders/blob/02a18078aa9b6533c25fd28060f62f1452fd9157/lib/responders/flash_responder.rb#L64
respond_with(#user, :notice => "Hooray! Welcome!", :alert => "Woot! You failed.")
Therefore you can do it like this respond_with #model, alert: 'Custom alert messsage'
I found this to be the easiest solution:
ApplicationController < ActionController::Base
private
def respond_with *args
options = args.extract_options!
resource = args.first
options[:alert] ||= resource.errors.full_messages.to_sentence if resource.errors.any?
super resource, options
end
end
This way, I don't have to think about it anymore in each and every descendant controller.

Flash messages in rails 4 not showing (within partial, within modal)

The problem in brief: I'm working on a rails 4 app (4.1.8) and I'm trying to get flash[:notice] and flash[:alert] to show up under a form.
Two controllers: landingpage_controller and contacts_controller. The landingpage_controller serves a static landingpage through its show action and the contacts_controller has new and create actions, to store the contacts in a db table.
On the static landingpage, a modal with id="contact-modal" contains a partial with a simple_form_for #contact (see below). Upon submittal of the form, a db-entry is not created if the fields are not all filled out and a db-entry is created if the fields are filled out. However, no flash messages are displayed.
Wanted output:
Ideally the partial would re-load without leaving/closing the modal, with either: a success message and an empty form or a alert message and the form as it was upon submittal. How do I do this?
The controller: app/controllers/contacts_controller.rb
class ContactsController < ApplicationController
def new
#contact = Contact.new
render layout: "contact"
end
def create
#contact = Contact.new
respond_to do |format|
if #contact.save
flash[:notice] = "Success"
format.js
else
flash[:alert] = "Error"
format.js
end
end
end
private
def contact_params
params.require(:contact).permit(:email, :structure, :message_content)
end
end
The form: app/views/contacts/_new.html.haml
= simple_form_for #contact, html: { id: "contact-form"} do |c|
= c.input :email
= c.input :structure
= c.input :message_content
= c.button :submit
.messages-container
= if flash[:notice]
%p
= flash[:notice]
= if flash[:alert]
%p
= flash[:alert]
Routes:
resources :contacts, only: [:new, :create]
I'm aware that a partial reload probably involves AJAX. I've read several StackOverflow questions on this but have not been able to figure it out. See here, here and these two blog-posts: jetthoughts, ericlondon.
Your help is very much appreciated
There are several problems in your code:
views, that start with underscore are called partials and are not full actions, but just parts of reusable view code (you don't redirect to them, instead you use render since you usually don't want a full page reload.
1.1 Rename your _new.html.haml to _form.html.haml
2.1 Create a new view new.html.erb(I guess you have that already, otherwise your new action might not work properly) with content = render 'form'
From what I understand you don't want the modal to close, just to render a form after successful submission or if there is an error.
In that case:
1.create a create.js.erb file in your views/contacts folder
create.js.erb
$("#your_modal_id").html("<%= j( render 'form') %>")
2. change your create action
def create
#contact = Contact.new(contact_params)
respond_to do |format|
if #contact.save
flash[:notice] = "Success"
format.js
else
flash[:alert] = "Error"
format.js
end
end
end
to your form add remote: true
WARNING: This will leave your form filled in even if it is successful.
More about this topic see:
http://guides.rubyonrails.org/v4.1.8/working_with_javascript_in_rails.html#form-for
Hope it helps, and I hope I didn't forget anything

Using the respond_to method to render a js.erb partial

I have a feature where users like/unlike and dislike/undislike dishes. This is how I initially have my actions setup:
Dishes Controller
class DishesController < ApplicationController
def like
#dish.liked_by current_user
end
def dislike
#dish.disliked_by current_user
end
...
end
Considering the js.erb templates for each template are completely identical I wanted to create just one shared partial that each could use:
Original Setup
# like.js.erb, unlike.js.erb,dislike.js.erb,undislike.js.erb
$(".restaurant__dish-vote--<%= #dish.modifier_class %>").html('<%= escape_javascript(render "restaurants/menu_partials/dish_votes", dish: #dish) %>');
New Setup
# shared/_vote.js.erb
$(".restaurant__dish-vote--<%= #dish.modifier_class %>").html('<%= escape_javascript(render "restaurants/menu_partials/dish_votes", dish: #dish) %>');
Now I'm trying to refactor my controller to reflect these changes but the ajax functionality doesn't seem to work with the following:
class DishesController < ApplicationController
def like
respond_to do |format|
format.js { render partial: "dishes/shared/vote.js.erb" }
end
#dish.liked_by current_user
end
end
The action completes but the changes on the user-facing side are only reflected upon refresh. What am I doing wrong?
I think you should call #dish.liked_by current_user before render partial: "dishes/shared/vote.js.erb". The problem is that #dish is not updated yet.

call controller with ajax in rails

In ruby on rails project, when I create a reporter successfully, page is redirect to another action from another controller; and when the page is redirect, the page is reloaded. In this project, I have 2 controller:
reporters_controller.rb:
class ReportersController < ApplicationController
layout "reporter"
def new
#reporter = Reporter.new
#gomrokaddresses = Gomrokaddress.find(:all)
end
def create
#reporter = Reporter.new(reporter_params)
if #reporter.save
#redirect_to new_reporter_path
redirect_to new_problem_path(:id => #reporter.id)
else
#existreporter = Reporter.find_by(params[:rep_user_name])
redirect_to new_problem_path(:id => #existreporter.id)
end
end
problems_controller.rb
def new
#reporter = Reporter.find(params[:id])
#problem = #reporter.problems.build
end
def create
#reporter = Reporter.find(params[:id])
#problem = #reporter.problems.build(problem_params)
if #problem.save
redirect_to new_problem_path(:id => #reporter.id)
else
redirect_to new_problem_path(:id => #reporter.id)
end
end
reporter.rb
class Reporter < ActiveRecord::Base
has_many :problems
end
problem.rb
class Problem < ActiveRecord::Base
belongs_to :reporter
end
I create reporter and problem with form_for in view. When I complete form_for in new.html.erb (for reporter) and submit, create action (that exist in reporter_controller) is called, and then if information are true, page is redirect to /problems/new. Because of this redirect_to, the page is reload; I don't want reload the page, just when I create the reporter, the form_for of reporter replace with the form_for of problem. How can I do this?
Try this in your controller
redirect_to new_problem_path(:id => #reporter.id), format: 'js'
Hope this helps!
A controller action renders the corresponding view template by default. Here, the action "problems#new" automatically renders "views/problems/new.html.erb".
In your code, you've redirected to the URL represented by new_problem_path, and a GET request to that URL is routed to the "problems#new" action. Thus, the action is invoked and its template is loaded.
However, there are ways to override this default behavior if you want to call the action without loading the view template.
Also, redirect_to is different from AJAX. For AJAX, you'd use something like
def create
...
respond_to do |format|
if #reporter.save
format.html { redirect_to ... }
format.js
else
format.html { render action: ... }
format.js
end
end
end
and then add the option :remote => true to form_for in the form that you use to create the new reporter.
But I'm not sure if this would accomplish what you're trying to do. Could you please explain your question further?

Instantiate instance variable in helper method from controller

I have a helper which instantiates a model and renders a form. This form should be available to any view in the application
# support_form_helper
def support_form
#support_stats = SupportForm::Stat.find(get_stats_id)
#enquiry = SupportForm::Enquiry.new(stats_id: #support_stats.id)
render partial: 'support_form/enquiries/form'
end
And its rendered in the view:
# some_view.html.erb
<%= support_form %>
This is fine until I want to submit the form and validate it in the controller.
# enquiries_controller.rb
def create
#enquiry = SupportForm::Enquiry.new(params[:support_form_enquiry])
topic = #enquiry.topic
#stat = SupportForm::Stat.find(#enquiry.stats_id)
#stat.stats[topic] = #stat.stats[topic].to_i.next
respond_to do |format|
if #enquiry.valid? && #stat.save
format.html { redirect_to(root_path) }
else
format.html { redirect_to(:back) }
end
end
end
This is where I can't render the previous view with the errors attached to the invalid object. The helper gets invoked again and initializes a new #enquiries object, without the errors obviously.
How can I render the form in many views across an application and still return to the view with the object together with errors when it is invalid?
I found an answer which answers my question but its a bad idea:
Render the action that initiated update
def create
#enquiry = SupportForm::Enquiry.new(params[:support_form_enquiry])
topic = #enquiry.topic
#stat = SupportForm::Stat.find(#enquiry.stats_id)
#stat.stats[topic] = #stat.stats[topic].to_i.next
if #enquiry.valid? && #stat.save
redirect_to(root_path)
else
render Rails.application.routes.recognize_path(request.referer).values.join("/")
end
end
The problem is that there will likely be instance variables in the view that submitted the form and I would have to be able to instantiate all the instance variable in the application then.....not possible.
Currently I'm considering putting the errors in the flash hash... not something I want to do. With the original object returned i can repopulate the fields with the users input.
When you use redirect_to, rails will kick off a whole new controller & view sequence. Use
render "path/to/template/from/view/folder"`
instead.
A typical create action using this pattern would look like (for a 'post' object in this case):
def create
#post = Post.new(params[:post])
#created = #post.save
respond_to do |format|
if #created
flash[:notice] = 'Post was successfully created.'
format.html { redirect_to post_path(#post) }
format.js
else
format.html { render :action => :new }
format.js
end
end
end
Notice how if it's successfully created we do a full redirect to the "show" page for the post, but if it's not successful we just do a render.
You should probably modify your support_form helper so that it only creates a new #enquiry if it hasn't been created already:
def support_form
#support_stats = SupportForm::Stat.find(get_stats_id)
#enquiry ||= SupportForm::Enquiry.new(stats_id: #support_stats.id)
render partial: 'support_form/enquiries/form'
end
||= is shorthand for "equals itself or". If it hasn't been defined (or is nil or false) then it will fail the first part of the or and pass through to the second, where the object is created.
In your form partial, also, you should make sure you're using form_for, which will submit to the create or update action depending on whether the object has been saved already.

Resources