Send a variable from one action to another in ruby on rails - ruby-on-rails

Code in controller is
if params["type"] == "user"
respond_to do |format|
format.html { redirect_to home_user_path, notice: "abc" }
end
If I send variable with notice then work fine but I want to send with my own key like
format.html { redirect_to home_user_path, search: "abc" }
It doesn't recieve there

You must remember that you're not "sending" a variable to another action; you're invoking another action, and populating it with a variable (data):
1. Instance Variable
You can set an instance variable, which will then be available in the next action:
def your_action
#search = "abc"
respond_to do |format|
format.html { redirect_to home_user_path } #-> #search will be available in the other view
end
end
2. Sessions
You're currently trying to use sessions to populate the data:
def your_action
redirect_to home_user_path, search: "abc"
end
#app/views/controller/your_action.html.erb
<%= flash[:search] %>
3. URL
Finally, you can set the value in the URL through your routes:
def your_action
redirect_to home_user_path(search: "abc") #-> you'll probably need to set the user object too, judging from the route name
end
This should populate your route with some GET request params: url.com/action?param=value
The underpin of all of this, as mentioned, is that you're not sending a variable, you'll be initializing it in your current controller action, and then having the next action call it.

This is a problem with Ruby's optional braces: sometimes it's hard to see which method call something is being treated as an argument to.
Try this instead:
format.html { redirect_to home_user_path(search: "abc") }
this will redirect to home_user_path and set params[:search] to "abc".

Related

Rails - console says 'rendered' but the redirect not happening

I have written a Service Module that makes an API request, returns either a string with the error or a hash with parameters and included it in the Controller.
def create
#new_params = ApiRequest.create_login(params, customer_secret) #this returns either a string or hash
request_error? #if it's a string, it should immediately render :new
params[:login] = #new_params #substitutes the params from the form with the API response
#login = current_user.logins.new(login_params).save
end
Here's how it should redirect without trying to save
def request_error?
respond_to do |format|
if #new_params.is_a? String
#error = #new_params
format.html { render :new, notice: #error }
end
end
end
The problem is, in the console it says that it rendered :new (or even redirected_to new_login_path - if I were to use this instead of render) but the page doesn't even reload, just nothing happens. And even though it should have exited the create action earlier with render :new, it still tries to save the #login and fails.
My guess is that the long API Request breaks the action (>300ms for the whole controller action). What should I do in order to fix this?
It had nothing to do with the controller or request, the form_with was submitted with Ajax by default, in order to respond to html I had to specify local: true. The previous comment solves the 'exit early out of the controller action' problem.
The problem here is that you are trying to render but not stopping the progress of the controller action by returning.
Consider replacing the request_error? method being inlined to the action:
def create
#new_params = ApiRequest.create_login(params, customer_secret) #this returns either a string or hash
respond_to do |format|
if #new_params.is_a? String
#error = #new_params
format.html { render :new, notice: #error }
end
end
params[:login] = #new_params #substitutes the params from the form with the API response
#login = current_user.logins.new(login_params).save
end
Since you don't return when rendering within the conditional, the action continues and goes on to save your login.
To avoid this, you should return when you render within your conditional.
def create
#new_params = ApiRequest.create_login(params, customer_secret) #this returns either a string or hash
respond_to do |format|
if #new_params.is_a? String
#error = #new_params
format.html { render :new, notice: #error and return }
end
end
params[:login] = #new_params #substitutes the params from the form with the API response
#login = current_user.logins.new(login_params).save
end
Note, you can't do this if you continue to use the method request_error? as if you return within that method you pass control back to the action. Instead, you'd need to return a boolean, say, to determine whether you have rendered, like:
def request_error?
respond_to do |format|
if #new_params.is_a? String
#error = #new_params
format.html { render :new, notice: #error }
return true
else
return false
end
end
end
Then, return early if there is a request error:
def create
#new_params = ApiRequest.create_login(params, customer_secret) #this returns either a string or hash
return if request_error?
params[:login] = #new_params #substitutes the params from the form with the API response
#login = current_user.logins.new(login_params).save
end
Let me know if that helps at all.

Is it necessary to set instance variable in resource controller `#create` method?

Having resource Foobar with the following controller:
class FoobarController < ApplicationController
def new
#foobar = Foobar.new(baz: params[:baz])
#foobar.build_data
end
def create
#foobar = Foobar.new(foobar_params)
respond_with(#foobar)
end
# ...
end
Is it necessary to set instance variable #foobar in #create method? Could not I just write
def create
Foobar.new(foobar_params).tap &method(:respond_with)
end
?
It depends on what content types you respond with. The docs describe exactly what happens when you call respond_with. In your case, in the create action, respond_with is the same as the following, assuming you did not specify any other format than html in a respond_to call in your controller:
respond_to do |format|
if #foobar.save
flash[:notice] = 'Foobar was successfully created.'
format.html { redirect_to(#foobar) }
else
format.html { render action: "new" }
end
end
The only case where the #foobar instance variable would be necessary is, if there is a validation error and your new.html template includes #foobar. If the foobar_params are always valid, then respond_with will always respond with a redirect to the show action, so the instance variable is unnecessary.

linking to the product from the index in rails

When I save a PayerContract in the create form, it saves the PayerContract and creates a "success you have created PayerContract {X}" where {X} is the PayerContract name and redirects the user back to the index. I want to allow the user to click the {X} to go to the edit page for that PayerContract.
In my index page I have:
= link_to t('payer_contracts.edit'), edit_payer_contract_path())
I need to give the PayerContract.id to the edit_payer_contract_path function. So, it would look something like edit_payer_contract_path(330)
The warning message I have displayed in my controller is this:
def create
payer_contract_params = PayerContract.assign_params_to_payer_contract(params)
#payer_contract = PayerContract.new(payer_contract_params)
respond_to do |format|
begin
if #payer_contract.save
format.html { redirect_to payer_contracts_path, notice: t('common.successfully_created', entity: #payer_contract.description) }
end
end
end
end
I found out that when I do something like:
edit_payer_contract_path(PayerContract.find()) it returns all the PayerContract that looks like: expected an attributes Hash, got [{"id"=>224, "name" ="hello", "id"=> 332, "name" = "hello2", "id"=>555, "name" = "sup" }] etc.
How can I correctly link the user to the PayerContract they just created?
you can have it two ways. Either pass parameter along your redirect_to method. obviously, pass the id of newly created PayerContract. This way you will have the id in params and you can use it in your link_to method.
If you don't want to pass the parameter, try storing the newly created PayerContract in session after successfully creating PayerContract. This way after redirect you will still have access to the saved value from the session
something like below
def create
#foo = Foo.new(foo_params)
if foo.save
session[:foo_id] = #foo.id
redirect_to foo_path
end
end
As beniutek said you can pass parameter in redirect_to. But when user first visit index page id is not defined so you can check in view if it is define set the id to link_to as below.
def create
payer_contract_params = PayerContract.assign_params_to_payer_contract(params)
#payer_contract = PayerContract.new(payer_contract_params)
respond_to do |format|
begin
if #payer_contract.save
format.html { redirect_to payer_contracts_path(id: #payer_contract.id), notice: t('common.successfully_created', entity: #payer_contract.description) }
end
end
end
end
In view
if params[:id].present?
= link_to t('payer_contracts.edit'), edit_payer_contract_path(params[:id].to_i))

Conditional routing for nested resource in Rails controller #edit action, depending on where request came from

I have a Foo resource that has_many Bars. I'm using nested resources for a limited number of actions, but otherwise prefer to keep my routing for bars shallow. There are two ways to navigate to the edit view for the Bar object - either from the nested path that includes foo, or from the shallower bar path that isn't nested inside foo. For example, a user might click the edit button from the page at /foos/[:foo_id]/bar/[:bar_id]; or from /bars/[:bar_id].
In the first case, I want the controller to redirect the user back to the parent foo page: /foos/[:foo_id] after the record is updated. In the second case, I want it to redirect to the index view for bars: /bars. I believe I need some sort of conditional in the #edit action in the bars controller that will tell Rails where to go after #update executes.
# config/routes.rb
resources :foos do
resources :bars, only: [:new, :edit]
end
resources :bars
# bin/rake routes:
foo_bars POST /foos/:foo_id/bars(.:format) bars#create
new_foo_bar GET /foos/:foo_id/bars/new(.:format) bars#new
edit_foo_bar GET /foos/:foo_id/bars/:id/edit(.:format) bars#edit
bars GET /bars(.:format) bars#index
POST /bars(.:format) bars#create
new_bar GET /bars/new(.:format) bars#new
edit_bar GET /bars/:id/edit(.:format) bars#edit
bar GET /bars/:id(.:format) bars#show
PATCH /bars/:id(.:format) bars#update
PUT /bars/:id(.:format) bars#update
DELETE /bars/:id(.:format) bars#destroy
The controller for bars:
# app/controllers/bar_controller.rb
def edit
#bar = bar.find(params[:id])
#foo = #bar.foo
end
def update
#bar = bar.find(params[:id])
#foo = #bar.foo
respond_to do |format|
if #bar.update_attributes(bar_params)
format.html { redirect_to #foo, notice: "bar successfully updated" }
else
format.html { render action: "edit" }
end
end
end
I'm trying to change the redirect_to #foo line in the #update action so there is conditional logic that switches out #foo for #bars depending on where the #edit action was initiated. I've tried something like the following to test whether params[:foo] is present when the #edit action is called, setting an instance variable for the redirect.
def edit
if params[:foo]
#redirect_page = #foo
else
#redirect_page = #bars
end
#bar = bar.find(params[:id])
#foo = #bar.foo
end
def update
# code omitted...
format.html { redirect_to #redirect_page, notice: "bar successfully updated" }
# code omitted...
end
This doesn't work. Rails states cannot redirect to nil!. I've also tried something using a test based on URI(request.referer).path in the #edit action, without success.
I'm still not entirely clear how the Rails magic happens in the controller. I believe the #edit action is the proper place to define the conditional for the redirect (or through a method called in the #edit action), as that's where the controller will "see" the incoming request and know where it came from. But I can't quite figure out to capture that information, and pass it along to #update. Appreciate any guidance.
In your edit forms, add a hidden_field_tag:
<%= hidden_field_tag "route", request.env['PATH_INFO'] %>
Then in your controller, you can have an if statement and use a redirect_to based on what the params[:route] is.
I figured it out. The params[:route] method using request.env['PATH_INFO] wasn't working for me, because the 'PATH_INFO' variable in the form was providing the path handed off to the bars#update action, instead of the path where the bars#edit action was initiated.
After clicking "Edit" from the parent foo page at /foos/[:id] the params hash is:
>> params
=> {"controller"=>"bars", "action"=>"edit", "foo_id"=>"3786", "id"=>"16"}
There is no value for params[:route] when the form is first accessed - the hidden field is only added to the params hash after clicking "Update" in the edit form:
>> params[:route]
=> "/foos/3786/bars/16/edit"
This could work, but would require building logic to parse the route in order to redirect to /foos/[:foo_id]
It turned out to be simpler to use the Rails flash method to store the path for redirecting back to the source page. I did this by calling a custom method set_redirect_path in the BarsController, and calling it in bars#edit. This sets a value for the source in the flash, which is available in bars#update. Maybe there's a better/more conventional way to achieve this, but this seems to be a clean and simple way to do what I want.
# app/controllers/bars_controller.rb
def edit
set_redirect_path
#bar = bar.find(params[:id])
#foo = #bar.foo
end
def update
#bar = bar.find(params[:id])
#foo = #bar.foo
respond_to do |format|
if #bar.update_attributes(bar_params)
format.html { redirect_to flash[:source], notice: "bar successfully updated" }
format.xml { head :ok }
else
format.html { render action: "edit" }
format.xml { render xml: #bar.errors, status: :unprocessable_entity }
end
end
end
private
def set_redirect_path
flash[:source] = URI(request.referer).path
end
One advantage of this approach is I can now get rid of conditional logic in the shared partial app/views/bars/_list.html.haml that was required to determine whether clicking the "Edit" button should route to edit_foo_bar_path or to edit_bar_path (i.e. the former is chosen if #foo exists). Consequently, I can delete :edit for the nested resource :bars. Since the flash captures the incoming source of the request and stores it for reference in the #update action, all edit requests can use the same edit_bar_path, regardless of where they originate from. After update Rails redirects the user to the point where they initiated the #edit action.

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